Skip to content

Commit

Permalink
feat(dashboard): add ability to popup eventlist widget
Browse files Browse the repository at this point in the history
  • Loading branch information
Satont committed Sep 20, 2024
1 parent 3d49930 commit 839c04e
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 83 deletions.
10 changes: 9 additions & 1 deletion apps/api/internal/interceptors/dashboard_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ package interceptors

import (
"context"

model "github.com/satont/twir/libs/gomodels"
"github.com/twitchtv/twirp"
)

func (s *Service) DashboardId(next twirp.Method) twirp.Method {
return func(ctx context.Context, req interface{}) (interface{}, error) {
dashboardId := s.sessionManager.Get(ctx, "dashboardId")
ctx = context.WithValue(ctx, "dashboardId", dashboardId)
dbUser := ctx.Value("dbUser")

if dashboardId == nil {
ctx = context.WithValue(ctx, "dashboardId", dbUser.(*model.Users).ID)
} else {
ctx = context.WithValue(ctx, "dashboardId", dashboardId)
}

return next(ctx, req)
}
Expand Down
40 changes: 20 additions & 20 deletions apps/api/internal/interceptors/db_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ func (s *Service) getUserByApiKey(apiKey string) (*model.Users, error) {
func (s *Service) DbUserInterceptor(next twirp.Method) twirp.Method {
return func(ctx context.Context, req interface{}) (interface{}, error) {
apiKey := ctx.Value("apiKey")
if apiKey != nil {
castedApiKey, ok := apiKey.(string)
if !ok {
return nil, twirp.Internal.Error("internal error, wrong api key")
}

dbUser, err := s.getUserByApiKey(castedApiKey)
if err != nil {
s.logger.Error("get user by api key", slog.Any("err", err))
return nil, twirp.Internal.Error("internal error")
}
if dbUser == nil {
return nil, twirp.Unauthenticated.Error("not authenticated")
}
ctx = context.WithValue(ctx, "dbUser", dbUser)

return next(ctx, req)
}

sessionUser := s.sessionManager.Get(ctx, "dbUser")
if sessionUser == nil {
return nil, twirp.Unauthenticated.Error("not authenticated")
Expand All @@ -39,25 +58,6 @@ func (s *Service) DbUserInterceptor(next twirp.Method) twirp.Method {
return next(ctx, req)
}

if apiKey == nil || apiKey == "" {
return nil, twirp.Unauthenticated.Error("not authenticated")
}

castedApiKey, ok := apiKey.(string)
if !ok {
return nil, twirp.Internal.Error("internal error")
}

dbUser, err := s.getUserByApiKey(castedApiKey)
if err != nil {
s.logger.Error("get user by api key", slog.Any("err", err))
return nil, twirp.Internal.Error("internal error")
}
if dbUser == nil {
return nil, twirp.Unauthenticated.Error("not authenticated")
}
ctx = context.WithValue(ctx, "dbUser", dbUser)

return next(ctx, req)
return nil, twirp.Unauthenticated.Error("not authenticated")
}
}
67 changes: 35 additions & 32 deletions frontend/dashboard/src/api/dashboard.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import { useQuery } from '@tanstack/vue-query';
import { useSubscription } from '@urql/vue';
import { computed } from 'vue';

import { protectedApiClient } from './twirp';

import { graphql } from '@/gql';

export const useDashboardStats = () => useQuery({
queryKey: ['dashboardStats'],
queryFn: async () => {
const call = await protectedApiClient.getDashboardStats({});

return call.response;
},
});

export const useDashboardEvents = () => useQuery({
queryKey: ['dashboardEvents'],
queryFn: async () => {
const call = await protectedApiClient.getDashboardEventsList({});
return call.response;
},
});


export const useRealtimeDashboardStats = () => {
import { useQuery } from '@tanstack/vue-query'
import { useSubscription } from '@urql/vue'
import { computed } from 'vue'

import { protectedApiClient } from './twirp'

import { graphql } from '@/gql'

export function useDashboardStats() {
return useQuery({
queryKey: ['dashboardStats'],
queryFn: async () => {
const call = await protectedApiClient.getDashboardStats({})

return call.response
},
})
}

export function useDashboardEvents() {
return useQuery({
queryKey: ['dashboardEvents'],
queryFn: async () => {
const call = await protectedApiClient.getDashboardEventsList({})
return call.response
},
})
}

export function useRealtimeDashboardStats() {
const { data, isPaused, fetching } = useSubscription({
query: graphql(`
subscription dashboardStats {
Expand All @@ -42,11 +45,11 @@ export const useRealtimeDashboardStats = () => {
}
}
`),
});
})

const stats = computed(() => {
return data.value?.dashboardStats;
});
return data.value?.dashboardStats
})

return { stats, isPaused, fetching };
};
return { stats, isPaused, fetching }
}
21 changes: 20 additions & 1 deletion frontend/dashboard/src/api/twirp.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport'
import { AdminClient, ProtectedClient, UnProtectedClient } from '@twir/api/api.client'

import type { MethodInfo, NextUnaryFn, RpcOptions, UnaryCall } from '@protobuf-ts/runtime-rpc'

const transport = new TwirpFetchTransport({
baseUrl: `${window.location.origin}/api-old/v1`,
sendJson: import.meta.env.DEV
sendJson: import.meta.env.DEV,
interceptors: [
{
interceptUnary(next: NextUnaryFn, method: MethodInfo, input: object, options: RpcOptions): UnaryCall {
const locationQuery = new URLSearchParams(window.location.search)
const apiKey = locationQuery.get('apiKey')

if (apiKey) {
options.meta = {
...options.meta,
'Api-Key': apiKey,
}
}

return next(method, input, options)
},
},
],
})
export const protectedApiClient = new ProtectedClient(transport)
export const unprotectedApiClient = new UnProtectedClient(transport)
Expand Down
59 changes: 32 additions & 27 deletions frontend/dashboard/src/components/dashboard/card.vue
Original file line number Diff line number Diff line change
@@ -1,60 +1,65 @@
<script lang="ts" setup>
import { IconGripVertical, IconEyeOff } from '@tabler/icons-vue';
import { NCard, NButton } from 'naive-ui';
import { type CSSProperties, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import { IconEyeOff, IconGripVertical } from '@tabler/icons-vue'
import { NButton, NCard } from 'naive-ui'
import { useAttrs } from 'vue'
import { useI18n } from 'vue-i18n'
import { useWidgets, type WidgetItem } from './widgets.js';
import { type WidgetItem, useWidgets } from './widgets.js'
defineSlots<{
default: any,
action?: any
'header-extra'?: any
}>();
import type { CSSProperties } from 'vue'
withDefaults(defineProps<{
contentStyle?: CSSProperties
popup?: boolean
}>(), {
contentStyle: () => ({ padding: '0px' }),
});
})
defineSlots<{
'default': any
'action'?: any
'header-extra'?: any
}>()
const widgets = useWidgets()
const widgets = useWidgets();
const attrs = useAttrs() as { item: WidgetItem, [x: string]: unknown } | undefined
const attrs = useAttrs() as { item: WidgetItem, [x: string]: unknown };
function hideItem() {
if (!attrs) return
const hideItem = () => {
const item = widgets.value.find(item => item.i === attrs.item.i);
if (!item) return;
item.visible = false;
};
const item = widgets.value.find(item => item.i === attrs.item.i)
if (!item) return
item.visible = false
}
const { t } = useI18n();
const { t } = useI18n()
</script>

<template>
<n-card
<NCard
:segmented="{
content: true,
footer: 'soft'
footer: 'soft',
}"
header-style="padding: 5px;"
:content-style="contentStyle"
style="width: 100%; height: 100%"
v-bind="$attrs"
>
<template #header>
<template v-if="!popup" #header>
<div class="widgets-draggable-handle flex items-center">
<IconGripVertical class="w-5 h-5" />
{{ t(`dashboard.widgets.${attrs.item.i}.title`) }}
{{ t(`dashboard.widgets.${attrs?.item.i}.title`) }}
</div>
</template>
<template #header-extra>
<template v-if="!popup" #header-extra>
<div class="flex gap-1">
<slot name="header-extra" />
<n-button text size="small" @click="hideItem">
<NButton text size="small" @click="hideItem">
<IconEyeOff />
</n-button>
</NButton>
</div>
</template>
Expand All @@ -63,5 +68,5 @@ const { t } = useI18n();
<template #action>
<slot name="action" />
</template>
</n-card>
</NCard>
</template>
23 changes: 21 additions & 2 deletions frontend/dashboard/src/components/dashboard/events.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ import ReSubscribe from './events/resubscribe.vue'
import SubGift from './events/subgift.vue'
import Subscribe from './events/subscribe.vue'
import { useDashboardEvents } from '@/api/index.js'
import { useDashboardEvents, useProfile } from '@/api/index.js'
import UnbanRequestCreated from '@/components/dashboard/events/unban-request-created.vue'
import UnbanRequestResolved from '@/components/dashboard/events/unban-request-resolved.vue'
const props = defineProps<{
popup?: boolean
}>()
const { data: events, isLoading, refetch } = useDashboardEvents()
useIntervalFn(refetch, 1000)
Expand Down Expand Up @@ -79,11 +83,26 @@ const enabledEventsOptions = [
value: 11,
},
]
const { data } = useProfile()
function openPopup() {
if (!data.value) return
window.open(
`https://dev.twir.app/dashboard/popup/widgets/eventslist?apiKey=${data.value?.apiKey}`,
'_blank',
'width=400,height=600,popup=true',
)
}
</script>

<template>
<Card :content-style="{ padding: isLoading ? '10px' : '0px', height: '80%' }">
<Card :content-style="{ padding: isLoading ? '10px' : '0px', height: '80%' }" :popup="props.popup">
<template #header-extra>
<NButton :disabled="!data" secondary size="small" type="info" class="uppercase" @click="openPopup">
Popup
</NButton>
<NPopselect
v-model:value="enabledEvents" multiple :options="enabledEventsOptions"
trigger="click"
Expand Down
13 changes: 13 additions & 0 deletions frontend/dashboard/src/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ export function newRouter() {
path: '/dashboard/integrations/:integrationName',
component: () => import('../pages/IntegrationsCallback.vue'),
},
{
path: '/dashboard/popup',
component: () => import('../popup-layout/popup-layout.vue'),
children: [
{
path: '/dashboard/popup/widgets/eventslist',
component: () => import('../components/dashboard/events.vue'),
props: { popup: true },
},
],
},
{
path: '/dashboard',
component: () => import('../layout/layout.vue'),
Expand Down Expand Up @@ -196,6 +207,8 @@ export function newRouter() {
})

router.beforeEach(async (to, _, next) => {
if (to.path.startsWith('/dashboard/popup')) return next()

try {
const profileRequest = await urqlClient.value.executeQuery(profileQuery)
if (!profileRequest.data) {
Expand Down
40 changes: 40 additions & 0 deletions frontend/dashboard/src/popup-layout/popup-layout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
import {
NConfigProvider,
NDialogProvider,
NMessageProvider,
NNotificationProvider,
darkTheme,
lightTheme,
} from 'naive-ui'
import { computed } from 'vue'
import { Toaster } from '@/components/ui/toast'
import { useTheme } from '@/composables/use-theme'
const { theme } = useTheme()
const themeStyles = computed(() => theme.value === 'dark' ? darkTheme : lightTheme)
</script>

<template>
<NConfigProvider
:theme="themeStyles"
class="h-full"
:breakpoints="{ 'xs': 0, 's': 640, 'm': 1024, 'l': 1280, 'xl': 1536, 'xxl': 1920, '2xl': 2560 }"
>
<NNotificationProvider>
<NMessageProvider :duration="2500" :closable="true">
<NDialogProvider>
<RouterView />

<Toaster />
<Sonner />
</NDialogProvider>
</NMessageProvider>
</NNotificationProvider>
</NConfigProvider>
</template>

<style scoped>
</style>

0 comments on commit 839c04e

Please sign in to comment.