summaryrefslogtreecommitdiffstats
path: root/webapp/src/components/GroupModuleGroupInfo.vue
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/src/components/GroupModuleGroupInfo.vue')
-rw-r--r--webapp/src/components/GroupModuleGroupInfo.vue336
1 files changed, 336 insertions, 0 deletions
diff --git a/webapp/src/components/GroupModuleGroupInfo.vue b/webapp/src/components/GroupModuleGroupInfo.vue
new file mode 100644
index 0000000..8e06a3e
--- /dev/null
+++ b/webapp/src/components/GroupModuleGroupInfo.vue
@@ -0,0 +1,336 @@
+<i18n>
+{
+ "en": {
+ "info": "Info",
+ "groups": "Groups",
+ "subgroups": "Subgroups",
+ "clients": "Clients",
+ "name": "Name",
+ "description": "Description",
+ "ipranges": "IP Ranges",
+ "config": "iPXE Config",
+ "parents": "Parents",
+ "startIp": "Start IP",
+ "endIp": "End IP",
+ "more": "more"
+ },
+ "de": {
+ "info": "Info",
+ "groups": "Groups",
+ "subgroups": "Untergruppen",
+ "clients": "Clients",
+ "name": "Name",
+ "description": "Beschreibung",
+ "ipranges": "IP Bereiche",
+ "config": "iPXE Konfiguration",
+ "parents": "Übergruppen",
+ "startIp": "Start IP",
+ "endIp": "End IP",
+ "more": "mehr"
+ }
+}
+</i18n>
+
+<template>
+ <div>
+ <v-divider></v-divider>
+ <v-card-text>
+ <v-layout wrap>
+ <v-flex lg4 sm6 xs12 order-lg1 order-xs2>
+ <v-layout column>
+ <v-flex>
+ <div class="info-box">
+ <div class="body-2 info-heading"><v-icon>label</v-icon><span>{{ $t('name') }}</span></div>
+ <div class="info-text">
+ <v-text-field v-if="editMode" class="info-input" color="primary" v-model="info.name" hide-details tabindex="1"></v-text-field>
+ <div v-else>{{ group.name || '-' }}</div>
+ </div>
+ </div>
+ </v-flex>
+ <v-flex>
+ <div class="info-box">
+ <div class="body-2 info-heading"><v-icon>device_hub</v-icon><span>{{ $t('parents') }}</span></div>
+ <div class="info-text">
+ <select-box v-if="editMode" v-model="parents" :items="groupList" class="info-input" hide-details></select-box>
+ <div v-else class="chip-container non-selectable">
+ <v-tooltip v-for="parent in firstParents" :key="parent.id" top open-delay="800">
+ <template #activator="{ on }">
+ <v-chip v-on="on" small label style="width: calc(50% - 8px)" @click="openParent(parent)">
+ <span class="chip-text">{{ parent.name || parent.id }}</span>
+ </v-chip>
+ </template>
+ <span>{{ parent.name || parent.id }}</span>
+ </v-tooltip>
+ <span v-if="group.parents && group.parents.length > 5" class="and-more">+ {{ group.parents.length - 5 }} {{ $t('more') }}</span>
+ <span v-else-if="group.parents === undefined || group.parents.length === 0">-</span>
+ </div>
+ </div>
+ </div>
+ </v-flex>
+ <v-flex>
+ <div class="info-box">
+ <div class="body-2 info-heading"><v-icon>list</v-icon><span>{{ $t('config') }}</span></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>{{ group.config ? (group.config.name || group.config.id) : '-' }}</div>
+ </div>
+ </div>
+ </v-flex>
+ </v-layout>
+ </v-flex>
+ <v-flex lg4 sm6 xs12 order-lg2 order-xs3>
+ <div class="info-box">
+ <div class="body-2 info-heading"><v-icon>description</v-icon><span>{{ $t('description') }}</span></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" tabindex="2"></v-textarea>
+ <div v-else style="white-space: pre-wrap;">{{ group.description || '-' }}</div>
+ </div>
+ </div>
+
+ <div class="info-box">
+ <div class="body-2 info-heading"><v-icon>settings_ethernet</v-icon><span>{{ $t('ipranges') }}</span></div>
+ <div class="info-text">
+ <div v-if="editMode">
+ <div v-for="(iprange, index) in ipranges" :key="index">
+ <div class="iprange">
+ <v-btn class="iprange-button" small icon @click="removeIprange(index)"><v-icon>remove</v-icon></v-btn>
+ <v-text-field solo flat hide-details :label="$t('startIp')" color="primary" v-model="iprange.startIp" single-line></v-text-field>
+ <span class="ip-seperator">-</span>
+ <v-text-field solo flat hide-details :label="$t('endIp')" color="primary" v-model="iprange.endIp" single-line></v-text-field>
+ </div>
+ <v-divider></v-divider>
+ </div>
+ <div class="iprange-add-wrapper">
+ <v-btn class="iprange-button" small icon @click="addIprange"><v-icon>add</v-icon></v-btn>
+ </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-box">
+ <div v-if="!editMode">
+ <v-btn color="error" flat @click="deleteGroup" class="info-buttons">
+ <v-icon left>delete</v-icon>{{ $t('delete') }}
+ </v-btn>
+ <v-btn color="primary" flat @click="editInfo" class="info-buttons">
+ <v-icon left>create</v-icon>{{ $t('edit') }}
+ </v-btn>
+ </div>
+ <div v-else>
+ <v-btn color="primary" flat @click="cancelEdit" class="info-buttons">{{ $t('cancel') }}</v-btn>
+ <v-btn color="primary" @click="saveData" class="info-buttons" tabindex="3">
+ <v-icon left>save</v-icon>{{ $t('save') }}
+ </v-btn>
+ </div>
+ </div>
+ </v-flex>
+ </v-layout>
+ </v-card-text>
+ </div>
+</template>
+
+<script>
+import SelectBox from '@/components/SelectBox'
+import { mapState, mapMutations } from 'vuex'
+
+export default {
+ name: 'GroupModuleGroupInfo',
+ props: {
+ group: {
+ type: Object,
+ default: () => {}
+ },
+ tabIndex: {
+ type: Number
+ }
+ },
+ components: {
+ SelectBox
+ },
+ data () {
+ return {
+ editMode: false,
+ info: {
+ name: '',
+ description: '',
+ configId: null
+ },
+ parents: [],
+ ipranges: []
+ }
+ },
+ computed: {
+ ...mapState('groups', ['groupList', 'configList']),
+ firstParents () {
+ return this.group.parents ? this.group.parents.slice(0, 5) : []
+ }
+ },
+ watch: {
+ group (newValue, oldValue) {
+ if (newValue.id === 'create') this.editInfo()
+ else if (newValue.id !== oldValue.id) this.editMode = false
+ }
+ },
+ methods: {
+ ...mapMutations('groups', ['setDialog', 'setActiveTab', 'adjustTabSlider', 'deleteFromTabChain']),
+ removeIprange (index) {
+ this.ipranges.splice(index, 1)
+ },
+ addIprange () {
+ this.ipranges.push({ startIp: '', endIp: '' })
+ },
+ 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) : []
+ },
+ cancelEdit () {
+ this.editMode = false
+ if (this.group.id === 'create') {
+ this.deleteFromTabChain({ index: this.tabIndex, count: 1 })
+ this.setActiveTab(this.tabIndex - 1)
+ }
+ },
+ saveData () {
+ this.info.configId = this.info.configId === undefined ? null : this.info.configId
+ this.ipranges = this.ipranges.filter(iprange => iprange.startIp && iprange.endIp)
+ this.adjustTabSlider()
+ this.$store.dispatch('groups/saveGroup', {
+ id: this.group.id,
+ data: this.info,
+ parents: this.parents,
+ ipranges: this.ipranges,
+ tabIndex: this.tabIndex,
+ callback: this.updateUrl
+ })
+ this.editMode = false
+ },
+ deleteGroup () {
+ this.setDialog({ show: true, info: { action: 'delete', type: 'group', selected: [this.group] } })
+ },
+ updateUrl (id) {
+ this.$router.replace({
+ name: 'GroupModule.group',
+ params: { id, noReload: true }
+ })
+ },
+ openParent (parent) {
+ this.$store.dispatch('groups/loadGroup', { id: parent.id, name: parent.name, tabIndex: this.tabIndex, asParent: true, switchTab: true })
+ }
+ },
+ created () {
+ if (this.group.id === 'create') this.editInfo()
+ }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.info-buttons {
+ margin: 0;
+}
+
+.iprange {
+ display: flex;
+ align-items: center;
+ min-height: 34px;
+}
+
+.iprange-add-wrapper {
+ height: 32px;
+ display: flex;
+ align-items: center;
+}
+
+.iprange >>> .v-text-field.v-text-field--solo .v-input__control {
+ min-height: 32px;
+}
+
+.iprange >>> .v-label, .iprange >>> input {
+ font-size: 14px;
+}
+
+.chip-container {
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+}
+
+.chip-container >>> .v-chip__content {
+ width: 100%;
+ cursor: pointer;
+}
+
+.chip-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.and-more {
+ font-size: 13px;
+ display: flex;
+ align-items: center;
+ margin: 4px 17px;
+}
+
+.ip-seperator {
+ padding: 0 10px;
+}
+
+.iprange-button {
+ margin: 0 8px 0 0;
+}
+
+.info-box {
+ padding: 20px;
+}
+
+.info-input {
+ margin: 0;
+ padding: 0 0 1px 0;
+ overflow: hidden;
+}
+
+.info-heading {
+ display: flex;
+ align-items: center;
+ margin-bottom: 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>