mirror of
https://github.com/speatzle/nfsense.git
synced 2025-05-12 19:28:20 +00:00
Compare commits
14 commits
84488b3e63
...
ab8430824f
Author | SHA1 | Date | |
---|---|---|---|
ab8430824f | |||
0c27dffef9 | |||
f8752f99af | |||
185c1e723d | |||
1bf33e54b3 | |||
439d105858 | |||
efa043ed35 | |||
f1d7b57a21 | |||
3eb13e20d2 | |||
8e9d29327d | |||
60fabb254f | |||
1af5af41cc | |||
b360bdf978 | |||
43454b1641 |
16 changed files with 126 additions and 54 deletions
|
@ -3,33 +3,25 @@ const props = withDefaults(defineProps<{
|
|||
data: any[],
|
||||
component?: string,
|
||||
componentProp?: '',
|
||||
ellipsis?: number
|
||||
ellipsis?: number,
|
||||
empty?: string
|
||||
}>(), {
|
||||
data: () => [],
|
||||
component: '',
|
||||
componentProp: '',
|
||||
ellipsis: 2,
|
||||
ellipsis: 10,
|
||||
empty: '',
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="pillbar">
|
||||
<div v-if="props.data.length != 0" class="pillstack">
|
||||
<div v-for="(item, index) of props.data.slice(0, ellipsis)" :key="index" class="pill">
|
||||
<component v-bind="{[props.componentProp]: item}" :is="props.component" v-if="props.component !== ''"/>
|
||||
<template v-else>{{ item }}</template>
|
||||
</div>
|
||||
<div v-if="props.data.length >= props.ellipsis" class="pill">...</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ props.empty }}
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.pillbar {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.pill {
|
||||
border: 1px solid var(--cl-fg);
|
||||
padding: 0.25rem;
|
||||
}
|
||||
</style>
|
19
client/src/components/display/ElementDisplay.vue
Normal file
19
client/src/components/display/ElementDisplay.vue
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<{
|
||||
data: any,
|
||||
component?: string,
|
||||
componentProp?: '',
|
||||
}>(), {
|
||||
data: '',
|
||||
component: '',
|
||||
componentProp: '',
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="data" class="pillbar">
|
||||
<div class="pill">
|
||||
<component v-bind="{[props.componentProp]: props.data}" :is="props.component" v-if="props.component !== ''"/>
|
||||
<template v-else>{{ props.data }}</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
12
client/src/components/display/EnumTypeDisplay.vue
Normal file
12
client/src/components/display/EnumTypeDisplay.vue
Normal file
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { variantOf } from '~/util';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
data: object | string,
|
||||
}>(), {
|
||||
data: '',
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
{{ variantOf(props.data) }}
|
||||
</template>
|
|
@ -108,8 +108,11 @@ function dragDropRow() {
|
|||
data.splice(draggedRow, 1);
|
||||
data.splice(draggedOverRow, 0, row);
|
||||
data = data;
|
||||
// Don't emit if we are at the same spot
|
||||
if (draggedRow !== draggedOverRow){
|
||||
emit('draggedRow', draggedRow, draggedOverRow);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset Drag & Remove Selection
|
||||
draggedRow = 0;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { Index, MaybeIndex, equals } from '../../util';
|
||||
import { Index, MaybeIndex, equals, variantOf } from '../../util';
|
||||
import { Fields } from './NicerForm.vue';
|
||||
|
||||
export type Variant = {
|
||||
|
@ -34,7 +34,7 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
// Local Variables for Two-Way bindings
|
||||
let modelValue: MaybeEnumValue = $ref(null);
|
||||
let modelValue = $ref(null as MaybeEnumValue);
|
||||
// Sync from v-model
|
||||
onMounted(() => {
|
||||
watch(() => props.modelValue, (val) => {
|
||||
|
@ -49,8 +49,14 @@ onMounted(() => {
|
|||
}, { deep: true, immediate: true });
|
||||
});
|
||||
// Sync to v-model
|
||||
watch($$(modelValue), (val) => {
|
||||
watch($$(modelValue), (val, oldVal) => {
|
||||
if (equals(val, props.modelValue)) return;
|
||||
const [oldVariant, newVariant] = [variantOf(oldVal), variantOf(val)];
|
||||
// Wipe variant values if variant definitions change
|
||||
if (typeof val === 'object'
|
||||
&& val && newVariant && oldVariant
|
||||
&& !equals(props.variants[newVariant].fields, props.variants[oldVariant].fields))
|
||||
val[newVariant] = formValue = {};
|
||||
emit('update:modelValue', typeof val === 'string' ? val : Object.assign({}, val));
|
||||
}, { deep: true });
|
||||
|
||||
|
|
|
@ -121,3 +121,20 @@ tbody tr:hover, th:hover {
|
|||
tbody tr.selected {
|
||||
background-color: var(--cl-table-sl)
|
||||
}
|
||||
|
||||
.pillbar {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.pillstack {
|
||||
align-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.pill {
|
||||
border: 1px solid var(--cl-fg);
|
||||
padding: 0.25rem;
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from '../../api';
|
||||
import getPlugins from '../../plugins';
|
||||
import ArrayDisplay from '~/components/display/ArrayDisplay.vue';
|
||||
import ElementDisplay from '~/components/display/ElementDisplay.vue';
|
||||
const p = getPlugins();
|
||||
|
||||
let rules = $ref([]);
|
||||
|
@ -9,11 +11,11 @@ let selection = $ref([] as number[]);
|
|||
|
||||
const columns = [
|
||||
{ heading: 'Name', path: 'name' },
|
||||
{ heading: 'Source', path: 'source_addresses' },
|
||||
{ heading: 'Destination', path: 'destination_addresses' },
|
||||
{ heading: 'Service', path: 'services' },
|
||||
{ heading: 'Translated Address', path: 'dnat_address' },
|
||||
{ heading: 'Translated Service', path: 'dnat_service' },
|
||||
{ heading: 'Source', path: 'source_addresses', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Destination', path: 'destination_addresses', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Service', path: 'services', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Translated Address', path: 'dnat_address', component: markRaw(ElementDisplay), componentProp: 'data' },
|
||||
{ heading: 'Translated Service', path: 'dnat_service', component: markRaw(ElementDisplay), componentProp: 'data' },
|
||||
{ heading: 'Counter', path: 'counter' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
];
|
||||
|
@ -59,7 +61,7 @@ onMounted(async() => {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<TableView v-model:selection="selection" v-model:data="rules" title="DNAT Rules" :columns="columns" :loading="loading" :table-props="{sort:true, sortSelf: true, draggable: true}" @dragged-row="draggedRow">
|
||||
<TableView v-model:selection="selection" v-model:data="rules" title="DNAT Rules" :columns="columns" :loading="loading" :table-props="{draggable: true}" @dragged-row="draggedRow">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/firewall/destination_nat_rules/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/firewall/destination_nat_rules/edit/' + selection[0]">Edit</router-link>
|
||||
|
|
|
@ -15,6 +15,7 @@ const columns = [
|
|||
{ heading: 'Service', path: 'services', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Verdict', path: 'verdict' },
|
||||
{ heading: 'Counter', path: 'counter' },
|
||||
{ heading: 'Log', path: 'log' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
];
|
||||
|
||||
|
@ -59,7 +60,7 @@ onMounted(async() => {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<TableView v-model:selection="selection" v-model:data="rules" title="Forward Rules" :columns="columns" :loading="loading" :table-props="{sort:true, sortSelf: true, draggable: true}" @dragged-row="draggedRow">
|
||||
<TableView v-model:selection="selection" v-model:data="rules" title="Forward Rules" :columns="columns" :loading="loading" :table-props="{draggable: true}" @dragged-row="draggedRow">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/firewall/forward_rules/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/firewall/forward_rules/edit/' + selection[0]">Edit</router-link>
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from '../../api';
|
||||
import getPlugins from '../../plugins';
|
||||
import ArrayDisplay from '~/components/display/ArrayDisplay.vue';
|
||||
import ElementDisplay from '~/components/display/ElementDisplay.vue';
|
||||
import EnumTypeDisplay from '~/components/display/EnumTypeDisplay.vue';
|
||||
|
||||
const p = getPlugins();
|
||||
|
||||
let rules = $ref([]);
|
||||
|
@ -9,11 +13,12 @@ let selection = $ref([] as number[]);
|
|||
|
||||
const columns = [
|
||||
{ heading: 'Name', path: 'name' },
|
||||
{ heading: 'Source', path: 'source_addresses' },
|
||||
{ heading: 'Destination', path: 'destination_addresses' },
|
||||
{ heading: 'Service', path: 'services' },
|
||||
{ heading: 'Translated Address', path: 'snat_type.snat.address' },
|
||||
{ heading: 'Translated Service', path: 'snat_type.snat.service' },
|
||||
{ heading: 'Source', path: 'source_addresses', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Destination', path: 'destination_addresses', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Service', path: 'services', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Type', path: 'snat_type', component: markRaw(EnumTypeDisplay), componentProp: 'data' },
|
||||
{ heading: 'Translated Address', path: 'snat_type.snat.address', component: markRaw(ElementDisplay), componentProp: 'data' },
|
||||
{ heading: 'Translated Service', path: 'snat_type.snat.service', component: markRaw(ElementDisplay), componentProp: 'data' },
|
||||
{ heading: 'Counter', path: 'counter' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
];
|
||||
|
@ -59,7 +64,7 @@ onMounted(async() => {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<TableView v-model:selection="selection" v-model:data="rules" title="SNAT Rules" :columns="columns" :loading="loading" :table-props="{sort:true, sortSelf: true, draggable: true}" @dragged-row="draggedRow">
|
||||
<TableView v-model:selection="selection" v-model:data="rules" title="SNAT Rules" :columns="columns" :loading="loading" :table-props="{draggable: true}" @dragged-row="draggedRow">
|
||||
<button @click="load">Refresh</button>
|
||||
<router-link class="button" to="/firewall/source_nat_rules/edit">Create</router-link>
|
||||
<router-link class="button" :class="{ disabled: selection.length != 1 }" :to="'/firewall/source_nat_rules/edit/' + selection[0]">Edit</router-link>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from '../../api';
|
||||
import getPlugins from '../../plugins';
|
||||
import ArrayDisplay from '~/components/display/ArrayDisplay.vue';
|
||||
import EnumTypeDisplay from '~/components/display/EnumTypeDisplay.vue';
|
||||
const p = getPlugins();
|
||||
|
||||
let interfaces = $ref({});
|
||||
|
@ -9,10 +11,15 @@ 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' },
|
||||
{ heading: 'Alias', path: 'alias' },
|
||||
{ heading: 'Type', path: 'interface_type', component: markRaw(EnumTypeDisplay), componentProp: 'data' },
|
||||
{ heading: 'Addressing Mode', path: 'addressing_mode', component: markRaw(EnumTypeDisplay), componentProp: 'data' },
|
||||
{ heading: 'Address', path: 'addressing_mode.static.address' },
|
||||
{ heading: 'Vlan Parent', path: 'interface_type.vlan.parent' },
|
||||
{ heading: 'Vlan ID', path: 'interface_type.vlan.id' },
|
||||
{ heading: 'Bond Members', path: 'interface_type.bond.members', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Bridge Members', path: 'interface_type.bridge.members', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
];
|
||||
|
||||
const displayData = $computed(() => {
|
||||
|
@ -21,7 +28,8 @@ const displayData = $computed(() => {
|
|||
for (const index in interfaces) {
|
||||
data.push({
|
||||
name: interfaces[index].name,
|
||||
type: interfaces[index].type,
|
||||
alias: interfaces[index].alias,
|
||||
interface_type: interfaces[index].interface_type,
|
||||
addressing_mode: interfaces[index].addressing_mode,
|
||||
address: interfaces[index].address,
|
||||
comment: interfaces[index].comment,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from '../../api';
|
||||
import getPlugins from '../../plugins';
|
||||
import EnumTypeDisplay from '~/components/display/EnumTypeDisplay.vue';
|
||||
const p = getPlugins();
|
||||
|
||||
let addresses = $ref([]);
|
||||
|
@ -9,7 +10,7 @@ let selection = $ref([] as number[]);
|
|||
|
||||
const columns = [
|
||||
{ heading: 'Name', path: 'name' },
|
||||
{ heading: 'Type', path: 'type' },
|
||||
{ heading: 'Type', path: 'type', component: markRaw(EnumTypeDisplay), componentProp: 'data' },
|
||||
{ heading: 'Value', path: 'value' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
];
|
||||
|
@ -33,7 +34,7 @@ const displayData = $computed(() => {
|
|||
data.push({
|
||||
name: addresses[index].name,
|
||||
value: getAddressValue(addresses[index]),
|
||||
type: addresses[index].type,
|
||||
type: addresses[index].address_type,
|
||||
comment: addresses[index].comment,
|
||||
});
|
||||
}
|
||||
|
@ -42,7 +43,7 @@ const displayData = $computed(() => {
|
|||
|
||||
function getAddressValue(s: any): string {
|
||||
let value: string;
|
||||
switch (s.type) {
|
||||
switch (s.address_type) {
|
||||
case 'host':
|
||||
value = s.host;
|
||||
break;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from '../../api';
|
||||
import getPlugins from '../../plugins';
|
||||
import EnumTypeDisplay from '~/components/display/EnumTypeDisplay.vue';
|
||||
const p = getPlugins();
|
||||
|
||||
let services = $ref({});
|
||||
|
@ -9,7 +10,7 @@ let selection = $ref([] as number[]);
|
|||
|
||||
const columns = [
|
||||
{ heading: 'Name', path: 'name' },
|
||||
{ heading: 'Type', path: 'type' },
|
||||
{ heading: 'Type', path: 'type', component: markRaw(EnumTypeDisplay), componentProp: 'data' },
|
||||
{ heading: 'Value', path: 'value' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from '../../api';
|
||||
import getPlugins from '../../plugins';
|
||||
import ArrayDisplay from '~/components/display/ArrayDisplay.vue';
|
||||
const p = getPlugins();
|
||||
|
||||
let servers = $ref([]);
|
||||
|
@ -9,6 +10,7 @@ let selection = $ref([] as number[]);
|
|||
|
||||
const columns = [
|
||||
{ heading: 'Interface', path: 'interface' },
|
||||
{ heading: 'Pool', path: 'pool', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
];
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from '../../api';
|
||||
import getPlugins from '../../plugins';
|
||||
import ArrayDisplay from '~/components/display/ArrayDisplay.vue';
|
||||
const p = getPlugins();
|
||||
|
||||
let interfaces = $ref({});
|
||||
|
@ -10,7 +11,7 @@ let selection = $ref([] as number[]);
|
|||
const columns = [
|
||||
{ heading: 'Name', path: 'name' },
|
||||
{ heading: 'Listen Port', path: 'listen_port' },
|
||||
{ heading: 'Peers', path: 'peers' },
|
||||
{ heading: 'Peers', path: 'peers', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
|
||||
];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { apiCall } from '../../api';
|
||||
import getPlugins from '../../plugins';
|
||||
import ArrayDisplay from '~/components/display/ArrayDisplay.vue';
|
||||
const p = getPlugins();
|
||||
|
||||
let peers = $ref({});
|
||||
|
@ -9,7 +10,7 @@ let selection = $ref([] as number[]);
|
|||
|
||||
const columns = [
|
||||
{ heading: 'Name', path: 'name' },
|
||||
{ heading: 'Allowed IPs', path: 'allowed_ips' },
|
||||
{ heading: 'Allowed IPs', path: 'allowed_ips', component: markRaw(ArrayDisplay), componentProp: 'data' },
|
||||
{ heading: 'Endpoint', path: 'endpoint' },
|
||||
{ heading: 'Persistent Keepalive', path: 'persistent_keepalive' },
|
||||
{ heading: 'Comment', path: 'comment' },
|
||||
|
|
|
@ -16,6 +16,7 @@ export function isNullish(value: any) {
|
|||
}
|
||||
|
||||
export function variantOf(enumValue: any) {
|
||||
if (enumValue === null) return null;
|
||||
if (typeof enumValue === 'string') return enumValue;
|
||||
else return Object.entries(enumValue)[0][0];
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue