diff --git a/apps/admin/src/composables/useSmokeTestsAllowed.ts b/apps/admin/src/composables/useSmokeTestsAllowed.ts new file mode 100644 index 0000000..1998dbd --- /dev/null +++ b/apps/admin/src/composables/useSmokeTestsAllowed.ts @@ -0,0 +1,24 @@ +import { ref } from 'vue'; +import api from '../api'; + +const allowed = ref(null); +let loadPromise: Promise | null = null; + +export function useSmokeTestsAllowed() { + async function ensureLoaded() { + if (allowed.value !== null) return; + if (!loadPromise) { + loadPromise = (async () => { + try { + const { data } = await api.get('/admin/system/smoke-tests'); + allowed.value = !!data.data?.allowed; + } catch { + allowed.value = false; + } + })(); + } + await loadPromise; + } + + return { allowed, ensureLoaded }; +} diff --git a/apps/admin/src/layouts/ManageLayout.vue b/apps/admin/src/layouts/ManageLayout.vue index 774610f..69bc24c 100644 --- a/apps/admin/src/layouts/ManageLayout.vue +++ b/apps/admin/src/layouts/ManageLayout.vue @@ -3,6 +3,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'; import { RouterView, RouterLink, useRoute, useRouter } from 'vue-router'; import { useAuthStore } from '../stores/auth'; import { useAdminLocale } from '../composables/useAdminLocale'; +import { useSmokeTestsAllowed } from '../composables/useSmokeTestsAllowed'; import AdminLocaleSwitcher from '../components/AdminLocaleSwitcher.vue'; import AdminNavIcon from '../components/AdminNavIcon.vue'; import { resolveAdminBreadcrumb } from '../utils/admin-breadcrumb'; @@ -11,23 +12,30 @@ const route = useRoute(); const router = useRouter(); const auth = useAuthStore(); const { t } = useAdminLocale(); +const { allowed: smokeTestsAllowed, ensureLoaded: ensureSmokeTestsAllowed } = useSmokeTestsAllowed(); const sidebarOpen = ref(false); const isMobileNav = ref(false); -const adminMenus = computed(() => [ - { path: '/', label: t('nav.dashboard'), icon: 'dashboard', matchPrefix: true }, - { path: '/matches', label: t('nav.matches'), icon: 'matches', matchPrefix: true }, - { path: '/users', label: t('nav.agents_players'), icon: 'users' }, - { path: '/finance-logs', label: t('nav.finance_logs'), icon: 'finance' }, - { path: '/deposit', label: t('nav.deposit_manage'), icon: 'deposit', matchPrefix: true }, - { path: '/cashback', label: t('nav.cashback'), icon: 'cashback' }, - { path: '/bets', label: t('nav.bets'), icon: 'bets' }, - { path: '/contents', label: t('nav.contents'), icon: 'contents' }, - { path: '/media', label: t('nav.media'), icon: 'media' }, - { path: '/audit', label: t('nav.audit'), icon: 'audit' }, - { path: '/smoke-tests', label: t('nav.smoke_tests'), icon: 'smoke-tests' }, -]); +const adminMenus = computed(() => { + const items = [ + { path: '/', label: t('nav.dashboard'), icon: 'dashboard', matchPrefix: true }, + { path: '/matches', label: t('nav.matches'), icon: 'matches', matchPrefix: true }, + { path: '/users', label: t('nav.agents_players'), icon: 'users' }, + { path: '/finance-logs', label: t('nav.finance_logs'), icon: 'finance' }, + { path: '/deposit', label: t('nav.deposit_manage'), icon: 'deposit', matchPrefix: true }, + { path: '/cashback', label: t('nav.cashback'), icon: 'cashback' }, + { path: '/bets', label: t('nav.bets'), icon: 'bets' }, + { path: '/contents', label: t('nav.contents'), icon: 'contents' }, + { path: '/media', label: t('nav.media'), icon: 'media' }, + { path: '/audit', label: t('nav.audit'), icon: 'audit' }, + { path: '/smoke-tests', label: t('nav.smoke_tests'), icon: 'smoke-tests' }, + ]; + if (smokeTestsAllowed.value === false) { + return items.filter((item) => item.path !== '/smoke-tests'); + } + return items; +}); const agentMenus = computed(() => [ { path: '/', label: t('nav.dashboard'), icon: 'dashboard' }, @@ -114,6 +122,9 @@ function logout() { onMounted(() => { syncMobileNav(); window.addEventListener('resize', syncMobileNav); + if (auth.isAdmin.value) { + void ensureSmokeTestsAllowed(); + } }); onUnmounted(() => { diff --git a/apps/admin/src/router/index.ts b/apps/admin/src/router/index.ts index 4a75daa..38195bf 100644 --- a/apps/admin/src/router/index.ts +++ b/apps/admin/src/router/index.ts @@ -1,5 +1,6 @@ import { createRouter, createWebHistory } from 'vue-router'; import { useAuthStore } from '../stores/auth'; +import { useSmokeTestsAllowed } from '../composables/useSmokeTestsAllowed'; import { ensureStaffSession } from '../utils/session-hydrate'; const router = createRouter({ @@ -107,7 +108,7 @@ const router = createRouter({ { path: 'smoke-tests', component: () => import('../views/SmokeTests.vue'), - meta: { adminOnly: true }, + meta: { adminOnly: true, smokeTestsOnly: true }, }, { path: 'media', @@ -185,6 +186,12 @@ router.beforeEach(async (to) => { return '/'; } + if (to.meta.smokeTestsOnly) { + const { ensureLoaded, allowed } = useSmokeTestsAllowed(); + await ensureLoaded(); + if (!allowed.value) return '/'; + } + return true; }); diff --git a/apps/admin/src/views/SmokeTests.vue b/apps/admin/src/views/SmokeTests.vue index 0bc192a..59c5287 100644 --- a/apps/admin/src/views/SmokeTests.vue +++ b/apps/admin/src/views/SmokeTests.vue @@ -1,11 +1,15 @@