Skip to content

Commit 3d5f29a

Browse files
authored
feat: continued post qa for servers in app (#5818)
* fix: intercom in app * feat: Logs.vue dynamic console resizing with window + padding problem * fix: search highlight with decorator + change to be better * fix: qa * fix: allow paper+purpur into app csp * fix: lint
1 parent 37b0f7f commit 3d5f29a

15 files changed

Lines changed: 380 additions & 87 deletions

File tree

apps/app-frontend/src/App.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import { useQuery } from '@tanstack/vue-query'
6060
import { getVersion } from '@tauri-apps/api/app'
6161
import { invoke } from '@tauri-apps/api/core'
6262
import { getCurrentWindow } from '@tauri-apps/api/window'
63+
import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
6364
import { openUrl } from '@tauri-apps/plugin-opener'
6465
import { type } from '@tauri-apps/plugin-os'
6566
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
@@ -446,7 +447,7 @@ router.afterEach((to, from, failure) => {
446447
failed: failure,
447448
})
448449
setTimeout(() => {
449-
if (!suspensePending) {
450+
if (!suspensePending && stateInitialized.value) {
450451
loading.stopLoading()
451452
}
452453
}, 100)
@@ -504,9 +505,27 @@ setupAuthProvider(credentials, async (_redirectPath) => {
504505
await signIn()
505506
})
506507
508+
async function validateSession(sessionToken) {
509+
try {
510+
const response = await tauriFetch(`${config.labrinthBaseUrl}/v2/user`, {
511+
method: 'GET',
512+
headers: { Authorization: sessionToken },
513+
})
514+
if (response.status === 401) return false
515+
return true
516+
} catch {
517+
return true
518+
}
519+
}
520+
507521
async function fetchCredentials() {
508522
const creds = await getCreds().catch(handleError)
509523
if (creds && creds.user_id) {
524+
if (creds.session && !(await validateSession(creds.session))) {
525+
await logout().catch(handleError)
526+
credentials.value = null
527+
return
528+
}
510529
creds.user = await get_user(creds.user_id, 'bypass').catch(handleError)
511530
}
512531
credentials.value = creds ?? null

apps/app-frontend/src/components/ui/SplashScreen.vue

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,24 +100,38 @@ const loadingProgress = ref(0)
100100
const hidden = ref(false)
101101
const message = ref()
102102
103+
// const MIN_DISPLAY_MS = 1000
104+
// const mountedAt = Date.now()
105+
103106
const loading = useLoading()
104107
105-
watch(loading, (newValue) => {
106-
if (!newValue.barEnabled) {
107-
if (loading.loading) {
108-
loadingProgress.value = 0
109-
fakeLoadingIncrease()
110-
} else {
111-
loadingProgress.value = 100
112-
doneLoading.value = true
113-
114-
setTimeout(() => {
115-
hidden.value = true
116-
loading.setEnabled(true)
117-
}, 50)
108+
watch(
109+
loading,
110+
(newValue) => {
111+
if (!newValue.barEnabled) {
112+
if (loading.loading) {
113+
loadingProgress.value = 0
114+
fakeLoadingIncrease()
115+
} else {
116+
// const elapsed = Date.now() - mountedAt
117+
// const delay = Math.max(0, MIN_DISPLAY_MS - elapsed)
118+
119+
// setTimeout(() => {
120+
// if (loading.loading) return
121+
122+
loadingProgress.value = 100
123+
doneLoading.value = true
124+
125+
setTimeout(() => {
126+
hidden.value = true
127+
loading.setEnabled(true)
128+
}, 50)
129+
// }, delay)
130+
}
118131
}
119-
}
120-
})
132+
},
133+
{ immediate: true },
134+
)
121135
122136
function fakeLoadingIncrease() {
123137
if (loadingProgress.value < 95) {

apps/app-frontend/src/pages/hosting/manage/Index.vue

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<template>
2-
<div class="h-full w-full py-6">
2+
<div class="h-full w-full pt-6">
33
<ServersManageRootLayout
44
:server-id="serverId"
55
:reload-page="() => router.go(0)"
66
:resolve-viewer="resolveViewer"
77
:show-copy-id-action="themeStore.devMode"
8+
:auth-user="authUser"
9+
:fetch-intercom-token="fetchIntercomToken"
810
:navigate-to-billing="() => openUrl('https://modrinth.com/settings/billing')"
911
:navigate-to-servers="() => router.push('/hosting/manage')"
1012
:browse-modpacks="
@@ -48,10 +50,12 @@
4850
import type { Archon, Labrinth } from '@modrinth/api-client'
4951
import { injectAuth, LoadingIndicator, ServersManageRootLayout } from '@modrinth/ui'
5052
import { useQuery } from '@tanstack/vue-query'
53+
import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
5154
import { openUrl } from '@tauri-apps/plugin-opener'
5255
import { computed, watch } from 'vue'
5356
import { useRoute, useRouter } from 'vue-router'
5457
58+
import { config } from '@/config'
5559
import { get_user } from '@/helpers/cache'
5660
import { get as getCreds } from '@/helpers/mr_auth'
5761
import { useBreadcrumbs } from '@/store/breadcrumbs'
@@ -97,6 +101,37 @@ watch(
97101
},
98102
)
99103
104+
const authUser = computed(() => {
105+
const user = auth.user.value
106+
if (!user?.id) return undefined
107+
return {
108+
id: user.id,
109+
username: user.username,
110+
email: user.email ?? '',
111+
created: user.created,
112+
}
113+
})
114+
115+
async function fetchIntercomToken(): Promise<{ token: string }> {
116+
const credentials = await getCreds()
117+
if (!credentials?.session) {
118+
throw new Error('Not authenticated')
119+
}
120+
const response = await tauriFetch(
121+
`${config.siteUrl}/api/intercom/messenger-jwt?server_id=${encodeURIComponent(serverId.value)}`,
122+
{
123+
method: 'GET',
124+
headers: {
125+
Authorization: `Bearer ${credentials.session}`,
126+
},
127+
},
128+
)
129+
if (!response.ok) {
130+
throw new Error(`Failed to fetch Intercom token: ${response.status}`)
131+
}
132+
return (await response.json()) as { token: string }
133+
}
134+
100135
async function resolveViewer(): Promise<{ userId: string | null; userRole: string | null }> {
101136
const credentials = await getCreds().catch(() => null)
102137
if (!credentials?.user_id) {

apps/app-frontend/src/pages/instance/Index.vue

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<template>
2-
<div v-if="instance">
3-
<div class="p-6 pr-2 pb-4" @contextmenu.prevent.stop="(event) => handleRightClick(event)">
2+
<div v-if="instance" class="flex h-full flex-col">
3+
<div
4+
class="shrink-0 p-6 pr-2 pb-4"
5+
@contextmenu.prevent.stop="(event) => handleRightClick(event)"
6+
>
47
<ExportModal ref="exportModal" :instance="instance" />
58
<InstanceSettingsModal
69
:key="instance.path"
@@ -205,10 +208,10 @@
205208
</template>
206209
</ContentPageHeader>
207210
</div>
208-
<div class="px-6">
211+
<div class="shrink-0 px-6">
209212
<NavTabs :links="tabs" />
210213
</div>
211-
<div v-if="!!instance" class="p-6 pt-4">
214+
<div v-if="!!instance" class="min-h-0 flex-1 overflow-y-auto p-6 pt-4">
212215
<RouterView
213216
v-if="route.path.startsWith('/instance')"
214217
v-slot="{ Component }"

apps/app/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"capabilities": ["ads", "core", "plugins"],
8888
"csp": {
8989
"default-src": "'self' customprotocol: asset:",
90-
"connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.nodes.modrinth.com https://*.posthog.com https://posthog.modrinth.com https://*.sentry.io https://api.mclo.gs http://textures.minecraft.net https://textures.minecraft.net https://js.stripe.com https://*.stripe.com wss://*.stripe.com wss://*.nodes.modrinth.com wss://*.ts.net 'self' data: blob:",
90+
"connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.nodes.modrinth.com https://*.posthog.com https://posthog.modrinth.com https://*.sentry.io https://api.mclo.gs http://textures.minecraft.net https://textures.minecraft.net https://js.stripe.com https://*.stripe.com wss://*.stripe.com wss://*.nodes.modrinth.com wss://*.ts.net https://fill.papermc.io https://api.purpurmc.org 'self' data: blob:",
9191
"font-src": ["https://cdn-raw.modrinth.com/fonts/"],
9292
"img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost http://textures.minecraft.net blob: data:",
9393
"style-src": "'unsafe-inline' 'self'",

apps/frontend/src/server/routes/api/intercom/messenger-jwt.get.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ export default defineEventHandler(async (event): Promise<IntercomTokenResponse>
5757
})
5858
}
5959

60-
const authToken = getCookie(event, 'auth-token')
60+
const authHeader = getRequestHeader(event, 'authorization')
61+
const bearerToken = authHeader?.toLowerCase().startsWith('bearer ')
62+
? authHeader.slice(7).trim()
63+
: undefined
64+
const authToken = bearerToken || getCookie(event, 'auth-token')
6165
if (!authToken) {
6266
throw createError({
6367
statusCode: 401,

packages/ui/src/components/base/BaseTerminal.vue

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
<template>
22
<div
3-
class="flex w-full flex-col bg-surface-2 overflow-hidden rounded-[20px] border border-solid border-surface-4"
4-
:style="!fullscreen && componentHeight ? { minHeight: componentHeight + 'px' } : {}"
5-
:class="{ 'h-full': fullscreen }"
3+
class="flex h-full w-full flex-col bg-surface-2 overflow-hidden rounded-[20px] border border-solid border-surface-4"
64
>
75
<div ref="wrapperRef" class="relative min-h-0 flex-1 overflow-hidden pb-2 pt-1">
86
<div ref="containerRef" class="size-full" />
@@ -93,7 +91,6 @@ const containerRef = ref<HTMLElement | null>(null)
9391
const wrapperRef = ref<HTMLElement | null>(null)
9492
const inputRef = ref<HTMLElement | null>(null)
9593
const commandInput = ref('')
96-
const componentHeight = ref(0)
9794
9895
const snappedHeight = ref<number | null>(null)
9996
@@ -114,14 +111,10 @@ const {
114111
scrollback: props.scrollback,
115112
onReady: (term) => {
116113
nextTick(() => {
117-
updateComponentHeight()
118114
snapToRows()
119115
})
120116
emit('ready', term)
121117
},
122-
onResize: () => {
123-
updateComponentHeight()
124-
},
125118
})
126119
127120
function writeEmptyState() {
@@ -175,12 +168,21 @@ function handleWindowResize() {
175168
}, 50)
176169
}
177170
171+
function handleDocumentPointerDown(event: PointerEvent) {
172+
if (!terminal.value?.hasSelection()) return
173+
const target = event.target as Node | null
174+
if (target && containerRef.value?.contains(target)) return
175+
terminal.value.clearSelection()
176+
}
177+
178178
onMounted(() => {
179179
window.addEventListener('resize', handleWindowResize)
180+
document.addEventListener('pointerdown', handleDocumentPointerDown)
180181
})
181182
182183
onBeforeUnmount(() => {
183184
window.removeEventListener('resize', handleWindowResize)
185+
document.removeEventListener('pointerdown', handleDocumentPointerDown)
184186
if (resizeDebounce) clearTimeout(resizeDebounce)
185187
})
186188
@@ -199,20 +201,10 @@ watch(
199201
})
200202
} else {
201203
snappedHeight.value = null
202-
componentHeight.value = 0
203204
}
204205
},
205206
)
206207
207-
function updateComponentHeight() {
208-
const screen = containerRef.value?.querySelector('.xterm-screen') as HTMLElement | null
209-
if (!screen) return
210-
const screenH = screen.offsetHeight
211-
const inputH = inputRef.value?.offsetHeight ?? 0
212-
const borderW = 2
213-
componentHeight.value = screenH + getWrapperMargins() + inputH + borderW
214-
}
215-
216208
const submitCommand = () => {
217209
const cmd = commandInput.value.trim()
218210
if (!cmd) return

packages/ui/src/composables/server-manage-core-runtime.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ const appendGraphData = (dataArray: number[], newValue: number): number[] => {
6363
return updated
6464
}
6565

66+
const STALE_STATS_THRESHOLD_MS = 5000
67+
const STALE_STATS_PUSH_INTERVAL_MS = 1000
68+
6669
const mapPowerStateFromStateEvent = (
6770
data: Archon.Websocket.v0.WSStateEvent,
6871
): Archon.Websocket.v0.PowerState => {
@@ -101,6 +104,8 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
101104
const ramData = ref<number[]>([])
102105

103106
let uptimeIntervalId: ReturnType<typeof setInterval> | null = null
107+
let staleStatsTimeoutId: ReturnType<typeof setTimeout> | null = null
108+
let staleStatsIntervalId: ReturnType<typeof setInterval> | null = null
104109

105110
const markBackupCancelled =
106111
options.markBackupCancelled ??
@@ -183,6 +188,43 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
183188
}
184189
}
185190

191+
const clearStaleStatsTimers = () => {
192+
if (staleStatsTimeoutId) {
193+
clearTimeout(staleStatsTimeoutId)
194+
staleStatsTimeoutId = null
195+
}
196+
if (staleStatsIntervalId) {
197+
clearInterval(staleStatsIntervalId)
198+
staleStatsIntervalId = null
199+
}
200+
}
201+
202+
const pushZeroStats = () => {
203+
if (!shouldProcessEvent()) return
204+
cpuData.value = appendGraphData(cpuData.value, 0)
205+
ramData.value = appendGraphData(ramData.value, 0)
206+
stats.value = {
207+
current: {
208+
...stats.value.current,
209+
cpu_percent: 0,
210+
ram_usage_bytes: 0,
211+
},
212+
past: { ...stats.value.current },
213+
graph: {
214+
cpu: cpuData.value,
215+
ram: ramData.value,
216+
},
217+
}
218+
}
219+
220+
const armStaleStatsWatchdog = () => {
221+
clearStaleStatsTimers()
222+
staleStatsTimeoutId = setTimeout(() => {
223+
pushZeroStats()
224+
staleStatsIntervalId = setInterval(pushZeroStats, STALE_STATS_PUSH_INTERVAL_MS)
225+
}, STALE_STATS_THRESHOLD_MS)
226+
}
227+
186228
const updatePowerState = (
187229
state: Archon.Websocket.v0.PowerState,
188230
details?: { oom_killed?: boolean; exit_code?: number },
@@ -209,6 +251,7 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
209251
}
210252

211253
const handleStats = (data: Archon.Websocket.v0.WSStatsEvent) => {
254+
armStaleStatsWatchdog()
212255
updateStats({
213256
cpu_percent: data.cpu_percent,
214257
ram_usage_bytes: data.ram_usage_bytes,
@@ -280,6 +323,7 @@ export function useServerManageCoreRuntime(options: UseServerManageCoreRuntimeOp
280323
}
281324

282325
stopUptimeTicker()
326+
clearStaleStatsTimers()
283327
connectedSocketServerId.value = null
284328
isConnected.value = false
285329
isWsAuthIncorrect.value = false

packages/ui/src/composables/terminal.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,17 @@ export function useTerminal(options: UseTerminalOptions): UseTerminalReturn {
193193
term.options.disableStdin = true
194194
term.write('\x1b[?25l')
195195

196+
// term.attachCustomKeyEventHandler((e) => {
197+
// if (e.type !== 'keydown') return true
198+
// const mod = e.ctrlKey || e.metaKey
199+
// if (!mod) return true
200+
// const key = e.key.toLowerCase()
201+
// if (key === 'c' || key === 'insert' || key === 'a') {
202+
// return false
203+
// }
204+
// return true
205+
// })
206+
196207
wheelHandler = (e: WheelEvent) => {
197208
e.preventDefault()
198209
}

0 commit comments

Comments
 (0)