mirror of
https://github.com/speatzle/nfsense.git
synced 2025-05-11 19:08:20 +00:00
Added Nav Dropdowns
- Also centered nfsense logo - TODO: Find way for nested dropdowns to not look dumb in reduced state
This commit is contained in:
parent
eac98192be
commit
db4d33e354
3 changed files with 127 additions and 25 deletions
|
@ -16,28 +16,44 @@ import ITimeServer from '~icons/carbon/server-time';
|
||||||
import IWireguard from '~icons/simple-icons/wireguard';
|
import IWireguard from '~icons/simple-icons/wireguard';
|
||||||
import IDHCPServer from '~icons/material-symbols/book-rounded';
|
import IDHCPServer from '~icons/material-symbols/book-rounded';
|
||||||
import IUser from '~icons/mdi/user';
|
import IUser from '~icons/mdi/user';
|
||||||
|
import IServer from '~icons/ri/server-line';
|
||||||
|
import INodes from '~icons/fa6-solid/share-nodes';
|
||||||
|
import IList from '~icons/material-symbols/format-list-bulleted';
|
||||||
|
import IFirewall from '~icons/mdi/wall-fire';
|
||||||
|
import INetwork from '~icons/mdi/lan';
|
||||||
|
|
||||||
enum NavState { Open, Reduced, Collapsed };
|
enum NavState { Open, Reduced, Collapsed };
|
||||||
const NavStateCount = 3;
|
const NavStateCount = 3;
|
||||||
let navState = $ref(NavState.Open);
|
let navState = $ref(NavState.Open);
|
||||||
const navRoutes = {
|
|
||||||
'/': { icon: IDashboard, caption: 'Dashboard' },
|
const navRoutesNew = [
|
||||||
'/firewall/forward_rules': { icon: IRule, caption: 'Rules' },
|
{ caption: 'Dashboard', icon: IDashboard, href: '/' },
|
||||||
'/firewall/source_nat_rules': { icon: ISNAT, caption: 'SNAT' },
|
{ caption: 'Firewall', icon: IFirewall, children: [
|
||||||
'/firewall/destination_nat_rules': { icon: IDNAT, caption: 'DNAT' },
|
{ caption: 'Rules', icon: IRule, href: '/firewall/forward_rules' },
|
||||||
'/network/interfaces': { icon: IEthernet, caption: 'Interfaces' },
|
{ caption: 'SNAT', icon: ISNAT, href: '/firewall/source_nat_rules' },
|
||||||
'/network/static_routes': { icon: IStaticRoutes, caption: 'Static Routes' },
|
{ caption: 'DNAT', icon: IDNAT, href: '/firewall/destination_nat_rules' },
|
||||||
'/object/addresses': { icon: IAddress, caption: 'Addresses' },
|
]},
|
||||||
'/object/services': { icon: IService, caption: 'Services' },
|
{ caption: 'Network', icon: INetwork, children: [
|
||||||
'/service/dhcp_servers': { icon: IDHCPServer, caption: 'DHCP Server' },
|
{ caption: 'Interfaces', icon: IEthernet, href: '/network/interfaces' },
|
||||||
'/service/dns_servers': { icon: IDNSServer, caption: 'DNS Server' },
|
{ caption: 'Static Routes', icon: IStaticRoutes, href: '/network/static_routes' },
|
||||||
'/service/ntp_servers': { icon: ITimeServer, caption: 'NTP Server' },
|
] },
|
||||||
'/vpn/wireguard_status': { icon: IWireguard, caption: 'Wireguard Status' },
|
{ caption: 'Objects', icon: IList, children: [
|
||||||
'/vpn/wireguard_interfaces': { icon: IWireguard, caption: 'Wireguard Interfaces' },
|
{ caption: 'Addresses', icon: IAddress, href: '/object/addresses' },
|
||||||
'/vpn/wireguard_peers': { icon: IWireguard, caption: 'Wireguard Peers' },
|
{ caption: 'Services', icon: IService, href: '/object/services' },
|
||||||
'/system/users': { icon: IUser, caption: 'Users' },
|
] },
|
||||||
'/config/config': { icon: IConfig, caption: 'Config' },
|
{ caption: 'Services', icon: IServer, children: [
|
||||||
};
|
{ caption: 'DHCP', icon: IDHCPServer, href: '/service/dhcp_servers' },
|
||||||
|
{ caption: 'DNS', icon: IDNSServer, href: '/service/dns_servers' },
|
||||||
|
{ caption: 'NTP', icon: ITimeServer, href: '/service/ntp_servers' },
|
||||||
|
{ caption: 'Wireguard', icon: IWireguard, children: [
|
||||||
|
{ caption: 'Status', icon: IDashboard, href: '/vpn/wireguard_status' },
|
||||||
|
{ caption: 'Interfaces', icon: IEthernet, href: '/vpn/wireguard_interfaces' },
|
||||||
|
{ caption: 'Peers', icon: INodes, href: '/vpn/wireguard_peers' },
|
||||||
|
]},
|
||||||
|
] },
|
||||||
|
{ caption: 'Users', icon: IUser, href: '/system/users' },
|
||||||
|
{ caption: 'Config', icon: IConfig, href: '/config/config' },
|
||||||
|
];
|
||||||
|
|
||||||
enum AuthState { Unauthenticated, MfaRequired, Authenticated };
|
enum AuthState { Unauthenticated, MfaRequired, Authenticated };
|
||||||
let authState = $ref(AuthState.Unauthenticated);
|
let authState = $ref(AuthState.Unauthenticated);
|
||||||
|
@ -130,12 +146,7 @@ onMounted(async() => {
|
||||||
|
|
||||||
<div class="nav-body cl-secondary cl-force-dark">
|
<div class="nav-body cl-secondary cl-force-dark">
|
||||||
<div>
|
<div>
|
||||||
<template v-for="(options, route) in navRoutes" :key="route">
|
<NavElements :routes="navRoutesNew" :click-handler="collapseNavIfMobile"/>
|
||||||
<router-link :to="route" class="button" @click="collapseNavIfMobile">
|
|
||||||
<component :is="options.icon"/>
|
|
||||||
{{ options.caption }}
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<router-link class="button" to="/help"><i-material-symbols-help-outline/></router-link>
|
<router-link class="button" to="/help"><i-material-symbols-help-outline/></router-link>
|
||||||
|
@ -190,7 +201,7 @@ onMounted(async() => {
|
||||||
.page-header { grid-area: PH; }
|
.page-header { grid-area: PH; }
|
||||||
.page-content { grid-area: PC; }
|
.page-content { grid-area: PC; }
|
||||||
|
|
||||||
.nav-head { font-weight: bold; }
|
.nav-head { font-weight: bold; text-align: center; }
|
||||||
.nav-head > svg { display: none; }
|
.nav-head > svg { display: none; }
|
||||||
.nav-head > h1 { flex-grow: 1; }
|
.nav-head > h1 { flex-grow: 1; }
|
||||||
|
|
||||||
|
|
63
client/src/components/layout/NavDropdown.vue
Normal file
63
client/src/components/layout/NavDropdown.vue
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NavRoute } from './NavElements.vue';
|
||||||
|
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
icon?: string | Component,
|
||||||
|
caption?: string,
|
||||||
|
children: NavRoute[],
|
||||||
|
clickHandler?: () => void,
|
||||||
|
}>(), {
|
||||||
|
icon: '',
|
||||||
|
caption: '',
|
||||||
|
children: () => [],
|
||||||
|
clickHandler: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
let expanded = $ref(false);
|
||||||
|
|
||||||
|
function tallyChildren(routes: NavRoute[]) {
|
||||||
|
let count = routes.length;
|
||||||
|
for (const route of routes)
|
||||||
|
if (route.children)
|
||||||
|
count += tallyChildren(route.children);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div :class="{'nav-dropdown-expanded': expanded}" :style="`--predicted-height: ${2.5 * tallyChildren(children)}rem;`">
|
||||||
|
<div class="button" @click="expanded = !expanded">
|
||||||
|
<component v-if="(typeof icon !== 'string')" :is="icon"/>
|
||||||
|
<template v-else>{{ icon }}</template>
|
||||||
|
<span v-text="caption"/>
|
||||||
|
<i-material-symbols-expand-more class="nav-dropdown-expand-icon" width="1em" height="1em"/>
|
||||||
|
</div>
|
||||||
|
<div class="nav-dropdown-body">
|
||||||
|
<NavElements :routes="children" :click-handler="clickHandler"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
span {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.nav-dropdown-body > :is(button, .button) {
|
||||||
|
padding-left: calc(0.5rem - 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-dropdown-body {
|
||||||
|
transition: all 0.1s ease-out;
|
||||||
|
max-height: 0px;
|
||||||
|
border-left: 1px solid white;
|
||||||
|
}
|
||||||
|
.nav-dropdown-expanded > .nav-dropdown-body {
|
||||||
|
max-height: var(--predicted-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-dropdown-expand-icon {
|
||||||
|
transition: all 0.1s ease-out;
|
||||||
|
}
|
||||||
|
.nav-dropdown-expanded > div > .nav-dropdown-expand-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
</style>
|
28
client/src/components/layout/NavElements.vue
Normal file
28
client/src/components/layout/NavElements.vue
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export type NavRoute = {
|
||||||
|
icon?: Component,
|
||||||
|
caption?: string,
|
||||||
|
children?: NavRoute[],
|
||||||
|
href?: string,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script setup lang="ts">
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
routes?: NavRoute[],
|
||||||
|
clickHandler?: () => void,
|
||||||
|
}>(), {
|
||||||
|
routes: () => [],
|
||||||
|
clickHandler: () => {},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<template v-if="routes">
|
||||||
|
<template v-for="route of routes" :key="route.href">
|
||||||
|
<router-link v-if="route.href" :to="route.href" class="button" @click="clickHandler" :title="route.caption">
|
||||||
|
<component :is="route.icon"/>
|
||||||
|
{{ route.caption }}
|
||||||
|
</router-link>
|
||||||
|
<NavDropdown v-else v-bind="route"/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
Loading…
Add table
Reference in a new issue