|
|
|
|
<script setup>
|
|
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
|
|
import { storage } from "@/utils/storage.js";
|
|
|
|
|
import DrawerComponent from "@/components/common/DrawerComponent.vue";
|
|
|
|
|
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
|
|
|
|
|
import { UserManagerService } from "@/components/service/management/userManagerService";
|
|
|
|
|
import SidebarHeader from "@/components/common/SidebarHeader.vue";
|
|
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
const username = ref("");
|
|
|
|
|
const projectName = ref(localStorage.getItem("projectName") || "");
|
|
|
|
|
|
|
|
|
|
// ----------------------
|
|
|
|
|
// Admin 판별 + Admin 모드
|
|
|
|
|
// ----------------------
|
|
|
|
|
const isAdmin = ref(false); // 관리자 계정 여부
|
|
|
|
|
const adminMode = ref(false); // 설정 버튼으로 토글되는 관리자 메뉴 모드
|
|
|
|
|
const lastNonAdminPath = ref("/home"); // 마지막 일반 경로 저장
|
|
|
|
|
|
|
|
|
|
function computeIsAdmin() {
|
|
|
|
|
try {
|
|
|
|
|
const raw =
|
|
|
|
|
typeof storage?.getAuth === "function"
|
|
|
|
|
? storage.getAuth()
|
|
|
|
|
: JSON.parse(localStorage.getItem("autoflow-auth") || "null");
|
|
|
|
|
|
|
|
|
|
const roles = raw?.userInfo?.roles ?? raw?.roles ?? [];
|
|
|
|
|
const authCd = raw?.userInfo?.authCd ?? raw?.authCd ?? raw?.auth;
|
|
|
|
|
const inRoles = Array.isArray(roles)
|
|
|
|
|
? roles.includes("ROLE_ADMIN")
|
|
|
|
|
: roles === "ROLE_ADMIN";
|
|
|
|
|
isAdmin.value = inRoles || authCd === "ADMIN";
|
|
|
|
|
} catch {
|
|
|
|
|
isAdmin.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 설정 버튼 토글
|
|
|
|
|
function toggleAdmin() {
|
|
|
|
|
if (!isAdmin.value) return;
|
|
|
|
|
if (adminMode.value) {
|
|
|
|
|
adminMode.value = false;
|
|
|
|
|
router.push(lastNonAdminPath.value || "/home");
|
|
|
|
|
} else {
|
|
|
|
|
adminMode.value = true;
|
|
|
|
|
if (!route.meta?.requiresAdmin) router.push("/project");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ----------------------
|
|
|
|
|
// 상단 드롭다운 메뉴
|
|
|
|
|
// ----------------------
|
|
|
|
|
const menu = ref([]);
|
|
|
|
|
const menuItems = [
|
|
|
|
|
{ title: "Select Project", click: () => goSelect() },
|
|
|
|
|
{
|
|
|
|
|
title: "Change Password",
|
|
|
|
|
click: () => {
|
|
|
|
|
/* open modal */
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{ title: "Logout", icon: "mdi-logout", click: () => logOut() },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const drawer = ref(null);
|
|
|
|
|
const pageTitle = computed(() => route.meta.title);
|
|
|
|
|
const pagePath = computed(() => route.path);
|
|
|
|
|
|
|
|
|
|
// ✅ 유저 메뉴와 동일한 active 계산
|
|
|
|
|
const isLinkActive = (link) => route.path.includes(link);
|
|
|
|
|
|
|
|
|
|
const settingsLabel = computed(() =>
|
|
|
|
|
adminMode.value ? "Back to Console" : "Settings",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function updateUsername() {
|
|
|
|
|
const auth = storage.getAuth?.() ?? null;
|
|
|
|
|
username.value = auth?.userInfo?.username ?? auth?.username ?? "";
|
|
|
|
|
}
|
|
|
|
|
function refreshProjectName() {
|
|
|
|
|
const v = localStorage.getItem("projectName");
|
|
|
|
|
projectName.value = v ? v : "";
|
|
|
|
|
}
|
|
|
|
|
function goSelect() {
|
|
|
|
|
router.push("/select");
|
|
|
|
|
}
|
|
|
|
|
function logOut() {
|
|
|
|
|
UserManagerService.signOut()
|
|
|
|
|
.catch(console.error)
|
|
|
|
|
.finally(() => {
|
|
|
|
|
localStorage.removeItem("autoflow-auth");
|
|
|
|
|
localStorage.removeItem("projectName");
|
|
|
|
|
localStorage.removeItem("projectId");
|
|
|
|
|
username.value = "";
|
|
|
|
|
projectName.value = "";
|
|
|
|
|
sessionStorage.removeItem("initialRedirectDone");
|
|
|
|
|
adminMode.value = false;
|
|
|
|
|
router.push("/login");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// storage 변경 반영
|
|
|
|
|
function onStorage(e) {
|
|
|
|
|
if (!e.key || e.key === "projectName") refreshProjectName();
|
|
|
|
|
if (!e.key || e.key === "autoflow-auth" || e.key === "auth") {
|
|
|
|
|
updateUsername();
|
|
|
|
|
computeIsAdmin();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const goMain = () => {
|
|
|
|
|
router.push("/home");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 마지막 일반 경로 추적
|
|
|
|
|
watch(
|
|
|
|
|
() => route.fullPath,
|
|
|
|
|
() => {
|
|
|
|
|
refreshProjectName();
|
|
|
|
|
const isAdminRoute = route.matched.some((r) => r.meta?.requiresAdmin);
|
|
|
|
|
if (!isAdminRoute) lastNonAdminPath.value = route.fullPath || "/home";
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
updateUsername();
|
|
|
|
|
computeIsAdmin();
|
|
|
|
|
refreshProjectName();
|
|
|
|
|
menu.value = menuItems;
|
|
|
|
|
window.addEventListener("storage", onStorage);
|
|
|
|
|
});
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
window.removeEventListener("storage", onStorage);
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<v-app>
|
|
|
|
|
<!-- 사이드바 -->
|
|
|
|
|
<v-navigation-drawer
|
|
|
|
|
v-model="drawer"
|
|
|
|
|
border="0"
|
|
|
|
|
hide-overlay
|
|
|
|
|
permanent
|
|
|
|
|
v-if="!route.meta.hideSidebar"
|
|
|
|
|
:rail="false"
|
|
|
|
|
>
|
|
|
|
|
<!-- 헤더 -->
|
|
|
|
|
<v-card
|
|
|
|
|
:ripple="false"
|
|
|
|
|
flat
|
|
|
|
|
class="bg-shades-transparent d-flex w-100 justify-center text-h5 pa-4 pb-16"
|
|
|
|
|
@click="goMain"
|
|
|
|
|
>
|
|
|
|
|
<SidebarHeader />
|
|
|
|
|
</v-card>
|
|
|
|
|
|
|
|
|
|
<!-- 기본(일반 사용자) 메뉴 -->
|
|
|
|
|
<DrawerComponent v-if="!adminMode" />
|
|
|
|
|
|
|
|
|
|
<!-- 관리자 메뉴: 유저 메뉴와 동일한 룩/여백/활성 스타일 -->
|
|
|
|
|
<template v-else>
|
|
|
|
|
<v-card flat class="mx-auto">
|
|
|
|
|
<v-list nav class="pa-5 pt-0">
|
|
|
|
|
<v-list-item
|
|
|
|
|
rounded
|
|
|
|
|
title="Projects"
|
|
|
|
|
value="projects"
|
|
|
|
|
to="/project"
|
|
|
|
|
prepend-icon="mdi-briefcase"
|
|
|
|
|
:active="isLinkActive('/project')"
|
|
|
|
|
:active-color="isLinkActive('/project') ? 'primary' : null"
|
|
|
|
|
density="compact"
|
|
|
|
|
class="pa-2 rounded-lg"
|
|
|
|
|
style="padding-inline-start: 10px"
|
|
|
|
|
/>
|
|
|
|
|
<v-list-item
|
|
|
|
|
rounded
|
|
|
|
|
title="Users"
|
|
|
|
|
value="users"
|
|
|
|
|
to="/users"
|
|
|
|
|
prepend-icon="mdi-account-multiple"
|
|
|
|
|
:active="isLinkActive('/users')"
|
|
|
|
|
:active-color="isLinkActive('/users') ? 'primary' : null"
|
|
|
|
|
density="compact"
|
|
|
|
|
class="pa-2 rounded-lg"
|
|
|
|
|
style="padding-inline-start: 10px"
|
|
|
|
|
/>
|
|
|
|
|
</v-list>
|
|
|
|
|
</v-card>
|
|
|
|
|
</template>
|
|
|
|
|
</v-navigation-drawer>
|
|
|
|
|
<v-app-bar class="bg-shades-transparent" flat>
|
|
|
|
|
<v-spacer />
|
|
|
|
|
|
|
|
|
|
<v-tooltip v-if="isAdmin" location="bottom" text="Settings">
|
|
|
|
|
<template #activator="{ props }">
|
|
|
|
|
<v-btn
|
|
|
|
|
icon
|
|
|
|
|
color="primary"
|
|
|
|
|
class="mr-3"
|
|
|
|
|
v-bind="props"
|
|
|
|
|
@click="toggleAdmin"
|
|
|
|
|
aria-label="Settings"
|
|
|
|
|
>
|
|
|
|
|
<v-icon>mdi-cog</v-icon>
|
|
|
|
|
</v-btn>
|
|
|
|
|
</template>
|
|
|
|
|
</v-tooltip>
|
|
|
|
|
|
|
|
|
|
<!-- 프로젝트 선택 -->
|
|
|
|
|
<v-tooltip location="bottom" text="Project">
|
|
|
|
|
<template #activator="{ props }">
|
|
|
|
|
<v-btn
|
|
|
|
|
icon
|
|
|
|
|
color="primary"
|
|
|
|
|
class="mr-3"
|
|
|
|
|
@click="goSelect"
|
|
|
|
|
v-bind="props"
|
|
|
|
|
>
|
|
|
|
|
<v-icon>mdi-home</v-icon>
|
|
|
|
|
</v-btn>
|
|
|
|
|
</template>
|
|
|
|
|
</v-tooltip>
|
|
|
|
|
|
|
|
|
|
<div style="min-width: 180px" class="d-flex flex-column align-end">
|
|
|
|
|
<div class="font-weight-black">{{ username || "GUEST" }}</div>
|
|
|
|
|
<div class="text-subtitle-2">
|
|
|
|
|
{{ projectName || "No Project Selected" }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<v-menu location="bottom end">
|
|
|
|
|
<template #activator="{ props }">
|
|
|
|
|
<v-btn icon color="primary" v-bind="props" class="mr-3">
|
|
|
|
|
<v-icon>mdi-arrow-down-drop-circle-outline</v-icon>
|
|
|
|
|
</v-btn>
|
|
|
|
|
</template>
|
|
|
|
|
<v-list>
|
|
|
|
|
<v-list-item
|
|
|
|
|
v-for="(item, index) in menu"
|
|
|
|
|
:key="index"
|
|
|
|
|
:value="index"
|
|
|
|
|
@click="item.click"
|
|
|
|
|
:prepend-icon="item.icon"
|
|
|
|
|
>
|
|
|
|
|
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
|
|
|
|
</v-list-item>
|
|
|
|
|
</v-list>
|
|
|
|
|
</v-menu>
|
|
|
|
|
</v-app-bar>
|
|
|
|
|
|
|
|
|
|
<v-main>
|
|
|
|
|
<v-container
|
|
|
|
|
fluid
|
|
|
|
|
class="pa-16 background d-flex justify-center"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
>
|
|
|
|
|
<slot></slot>
|
|
|
|
|
</v-container>
|
|
|
|
|
</v-main>
|
|
|
|
|
</v-app>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="sass"></style>
|