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>
      <div class="relative">
        <div class="app-user-bar">
          <div v-if="authStore.isAuthenticated" class="user-info">
            <span v-if="authStore.user?.display_name" class="user-name">
              {{ authStore.user.display_name }}
            </span>
            <GnButton variant="ghost" size="sm" @click="handleLogout">
              <template #icon>
                <i class="ph ph-sign-out" />
              </template>
              Logout
            </GnButton>
          </div>
          <GnButton
            v-else
            variant="ghost"
            size="sm"
            @click="handleLogin"
          >
            <template #icon>
              <i class="ph ph-sign-in" />
            </template>
            Login
          </GnButton>
        </div>
        <slot />
      </div>
    </template>
  </GnNavigationShell>
</template>

<script setup>
import { computed } from "vue";
import { useRoute } from "vue-router";
import { GnNavigationShell, GnButton } 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 handleLogin() {
  const returnTo = window.location.hash || "#/";
  window.location.href = `/auth/login?return_to=${encodeURIComponent(returnTo)}`;
}

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

<style scoped>
.app-user-bar {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.user-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.user-name {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-text-secondary, #6b7280);
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>