- Docker infrastructure (Dockerfile, docker-compose.yml, genieacs-sim) - AdonisJS v7 app with Inertia.js + Vue 3 - MongoDB integration - CWMP/TR-069 state machine engine - Configuration engine with compliance tracking - Branding: ADAM ACS (favicon, navbar, locales) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
233 lines
6.3 KiB
Vue
233 lines
6.3 KiB
Vue
<script setup lang="ts">
|
|
import { Link, Head, usePage } from '@inertiajs/vue3'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { ref } from 'vue'
|
|
|
|
const { t, locale } = useI18n()
|
|
const currentLocale = ref(locale.value)
|
|
const page = usePage()
|
|
const langMenuOpen = ref(false)
|
|
|
|
function switchLocale(lang: string) {
|
|
locale.value = lang
|
|
currentLocale.value = lang
|
|
langMenuOpen.value = false
|
|
}
|
|
|
|
function toggleLangMenu() {
|
|
langMenuOpen.value = !langMenuOpen.value
|
|
}
|
|
|
|
function isActive(paths: string | string[]): boolean {
|
|
const url = page.url
|
|
if (Array.isArray(paths)) {
|
|
return paths.some((p) => url === p || (p !== '/' && url.startsWith(p)))
|
|
}
|
|
return url === paths || (paths !== '/' && url.startsWith(paths))
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Head :title="page.component" />
|
|
|
|
<div class="d-flex flex-column min-vh-100">
|
|
<!-- Navbar -->
|
|
<header>
|
|
<div class="bg-dark">
|
|
<div class="container-xxl">
|
|
<nav id="main-menu" class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
<!-- Brand / Logo -->
|
|
<h1 class="navbar-brand mb-0 me-3">
|
|
<Link href="/">
|
|
<span class="visually-hidden">{{ t('header.title') }}</span>
|
|
<svg width="30" height="30" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<rect width="100" height="100" fill="#ff7900"/>
|
|
<rect x="14.29" y="71.43" width="71.43" height="14.29" fill="#fff"/>
|
|
</svg>
|
|
</Link>
|
|
</h1>
|
|
|
|
<!-- Mobile toggle -->
|
|
<button
|
|
class="navbar-toggler"
|
|
type="button"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#navbarContent"
|
|
aria-controls="navbarContent"
|
|
aria-expanded="false"
|
|
aria-label="Toggle navigation"
|
|
>
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
|
|
<!-- Nav links -->
|
|
<ul class="navbar-nav_container navbar-nav">
|
|
<li class="nav-item">
|
|
<Link
|
|
href="/"
|
|
class="nav-link"
|
|
:class="{ active: isActive('/') && !page.url.startsWith('/configurations') && !page.url.startsWith('/commands') }"
|
|
>
|
|
{{ t('header.devices') }}
|
|
</Link>
|
|
</li>
|
|
<li class="nav-item">
|
|
<Link
|
|
href="/configurations"
|
|
class="nav-link"
|
|
:class="{ active: isActive('/configurations') }"
|
|
>
|
|
{{ t('header.configurations') }}
|
|
</Link>
|
|
</li>
|
|
<li class="nav-item">
|
|
<Link
|
|
href="/firmware-catalog"
|
|
class="nav-link"
|
|
:class="{ active: isActive('/firmware-catalog') }"
|
|
>
|
|
{{ t('header.firmware') }}
|
|
</Link>
|
|
</li>
|
|
<li class="nav-item">
|
|
<Link
|
|
href="/commands"
|
|
class="nav-link"
|
|
:class="{ active: isActive('/commands') }"
|
|
>
|
|
{{ t('header.commands') }}
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Right: language dropdown -->
|
|
<div class="navbar-right">
|
|
<ul class="navbar-nav">
|
|
<li class="nav-item dropdown" :class="{ show: langMenuOpen }">
|
|
<a
|
|
class="nav-link dropdown-toggle"
|
|
href="javascript:void(0);"
|
|
role="button"
|
|
@click="toggleLangMenu"
|
|
:aria-expanded="langMenuOpen"
|
|
>
|
|
<span>{{ currentLocale.toUpperCase() }}</span>
|
|
</a>
|
|
<ul class="dropdown-menu dropdown-menu-end" :class="{ show: langMenuOpen }">
|
|
<li>
|
|
<a
|
|
class="dropdown-item"
|
|
:class="{ active: currentLocale === 'fr' }"
|
|
href="javascript:void(0);"
|
|
@click="switchLocale('fr')"
|
|
>
|
|
FR — Français
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
class="dropdown-item"
|
|
:class="{ active: currentLocale === 'en' }"
|
|
href="javascript:void(0);"
|
|
@click="switchLocale('en')"
|
|
>
|
|
EN — English
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Content -->
|
|
<main class="flex-grow-1" style="background-color: #f6f6f6;">
|
|
<slot />
|
|
</main>
|
|
|
|
<!-- Footer -->
|
|
<footer class="footer navbar fixed-bottom bg-dark" data-bs-theme="dark">
|
|
<h2 class="visually-hidden">Sitemap & information</h2>
|
|
<div class="container-xxl footer-terms">
|
|
<ul class="navbar-nav flex-row gap-md-3">
|
|
<li class="fw-bold text-white">© Orange 2025</li>
|
|
<li><a class="nav-link" href="#">{{ t('footer.legal') }}</a></li>
|
|
<li><a class="nav-link" href="#">{{ t('footer.privacy') }}</a></li>
|
|
</ul>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
main {
|
|
padding-bottom: 80px;
|
|
}
|
|
|
|
#main-menu {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
#main-menu .navbar-brand {
|
|
margin: 0;
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
#main-menu .navbar-brand a {
|
|
display: inline-block;
|
|
padding-bottom: 0.25rem;
|
|
}
|
|
|
|
#main-menu .navbar-brand svg {
|
|
display: block;
|
|
}
|
|
|
|
#main-menu .nav-link.active {
|
|
padding-bottom: 0.75rem;
|
|
}
|
|
|
|
.navbar-nav_container {
|
|
display: flex;
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
gap: 0.25rem;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.navbar-right {
|
|
margin-left: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.navbar-right .navbar-nav {
|
|
display: flex;
|
|
flex-direction: row;
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.navbar-right .nav-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.nav-item.dropdown .dropdown-menu {
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 0;
|
|
left: auto;
|
|
}
|
|
|
|
.dropdown-item.active {
|
|
background-color: #ff7900;
|
|
color: #fff;
|
|
}
|
|
</style>
|