mirror of
https://github.com/speatzle/nfsense.git
synced 2025-09-13 15:19:08 +00:00
Merge branch 'main' into feat-custom-multiselect
This commit is contained in:
commit
1b2a1ec6e1
75 changed files with 3050 additions and 969 deletions
|
@ -12,6 +12,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.8.2",
|
||||
"@vee-validate/zod": "^4.8.4",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"@vueuse/head": "^1.1.15",
|
||||
"axios": "^1.3.4",
|
||||
|
@ -25,7 +26,9 @@
|
|||
"vue": "^3.2.45",
|
||||
"vue-i18n": "9",
|
||||
"vue-router": "4",
|
||||
"ws": "^8.13.0"
|
||||
"vue-toast-notification": "^3.0",
|
||||
"ws": "^8.13.0",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.30",
|
||||
|
|
1380
client/pnpm-lock.yaml
generated
1380
client/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -5,9 +5,12 @@ import { authenticate, logout, checkAuthentication, setup } from "./api";
|
|||
import IDashboard from '~icons/ri/dashboard-2-line';
|
||||
import IRule from '~icons/material-symbols/rule-folder-outline-sharp';
|
||||
import IAddress from '~icons/eos-icons/ip';
|
||||
import IEthernet from '~icons/bi/ethernet';
|
||||
import IService from '~icons/material-symbols/home-repair-service';
|
||||
import ISNAT from '~icons/mdi/arrow-expand-right';
|
||||
import IDNAT from '~icons/mdi/arrow-collapse-right';
|
||||
import IConfig from '~icons/grommet-icons/document-config';
|
||||
import IStaticRoutes from '~icons/material-symbols/drive-folder-upload-outline-sharp';
|
||||
|
||||
enum NavState { Open, Reduced, Collapsed };
|
||||
const NavStateCount = 3;
|
||||
|
@ -17,8 +20,11 @@ const navRoutes = {
|
|||
"/firewall/forwardrules": { icon: IRule, caption: "Rules" },
|
||||
"/firewall/sourcenatrules": { icon: ISNAT, caption: "SNAT" },
|
||||
"/firewall/destinationnatrules": { icon: IDNAT, caption: "DNAT" },
|
||||
"/network/interfaces": { icon: IEthernet, caption: "Interfaces" },
|
||||
"/network/staticroutes": { icon: IStaticRoutes, caption: "Static Routes" },
|
||||
"/object/addresses": { icon: IAddress, caption: "Addresses" },
|
||||
"/object/services": { icon: IService, caption: "Services" },
|
||||
"/config/config": { icon: IConfig, caption: "Config" },
|
||||
};
|
||||
|
||||
enum AuthState { Unauthenticated, MfaRequired, Authenticated };
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// import WebSocketServer from 'ws';
|
||||
import JsonRPC from 'simple-jsonrpc-js';
|
||||
import axios from "axios";
|
||||
import { useToast } from 'vue-toast-notification';
|
||||
|
||||
const $toast = useToast();
|
||||
|
||||
let jrpc = new JsonRPC.connect_xhr('/api');
|
||||
// let socket = new WebSocket("ws://"+ window.location.host +"/ws/api");
|
||||
|
@ -21,6 +24,7 @@ export async function apiCall(method: string, params: Record<string, any>): Prom
|
|||
if (ex.code === 401) {
|
||||
UnauthorizedCallback();
|
||||
} else {
|
||||
$toast.error(method+ ': ' + ex.message);
|
||||
console.debug("api call epic fail", ex);
|
||||
}
|
||||
return { Data: null, Error: ex};
|
||||
|
|
87
client/src/components/NiceForm.vue
Normal file
87
client/src/components/NiceForm.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const props = defineModel<{
|
||||
title: string
|
||||
validationSchema: Record<string, string | Function>,
|
||||
sections: {
|
||||
title: string
|
||||
fields: {
|
||||
key: string,
|
||||
label: string,
|
||||
as: string,
|
||||
props: any,
|
||||
default: any,
|
||||
enabled?: (values: Record<string, any>) => Boolean,
|
||||
rules?: (value: any) => true | string,
|
||||
}[],
|
||||
}[],
|
||||
modelValue: any,
|
||||
submit: (value: any) => boolean,
|
||||
discard: () => void,
|
||||
}>();
|
||||
|
||||
let { sections, submit, discard, validationSchema } = $(props);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ValidationForm as="div" v-slot="{ values, handleSubmit }" @submit="submit" :validationSchema="validationSchema">
|
||||
<template v-for="(section, index) in sections" :key="index">
|
||||
<h4 v-if="section.title">{{ section.title }}</h4>
|
||||
<div class="section">
|
||||
<template v-for="(field, index) in section.fields" :key="index">
|
||||
<template v-if="field.enabled ? field.enabled(values) : true">
|
||||
<label :for="field.key" v-text="field.label" />
|
||||
<Field v-if="field.as == 'NumberBox'" :name="field.key" :as="field.as" :rules="field.rules" v-bind="field.props" @update:modelValue="values[field.key] = Number(values[field.key])"/>
|
||||
<Field v-else :name="field.key" :as="field.as" :rules="field.rules" v-bind="field.props"/>
|
||||
<ErrorMessage :name="field.key" />
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div class="actions">
|
||||
<div class="flex-grow"/>
|
||||
<button @click="handleSubmit($event, submit)">Submit</button>
|
||||
<div class="space"/>
|
||||
<button @click="discard">Discard</button>
|
||||
<div class="flex-grow"/>
|
||||
</div>
|
||||
<p>{{ values }}</p>
|
||||
</ValidationForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.section {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.actions {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.space {
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.actions > button {
|
||||
flex-grow: true;
|
||||
}
|
||||
|
||||
h4,
|
||||
p {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
h4 {
|
||||
background-color: var(--cl-bg-hl);
|
||||
padding: 0.3rem;
|
||||
padding-left: 0.5rem;
|
||||
;
|
||||
}
|
||||
</style>
|
|
@ -15,15 +15,20 @@ const props = defineModel<{
|
|||
sortSelf?: boolean,
|
||||
sortBy?: string,
|
||||
sortDesc?: boolean,
|
||||
selection?: number[],
|
||||
draggable?: boolean,
|
||||
}>();
|
||||
let { columns, data, sort, sortSelf, sortBy, sortDesc } = $(props);
|
||||
let { columns, data, sort, sortSelf, sortBy, sortDesc, selection, draggable } = $(props);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'rowAction', index: number): void,
|
||||
(event: 'selectionChanged'): void
|
||||
(event: 'draggedRow', draggedRow: number, draggedOverRow: number): void,
|
||||
}>();
|
||||
|
||||
let selection = $ref([] as number[]);
|
||||
if (selection == undefined) {
|
||||
selection = [];
|
||||
}
|
||||
|
||||
const displayData = $computed(() => (sortSelf && sortBy !== '')
|
||||
? data?.sort((a, b) => {
|
||||
|
@ -97,7 +102,9 @@ function dragDropRow() {
|
|||
data.splice(draggedRow, 1);
|
||||
data.splice(draggedOverRow, 0, row);
|
||||
data = data;
|
||||
emit("draggedRow", draggedRow, draggedOverRow);
|
||||
}
|
||||
|
||||
// Reset drag data
|
||||
draggedRow = 0;
|
||||
draggedOverRow = 0;
|
||||
|
@ -123,7 +130,7 @@ function dragDropRow() {
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in displayData" :key="index"
|
||||
draggable="true"
|
||||
:draggable="draggable"
|
||||
@click="() => rowSelection(index)"
|
||||
@dblclick="() => emit('rowAction', index)"
|
||||
@dragstart="() => draggedRow = index"
|
||||
|
|
36
client/src/components/TableView.vue
Normal file
36
client/src/components/TableView.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const props = defineModel<{
|
||||
title: string,
|
||||
loading: boolean,
|
||||
columns?: {
|
||||
heading: string,
|
||||
path: string,
|
||||
component?: Component,
|
||||
}[],
|
||||
data: Record<string, any>[],
|
||||
tableProps: any,
|
||||
selection?: number[],
|
||||
}>();
|
||||
|
||||
let { title, loading, columns, data, selection, tableProps } = $(props);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'draggedRow', draggedRow: number, draggedOverRow: number): void,
|
||||
}>();
|
||||
|
||||
async function draggedRow(draggedRow: number, draggedOverRow: number) {
|
||||
emit("draggedRow", draggedRow, draggedOverRow);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader :title="title">
|
||||
<slot/>
|
||||
</PageHeader>
|
||||
<div v-if="loading" >Loading...</div>
|
||||
<NiceTable v-else :columns="columns" v-model:selection="selection" @draggedRow="draggedRow" v-bind="tableProps" :data="data"/>
|
||||
</div>
|
||||
</template>
|
21
client/src/components/inputs/CheckBox.vue
Normal file
21
client/src/components/inputs/CheckBox.vue
Normal file
|
@ -0,0 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const props = defineModel<{
|
||||
modelValue: boolean,
|
||||
}>();
|
||||
let { modelValue } = $(props);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @click="() => modelValue = !modelValue">
|
||||
<i-material-symbols-check-box-outline v-if="modelValue"/>
|
||||
<i-material-symbols-check-box-outline-blank v-else/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
18
client/src/components/inputs/MultilineTextBox.vue
Normal file
18
client/src/components/inputs/MultilineTextBox.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const props = defineModel<{
|
||||
modelValue: string,
|
||||
}>();
|
||||
let { modelValue } = $(props);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<textarea v-model="modelValue" rows="5"/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
17
client/src/components/inputs/NumberBox.vue
Normal file
17
client/src/components/inputs/NumberBox.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const props = defineModel<{
|
||||
modelValue: number,
|
||||
min?: number,
|
||||
max?: number,
|
||||
}>();
|
||||
let { modelValue, min, max } = $(props);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input type="number" v-model.number="modelValue" :min="min" :max="max">
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
51
client/src/components/inputs/PillBar.vue
Normal file
51
client/src/components/inputs/PillBar.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const props = defineModel<{
|
||||
options: {
|
||||
name: string,
|
||||
key: string,
|
||||
icon: Component,
|
||||
}[],
|
||||
modelValue: number | string,
|
||||
useIndex: boolean,
|
||||
}>();
|
||||
let { options, modelValue, useIndex } = $(props);
|
||||
|
||||
function setSelection(option: any, index: number){
|
||||
if (useIndex) {
|
||||
modelValue = index
|
||||
} else {
|
||||
modelValue = option.key
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
if (modelValue === undefined) {
|
||||
if (useIndex) {
|
||||
modelValue = 0
|
||||
} else {
|
||||
modelValue = options[0].key
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button class="option" v-for="(option, index) in options" :key="index" :class="{selected: modelValue == index || modelValue == option.key}" @click="setSelection(option, index)">
|
||||
<i class="material-icons" v-if="option.icon">{{ option.icon }}</i>
|
||||
{{ option.name }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--cl-bg-sl);
|
||||
}
|
||||
</style>
|
15
client/src/components/inputs/TextBox.vue
Normal file
15
client/src/components/inputs/TextBox.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const props = defineModel<{
|
||||
modelValue: string,
|
||||
}>();
|
||||
let { modelValue } = $(props);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-model="modelValue">
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -1,44 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const props = defineModel<{
|
||||
options: {
|
||||
name: string,
|
||||
icon: Component,
|
||||
selected: boolean,
|
||||
}[],
|
||||
}>();
|
||||
let { options } = $(props);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'selectionChanged'): void
|
||||
}>();
|
||||
|
||||
function select(option: any) {
|
||||
for(let opt of options) {
|
||||
opt.selected = false;
|
||||
}
|
||||
option.selected = true;
|
||||
emit('selectionChanged');
|
||||
console.debug("selected", options);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button class="option" v-for="(option, index) in options" :key="index" :class="{selected:option.selected}" @click="select(option)">
|
||||
<i class="material-icons" v-if="option.icon">{{ option.icon }}</i>
|
||||
{{ option.name }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--cl-bg-sl);
|
||||
}
|
||||
</style>
|
84
client/src/definitions.ts
Normal file
84
client/src/definitions.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { toFormValidator } from '@vee-validate/zod';
|
||||
import * as zod from 'zod';
|
||||
|
||||
export const editTypes: { [key: string]: { [key: string]: any } } = {
|
||||
"firewall": {
|
||||
name: "Firewall",
|
||||
"forwardrules": {
|
||||
name: "ForwardRule",
|
||||
validationSchema: toFormValidator(
|
||||
zod.object({
|
||||
name: zod.string(),
|
||||
verdict: zod.string(),
|
||||
counter: zod.boolean(),
|
||||
comment: zod.string().optional(),
|
||||
}),
|
||||
),
|
||||
sections: [
|
||||
{
|
||||
fields: [
|
||||
{ key: "name", label: "Name", as: "TextBox" },
|
||||
{ key: "verdict", label: "Verdict", as: "PillBar", props: { options: [{ name: 'Accept', key: 'accept' }, { name: 'Drop', key: 'drop' }, { name: 'Continue', key: 'continue' }] } },
|
||||
{ key: "counter", label: "Counter", as: "CheckBox", },
|
||||
{ key: "comment", label: "Comment", as: "MultilineTextBox", },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"network": {
|
||||
name: "Network",
|
||||
"interfaces": {
|
||||
name: "Interface",
|
||||
validationSchema: toFormValidator(
|
||||
zod.object({
|
||||
name: zod.string(),
|
||||
type: zod.string(),
|
||||
hardware_interface: zod.string().optional(),
|
||||
vlan_id: zod.number().optional(),
|
||||
comment: zod.string().optional(),
|
||||
}),
|
||||
),
|
||||
sections: [
|
||||
{
|
||||
fields: [
|
||||
{ key: "name", label: "Name", as: "TextBox", default: "placeholder" },
|
||||
{ key: "type", label: "Type", as: "PillBar", props: { options: [{ name: 'Hardware', key: 'hardware' }, { name: 'VLAN', key: 'vlan' }, { name: 'Bond', key: 'bond' }, { name: 'Bridge', key: 'bridge' }] } },
|
||||
{ key: "hardware_device", label: "Hardware Device", as: "TextBox", enabled: (values: any) => (values["type"] == 'hardware') },
|
||||
{ key: "vlan_parent", label: "VLAN Parent", as: "TextBox", enabled: (values: any) => (values["type"] == 'vlan') },
|
||||
{ key: "vlan_id", label: "VLAN ID", as: "NumberBox", props: { min: 1, max: 4094 }, enabled: (values: any) => (values["type"] == 'vlan') },
|
||||
{ key: "bond_members", label: "Bond Members", as: "TextBox", enabled: (values: any) => (values["type"] == 'bond') },
|
||||
{ key: "bridge_members", label: "Bridge Members", as: "TextBox", enabled: (values: any) => (values["type"] == 'bridge') },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Addressing",
|
||||
fields: [
|
||||
{ key: "addressing_mode", label: "Addressing Mode", as: "PillBar", props: { options: [{ name: 'None', key: 'none' }, { name: 'Static', key: 'static' }, { name: 'DHCP', key: 'dhcp' }] } },
|
||||
{ key: "address", label: "Address", as: "TextBox", enabled: (values: any) => (values["addressing_mode"] == 'static') },
|
||||
{ key: "comment", label: "Comment", as: "MultilineTextBox" },
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
"staticroutes": {
|
||||
name: "StaticRoute",
|
||||
validationSchema: toFormValidator(
|
||||
zod.object({
|
||||
name: zod.string(),
|
||||
}),
|
||||
),
|
||||
sections: [
|
||||
{
|
||||
fields: [
|
||||
{ key: "name", label: "Name", as: "TextBox", },
|
||||
{ key: "interface", label: "Interface", as: "TextBox" },
|
||||
{ key: "gateway", label: "Gateway", as: "TextBox" },
|
||||
{ key: "destination", label: "Destination", as: "TextBox" },
|
||||
{ key: "metric", label: "Metric", as: "NumberBox" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -3,4 +3,5 @@
|
|||
.pad { padding: 0.5rem; }
|
||||
.gap { gap: 0.5rem; }
|
||||
.flex-grow { flex-grow: 1; }
|
||||
.flex-row { flex-direction: row; }
|
||||
.flex-row { flex-direction: row; }
|
||||
.scroll { overflow-y:auto; }
|
|
@ -47,7 +47,7 @@ th:hover {
|
|||
}
|
||||
th, td {
|
||||
padding: 0.5rem;
|
||||
border: 0.125rem solid var(--cl-fg);
|
||||
border: 0.125rem solid var(--cl-bg-el);
|
||||
}
|
||||
th > *{
|
||||
justify-content: center;
|
||||
|
@ -77,6 +77,11 @@ button, .button {
|
|||
background-color: var(--cl-bg-hl);
|
||||
}
|
||||
|
||||
.button:disabled, button:disabled, .disabled {
|
||||
background-color: var(--cl-bg-hl);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
background-color: var(--cl-bg-hl);
|
||||
border: 1px solid var(--cl-fg);
|
||||
|
|
|
@ -3,11 +3,21 @@ import './global-styles/components.css';
|
|||
import './global-styles/colors.css';
|
||||
import './global-styles/mlfe.css';
|
||||
import './global-styles/transitions.css';
|
||||
import 'vue-toast-notification/dist/theme-default.css';
|
||||
|
||||
import PillBar from "./components/inputs/PillBar.vue";
|
||||
import TextBox from "./components/inputs/TextBox.vue";
|
||||
import NumberBox from "./components/inputs/NumberBox.vue";
|
||||
import MultilineTextBox from "./components/inputs/MultilineTextBox.vue";
|
||||
import CheckBox from "./components/inputs/CheckBox.vue";
|
||||
|
||||
import { Form, Field, ErrorMessage } from 'vee-validate';
|
||||
|
||||
import App from './App.vue';
|
||||
import { createHead } from '@vueuse/head';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import routes from '~pages';
|
||||
import ToastPlugin from 'vue-toast-notification';
|
||||
|
||||
const app = createApp(App);
|
||||
const head = createHead();
|
||||
|
@ -18,5 +28,16 @@ const router = createRouter({
|
|||
|
||||
app.use(router);
|
||||
app.use(head);
|
||||
app.use(ToastPlugin);
|
||||
|
||||
// Global Components
|
||||
app.component('PillBar', PillBar);
|
||||
app.component('TextBox', TextBox);
|
||||
app.component('NumberBox', NumberBox);
|
||||
app.component('MultilineTextBox', MultilineTextBox);
|
||||
app.component('CheckBox', CheckBox);
|
||||
app.component('ValidationForm', Form);
|
||||
app.component('Field', Field);
|
||||
app.component('ErrorMessage', ErrorMessage);
|
||||
|
||||
app.mount('#app');
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
const props = $defineProps<{entity: string, id: string}>();
|
||||
const { entity, id } = $(props);
|
||||
|
||||
const pageTypes: { [key: string]: any } = {
|
||||
"rules": { title: "Rules" },
|
||||
"addresses": { title: "Addresses"},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader :title="pageTypes[entity].title"/>
|
||||
{{ entity }} {{ id }}
|
||||
</div>
|
||||
</template>
|
|
@ -1,90 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
const props = $defineProps<{entity: string}>();
|
||||
const { entity } = $(props);
|
||||
|
||||
const pageTypes: { [key: string]: any } = {
|
||||
"rules": { title: "Rules" },
|
||||
"addresses": { title: "Addresses"},
|
||||
};
|
||||
|
||||
let searchTerm = $ref("");
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader :title="pageTypes[entity].title">
|
||||
<input class="search-bar" placeholder="Search..." v-model="searchTerm"/>
|
||||
<button>
|
||||
<i-material-symbols-add/>
|
||||
</button>
|
||||
</PageHeader>
|
||||
<NiceTable :columns="{fname: 'First Name', lname: 'Last Name'}" :sort-self="true" :data="[
|
||||
{
|
||||
fname: 'Haynes',
|
||||
lname: 'Chavez'
|
||||
}, {
|
||||
fname: 'Brennan',
|
||||
lname: 'Bradley'
|
||||
}, {
|
||||
fname: 'Blanchard',
|
||||
lname: 'Thornton'
|
||||
}, {
|
||||
fname: 'Benjamin',
|
||||
lname: 'Nash'
|
||||
}, {
|
||||
fname: 'Jan',
|
||||
lname: 'Bradford'
|
||||
}, {
|
||||
fname: 'Zelma',
|
||||
lname: 'Spears'
|
||||
}, {
|
||||
fname: 'Freeman',
|
||||
lname: 'Page'
|
||||
}, {
|
||||
fname: 'Wilson',
|
||||
lname: 'Carlson'
|
||||
}, {
|
||||
fname: 'Lewis',
|
||||
lname: 'Fuentes'
|
||||
}, {
|
||||
fname: 'Vega',
|
||||
lname: 'Villarreal'
|
||||
}, {
|
||||
fname: 'Carolyn',
|
||||
lname: 'Cardenas'
|
||||
}, {
|
||||
fname: 'Angie',
|
||||
lname: 'Adams'
|
||||
}, {
|
||||
fname: 'Richards',
|
||||
lname: 'Leon'
|
||||
}, {
|
||||
fname: 'Velma',
|
||||
lname: 'Fields'
|
||||
}, {
|
||||
fname: 'Witt',
|
||||
lname: 'Lowe'
|
||||
}, {
|
||||
fname: 'Waters',
|
||||
lname: 'Leblanc'
|
||||
}, {
|
||||
fname: 'Henry',
|
||||
lname: 'Lloyd'
|
||||
}, {
|
||||
fname: 'Boone',
|
||||
lname: 'Greer'
|
||||
}, {
|
||||
fname: 'Willis',
|
||||
lname: 'Stark'
|
||||
}, {
|
||||
fname: 'Dickson',
|
||||
lname: 'Spencer'
|
||||
}
|
||||
].filter(x => (`${x.fname} ${x.lname}`).toLowerCase().includes(searchTerm.toLowerCase()))"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
29
client/src/pages/[subsystem]/[entity]/edit/[id].vue
Normal file
29
client/src/pages/[subsystem]/[entity]/edit/[id].vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import { editTypes } from "../../../../definitions";
|
||||
import getPlugins from '../../../../plugins';
|
||||
const p = getPlugins();
|
||||
|
||||
const props = $defineProps<{subsystem: string, entity: string, id: string}>();
|
||||
const { subsystem, entity, id } = $(props);
|
||||
|
||||
let data = $ref({} as {});
|
||||
|
||||
async function update() {
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="editTypes[subsystem][entity]">
|
||||
<PageHeader :title="'Edit ' + editTypes[subsystem][entity].name">
|
||||
<button @click="update">Update</button>
|
||||
<button @click="$router.go(-1)">Discard</button>
|
||||
</PageHeader>
|
||||
<NiceForm class="scroll cl-secondary" :sections="editTypes[subsystem][entity].sections" v-model="data"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<PageHeader title="Error"/>
|
||||
No editType for this Entity
|
||||
</div>
|
||||
</template>
|
34
client/src/pages/[subsystem]/[entity]/edit/index.vue
Normal file
34
client/src/pages/[subsystem]/[entity]/edit/index.vue
Normal file
|
@ -0,0 +1,34 @@
|
|||
<script setup lang="ts">
|
||||
import { editTypes } from "../../../../definitions";
|
||||
import { apiCall } from "../../../../api";
|
||||
import getPlugins from '../../../../plugins';
|
||||
const p = getPlugins();
|
||||
|
||||
const props = $defineProps<{subsystem: string, entity: string}>();
|
||||
const { subsystem, entity } = $(props);
|
||||
|
||||
let data = $ref({});
|
||||
|
||||
async function create(value: any) {
|
||||
console.debug("value", value);
|
||||
let res = await apiCall(editTypes[subsystem].name +".Create"+ editTypes[subsystem][entity].name, value);
|
||||
if (res.Error === null) {
|
||||
p.toast.success("Created " + editTypes[subsystem][entity].name);
|
||||
p.router.go(-1);
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="editTypes[subsystem][entity]">
|
||||
<PageHeader :title="'Create ' + editTypes[subsystem][entity].name">
|
||||
</PageHeader>
|
||||
<NiceForm class="scroll cl-secondary" :submit="create" :discard="() => $router.go(-1)" :sections="editTypes[subsystem][entity].sections" v-model="data"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<PageHeader title="Error"/>
|
||||
No editType for this Entity
|
||||
</div>
|
||||
</template>
|
77
client/src/pages/config/config.vue
Normal file
77
client/src/pages/config/config.vue
Normal file
|
@ -0,0 +1,77 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from "../../api";
|
||||
import getPlugins from '../../plugins';
|
||||
const p = getPlugins();
|
||||
|
||||
|
||||
let changelog = $ref([]);
|
||||
let loading = $ref(false);
|
||||
|
||||
const columns = [
|
||||
{heading: 'Path', path: 'path'},
|
||||
{heading: 'Type', path: 'type'},
|
||||
{heading: 'From', path: 'from'},
|
||||
{heading: 'To', path: 'to'},
|
||||
];
|
||||
|
||||
const displayData = $computed(() => {
|
||||
let data: any;
|
||||
data = [];
|
||||
for (const change of changelog) {
|
||||
data.push({
|
||||
path: change.path,
|
||||
type: change.type,
|
||||
from: change.from,
|
||||
to: change.to,
|
||||
});
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
async function load(){
|
||||
loading = true
|
||||
let res = await apiCall("Config.GetPendingChangelog", {});
|
||||
if (res.Error === null) {
|
||||
console.debug("changelog", res.Data.Changelog);
|
||||
changelog = res.Data.Changelog;
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function apply(){
|
||||
let res = await apiCall("Config.ApplyPendingChanges", {});
|
||||
if (res.Error === null) {
|
||||
console.debug("apply");
|
||||
p.toast.success("Applied Pending Config");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load()
|
||||
}
|
||||
|
||||
async function discard(){
|
||||
let res = await apiCall("Config.DiscardPendingChanges", {});
|
||||
if (res.Error === null) {
|
||||
console.debug("discard");
|
||||
p.toast.success("Discarded Pending Config");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load()
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
load();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableView title="Pending Changes" :columns="columns" :loading="loading" v-model:data="displayData" :table-props="{sort:true, sortSelf: true}">
|
||||
<button @click="load">Refresh</button>
|
||||
<button @click="apply">Apply</button>
|
||||
<button @click="discard">Discard</button>
|
||||
</TableView>
|
||||
</template>
|
|
@ -1,38 +1,67 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from "../../api";
|
||||
import getPlugins from '../../plugins';
|
||||
const p = getPlugins();
|
||||
|
||||
let rules = $ref([]);
|
||||
let loading = $ref(false);
|
||||
let selection = $ref([] as number[]);
|
||||
|
||||
const columns = [
|
||||
{heading: 'Name', path: 'name'},
|
||||
{heading: 'Source', path: 'match.source_addresses'},
|
||||
{heading: 'Destination', path: 'match.destination_addresses'},
|
||||
{heading: 'Service', path: 'match.services'},
|
||||
{heading: 'Verdict', path: 'verdict'},
|
||||
{heading: 'Counter', path: 'counter'},
|
||||
{heading: 'Comment', path: 'comment'},
|
||||
];
|
||||
|
||||
async function loadRules(){
|
||||
async function load(){
|
||||
let res = await apiCall("Firewall.GetDestinationNATRules", {});
|
||||
if (res.Error === null) {
|
||||
rules = res.Data.DestinationNATRules;
|
||||
rules = res.Data.destination_nat_rules;
|
||||
console.debug("rules", rules);
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRule(){
|
||||
let res = await apiCall("Firewall.DeleteDestinationNATRule", {index: selection[0]});
|
||||
if (res.Error === null) {
|
||||
console.debug("deleted rule");
|
||||
p.toast.success("Deleted Rule");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
async function draggedRow(draggedRow: number, draggedOverRow: number) {
|
||||
console.log("dragged", draggedRow, draggedOverRow);
|
||||
let res = await apiCall("Firewall.MoveDestinationNATRule", {index: draggedRow, to_index: draggedOverRow});
|
||||
if (res.Error === null) {
|
||||
console.debug("moved rule");
|
||||
p.toast.success("Moved Rule");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
loadRules();
|
||||
load();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader title="DNAT Rules">
|
||||
<button @click="loadRules">Load Rules</button>
|
||||
</PageHeader>
|
||||
<NiceTable :columns="columns" v-model:data="rules"/>
|
||||
<TableView title="DNAT Rules" :columns="columns" :loading="loading" @draggedRow="draggedRow" v-model:selection="selection" v-model:data="rules" :table-props="{sort:true, sortSelf: true, draggable: true}">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/firewall/destinationnatrules/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/firewall/destinationnatrules/edit/' + selection[0]">Edit</router-link>
|
||||
<button @click="deleteRule" :disabled="selection.length != 1">Delete</button>
|
||||
</TableView>
|
||||
</div>
|
||||
</template>
|
|
@ -1,7 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from "../../api";
|
||||
import getPlugins from '../../plugins';
|
||||
const p = getPlugins();
|
||||
|
||||
let rules = $ref([]);
|
||||
let loading = $ref(false);
|
||||
let selection = $ref([] as number[]);
|
||||
|
||||
const columns = [
|
||||
{heading: 'Name', path: 'name'},
|
||||
{heading: 'Source', path: 'match.source_addresses'},
|
||||
|
@ -12,27 +17,52 @@ const columns = [
|
|||
{heading: 'Comment', path: 'comment'},
|
||||
];
|
||||
|
||||
async function loadRules(){
|
||||
async function load(){
|
||||
let res = await apiCall("Firewall.GetForwardRules", {});
|
||||
if (res.Error === null) {
|
||||
rules = res.Data.ForwardRules;
|
||||
rules = res.Data.forward_rules;
|
||||
console.debug("rules", rules);
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRule(){
|
||||
let res = await apiCall("Firewall.DeleteForwardRule", {index: selection[0]});
|
||||
if (res.Error === null) {
|
||||
console.debug("deleted rule");
|
||||
p.toast.success("Deleted Rule");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
async function draggedRow(draggedRow: number, draggedOverRow: number) {
|
||||
console.log("dragged", draggedRow, draggedOverRow);
|
||||
let res = await apiCall("Firewall.MoveForwardRule", {index: draggedRow, to_index: draggedOverRow});
|
||||
if (res.Error === null) {
|
||||
console.debug("moved rule");
|
||||
p.toast.success("Moved Rule");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
loadRules();
|
||||
load();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader title="Forward Rules">
|
||||
<button @click="loadRules">Load Rules</button>
|
||||
</PageHeader>
|
||||
<NiceTable :columns="columns" v-model:data="rules"/>
|
||||
<TableView title="Forward Rules" :columns="columns" :loading="loading" @draggedRow="draggedRow" v-model:selection="selection" v-model:data="rules" :table-props="{sort:true, sortSelf: true, draggable: true}">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/firewall/forwardrules/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/firewall/forwardrules/edit/' + selection[0]">Edit</router-link>
|
||||
<button @click="deleteRule" :disabled="selection.length != 1">Delete</button>
|
||||
</TableView>
|
||||
</div>
|
||||
</template>
|
|
@ -1,38 +1,67 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from "../../api";
|
||||
import getPlugins from '../../plugins';
|
||||
const p = getPlugins();
|
||||
|
||||
let rules = $ref([]);
|
||||
let loading = $ref(false);
|
||||
let selection = $ref([] as number[]);
|
||||
|
||||
const columns = [
|
||||
{heading: 'Name', path: 'name'},
|
||||
{heading: 'Source', path: 'match.source_addresses'},
|
||||
{heading: 'Destination', path: 'match.destination_addresses'},
|
||||
{heading: 'Service', path: 'match.services'},
|
||||
{heading: 'Verdict', path: 'verdict'},
|
||||
{heading: 'Counter', path: 'counter'},
|
||||
{heading: 'Comment', path: 'comment'},
|
||||
];
|
||||
|
||||
async function loadRules(){
|
||||
async function load(){
|
||||
let res = await apiCall("Firewall.GetSourceNATRules", {});
|
||||
if (res.Error === null) {
|
||||
rules = res.Data.SourceNATRules;
|
||||
rules = res.Data.source_nat_rules;
|
||||
console.debug("rules", rules);
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRule(){
|
||||
let res = await apiCall("Firewall.DeleteSourceNATRule", {index: selection[0]});
|
||||
if (res.Error === null) {
|
||||
console.debug("deleted rule");
|
||||
p.toast.success("Deleted Rule");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
async function draggedRow(draggedRow: number, draggedOverRow: number) {
|
||||
console.log("dragged", draggedRow, draggedOverRow);
|
||||
let res = await apiCall("Firewall.MoveSourceNATRule", {index: draggedRow, to_index: draggedOverRow});
|
||||
if (res.Error === null) {
|
||||
console.debug("moved rule");
|
||||
p.toast.success("Moved Rule");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
loadRules();
|
||||
load();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader title="SNAT Rules">
|
||||
<button @click="loadRules">Load Rules</button>
|
||||
</PageHeader>
|
||||
<NiceTable :columns="columns" v-model:data="rules"/>
|
||||
<TableView title="SNAT Rules" :columns="columns" :loading="loading" @draggedRow="draggedRow" v-model:selection="selection" v-model:data="rules" :table-props="{sort:true, sortSelf: true, draggable: true}">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/firewall/sourcenatrules/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/firewall/sourcenatrules/edit/' + selection[0]">Edit</router-link>
|
||||
<button @click="deleteRule" :disabled="selection.length != 1">Delete</button>
|
||||
</TableView>
|
||||
</div>
|
||||
</template>
|
|
@ -1,39 +1,39 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from "../api";
|
||||
|
||||
async function doShit(){
|
||||
apiCall("Firewall.GetForwardRules", {});
|
||||
let links = $ref([]);
|
||||
let loading = $ref(false);
|
||||
|
||||
async function load(){
|
||||
loading = true
|
||||
let res = await apiCall("Network.GetLinks", {});
|
||||
if (res.Error === null) {
|
||||
console.debug("links", res.Data.Links);
|
||||
links = res.Data.Links;
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
let name = $ref("");
|
||||
let comment = $ref("");
|
||||
let counter = $ref(false);
|
||||
let options = $ref([{name: 'Accept'}, {name: 'Drop'}, {name: 'Continue'}]);
|
||||
onMounted(async() => {
|
||||
load();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="overflow-y: auto;">
|
||||
<PageHeader title="Dashboard">
|
||||
<button @click="doShit">Example Buttons</button>
|
||||
</PageHeader>
|
||||
<form @submit="$event => $event.preventDefault()" class="cl-secondary">
|
||||
<h3>Create Rule</h3>
|
||||
<label for="name" v-text="'Name'"/>
|
||||
<input name="name" v-model="name"/>
|
||||
<label for="counter" v-text="'Counter'"/>
|
||||
<input name="counter" type="checkbox" v-model="counter"/>
|
||||
<label for="comment" v-text="'Comment'"/>
|
||||
<textarea name="comment" v-model="comment"></textarea>
|
||||
<label for="verdict" v-text="'Verdict'"/>
|
||||
<pillbar :options="options" name="verdict" ></pillbar>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
<Multiselect/>
|
||||
asd
|
||||
<div v-if="!loading" v-for="(link, index) in links" :key="index">
|
||||
<p>{{ link.name }} {{ link.carrier_state }} {{ link.operational_state }}</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
66
client/src/pages/network/Interfaces.vue
Normal file
66
client/src/pages/network/Interfaces.vue
Normal file
|
@ -0,0 +1,66 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from "../../api";
|
||||
|
||||
let interfaces = $ref({});
|
||||
let loading = $ref(false);
|
||||
let selection = $ref([] as number[]);
|
||||
|
||||
const columns = [
|
||||
{heading: 'Name', path: 'name'},
|
||||
{heading: 'Type', path: 'type'},
|
||||
{heading: 'Members', path: 'members'},
|
||||
{heading: 'Addressing Mode', path: 'addressing_mode'},
|
||||
{heading: 'Address', path: 'address'},
|
||||
];
|
||||
|
||||
const displayData = $computed(() => {
|
||||
let data: any;
|
||||
data = [];
|
||||
for (const name in interfaces) {
|
||||
data.push({
|
||||
name,
|
||||
type: interfaces[name].type,
|
||||
addressing_mode: interfaces[name].addressing_mode,
|
||||
address: interfaces[name].address,
|
||||
comment: interfaces[name].comment,
|
||||
});
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
async function load(){
|
||||
loading = true
|
||||
let res = await apiCall("Network.GetInterfaces", {});
|
||||
if (res.Error === null) {
|
||||
console.debug("interfaces", res.Data.Interfaces);
|
||||
interfaces = res.Data.Interfaces;
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function deleteInterface(){
|
||||
let res = await apiCall("Network.DeleteInterface", {name: displayData[selection[0]].name});
|
||||
if (res.Error === null) {
|
||||
console.debug("deleted interface");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
load();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableView title="Interfaces" :columns="columns" :loading="loading" v-model:selection="selection" v-model:data="displayData" :table-props="{sort:true, sortSelf: true}">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/network/interfaces/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/network/interfaces/edit/' + selection[0]">Edit</router-link>
|
||||
<button @click="deleteInterface" :disabled="selection.length != 1">Delete</button>
|
||||
</TableView>
|
||||
</template>
|
51
client/src/pages/network/StaticRoutes.vue
Normal file
51
client/src/pages/network/StaticRoutes.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from "../../api";
|
||||
|
||||
let staticRoutes = $ref([]);
|
||||
let loading = $ref(false);
|
||||
let selection = $ref([] as number[]);
|
||||
|
||||
const columns = [
|
||||
{heading: 'Name', path: 'name'},
|
||||
{heading: 'Interface', path: 'interface'},
|
||||
{heading: 'Gateway', path: 'gateway'},
|
||||
{heading: 'Destination', path: 'destination'},
|
||||
{heading: 'Metric', path: 'metric'},
|
||||
];
|
||||
|
||||
async function load(){
|
||||
loading = true
|
||||
let res = await apiCall("Network.GetStaticRoutes", {});
|
||||
if (res.Error === null) {
|
||||
console.debug("staticRoutes", res.Data.StaticRoutes);
|
||||
staticRoutes = res.Data.StaticRoutes;
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function deleteStaticRoutes(){
|
||||
let res = await apiCall("Network.DeleteStaticRoute", {index: selection[0]});
|
||||
if (res.Error === null) {
|
||||
console.debug("deleted static routes");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
load();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableView title="Static Routes" :columns="columns" :loading="loading" v-model:selection="selection" v-model:data="staticRoutes" :table-props="{sort:true, sortSelf: true}">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/network/staticroutes/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/network/staticroutes/edit/' + selection[0]">Edit</router-link>
|
||||
<button @click="deleteStaticRoutes" :disabled="selection.length != 1">Delete</button>
|
||||
</TableView>
|
||||
</template>
|
|
@ -2,6 +2,9 @@
|
|||
import { apiCall } from "../../api";
|
||||
|
||||
let addresses = $ref([]);
|
||||
let loading = $ref(false);
|
||||
let selection = $ref([] as number[]);
|
||||
|
||||
const columns = [
|
||||
{heading: 'Name', path: 'name'},
|
||||
{heading: 'Type', path: 'type'},
|
||||
|
@ -10,6 +13,7 @@ const columns = [
|
|||
];
|
||||
|
||||
async function load(){
|
||||
loading = true
|
||||
let res = await apiCall("Object.GetAddresses", {});
|
||||
if (res.Error === null) {
|
||||
addresses = res.Data.Addresses;
|
||||
|
@ -17,6 +21,52 @@ async function load(){
|
|||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
const displayData = $computed(() => {
|
||||
let data: any;
|
||||
data = [];
|
||||
for (const name in addresses) {
|
||||
data.push({
|
||||
name,
|
||||
value: getAddressValue(addresses[name]),
|
||||
type: addresses[name].type,
|
||||
comment: addresses[name].comment,
|
||||
});
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
function getAddressValue(s: any): string {
|
||||
let value: string;
|
||||
switch (s.type) {
|
||||
case "host":
|
||||
value = s.host;
|
||||
break;
|
||||
case "range":
|
||||
value = s.range;
|
||||
break;
|
||||
case "network":
|
||||
value = s.network;
|
||||
break;
|
||||
case "group":
|
||||
value = s.children;
|
||||
break;
|
||||
default:
|
||||
value = "unkown";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async function deleteAddress(){
|
||||
let res = await apiCall("Object.DeleteAddress", {name: displayData[selection[0]].name});
|
||||
if (res.Error === null) {
|
||||
console.debug("deleted address");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
|
@ -26,10 +76,10 @@ onMounted(async() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader title="Addresses">
|
||||
<button @click="load">Load Addresses</button>
|
||||
</PageHeader>
|
||||
<NiceTable :columns="columns" v-model:data="addresses"/>
|
||||
</div>
|
||||
<TableView title="Addresses" :columns="columns" :loading="loading" v-model:selection="selection" v-model:data="displayData" :table-props="{sort:true, sortSelf: true}">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/object/addresses/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/object/addresses/edit/' + selection[0]">Edit</router-link>
|
||||
<button @click="deleteAddress" :disabled="selection.length != 1">Delete</button>
|
||||
</TableView>
|
||||
</template>
|
|
@ -1,7 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from "../../api";
|
||||
|
||||
let services = $ref([]);
|
||||
let services = $ref({});
|
||||
let loading = $ref(false);
|
||||
let selection = $ref([] as number[]);
|
||||
|
||||
const columns = [
|
||||
{heading: 'Name', path: 'name'},
|
||||
{heading: 'Type', path: 'type'},
|
||||
|
@ -9,14 +12,66 @@ const columns = [
|
|||
{heading: 'Comment', path: 'comment'},
|
||||
];
|
||||
|
||||
const displayData = $computed(() => {
|
||||
let data: any;
|
||||
data = [];
|
||||
for (const name in services) {
|
||||
data.push({
|
||||
name,
|
||||
value: getServiceValue(services[name]),
|
||||
type: services[name].type,
|
||||
comment: services[name].comment,
|
||||
});
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
function getServiceValue(s: any): string {
|
||||
let value: string;
|
||||
switch (s.type) {
|
||||
case "tcp":
|
||||
case "udp":
|
||||
value = getServicePortRange(s);
|
||||
break;
|
||||
case "icmp":
|
||||
value = "icmp";
|
||||
break;
|
||||
case "group":
|
||||
value = s.children;
|
||||
break;
|
||||
default:
|
||||
value = "unkown";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function getServicePortRange(s:any): string {
|
||||
if (s.dport_end) {
|
||||
return s.dport_start + "-" + s.dport_end;
|
||||
}
|
||||
return s.dport_start;
|
||||
}
|
||||
|
||||
async function load(){
|
||||
loading = true
|
||||
let res = await apiCall("Object.GetServices", {});
|
||||
if (res.Error === null) {
|
||||
console.debug("services", res.Data.Services);
|
||||
services = res.Data.Services;
|
||||
console.debug("services", services);
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function deleteService(){
|
||||
let res = await apiCall("Object.DeleteService", {name: displayData[selection[0]].name});
|
||||
if (res.Error === null) {
|
||||
console.debug("deleted service");
|
||||
} else {
|
||||
console.debug("error", res);
|
||||
}
|
||||
load();
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
|
@ -26,10 +81,10 @@ onMounted(async() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader title="services">
|
||||
<button @click="load">Load Services</button>
|
||||
</PageHeader>
|
||||
<NiceTable :columns="columns" v-model:data="services"/>
|
||||
</div>
|
||||
<TableView title="Services" :columns="columns" :loading="loading" v-model:selection="selection" v-model:data="displayData" :table-props="{sort:true, sortSelf: true}">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/object/services/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/object/services/edit/' + selection[0]">Edit</router-link>
|
||||
<button @click="deleteService" :disabled="selection.length != 1">Delete</button>
|
||||
</TableView>
|
||||
</template>
|
9
client/src/plugins.ts
Normal file
9
client/src/plugins.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from "vue-toast-notification";
|
||||
|
||||
export default function initiateCommonPlugins() {
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
||||
return { router, toast };
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue