Newer
Older
smart-home-server / webclient / src / components / layout / AppShell.vue
<template>
  <GnNavigationShell
    brand="SHSERV WEB CLIENT"
    logo-src=""
    title="Navigation"
    subtitle="Smart Home"
    :items="navItems"
    active-match="prefix"
    :current="pageTitle"
  >
    <template #content>
      <slot />
    </template>

    <template #footer>
      <GnUserCard
        v-if="authStore.isAuthenticated"
        :name="authStore.user?.display_name || 'User'"
        :email="authStore.user?.email || ''"
        :role="formatRole(authStore.user?.system_role)"
        :avatar="{
          src: authStore.user?.avatar_url || '',
          initials: getInitials(authStore.user?.display_name),
          size: 'sm',
        }"
        :href="authStore.user?.gauth_profile_url || ''"
        compact
        :actions="[
          {
            label: 'Logout',
            icon: 'ph-sign-out',
            variant: 'ghost',
            onClick: handleLogout,
          },
        ]"
      />
      <GnButton
        v-else
        variant="primary"
        size="sm"
        @click="handleLogin"
      >
        <template #icon>
          <i class="ph ph-sign-in" />
        </template>
        Sign in
      </GnButton>
    </template>
  </GnNavigationShell>
</template>

<script setup>
import { computed } from "vue";
import { useRoute } from "vue-router";
import { GnNavigationShell, GnButton, GnUserCard } from "gnexus-ui-kit/vue";
import { useAuthStore } from "../../stores/auth.js";

const route = useRoute();
const authStore = useAuthStore();

const PAGE_TITLES = {
  login: "Login",
  "areas-favorites": "Favorites",
  "areas-tree": "Areas",
  "area-detail": "Area",
  devices: "Devices",
  "device-detail": "Device",
  "devices-scanning": "Scanning",
  "scripts-actions": "Actions",
  "scripts-regular": "Regular",
  "scripts-scopes": "Scopes",
  "script-detail": "Script",
  firmwares: "Firmwares",
};

const pageTitle = computed(() => {
  const name = route?.name;
  if (name && PAGE_TITLES[name]) {
    return PAGE_TITLES[name];
  }
  return "";
});

const ALL_NAV_ITEMS = [
  { label: "Favorites", to: "/areas/favorites", icon: "ph-bookmarks", permission: "areas.view" },
  { label: "Areas", to: "/areas/tree", icon: "ph-map-trifold", permission: "areas.view" },
  { label: "Devices", to: "/devices", icon: "ph-cpu", permission: "devices.view" },
  { label: "Scanning", to: "/devices/scanning", icon: "ph-magnifying-glass", permission: "devices.scan" },
  { label: "Actions", to: "/scripts/actions", icon: "ph-play", permission: "scripts.run" },
  { label: "Regular", to: "/scripts/regular", icon: "ph-clock", permission: "scripts.view" },
  { label: "Scopes", to: "/scripts/scopes", icon: "ph-brackets-curly", permission: "scripts.view" },
  { label: "Firmwares", to: "/firmwares", icon: "ph-cloud-arrow-down", permission: "firmware.view" },
];

const navItems = computed(() => {
  return ALL_NAV_ITEMS.filter((item) => {
    if (!item.permission) {
      return true;
    }
    return authStore.hasPermission(item.permission);
  });
});

function getInitials(name) {
  if (!name) return "";
  return name
    .split(/\s+/)
    .slice(0, 2)
    .map((w) => w[0]?.toUpperCase())
    .join("");
}

function formatRole(role) {
  if (!role) return "";
  return role.charAt(0).toUpperCase() + role.slice(1);
}

function handleLogin() {
  const returnTo = window.location.href;
  window.location.href = `/proxy.php?path=/auth/login&return_to=${encodeURIComponent(returnTo)}`;
}

function closeDrawer() {
  const backdrop = document.querySelector(".nav-drawer-backdrop");
  if (backdrop) {
    backdrop.click();
  }
}

function handleLogout() {
  closeDrawer();
  authStore.logout();
}
</script>