diff --git a/layouts/blank.vue b/layouts/blank.vue new file mode 100644 index 00000000..d01c2136 --- /dev/null +++ b/layouts/blank.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/layouts/default.vue b/layouts/default.vue index 38dce44f..eb726039 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -4,6 +4,7 @@ class="main-layout__sidebar" :api-version="apiVersion" :client-version="clientVersion" + :profile="profile" />
@@ -34,10 +35,11 @@ export default defineComponent({ const { themeType, isFixedHeader } = storeToRefs(settingsStore); const { - api: { getVersion }, + api: { getVersion, getProfile }, } = useSettings(); const apiVersion = await getVersion(); + const profile = await getProfile(); const { events } = useEvents(); @@ -55,6 +57,7 @@ export default defineComponent({ ? `v${apiVersion}` : `@${apiVersion}`, clientVersion, + profile, }; }, }); @@ -69,10 +72,7 @@ export default defineComponent({ .main-layout__sidebar { @apply w-10 md:w-14 lg:w-16 flex-none border-r border-gray-200 dark:border-gray-700 z-50 w-full h-full sticky top-0 h-screen max-h-screen; -} - -.main-layout__header { - @apply flex-none w-full h-10; + @include layout-sidebar; } .main-layout__content { @@ -82,8 +82,4 @@ export default defineComponent({ @apply flex flex-col h-full flex-1; } } - -.main-layout__sidebar { - @include layout-sidebar; -} diff --git a/middleware/auth.global.ts b/middleware/auth.global.ts new file mode 100644 index 00000000..e4301b42 --- /dev/null +++ b/middleware/auth.global.ts @@ -0,0 +1,22 @@ +import { useNuxtApp, navigateTo } from "#app" + +export default defineNuxtRouteMiddleware(async (to, from) => { + const app = useNuxtApp() + const {localStorage} = window; + + if (!app.$appSettings.auth.enabled) { + return; + } + + // todo: move token to a store + if (to.name !== 'login' && !app.$authToken.token) { + return navigateTo('/login'); + } + + if (to.name === 'login' && to?.query?.token) { + localStorage?.setItem('token', to.query.token); + // todo: use store + app.$authToken.token = to.query.token; + return navigateTo('/'); + } +}) diff --git a/nuxt.config.ts b/nuxt.config.ts index 7d140fde..ee3d550c 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -26,6 +26,9 @@ export default defineNuxtConfig({ ], }, }, + plugins: [ + '~/plugins/auth', + ], dir: { static: 'static', }, diff --git a/pages/login.vue b/pages/login.vue new file mode 100644 index 00000000..ebac9cd6 --- /dev/null +++ b/pages/login.vue @@ -0,0 +1,88 @@ + + + + + + diff --git a/plugins/auth.ts b/plugins/auth.ts new file mode 100644 index 00000000..32b38540 --- /dev/null +++ b/plugins/auth.ts @@ -0,0 +1,42 @@ +import { useSettings } from "~/src/shared/lib/use-settings"; + +const {localStorage} = window; + +// todo: use store for token +export default defineNuxtPlugin(async () => { + const { + api: {getSettings}, + } = useSettings(); + + let settings = { + auth: { + enabled: false, + login_url: '/login', + }, + version: '0.0.0', + } + + try { + settings = await getSettings() + } catch (e) { + console.error('Server is not available!') + } + + if (!settings.auth.enabled) { + return { + provide: { + authToken: {token: null}, + appSettings: settings + } + } + } + + const token: string | null = localStorage?.getItem('token') + + return { + provide: { + authToken: {token}, + appSettings: settings + } + } +}) diff --git a/src/entities/sentry/ui/sentry-exception/sentry-exception-frame.vue b/src/entities/sentry/ui/sentry-exception/sentry-exception-frame.vue index 7c1580e8..2e261aa6 100644 --- a/src/entities/sentry/ui/sentry-exception/sentry-exception-frame.vue +++ b/src/entities/sentry/ui/sentry-exception/sentry-exception-frame.vue @@ -124,7 +124,7 @@ const toggleOpen = () => { } .sentry-exception-frame__head-title-dd { - @apply w-5 h-4 flex justify-center border border-purple-300 shadow bg-white dark:bg-gray-600 py-1 rounded transform rotate-180; + @apply w-5 h-4 flex justify-center shadow py-1 rounded transform rotate-180; } .sentry-exception-frame__head-title-dd--visible { diff --git a/src/entities/sentry/ui/sentry-exception/sentry-exception.vue b/src/entities/sentry/ui/sentry-exception/sentry-exception.vue index 1216c149..db7556eb 100644 --- a/src/entities/sentry/ui/sentry-exception/sentry-exception.vue +++ b/src/entities/sentry/ui/sentry-exception/sentry-exception.vue @@ -16,10 +16,10 @@ const exceptionFrames = computed(() => { const frames = props.exception.stacktrace.frames || []; if (props.maxFrames > 0) { - return frames.reverse().slice(0, props.maxFrames); + return frames.slice(0, props.maxFrames); } - return frames; + return [...frames].reverse(); }); diff --git a/src/shared/lib/io/centrifuge.ts b/src/shared/lib/io/centrifuge.ts index bd59c110..29e54eb0 100644 --- a/src/shared/lib/io/centrifuge.ts +++ b/src/shared/lib/io/centrifuge.ts @@ -42,10 +42,10 @@ class WSConnection { return WSConnection.instance; } - public getCentrifuge () { + public getCentrifuge() { return this.centrifuge; } } -export const useCentrifuge: TUseCentrifuge = () => ({ centrifuge: WSConnection.getInstance().getCentrifuge() }) +export const useCentrifuge: TUseCentrifuge = () => ({centrifuge: WSConnection.getInstance().getCentrifuge()}) diff --git a/src/shared/lib/io/use-events-requests.ts b/src/shared/lib/io/use-events-requests.ts index 4a967604..14fb8bdf 100644 --- a/src/shared/lib/io/use-events-requests.ts +++ b/src/shared/lib/io/use-events-requests.ts @@ -1,5 +1,6 @@ -import type { EventId, EventType , ServerEvent } from '../../types'; +import type { EventId, EventType, ServerEvent } from '../../types'; import { REST_API_URL } from "./constants"; +import { useNuxtApp } from "#app" type TUseEventsRequests = () => { getAll: () => Promise[]>, @@ -11,23 +12,33 @@ type TUseEventsRequests = () => { getEventRestUrl: (param: EventId | undefined) => string } +// TODO: add 403 response handling + export const useEventsRequests: TUseEventsRequests = () => { - const getEventRestUrl = (param?: string) => `${REST_API_URL}/api/event${param ? `/${param}` : 's'}` + const app = useNuxtApp() + const token: string | null = app.$authToken.token + const headers = {"X-Auth-Token": token} + const getEventRestUrl = (param?: string): string => `${REST_API_URL}/api/event${param ? `/${param}` : 's'}` - const getAll = () => fetch(getEventRestUrl()) + const getAll = () => fetch(getEventRestUrl(), {headers}) .then((response) => response.json()) .then((response) => { if (response?.data) { return response.data as ServerEvent[] } + if (response?.code === 403) { + console.error('Forbidden') + return []; + } + console.error('Fetch Error') return []; }) .then((events: ServerEvent[]) => events) - const getSingle = (id: EventId) => fetch(getEventRestUrl(id)) + const getSingle = (id: EventId) => fetch(getEventRestUrl(id), {headers}) .then((response) => response.json()) .then((response) => { if (response?.data) { @@ -36,22 +47,30 @@ export const useEventsRequests: TUseEventsRequests = () => { return null; }) - const deleteSingle = (id: EventId) => fetch(getEventRestUrl(id), { method: 'DELETE' }) + const deleteSingle = (id: EventId) => fetch(getEventRestUrl(id), {method: 'DELETE', headers}) .catch((err) => { console.error('Fetch Error', err) }) - const deleteAll = () => fetch(getEventRestUrl(), { method: 'DELETE' }) + const deleteAll = () => fetch(getEventRestUrl(), {method: 'DELETE', headers}) .catch((err) => { console.error('Fetch Error', err) }) - const deleteList = (uuids: EventId[]) => fetch(getEventRestUrl(), { method: 'DELETE', body: JSON.stringify({ uuids }) }) + const deleteList = (uuids: EventId[]) => fetch(getEventRestUrl(), { + method: 'DELETE', + headers, + body: JSON.stringify({uuids}) + }) .catch((err) => { console.error('Fetch Error', err) }) - const deleteByType = (type: EventType) => fetch(getEventRestUrl(), { method: 'DELETE', body: JSON.stringify({type}) }) + const deleteByType = (type: EventType) => fetch(getEventRestUrl(), { + method: 'DELETE', + headers, + body: JSON.stringify({type}) + }) .catch((err) => { console.error('Fetch Error', err) }) diff --git a/src/shared/lib/use-api-transport/use-api-transport.ts b/src/shared/lib/use-api-transport/use-api-transport.ts index 1deb1fee..4d4f07b9 100644 --- a/src/shared/lib/use-api-transport/use-api-transport.ts +++ b/src/shared/lib/use-api-transport/use-api-transport.ts @@ -8,6 +8,8 @@ const CHECK_CONNECTION_INTERVAL: number = 10000 let isEventsEmitted: boolean = false export const useApiTransport = () => { + const nuxtApp = useNuxtApp() + const token = nuxtApp.$authToken.token const {centrifuge} = useCentrifuge() const eventsStore = useEventStore() const connectionStore = useConnectionStore() @@ -64,13 +66,12 @@ export const useApiTransport = () => { checkWSConnectionFail(async () => { const events = await getAll(); - eventsStore.addList(events); }) const deleteEvent = (eventId: EventId) => { if (getWSConnection()) { - return centrifuge.rpc(`delete:api/event/${eventId}`, undefined) + return centrifuge.rpc(`delete:api/event/${eventId}`, {token}) } return deleteSingle(eventId); @@ -78,7 +79,7 @@ export const useApiTransport = () => { const deleteEventsAll = () => { if (getWSConnection()) { - return centrifuge.rpc(`delete:api/events`, undefined) + return centrifuge.rpc(`delete:api/events`, {token}) } return deleteAll(); @@ -94,7 +95,7 @@ export const useApiTransport = () => { } if (getWSConnection()) { - return centrifuge.rpc(`delete:api/events`, {uuids}) + return centrifuge.rpc(`delete:api/events`, {uuids, token}) } return deleteList(uuids); @@ -102,7 +103,7 @@ export const useApiTransport = () => { const deleteEventsByType = (type: EventType) => { if (getWSConnection()) { - return centrifuge.rpc(`delete:api/events`, {type}) + return centrifuge.rpc(`delete:api/events`, {type, token}) } return deleteByType(type); @@ -111,13 +112,14 @@ export const useApiTransport = () => { // NOTE: works only with ws const rayStopExecution = (hash: RayContentLock["name"]) => { centrifuge.rpc(`post:api/ray/locks/${hash}`, { - stop_execution: true + stop_execution: true, + token }) } // NOTE: works only with ws const rayContinueExecution = (hash: RayContentLock["name"]) => { - centrifuge.rpc(`post:api/ray/locks/${hash}`, undefined) + centrifuge.rpc(`post:api/ray/locks/${hash}`, {token}) } return { diff --git a/src/shared/lib/use-settings/use-settings.ts b/src/shared/lib/use-settings/use-settings.ts index 4a88af08..3dc74626 100644 --- a/src/shared/lib/use-settings/use-settings.ts +++ b/src/shared/lib/use-settings/use-settings.ts @@ -8,14 +8,29 @@ type TUseSettings = { } export const useSettings = (): TUseSettings => { + const nuxtApp = useNuxtApp() + + // todo: we can get version from settings const getAppVersion = () => fetch(`${REST_API_URL}/api/version`) .then((response) => response.json()) .then((response) => response?.version || 'unknown') .catch(() => 'unknown'); + const getAppSettings = () => fetch(`${REST_API_URL}/api/settings`) + .then((response) => response.json()) + .catch(() => 'unknown'); + + const getProfile = () => fetch(`${REST_API_URL}/api/me`, { + headers: {"X-Auth-Token": nuxtApp.$authToken.token} + }) + .then((response) => response.json()) + .catch(() => 'unknown'); + return { api: { getVersion: getAppVersion, + getProfile, + getSettings: getAppSettings } } } diff --git a/src/shared/ui/icon-svg/icon-svg-originals/avatar.svg b/src/shared/ui/icon-svg/icon-svg-originals/avatar.svg new file mode 100644 index 00000000..e69de29b diff --git a/src/widgets/ui/layout-sidebar/layout-sidebar.vue b/src/widgets/ui/layout-sidebar/layout-sidebar.vue index a966e852..544c3c2d 100644 --- a/src/widgets/ui/layout-sidebar/layout-sidebar.vue +++ b/src/widgets/ui/layout-sidebar/layout-sidebar.vue @@ -7,6 +7,7 @@ import { useConnectionStore } from "~/stores/connections"; type Props = { apiVersion: string; clientVersion: string; + profile: object; }; defineProps(); @@ -64,6 +65,14 @@ const connectionText = computed(
+
+
+ +
+