summaryrefslogtreecommitdiffstats
path: root/webapp/src/components/IprangeModule.vue
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/src/components/IprangeModule.vue')
-rw-r--r--webapp/src/components/IprangeModule.vue236
1 files changed, 236 insertions, 0 deletions
diff --git a/webapp/src/components/IprangeModule.vue b/webapp/src/components/IprangeModule.vue
new file mode 100644
index 0000000..5c37f60
--- /dev/null
+++ b/webapp/src/components/IprangeModule.vue
@@ -0,0 +1,236 @@
+<i18n>
+{
+ "en": {
+ "ipranges": "IP Ranges",
+ "deleteIpranges": "Delete one iprange | Delete {0} ipranges",
+ "createIprange": "Create IP range",
+ "id": "ID",
+ "startIp": "Start IP",
+ "endIp": "End IP",
+ "group": "Group",
+ "deleteTitle": "Delete this IP range? | Delete these {0} IP ranges?",
+ "createTitle": "Create IP Range",
+ "editTitle": "Edit IP Range",
+ "required": "Required.",
+ "invalidIp": "Invalid IP Address.",
+ "deleteSuccess": "Successfully deleted IP ranges.",
+ "updateSuccess": "Successfully updated IP range.",
+ "createSuccess": "Successfully created IP range."
+ },
+ "de": {
+ "ipranges": "IP Bereiche",
+ "deleteIpranges": "Einen IP Bereich löschen | {0} IP Bereiche löschen",
+ "createIprange": "IP Bereich erstellen",
+ "id": "ID",
+ "startIp": "Start IP",
+ "endIp": "End IP",
+ "group": "Gruppe",
+ "deleteTitle": "Diesen IP Bereich löschen? | Diese {0} IP Bereiche löschen?",
+ "createTitle": "IP Bereich erstellen",
+ "editTitle": "IP Bereich bearbeiten",
+ "required": "Benötigt.",
+ "invalidIp": "Ungültige IP Adresse.",
+ "deleteSuccess": "IP Bereiche erfolgreich gelöscht.",
+ "updateSuccess": "IP Bereich erfolgreich aktualisiert.",
+ "createSuccess": "IP Bereich erfolgreich erstellen."
+ }
+}
+</i18n>
+
+<template>
+ <v-container fill-height>
+ <v-layout>
+ <v-flex xl10 offset-xl1 lg12>
+ <v-card class="tabbar-card">
+ <v-tabs v-model="tabs" grow hide-slider :dark="tabsDark" :color="tabsColor" :slider-color="tabsSliderColor">
+ <v-tab><v-icon class="tabbar-tabicon">settings_ethernet</v-icon>{{ $t('ipranges') }}</v-tab>
+ </v-tabs>
+ </v-card>
+ <v-tabs-items v-model="tabs" style="padding-bottom: 20px">
+ <v-tab-item>
+ <v-subheader>{{ $t('ipranges') }}</v-subheader>
+ <v-card>
+ <data-table v-model="selected" :headers="headers" :items="ipranges" @dblclick="dialog = { show: true, type: 'edit', iprange: { ...$event } }" min-width="840px" copy-button>
+ <template #group="row">
+ <v-btn small flat :to="{ name: 'GroupModule.group', params: { id: row.item.groupId } }" class="ma-0" style="text-transform: none;">
+ {{ row.item.group.name }}
+ <v-icon small class="ml-2">open_in_new</v-icon>
+ </v-btn>
+ </template>
+ <template #actions="row">
+ <div style="text-align: right">
+ <v-btn flat icon @click.stop="dialog = { show: true, type: 'edit', iprange: { ...row.item } }" class="ma-0"><v-icon color="primary">edit</v-icon></v-btn>
+ </div>
+ </template>
+ </data-table>
+ </v-card>
+ <div class="text-xs-right">
+ <v-btn flat color="error" @click="dialog = { show: true, type: 'delete' }" :disabled="selected.length === 0">
+ <v-icon left>delete</v-icon>{{ $tc('deleteIpranges', selected.length, [selected.length]) }}
+ </v-btn>
+ <v-btn flat color="success" @click="dialog = { show: true, type: 'create', iprange: {} }"><v-icon left>create</v-icon>{{ $t('createIprange') }}</v-btn>
+ </div>
+ </v-tab-item>
+ </v-tabs-items>
+ </v-flex>
+ </v-layout>
+
+ <v-dialog
+ v-model="dialog.show"
+ :max-width="dialog.type === 'delete' ? '500px' : '500px'"
+ scrollable
+ :persistent="dialog.type !== 'delete'"
+ :fullscreen="$vuetify.breakpoint.smAndDown"
+ lazy
+ >
+ <v-card>
+ <v-card-title class="elevation-3">
+ <div class="headline">{{ dialogTitle }}</div>
+ </v-card-title>
+ <v-card-text style="height: 100%">
+
+ <div v-if="dialog.type === 'delete'" style="padding: 14px;">
+ <RecycleScroller
+ :items="selected"
+ :item-size="24"
+ page-mode
+ >
+ <div slot-scope="{ item }">[{{ item.id }}] {{ item.startIp }} - {{ item.endIp }}, {{ item.group.name }}</div>
+ </RecycleScroller>
+ </div>
+
+ <v-form v-else ref="form" style="padding: 14px;">
+ <select-box :rules="[rules.group]" prepend-icon="category" :label="$t('group')" v-model="dialog.iprange.selectedGroup" :items="groupList" single-select></select-box>
+ <v-text-field :rules="[rules.ip]" validate-on-blur prepend-icon="vertical_align_top" :label="$t('startIp')" color="primary" v-model="dialog.iprange.startIp"></v-text-field>
+ <v-text-field :rules="[rules.ip]" validate-on-blur prepend-icon="vertical_align_bottom" :label="$t('endIp')" color="primary" v-model="dialog.iprange.endIp"></v-text-field>
+ </v-form>
+
+ </v-card-text>
+
+ <v-divider></v-divider>
+
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn flat="flat" @click="dialog.show = false">{{ $t('cancel') }}</v-btn>
+ <v-btn :color="dialogColor" @click="submitAction">{{ dialogButtonText }}</v-btn>
+ </v-card-actions>
+ </v-card>
+
+ </v-dialog>
+ </v-container>
+</template>
+
+<script>
+import SelectBox from '@/components/SelectBox'
+import DataTable from '@/components/DataTable'
+import { mapState, mapGetters } from 'vuex'
+
+export default {
+ name: 'IprangeModule',
+ components: {
+ SelectBox,
+ DataTable
+ },
+ data () {
+ return {
+ tabs: 0,
+ ipranges: [],
+ selected: [],
+ dialog: { show: false, type: null, iprange: {} }
+ }
+ },
+ computed: {
+ ...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor']),
+ ...mapState('groups', ['groupList']),
+ headers () {
+ return [
+ { key: 'id', text: this.$t('id'), width: '50px' },
+ { key: 'startIp', text: this.$t('startIp'), width: '120px' },
+ { key: 'endIp', text: this.$t('endIp'), width: '140px' },
+ { key: 'group', text: this.$t('group'), copyKey: 'groupId', sortKey: 'groupName' },
+ { key: 'actions', width: '60px' }
+ ]
+ },
+ rules () {
+ return {
+ group: value => value.length > 0 || this.$t('required'),
+ ip: value => {
+ if (!value) return this.$t('required')
+ value = value.split('.')
+ if (value.length !== 4) return this.$t('invalidIp')
+ for (let i in value) {
+ if (value[i] < 0 || value[i] > 255) return this.$t('invalidIp')
+ }
+ return true
+ }
+ }
+ },
+ dialogColor () {
+ if (this.dialog.type === 'delete') return 'error'
+ if (this.dialog.type === 'create') return 'success'
+ return 'primary'
+ },
+ dialogTitle () {
+ if (this.dialog.type === 'delete') return this.$tc('deleteTitle', this.selected.length, [this.selected.length])
+ if (this.dialog.type === 'create') return this.$t('createTitle')
+ return this.$t('editTitle')
+ },
+ dialogButtonText () {
+ if (this.dialog.type === 'delete') return this.$t('delete')
+ if (this.dialog.type === 'create') return this.$t('create')
+ return this.$t('save')
+ }
+ },
+ watch: {
+ dialog (value) {
+ if (this.$refs.form) this.$refs.form.resetValidation()
+ if (value.type === 'edit') this.$set(this.dialog.iprange, 'selectedGroup', [this.dialog.iprange.group])
+ }
+ },
+ methods: {
+ async submitAction () {
+ let text = ''
+ if (this.dialog.type === 'delete') {
+ await this.$http.post('/api/ipranges/?delete', {
+ ids: this.selected.map(x => x.id)
+ })
+ text = this.$t('deleteSuccess')
+ } else {
+ if (this.$refs.form && !this.$refs.form.validate()) return
+ let url = '/api/ipranges'
+ if (this.dialog.iprange.id) {
+ url += '/' + this.dialog.iprange.id
+ text = this.$t('updateSuccess')
+ } else {
+ text = this.$t('createSuccess')
+ }
+ await this.$http.post(url, {
+ startIp: this.dialog.iprange.startIp,
+ endIp: this.dialog.iprange.endIp,
+ groupId: this.dialog.iprange.selectedGroup[0].id
+ })
+ }
+ this.dialog.show = false
+ this.$snackbar({ text, color: 'success' })
+ this.loadIpranges()
+ },
+ loadIpranges () {
+ this.$http.get('/api/ipranges').then(response => {
+ response.data.forEach(iprange => {
+ iprange.groupName = iprange.group.name
+ })
+ this.ipranges = Object.freeze(response.data)
+ })
+ }
+ },
+ created () {
+ this.$store.dispatch('groups/loadGroupList')
+ this.loadIpranges()
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+
+</style>