ipnetwork v2 update
This commit is contained in:
@@ -1,226 +1,285 @@
|
||||
// IpNetwork.js
|
||||
Vue.component('IpNetwork', {
|
||||
//language=Vue
|
||||
template: `
|
||||
<tt-card>
|
||||
|
||||
<tt-table :fetch-url="window['TT_CONFIG']['IPNETWORK_API_URL'] + '?do=get'"
|
||||
:config="IpNetworkTableConfig"
|
||||
@row-click="(row) => row.cidr !== '32' && switchCurrentNetwork(row.id)"
|
||||
@reset-table="switchCurrentNetwork"
|
||||
small ssr disable-initial-fetch ref="table">
|
||||
|
||||
<template v-slot:top-buttons>
|
||||
|
||||
<button type="button" class="btn btn-primary"
|
||||
@click="switchCurrentNetwork(currentNetworkData.parent_network_id)"
|
||||
:disabled="!currentNetworkData">
|
||||
<i class="fas fa-sync-alt"></i>Go Back
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-primary" @click="openModal(false)">
|
||||
<i class="fas fa-sync-alt"></i>Add new Network Space
|
||||
</button>
|
||||
|
||||
</template>
|
||||
|
||||
<!-- add $slots.expandedRow to the table component and display discription -->
|
||||
<template v-slot:expandedRow="{row}">
|
||||
<span style="white-space: pre;" v-if="row.description" v-text="row.description"></span>
|
||||
<span v-else>No description</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:actions="{ row }">
|
||||
<div style="display: flex; justify-content: space-around; align-items: center; min-width: 20px">
|
||||
<a style="cursor: pointer;" @click.stop="openModal(row)"><i class="far fa-edit text-primary"
|
||||
title="Editieren"></i></a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</tt-table>
|
||||
|
||||
<!-- add modal -->
|
||||
<div class="modal show d-block" tabindex="-1" role="dialog" style="background: rgba(0, 0, 0, 0.5);"
|
||||
ref="addModal" @click="addModal = false" @keydown.esc="addModal = false" v-if="addModal === true">
|
||||
<div class="modal-dialog" role="document" @click.stop>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit DNS Record</h5>
|
||||
<button type="button" class="close" @click="addModal = false">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="wrapper" style="display:grid; grid-template-columns: 3fr 1fr 2fr; grid-gap: 12px">
|
||||
<div class="form-group">
|
||||
<label for="network_address">Network Address</label>
|
||||
<input type="text" class="form-control" id="network_address"
|
||||
:disabled="!!addModalData.id"
|
||||
v-model="addModalData.network_address">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cidr">CIDR</label>
|
||||
<input type="text" class="form-control" id="cidr" :disabled="!!addModalData.id" v-model="addModalData.cidr">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status">Status</label>
|
||||
<select class="form-control" id="status" v-model="addModalData.status">
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
<option value="reserved">Reserved</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="grid-column: span 2">
|
||||
<label for="name_location">Name</label>
|
||||
<input type="text" class="form-control" id="name_location"
|
||||
v-model="addModalData.name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name_location">Location</label>
|
||||
<input type="text" class="form-control" id="name_location"
|
||||
v-model="addModalData.location">
|
||||
</div>
|
||||
<div class="form-group" style="grid-column: span 3">
|
||||
<label for="description">Description</label>
|
||||
<input type="text" class="form-control" id="description"
|
||||
v-model="addModalData.description">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" @click="addSubmit">Save</button>
|
||||
<button class="btn btn-danger" @click="deleteSubmit" v-if="addModalData.id">Delete</button>
|
||||
<button class="btn btn-secondary" @click="addModal = false">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div>
|
||||
<tt-button
|
||||
v-if="currentNetworkData"
|
||||
@click="navigateBack"
|
||||
icon="fas fa-arrow-left"
|
||||
text="Zurück"
|
||||
additional-class="btn-secondary mr-2"
|
||||
/>
|
||||
<tt-button
|
||||
@click="openModal(null)"
|
||||
icon="fas fa-plus"
|
||||
text="Netzwerk hinzufügen"
|
||||
additional-class="btn-primary"
|
||||
/>
|
||||
</div>
|
||||
<div style="max-width: 400px;min-width:300px; margin-left: 1rem;">
|
||||
<tt-autocomplete
|
||||
v-model="globalSearchTerm"
|
||||
@input="handleGlobalSearchSelect"
|
||||
:api-url="apiUrl + '/globalSearch'"
|
||||
placeholder="Suche nach IP, Name, Beschreibung..."
|
||||
sm
|
||||
no-form-group
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<tt-table
|
||||
:fetch-url="apiUrl + '/get'"
|
||||
:config="IpNetworkTableConfig"
|
||||
@row-click="row => row.cidr < 31 && switchCurrentNetwork(row.id)"
|
||||
@reset-table="resetFiltersAndSwitch"
|
||||
small
|
||||
ssr
|
||||
ref="table"
|
||||
>
|
||||
<template v-slot:expandedRow="{ row }">
|
||||
<div class="p-2 bg-light">
|
||||
<strong>Beschreibung:</strong>
|
||||
<span style="white-space: pre-wrap;" v-if="row.description" v-text="row.description"></span>
|
||||
<em v-else class="text-muted">Keine Beschreibung vorhanden.</em>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:actions="{ row }">
|
||||
<div class="text-center">
|
||||
<tt-button
|
||||
icon="far fa-edit"
|
||||
title="Bearbeiten"
|
||||
@click.stop="openModal(row)"
|
||||
additional-class="btn-link text-primary p-0"
|
||||
sm
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tt-table>
|
||||
|
||||
<tt-modal
|
||||
v-if="modalData"
|
||||
:show="true"
|
||||
@update:show="closeModal"
|
||||
@submit="submitModal"
|
||||
@delete="deleteNetwork"
|
||||
:delete="!!modalData.id"
|
||||
:title="modalData.id ? 'Netzwerk bearbeiten' : 'Netzwerk hinzufügen'"
|
||||
save-text="Speichern"
|
||||
delete-text="Löschen"
|
||||
>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-8">
|
||||
<label for="network_address">Netzwerkadresse / CIDR</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control" id="network_address" v-model="modalData.network_address" :disabled="!!modalData.id" required placeholder="z.B. 192.168.1.0">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">/</span>
|
||||
</div>
|
||||
<input type="number" class="form-control" style="max-width: 80px;" v-model="modalData.cidr" :disabled="!!modalData.id" required placeholder="24">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="status">Status</label>
|
||||
<select id="status" class="form-control form-control-sm" v-model="modalData.status" required>
|
||||
<option v-for="option in statusOptions" :value="option.value">{{ option.text }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control form-control-sm" id="name" v-model="modalData.name" required>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="location">Standort</label>
|
||||
<input type="text" class="form-control form-control-sm" id="location" v-model="modalData.location">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">Beschreibung</label>
|
||||
<textarea class="form-control form-control-sm" id="description" v-model="modalData.description" rows="3"></textarea>
|
||||
</div>
|
||||
</tt-modal>
|
||||
</tt-card>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
window: window,
|
||||
apiUrl: window['TT_CONFIG']['IPNETWORK_API_URL'],
|
||||
apiUrl: window.TT_CONFIG.API_BASE_URL,
|
||||
currentNetworkData: null,
|
||||
modalData: null,
|
||||
globalSearchTerm: '',
|
||||
isNavigating: false,
|
||||
searchDebounce: null,
|
||||
statusOptions: [
|
||||
{value: 'active', text: 'Aktiv'},
|
||||
{value: 'inactive', text: 'Inaktiv'},
|
||||
{value: 'reserved', text: 'Reserviert'}
|
||||
],
|
||||
IpNetworkTableConfig: {
|
||||
defaultPageSize: 50,
|
||||
customRowClass: function (row) {
|
||||
return row.cidr !== '32' ? 'tt-pointer' : '';
|
||||
},
|
||||
expandCondition: function (row) {
|
||||
return !!row.description;
|
||||
},
|
||||
customRowClass: row => (row.cidr < 31 ? 'tt-pointer' : ''),
|
||||
expandCondition: row => !!row.description,
|
||||
headers: [
|
||||
{text: 'Network Address', key: 'network_address_str'},
|
||||
{text: 'Name', key: 'name'},
|
||||
{
|
||||
text: 'Status', key: 'status', filter: 'iconSelect',
|
||||
filterOptions: [{value: 'active', text: 'Active', icon: 'fas fa-check text-success'},
|
||||
{value: 'inactive', text: 'Inactive', icon: 'fas fa-times text-danger'},
|
||||
{value: 'reserved', text: 'Reserved', icon: 'fas fa-lock text-warning'}]
|
||||
{text: 'Netzwerkadresse', key: 'network_address_str', sortable: true},
|
||||
{text: 'Name', key: 'name', sortable: true},
|
||||
{text: 'Status', key: 'status', filter: 'iconSelect', sortable: true,
|
||||
filterOptions: [
|
||||
{value: 'active', text: 'Aktiv', icon: 'fas fa-check text-success'},
|
||||
{value: 'inactive', text: 'Inaktiv', icon: 'fas fa-times text-danger'},
|
||||
{value: 'reserved', text: 'Reserviert', icon: 'fas fa-lock text-warning'}
|
||||
]
|
||||
},
|
||||
{text: 'Children', key: 'children', filter: 'numberRange'},
|
||||
{text: 'Aktionen', key: 'actions', sortable: false},
|
||||
{text: 'Subnetze', key: 'children', filter: 'numberRange', sortable: true},
|
||||
{text: 'Standort', key: 'location', sortable: true},
|
||||
{text: 'Aktionen', key: 'actions', sortable: false, class: 'text-center'},
|
||||
],
|
||||
tableHeader: 'IPAM',
|
||||
tableHeader: 'IPAM - Stamm',
|
||||
key: 'IpNetwork'
|
||||
},
|
||||
currentNetworkData: null,
|
||||
addModal: false,
|
||||
addModalData: {
|
||||
network_address: '',
|
||||
cidr: '',
|
||||
parent_network_id: '',
|
||||
status: 'active',
|
||||
name: '',
|
||||
description: '',
|
||||
location: '',
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
function popstateFunction() {
|
||||
const parentNetworkId = new URLSearchParams(window.location.search).get('parent_network_id');
|
||||
this.switchCurrentNetwork(parentNetworkId).then();
|
||||
watch: {
|
||||
globalSearchTerm(newValue) {
|
||||
if (this.isNavigating) {
|
||||
this.isNavigating = false;
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.searchDebounce);
|
||||
this.searchDebounce = setTimeout(() => {
|
||||
this.$refs.table.filters.globalSearch = this.globalSearchTerm;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
window.onpopstate = popstateFunction.bind(this);
|
||||
window.onpopstate.call(this)
|
||||
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('popstate', this.handlePopState);
|
||||
this.handlePopState(); // Initial load based on URL
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('popstate', this.handlePopState);
|
||||
},
|
||||
methods: {
|
||||
openModal(row = false) {
|
||||
if (row) {
|
||||
const data = JSON.parse(JSON.stringify(row));
|
||||
async handleGlobalSearchSelect(selectedNetworkString) {
|
||||
if (!selectedNetworkString || typeof selectedNetworkString !== 'string') {
|
||||
this.globalSearchTerm = selectedNetworkString;
|
||||
return;
|
||||
}
|
||||
this.globalSearchTerm = selectedNetworkString;
|
||||
|
||||
|
||||
this.addModalData = {
|
||||
id: data.id,
|
||||
network_address: data.network_address_str.split('/')[0],
|
||||
cidr: data.cidr,
|
||||
parent_network_id: this.currentNetworkData ? this.currentNetworkData.id : '',
|
||||
status: data.status,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
location: data.location,
|
||||
try {
|
||||
const response = await axios.get(`${this.apiUrl}/findNetworkByString`, { params: { network_string: selectedNetworkString } });
|
||||
if (response.data.success) {
|
||||
this.switchCurrentNetwork(response.data.navigateToId);
|
||||
this.isNavigating = true;
|
||||
this.globalSearchTerm = '';
|
||||
} else {
|
||||
window.notify('error', response.data.message || 'Netzwerk konnte nicht gefunden werden.');
|
||||
}
|
||||
} catch (e) {
|
||||
window.notify('error', e.response?.data?.message || 'Fehler bei der Netzwerksuche.');
|
||||
}
|
||||
},
|
||||
handlePopState() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const parentId = params.get('parent_network_id') || null;
|
||||
this.switchCurrentNetwork(parentId, false); // false to not push state
|
||||
},
|
||||
openModal(row = null) {
|
||||
if (row) {
|
||||
this.modalData = {
|
||||
id: row.id,
|
||||
network_address: row.network_address_str.split('/')[0],
|
||||
cidr: row.cidr,
|
||||
status: row.status,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
location: row.location,
|
||||
};
|
||||
} else {
|
||||
this.addModalData = {
|
||||
this.modalData = {
|
||||
network_address: '',
|
||||
cidr: '',
|
||||
parent_network_id: this.currentNetworkData ? this.currentNetworkData.id : '',
|
||||
status: 'active',
|
||||
name: '',
|
||||
description: '',
|
||||
location: '',
|
||||
};
|
||||
}
|
||||
this.addModal = true;
|
||||
},
|
||||
async switchCurrentNetwork(networkId = null) {
|
||||
if (!networkId) {
|
||||
this.$refs.table.$set(this.$refs.table.filters, 'parent_network_id', undefined);
|
||||
this.currentNetworkData = null;
|
||||
this.IpNetworkTableConfig.tableHeader = 'IPAM';
|
||||
this.$refs.table.disableDebounce = true;
|
||||
window.history.pushState({}, '', `?`);
|
||||
} else {
|
||||
this.$refs.table.disableDebounce = true;
|
||||
this.$refs.table.$set(this.$refs.table.filters, 'parent_network_id', networkId);
|
||||
window.history.pushState({}, '', `?parent_network_id=${networkId}`);
|
||||
closeModal() {
|
||||
this.modalData = null;
|
||||
},
|
||||
async switchCurrentNetwork(networkId = null, pushState = true) {
|
||||
this.$refs.table.filters = { ...this.$refs.table.filters, parent_network_id: networkId || undefined };
|
||||
|
||||
const response = await axios.post(`${this.apiUrl}?do=getById`, {id: networkId});
|
||||
this.currentNetworkData = response.data.network;
|
||||
this.IpNetworkTableConfig.tableHeader = `IPAM - ${this.currentNetworkData.network_address_str}/${this.currentNetworkData.cidr} - ${this.currentNetworkData.name}`;
|
||||
if (pushState) {
|
||||
const url = new URL(window.location);
|
||||
if (networkId) {
|
||||
url.searchParams.set('parent_network_id', networkId);
|
||||
} else {
|
||||
url.searchParams.delete('parent_network_id');
|
||||
}
|
||||
window.history.pushState({path: url.href}, '', url.href);
|
||||
}
|
||||
await this.$refs.table.fetchData();
|
||||
},
|
||||
async addSubmit() {
|
||||
const response = await axios.post(`${this.apiUrl}?do=${this.addModalData.id ? 'update' : 'create'}`,
|
||||
{
|
||||
...this.addModalData,
|
||||
parent_network_id: this.currentNetworkData ? this.currentNetworkData.id : null
|
||||
});
|
||||
if (response.data.status === 'success') {
|
||||
this.addModal = false;
|
||||
this.addModalData = {};
|
||||
window.notify('success', 'Network space created successfully');
|
||||
await this.$refs.table.fetchData();
|
||||
|
||||
if (networkId) {
|
||||
try {
|
||||
const response = await axios.get(`${this.apiUrl}/getById?id=${networkId}`);
|
||||
this.currentNetworkData = response.data.network;
|
||||
this.IpNetworkTableConfig.tableHeader = `IPAM - ${this.currentNetworkData.network_address_str} ${this.currentNetworkData.name ? '- ' + this.currentNetworkData.name : ''}`;
|
||||
} catch (e) {
|
||||
window.notify('error', 'Details des übergeordneten Netzwerks konnten nicht geladen werden.');
|
||||
this.currentNetworkData = null;
|
||||
this.IpNetworkTableConfig.tableHeader = 'IPAM - Stamm';
|
||||
}
|
||||
} else {
|
||||
window.notify('error', response.data.message);
|
||||
this.currentNetworkData = null;
|
||||
this.IpNetworkTableConfig.tableHeader = 'IPAM - Stamm';
|
||||
}
|
||||
},
|
||||
async deleteSubmit() {
|
||||
const response = await axios.post(`${this.apiUrl}?do=delete`, {id: this.addModalData.id});
|
||||
if (response.data.status === 'success') {
|
||||
this.addModal = false;
|
||||
this.addModalData = {};
|
||||
window.notify('success', 'Network space deleted successfully');
|
||||
await this.$refs.table.fetchData();
|
||||
} else {
|
||||
window.notify('error', response.data.message);
|
||||
resetFiltersAndSwitch() {
|
||||
this.globalSearchTerm = '';
|
||||
this.switchCurrentNetwork(null);
|
||||
},
|
||||
navigateBack() {
|
||||
window.history.back();
|
||||
},
|
||||
async submitModal() {
|
||||
const isUpdate = !!this.modalData.id;
|
||||
const url = isUpdate ? `${this.apiUrl}/update` : `${this.apiUrl}/create`;
|
||||
const payload = { ...this.modalData, parent_network_id: this.currentNetworkData?.id || null };
|
||||
|
||||
try {
|
||||
const response = await axios.post(url, payload);
|
||||
if (response.data.success) {
|
||||
window.notify('success', response.data.message);
|
||||
this.closeModal();
|
||||
this.$refs.table.refreshTable();
|
||||
} else {
|
||||
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten.');
|
||||
}
|
||||
} catch (e) {
|
||||
window.notify('error', e.response?.data?.message || 'Ein Netzwerkfehler ist aufgetreten.');
|
||||
}
|
||||
},
|
||||
async deleteNetwork() {
|
||||
if (!confirm('Sind Sie sicher, dass Sie dieses Netzwerk und alle untergeordneten Netzwerke löschen möchten?')) return;
|
||||
try {
|
||||
const response = await axios.post(`${this.apiUrl}/delete`, { id: this.modalData.id });
|
||||
if (response.data.success) {
|
||||
window.notify('success', response.data.message);
|
||||
this.closeModal();
|
||||
this.$refs.table.refreshTable();
|
||||
} else {
|
||||
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten.');
|
||||
}
|
||||
} catch (e) {
|
||||
window.notify('error', e.response?.data?.message || 'Ein Netzwerkfehler ist aufgetreten.');
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user