summaryrefslogtreecommitdiffstats
path: root/webapp/src
diff options
context:
space:
mode:
authorUdo Walter2019-02-24 02:33:52 +0100
committerUdo Walter2019-02-24 02:33:52 +0100
commit813195e3dfb5baae09aa11cddeef12fb0b9fb49b (patch)
tree58562c85d76efdad41afcd0664c5a8cdc8cb4a55 /webapp/src
parent[webapp/alerts] Remove unnecessary code which produced vue warnings (diff)
downloadbas-813195e3dfb5baae09aa11cddeef12fb0b9fb49b.tar.gz
bas-813195e3dfb5baae09aa11cddeef12fb0b9fb49b.tar.xz
bas-813195e3dfb5baae09aa11cddeef12fb0b9fb49b.zip
[webapp/groups] rework old tables to new data table
slightly redesigned the editing of groups and clients
Diffstat (limited to 'webapp/src')
-rw-r--r--webapp/src/components/DataTable.vue48
-rw-r--r--webapp/src/components/GroupModuleClientView.vue143
-rw-r--r--webapp/src/components/GroupModuleDialog.vue106
-rw-r--r--webapp/src/components/GroupModuleGroupView.vue190
-rw-r--r--webapp/src/store/groups.js4
5 files changed, 316 insertions, 175 deletions
diff --git a/webapp/src/components/DataTable.vue b/webapp/src/components/DataTable.vue
index 316da7b..aba0d3d 100644
--- a/webapp/src/components/DataTable.vue
+++ b/webapp/src/components/DataTable.vue
@@ -28,7 +28,7 @@
<template>
<div>
<v-layout wrap>
- <v-flex xs12 sm6 class="d-flex align-center search-wrapper">
+ <v-flex xs12 :sm6="!menuMode" class="d-flex align-center search-wrapper" :class="{ 'menu-mode-search-wrapper': menuMode }">
<div class="search-field-wrapper">
<div v-for="(s, index) in search" :key="index">
<v-text-field
@@ -61,10 +61,10 @@
<v-btn v-else icon @click="removeSearchField(s)"><v-icon>remove</v-icon></v-btn>
</div>
</div>
- <v-divider class="hidden-sm-and-up"></v-divider>
- <v-divider vertical class="hidden-xs-only"></v-divider>
+ <v-divider :class="{ 'hidden-sm-and-up': !menuMode }"></v-divider>
+ <v-divider v-if="!menuMode" vertical :class="{ 'hidden-xs-only': !menuMode }"></v-divider>
</v-flex>
- <v-flex style="padding: 6px 16px" class="d-flex align-start non-selectable" xs12 sm6>
+ <v-flex style="padding: 6px 16px" class="d-flex align-start non-selectable" xs12 :sm6="!menuMode">
<div class="d-flex align-center" style="font-size: 12px">
<div>
<v-tooltip top open-delay="800">
@@ -91,10 +91,11 @@
{{ filteredRows.length + ' ' + $t('entries') + ' (' + selected.length + ' ' + $t('selected') + ')' }}
</div>
- <div class="text-xs-right">
+ <div v-if="!pageMode && !menuMode" class="text-xs-right">
{{ $t('height') }}:
</div>
<v-select
+ v-if="!pageMode && !menuMode"
solo flat
class="rowcount-select"
v-model="rowCount"
@@ -114,15 +115,16 @@
<v-divider></v-divider>
<RecycleScroller
+ ref="scroller"
class="scroller non-selectable"
:style="scrollerStyle"
:items="filteredRows"
:item-height="48"
- :page-mode="rowCount <= 0"
+ :page-mode="(rowCount <= 0 || pageMode) && !menuMode"
@click.native.capture.passive="setShiftState"
>
<template slot="before-container">
- <div class="table-head-wrapper non-selectable" :style="{ 'min-width': minWidth }">
+ <div class="table-head-wrapper non-selectable" :style="{ 'min-width': minTableWidth }">
<div class="table-head">
<div class="non-selectable header-cell">
<v-icon @click="toggleSelectAll">
@@ -189,6 +191,14 @@ export default {
minWidth: {
type: String,
default: '600px'
+ },
+ pageMode: {
+ type: Boolean,
+ default: false
+ },
+ menuMode: {
+ type: Boolean,
+ default: false
}
},
data () {
@@ -211,9 +221,15 @@ export default {
searchableHeaders () {
return this.headers.filter(h => h.text !== undefined)
},
+ minTableWidth () {
+ return this.menuMode ? '200px' : this.minWidth
+ },
scrollerStyle () {
- const style = { '--min-table-width': this.minWidth }
- if (this.rowCount > 0) style.height = (Math.max(Math.min(this.rowCount, this.filteredRows.length), 1) * 48 + 58) + 'px'
+ const style = { '--min-table-width': this.minTableWidth }
+ var rowCount = this.rowCount
+ if (this.pageMode) rowCount = -1
+ else if (this.menuMode) rowCount = 6
+ if (rowCount > 0) style.height = (Math.max(Math.min(rowCount, this.filteredRows.length), 1) * 48 + 58) + 'px'
return style
},
dataKeys () { return this.headers.map(x => x.key) },
@@ -238,7 +254,8 @@ export default {
},
watch: {
rows () {
- this.processSelected(this.selected)
+ this.processSelected(this.value)
+ this.$emit('input', this.selected)
},
value: {
immediate: true,
@@ -270,6 +287,12 @@ export default {
}
},
methods: {
+ resetData () {
+ Object.assign(this.$data, this.$options.data.call(this))
+ this.processSelected(this.value)
+ this.$emit('input', this.selected)
+ if (this.$refs.scroller) this.$refs.scroller.$el.scrollTop = 0
+ },
selectItem (row, index) {
// Select or deselect this row
const selected = row.selected = !row.selected
@@ -448,6 +471,11 @@ export default {
}
}
+.menu-mode-search-wrapper {
+ flex-direction: column;
+ align-items: stretch;
+}
+
.search-field-wrapper {
display: flex;
flex-direction: column;
diff --git a/webapp/src/components/GroupModuleClientView.vue b/webapp/src/components/GroupModuleClientView.vue
index 03d9642..e47273b 100644
--- a/webapp/src/components/GroupModuleClientView.vue
+++ b/webapp/src/components/GroupModuleClientView.vue
@@ -30,60 +30,75 @@
<v-flex lg4 sm6 xs12 order-lg1 order-xs2>
<v-layout column>
<v-flex>
- <v-text-field v-if="editMode" prepend-icon="label" class="info-input" :label="$t('name')" color="primary" v-model="info.name"></v-text-field>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>label</v-icon><span>{{ $t('name') }}</span></div>
- <div class="info-text">{{ client.name || '-' }}</div>
+ <div class="info-text">
+ <v-text-field v-if="editMode" class="info-input" color="primary" v-model="info.name" hide-details></v-text-field>
+ <div v-else>{{ client.name || '-' }}</div>
+ </div>
</div>
</v-flex>
<v-flex>
- <v-autocomplete
- prepend-icon="device_hub"
- v-if="editMode"
- class="info-input"
- :items="$store.state.groups.groupList"
- v-model="groups"
- :menu-props="{ offsetY: '' }"
- :label="$t('groups')"
- color="primary"
- multiple
- item-value="id"
- item-text="name"
- return-object
- small-chips
- deletable-chips
- >
- </v-autocomplete>
- <div v-else class="info-input">
- <div class="body-2 info-heading"><v-icon>device_hub</v-icon><span>{{ $t('groups') }}</span></div>
+ <div class="info-box">
+ <div class="body-2 info-heading">
+ <v-icon>device_hub</v-icon><span>{{ $t('groups') }}</span>
+ <v-menu v-if="editMode" offset-y :close-on-content-click="false" :max-width="400" lazy>
+ <v-btn slot="activator" small icon class="info-heading-button"><v-icon>edit</v-icon></v-btn>
+ <v-card>
+ <data-table ref="datatable" v-model="groups" :headers="headers" :items="groupList" menu-mode></data-table>
+ </v-card>
+ </v-menu>
+ </div>
<div class="info-text">
- <template v-if="client.groups && client.groups.length > 0">
- <v-chip v-for="group in client.groups" :key="group.id" small>
+ <div>
+ <v-chip
+ v-for="(group, index) in groupChips"
+ :key="group.id"
+ v-if="index <= 5"
+ small
+ :close="editMode"
+ @input="removeGroupChip(index)"
+ >
{{ group.name || group.id }}
</v-chip>
- </template>
- <span v-else>-</span>
+ <span v-if="groupChips.length > 5" class="and-more-chip">...</span>
+ <span v-if="groupChips && groupChips.length === 0">-</span>
+ </div>
</div>
</div>
</v-flex>
<v-flex>
- <v-select v-if="editMode" class="info-input" prepend-icon="list" clearable item-text="name" item-value="id" :menu-props="{ offsetY: '' }" :label="$t('config')" color="primary" v-model="info.configId" :items="configList"></v-select>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>list</v-icon><span>{{ $t('config') }}</span></div>
- <div class="info-text">{{ configName || '-' }}</div>
+ <div class="info-text">
+ <v-select v-if="editMode"
+ class="info-input"
+ clearable
+ item-text="name"
+ item-value="id"
+ :menu-props="{ offsetY: '' }"
+ hide-details
+ color="primary"
+ v-model="info.configId"
+ :items="configList"
+ ></v-select>
+ <div v-else>{{ configName || '-' }}</div>
+ </div>
</div>
</v-flex>
</v-layout>
</v-flex>
<v-flex lg4 sm6 xs12 order-lg2 order-xs3>
- <v-textarea prepend-icon="description" v-if="editMode" rows="1" auto-grow class="info-input" :label="$t('description')" color="primary" v-model="info.description"></v-textarea>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>description</v-icon><span>{{ $t('description') }}</span></div>
- <div class="info-text">{{ client.description || '-' }}</div>
+ <div class="info-text">
+ <v-textarea v-if="editMode" class="info-input" rows="1" auto-grow hide-details color="primary" v-model="info.description"></v-textarea>
+ <div v-else style="white-space: pre-wrap;">{{ client.description || '-' }}</div>
+ </div>
</div>
</v-flex>
<v-flex lg4 xs12 order-lg3 order-xs1 class="text-xs-right">
- <div class="info-input">
+ <div class="info-box">
<div v-if="!editMode">
<v-btn color="error" flat @click="deleteClient" class="info-buttons">
<v-icon left>delete</v-icon>{{ $t('delete') }}
@@ -103,24 +118,30 @@
</v-layout>
<v-layout wrap>
<v-flex lg4 sm6 xs12>
- <v-text-field prepend-icon="language" v-if="editMode" class="info-input" :label="$t('ip')" color="primary" v-model="info.ip"></v-text-field>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>language</v-icon><span>{{ $t('ip') }}</span></div>
- <div class="info-text">{{ client.ip || '-' }}</div>
+ <div class="info-text">
+ <v-text-field v-if="editMode" class="info-input" color="primary" v-model="info.ip" hide-details></v-text-field>
+ <div v-else>{{ client.ip || '-' }}</div>
+ </div>
</div>
</v-flex>
<v-flex lg4 sm6 xs12>
- <v-text-field prepend-icon="memory" v-if="editMode" class="info-input" :label="$t('mac')" color="primary" v-model="info.mac"></v-text-field>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>memory</v-icon><span>{{ $t('mac') }}</span></div>
- <div class="info-text">{{ client.mac || '-' }}</div>
+ <div class="info-text">
+ <v-text-field v-if="editMode" class="info-input" color="primary" v-model="info.mac" hide-details></v-text-field>
+ <div v-else>{{ client.mac || '-' }}</div>
+ </div>
</div>
</v-flex>
<v-flex lg4 sm6 xs12>
- <v-text-field prepend-icon="fingerprint" v-if="editMode" class="info-input" :label="$t('uuid')" color="primary" v-model="info.uuid"></v-text-field>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>fingerprint</v-icon><span>{{ $t('uuid') }}</span></div>
- <div class="info-text">{{ client.uuid || '-' }}</div>
+ <div class="info-text">
+ <v-text-field v-if="editMode" class="info-input" color="primary" v-model="info.uuid" hide-details></v-text-field>
+ <div v-else>{{ client.uuid || '-' }}</div>
+ </div>
</div>
</v-flex>
</v-layout>
@@ -130,11 +151,15 @@
</template>
<script>
-import { mapMutations } from 'vuex'
+import DataTable from '@/components/DataTable'
+import { mapState, mapMutations } from 'vuex'
export default {
name: 'GroupModuleClientView',
props: ['tabIndex', 'client'],
+ components: {
+ DataTable
+ },
data () {
return {
editMode: false,
@@ -150,11 +175,18 @@ export default {
}
},
computed: {
- configList () {
- return this.$store.state.groups.configList
- },
+ ...mapState('groups', ['groupList', 'configList']),
configName () {
return this.$store.state.groups.configNames[this.client.configId]
+ },
+ groupChips () {
+ if (this.editMode) return this.groups
+ else return this.client.groups
+ },
+ headers () {
+ return [
+ { key: 'name', text: this.$t('name') }
+ ]
}
},
watch: {
@@ -165,6 +197,9 @@ export default {
},
methods: {
...mapMutations('groups', ['setDialog']),
+ removeGroupChip (index) {
+ this.groups.splice(index, 1)
+ },
editInfo () {
this.editMode = true
this.info.name = this.client.name
@@ -215,12 +250,14 @@ export default {
margin: 0;
}
-.info-input {
+.info-box {
padding: 20px;
}
-.info-input >>> input, .info-input >>> textarea {
- font-family: 'Roboto Mono';
+.info-input {
+ margin: 0;
+ padding: 0 0 1px 0;
+ overflow: hidden;
}
.info-heading {
@@ -229,12 +266,20 @@ export default {
margin-bottom: 10px;
}
+.info-heading-button {
+ margin: -2px 0 -2px 10px;
+}
+
.info-heading > span {
margin-left: 10px;
}
.info-text {
+ overflow-x: auto;
margin-left: 34px;
font-family: 'Roboto Mono';
+ min-height: 34px;
+ display: flex;
+ align-items: center;
}
</style>
diff --git a/webapp/src/components/GroupModuleDialog.vue b/webapp/src/components/GroupModuleDialog.vue
index 7c20e85..5dcab77 100644
--- a/webapp/src/components/GroupModuleDialog.vue
+++ b/webapp/src/components/GroupModuleDialog.vue
@@ -13,6 +13,10 @@
"add": {
"group": "Add groups | Add this group? | Add these {0} groups?",
"client": "Add clients | Add this client? | Add these {0} clients?"
+ },
+ "select": {
+ "group": "Select groups",
+ "client": "Select clients"
}
},
"success": {
@@ -35,7 +39,9 @@
},
"new": "New",
"id": "ID",
- "name": "Name"
+ "name": "Name",
+ "description": "Description",
+ "ip": "IP Address"
},
"de": {
"title": {
@@ -50,6 +56,10 @@
"add": {
"group": "Gruppen hinzufügen | Diese Gruppe hinzufügen? | Diese {0} Gruppen hinzufügen?",
"client": "Clienten hinzufügen | Diesen Clienten hinzufügen? | Diese {0} Clienten hinzufügen?"
+ },
+ "select": {
+ "group": "Gruppen auswählen",
+ "clienten": "Clienten auswählen"
}
},
"success": {
@@ -72,7 +82,9 @@
},
"new": "Neu",
"id": "ID",
- "name": "Name"
+ "name": "Name",
+ "description": "Beschreibung",
+ "ip": "IP Adresse"
}
}
</i18n>
@@ -81,10 +93,10 @@
<v-dialog
:value="dialog.show"
@input="setDialog({ show: $event })"
- :max-width="action === 'add' ? '1000px' : '500px'"
+ :max-width="actionWidthMap[action]"
scrollable
- :persistent="action === 'add'"
- :fullscreen="$vuetify.breakpoint.xsOnly"
+ :persistent="action === 'add' || action === 'select'"
+ :fullscreen="$vuetify.breakpoint.smAndDown"
>
<v-card>
<v-card-title primary-title class="dialog-title elevation-3">
@@ -94,25 +106,11 @@
<v-icon left>create</v-icon>{{ $t('new') }}
</v-btn>
</v-card-title>
- <v-card-text v-if="action === 'add'" class="table-container">
- <v-divider></v-divider>
- <component-search-table v-model="selected" :headers="headers" :items="items" select-all>
- <template slot="items" slot-scope="row">
- <tr :style="row.color" @click="row.data.selected = !row.data.selected">
- <td>
- <v-checkbox
- color="primary"
- v-model="row.data.selected"
- hide-details
- ></v-checkbox>
- </td>
- <td>{{ row.data.item.id }}</td>
- <td>{{ row.data.item.name }}</td>
- </tr>
- </template>
- </component-search-table>
+
+ <v-card-text v-if="action === 'add' || action === 'select'" class="table-container">
+ <data-table ref="datatable" v-model="selected" :headers="headers" :items="items" :page-mode="$vuetify.breakpoint.smAndDown"></data-table>
</v-card-text>
- <v-card-text v-else class="selected-list">
+ <v-card-text v-else-if="action === 'remove' || action === 'delete'" class="selected-list">
<v-checkbox
class="delete-checkbox"
v-if="dialog.info.action === 'remove'"
@@ -123,37 +121,53 @@
></v-checkbox>
<div v-for="item in dialog.info.selected" class="grey--text" :key="item.id">[{{ item.id }}] {{ item.name }}</div>
</v-card-text>
+
<v-divider></v-divider>
+
<v-card-actions>
<v-spacer></v-spacer>
<v-btn flat="flat" @click="setDialog({ show: false })">{{ $t('cancel') }}</v-btn>
- <v-btn :color="action === 'add' ? 'success' : 'error'" @click="submitAction">{{ $t(action) }}</v-btn>
+ <v-btn :color="actionColorMap[action]" @click="submitAction">{{ $t(action) }}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
-import ComponentSearchTable from '@/components/ComponentSearchTable'
+import DataTable from '@/components/DataTable'
import { mapState, mapMutations } from 'vuex'
export default {
name: 'GroupModuleDialog',
components: {
- ComponentSearchTable
+ DataTable
},
data () {
return {
selected: [],
- deleteInsteadOfRemove: false
+ deleteInsteadOfRemove: false,
+ actionColorMap: {
+ 'add': 'success',
+ 'remove': 'error',
+ 'delete': 'error',
+ 'select': 'primary'
+ },
+ actionWidthMap: {
+ 'add': '1000px',
+ 'remove': '500px',
+ 'delete': '500px',
+ 'select': '1000px'
+ }
}
},
computed: {
...mapState('groups', ['dialog', 'tabChain']),
headers () {
+ const lastColumn = this.dialog.info.type === 'client' ? 'ip' : 'description'
return [
- { text: this.$t('id'), value: 'id' },
- { text: this.$t('name'), value: 'name', width: '100000px' }
+ { key: 'id', text: this.$t('id'), width: '100px' },
+ { key: 'name', text: this.$t('name') },
+ { key: lastColumn, text: this.$t(lastColumn) }
]
},
action () {
@@ -168,19 +182,35 @@ export default {
items () {
if (this.dialog.info.type === 'group') return this.$store.state.groups.groupList
if (this.dialog.info.type === 'client') return this.$store.state.groups.clientList
+ },
+ dialogAction () {
+ return this.dialog.info ? this.dialog.info.action : undefined
}
},
- methods: {
- ...mapMutations('groups', ['setActiveTab', 'setTab']),
- setDialog (data) {
- this.$store.commit('groups/setDialog', data)
- if (data.show === false) {
- this.selected = []
- this.search = ''
- this.deleteInsteadOfRemove = false
- }
+ watch: {
+ dialogAction () {
+ if (this.$refs.datatable) this.$refs.datatable.resetData()
},
+ dialog: {
+ deep: true,
+ handler () {
+ if (this.dialog.show) {
+ this.deleteInsteadOfRemove = false
+ if (this.dialog.info.action === 'select') this.selected = this.dialog.info.selected
+ else this.selected = []
+ }
+ }
+ }
+ },
+ methods: {
+ ...mapMutations('groups', ['setActiveTab', 'setTab', 'setDialog']),
submitAction () {
+ if (this.action === 'select') {
+ this.dialog.info.callback(this.selected)
+ this.setDialog({ show: false })
+ return
+ }
+
const actionMap = {
'delete': { 'group': 'deleteGroups', 'client': 'deleteClients' },
'remove': { 'group': 'removeSubroups', 'client': 'removeClients' },
diff --git a/webapp/src/components/GroupModuleGroupView.vue b/webapp/src/components/GroupModuleGroupView.vue
index d3a66ab..41bff09 100644
--- a/webapp/src/components/GroupModuleGroupView.vue
+++ b/webapp/src/components/GroupModuleGroupView.vue
@@ -12,7 +12,8 @@
"config": "iPXE Config",
"parents": "Parents",
"startIp": "Start IP",
- "endIp": "End IP"
+ "endIp": "End IP",
+ "selectParents": "Select parents"
},
"de": {
"info": "Info",
@@ -26,7 +27,8 @@
"config": "iPXE Konfiguration",
"parents": "Übergruppen",
"startIp": "Start IP",
- "endIp": "End IP"
+ "endIp": "End IP",
+ "selectParents": "Übergruppen auswählen"
}
}
</i18n>
@@ -40,86 +42,103 @@
<v-flex lg4 sm6 xs12 order-lg1 order-xs2>
<v-layout column>
<v-flex>
- <v-text-field prepend-icon="label" v-if="editMode" class="info-input" :label="$t('name')" color="primary" v-model="info.name"></v-text-field>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>label</v-icon><span>{{ $t('name') }}</span></div>
- <div class="info-text">{{ group.name || '-' }}</div>
+ <div class="info-text">
+ <v-text-field v-if="editMode" class="info-input" color="primary" v-model="info.name" hide-details></v-text-field>
+ <div v-else>{{ group.name || '-' }}</div>
+ </div>
</div>
</v-flex>
<v-flex>
- <v-autocomplete
- prepend-icon="device_hub"
- v-if="editMode"
- class="info-input"
- :items="$store.state.groups.groupList"
- v-model="parents"
- :menu-props="{ offsetY: '' }"
- :label="$t('parents')"
- color="primary"
- multiple
- item-value="id"
- item-text="name"
- return-object
- small-chips
- deletable-chips
- >
- </v-autocomplete>
- <div v-else class="info-input">
- <div class="body-2 info-heading"><v-icon>device_hub</v-icon><span>{{ $t('parents') }}</span></div>
+ <div class="info-box">
+ <div class="body-2 info-heading">
+ <v-icon>device_hub</v-icon><span>{{ $t('parents') }}</span>
+ <v-menu v-if="editMode" offset-y :close-on-content-click="false" :max-width="400" lazy>
+ <v-btn slot="activator" small icon class="info-heading-button"><v-icon>edit</v-icon></v-btn>
+ <v-card>
+ <data-table ref="datatable" v-model="parents" :headers="headers" :items="groupList" menu-mode></data-table>
+ </v-card>
+ </v-menu>
+ </div>
<div class="info-text">
- <v-chip v-for="parent in group.parents" :key="parent.id" small>
- {{ parent.name || parent.id }}
- </v-chip>
- <span v-if="group.parents && group.parents.length === 0">-</span>
+ <div>
+ <v-chip
+ v-for="(parent, index) in parentChips"
+ :key="parent.id"
+ v-if="index <= 5"
+ small
+ :close="editMode"
+ @input="removeParentChip(index)"
+ >
+ {{ parent.name || parent.id }}
+ </v-chip>
+ <span v-if="parentChips.length > 5" class="and-more-chip">...</span>
+ <span v-if="parentChips && parentChips.length === 0">-</span>
+ </div>
</div>
</div>
</v-flex>
<v-flex>
- <v-select v-if="editMode" class="info-input" prepend-icon="list" clearable item-text="name" item-value="id" :menu-props="{ offsetY: '' }" :label="$t('config')" color="primary" v-model="info.configId" :items="configList"></v-select>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>list</v-icon><span>{{ $t('config') }}</span></div>
- <div class="info-text">{{ configName || '-' }}</div>
+ <div class="info-text">
+ <v-select v-if="editMode"
+ class="info-input"
+ clearable
+ item-text="name"
+ item-value="id"
+ :menu-props="{ offsetY: '' }"
+ hide-details
+ color="primary"
+ v-model="info.configId"
+ :items="configList"
+ ></v-select>
+ <div v-else>{{ configName || '-' }}</div>
+ </div>
</div>
</v-flex>
</v-layout>
</v-flex>
<v-flex lg4 sm6 xs12 order-lg2 order-xs3>
- <v-textarea prepend-icon="description" v-if="editMode" rows="1" auto-grow class="info-input" :label="$t('description')" color="primary" v-model="info.description"></v-textarea>
- <div v-else class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading"><v-icon>description</v-icon><span>{{ $t('description') }}</span></div>
- <pre class="info-text">{{ group.description || '-' }}</pre>
+ <div class="info-text">
+ <v-textarea v-if="editMode" class="info-input" rows="1" auto-grow hide-details color="primary" v-model="info.description"></v-textarea>
+ <div v-else style="white-space: pre-wrap;">{{ group.description || '-' }}</div>
+ </div>
</div>
- <div v-if="editMode" class="info-input">
+ <div class="info-box">
<div class="body-2 info-heading">
<v-icon>settings_ethernet</v-icon><span>{{ $t('ipranges') }}</span>
- <v-btn small icon @click="addIprange"><v-icon>add</v-icon></v-btn>
- </div>
- <div>
- <div v-for="(iprange, index) in ipranges" class="iprange" :key="index">
- <v-btn small icon class="remove-iprange" @click="removeIprange(index)"><v-icon>remove</v-icon></v-btn>
- <v-text-field :label="$t('startIp')" color="primary" v-model="iprange.startIp"></v-text-field>
- <span>-</span>
- <v-text-field :label="$t('endIp')" color="primary" v-model="iprange.endIp"></v-text-field>
- </div>
+ <v-btn v-if="editMode" small icon @click="addIprange" class="info-heading-button"><v-icon>add</v-icon></v-btn>
</div>
- </div>
- <div v-else class="info-input">
- <div class="body-2 info-heading"><v-icon>settings_ethernet</v-icon><span>{{ $t('ipranges') }}</span></div>
<div class="info-text">
- <table>
- <tr v-for="(iprange, index) in group.ipranges" :key="index">
- <td class="text-xs-right">{{ iprange.startIp }}</td>
- <td class="ip-seperator">-</td>
- <td>{{ iprange.endIp }}</td>
- </tr>
- </table>
- <div v-if="group.ipranges && group.ipranges.length === 0">-</div>
+ <div v-if="editMode">
+ <div v-for="(iprange, index) in ipranges" :key="index" class="iprange">
+ <v-btn class="iprange-remove-button" small icon @click="removeIprange(index)"><v-icon>remove</v-icon></v-btn>
+ <v-text-field class="info-input" hide-details :label="$t('startIp')" color="primary" v-model="iprange.startIp" single-line></v-text-field>
+ <span class="ip-seperator">-</span>
+ <v-text-field class="info-input" hide-details :label="$t('endIp')" color="primary" v-model="iprange.endIp" single-line></v-text-field>
+ </div>
+ <div v-if="ipranges && ipranges.length === 0">-</div>
+ </div>
+ <div v-else>
+ <table>
+ <tr v-for="(iprange, index) in group.ipranges" :key="index">
+ <td class="text-xs-right">{{ iprange.startIp }}</td>
+ <td class="ip-seperator">-</td>
+ <td>{{ iprange.endIp }}</td>
+ </tr>
+ </table>
+ <div v-if="group.ipranges && group.ipranges.length === 0">-</div>
+ </div>
</div>
</div>
</v-flex>
<v-flex lg4 xs12 order-lg3 order-xs1 class="text-xs-right">
- <div class="info-input">
+ <div class="info-box">
<div v-if="!editMode">
<v-btn color="error" flat @click="deleteGroup" class="info-buttons">
<v-icon left>delete</v-icon>{{ $t('delete') }}
@@ -162,14 +181,16 @@
<script>
import GroupModuleGroupList from '@/components/GroupModuleGroupList'
import GroupModuleClientList from '@/components/GroupModuleClientList'
-import { mapMutations } from 'vuex'
+import DataTable from '@/components/DataTable'
+import { mapState, mapMutations } from 'vuex'
export default {
name: 'GroupModuleGroupView',
props: ['tabIndex', 'group'],
components: {
GroupModuleGroupList,
- GroupModuleClientList
+ GroupModuleClientList,
+ DataTable
},
data () {
return {
@@ -180,15 +201,22 @@ export default {
configId: null
},
parents: [],
- ipranges: [{ startIp: '', endIp: '' }]
+ ipranges: []
}
},
computed: {
- configList () {
- return this.$store.state.groups.configList
- },
+ ...mapState('groups', ['groupList', 'configList']),
configName () {
return this.$store.state.groups.configNames[this.group.configId]
+ },
+ parentChips () {
+ if (this.editMode) return this.parents
+ else return this.group.parents
+ },
+ headers () {
+ return [
+ { key: 'name', text: this.$t('name') }
+ ]
}
},
watch: {
@@ -209,13 +237,16 @@ export default {
addIprange () {
this.ipranges.push({ startIp: '', endIp: '' })
},
+ removeParentChip (index) {
+ this.parents.splice(index, 1)
+ },
editInfo () {
this.editMode = true
this.info.name = this.group.name
this.info.description = this.group.description
this.info.configId = this.group.configId
this.parents = this.group.parents ? this.group.parents.slice(0) : []
- this.ipranges = this.group.ipranges ? this.group.ipranges.slice(0) : [{ startIp: '', endIp: '' }]
+ this.ipranges = this.group.ipranges ? this.group.ipranges.slice(0) : []
},
cancelEdit () {
this.editMode = false
@@ -226,6 +257,7 @@ export default {
},
saveData () {
this.info.configId = this.info.configId === undefined ? null : this.info.configId
+ this.ipranges = this.ipranges.filter(iprange => iprange.startIp && iprange.endIp)
this.$store.dispatch('groups/saveGroup', {
id: this.group.id,
data: this.info,
@@ -261,30 +293,29 @@ export default {
.iprange {
display: flex;
align-items: center;
+ min-height: 34px;
}
-.ip-seperator {
- padding: 0 10px;
+.iprange >>> input {
+ font-size: 14px;
}
-.iprange > .remove-iprange {
- margin: 0 8px 0 -2px;
+.ip-seperator {
+ padding: 0 10px;
}
-.iprange >>> input {
- font-size: 14px;
+.iprange-remove-button {
+ margin: 0 8px 0 0;
}
-.iprange > span {
- margin: 0 10px;
+.info-box {
+ padding: 20px;
}
.info-input {
- padding: 10px 20px;
-}
-
-.info-input >>> input, .info-input >>> textarea {
- font-family: 'Roboto Mono';
+ margin: 0;
+ padding: 0 0 1px 0;
+ overflow: hidden;
}
.info-heading {
@@ -293,6 +324,10 @@ export default {
margin-bottom: 10px;
}
+.info-heading-button {
+ margin: -2px 0 -2px 10px;
+}
+
.info-heading > span {
margin-left: 10px;
}
@@ -301,6 +336,9 @@ export default {
overflow-x: auto;
margin-left: 34px;
font-family: 'Roboto Mono';
+ min-height: 34px;
+ display: flex;
+ align-items: center;
}
.show-toggle {
diff --git a/webapp/src/store/groups.js b/webapp/src/store/groups.js
index 8315dd0..df00bcd 100644
--- a/webapp/src/store/groups.js
+++ b/webapp/src/store/groups.js
@@ -51,8 +51,8 @@ export default {
},
loadLists (context) {
Promise.all([axios.get('/api/groups'), axios.get('/api/clients')]).then(res => {
- context.commit('setGroupList', res[0].data.map(x => ({ id: x.id, name: x.name || x.id })))
- context.commit('setClientList', res[1].data.map(x => ({ id: x.id, name: x.name || x.id })))
+ context.commit('setGroupList', res[0].data)
+ context.commit('setClientList', res[1].data)
})
},
loadGroup (context, { id, name, tabIndex, switchTab, reload, save, placeholderName }) {