diff --git a/apps/app-frontend/src/platform/adapters/hosting/manage/layout.vue b/apps/app-frontend/src/platform/adapters/hosting/manage/layout.vue index 358ea3f7bdf..b0ec3f32c95 100644 --- a/apps/app-frontend/src/platform/adapters/hosting/manage/layout.vue +++ b/apps/app-frontend/src/platform/adapters/hosting/manage/layout.vue @@ -11,6 +11,9 @@ :show-copy-id-action="appSettings.devMode" :auth-user="authUser" :navigate-to-billing="() => openUrl('https://modrinth.com/settings/billing')" + :navigate-to-admin-billing=" + (ownerId: string) => openUrl(`https://modrinth.com/admin/billing/${ownerId}`) + " :navigate-to-servers="() => router.push('/hosting/manage')" :browse-modpacks=" ({ serverId: sid, worldId: wid, from }) => { diff --git a/apps/frontend/src/pages/hosting/manage/[id].vue b/apps/frontend/src/pages/hosting/manage/[id].vue index 5f6d5245f42..da869d41dfa 100644 --- a/apps/frontend/src/pages/hosting/manage/[id].vue +++ b/apps/frontend/src/pages/hosting/manage/[id].vue @@ -10,6 +10,7 @@ :products="products" :auth-user="authUser" :navigate-to-billing="() => router.push('/settings/billing')" + :navigate-to-admin-billing="(ownerId: string) => router.push(`/admin/billing/${ownerId}`)" :navigate-to-servers="() => router.push('/hosting/manage')" constrain-width :browse-modpacks=" diff --git a/packages/api-client/src/modules/archon/backups-queue/v1.ts b/packages/api-client/src/modules/archon/backups-queue/v1.ts index 2cee50301e7..f20911b678a 100644 --- a/packages/api-client/src/modules/archon/backups-queue/v1.ts +++ b/packages/api-client/src/modules/archon/backups-queue/v1.ts @@ -133,6 +133,14 @@ export class ArchonBackupsQueueV1Module extends AbstractModule { ) } + /** POST /v1/servers/:server_id/worlds/:world_id/backups-queue/:backup_id/undelete */ + public async undelete(serverId: string, worldId: string, backupId: string): Promise { + await this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue/${backupId}/undelete`, + { api: 'archon', version: 1, method: 'POST' }, + ) + } + /** POST /v1/servers/:server_id/worlds/:world_id/backups-queue/:backup_id/retry */ public async retry(serverId: string, worldId: string, backupId: string): Promise { await this.client.request( diff --git a/packages/api-client/src/modules/archon/servers/internal.ts b/packages/api-client/src/modules/archon/servers/internal.ts index 44911c265a4..cc33a3c2520 100644 --- a/packages/api-client/src/modules/archon/servers/internal.ts +++ b/packages/api-client/src/modules/archon/servers/internal.ts @@ -75,4 +75,35 @@ export class ArchonServersInternalModule extends AbstractModule { method: 'DELETE', }) } + + /** + * Temporarily lift a suspended server's suspension, or update its current temporary lift. + * POST /_internal/suspensions/server/:server_id/lift + */ + public async liftSuspension( + serverId: string, + request: Archon.Servers.Internal.LiftSuspensionRequest, + ): Promise { + return this.client.request( + `/suspensions/server/${serverId}/lift`, + { + api: 'archon', + version: 'internal', + method: 'POST', + body: request, + }, + ) + } + + /** + * End the temporary lift of a server's suspension before it expires. + * POST /_internal/suspensions/server/:server_id/resuspend + */ + public async resuspend(serverId: string): Promise { + return this.client.request(`/suspensions/server/${serverId}/resuspend`, { + api: 'archon', + version: 'internal', + method: 'POST', + }) + } } diff --git a/packages/api-client/src/modules/archon/types.ts b/packages/api-client/src/modules/archon/types.ts index 7492705f50a..7db11a8a6b8 100644 --- a/packages/api-client/src/modules/archon/types.ts +++ b/packages/api-client/src/modules/archon/types.ts @@ -111,6 +111,10 @@ export namespace Archon { | 'changed_server_subdomain' | 'server_reallocated' | 'server_plan_changed' + | 'server_suspended' + | 'server_unsuspended' + | 'server_suspension_lifted' + | 'server_resuspended' | 'user_invited' | 'user_invite_revoked' | 'user_permission_modified' @@ -589,6 +593,18 @@ export namespace Archon { export type LockServerRequest = { reason: string } + + export type LiftSuspensionRequest = { + /** How long to lift the suspension for, in minutes from now. */ + minutes: number + /** Whether to prevent the server from starting while the suspension is lifted. */ + restrict_power: boolean + } + + export type LiftSuspensionResponse = { + lifted_until: string + restrict_power: boolean + } } export namespace v0 { @@ -644,6 +660,8 @@ export namespace Archon { used_backup_quota: number status: Status suspension_reason: SuspensionReason | null + /** Set while support temporarily lifts the server's suspension. */ + suspension_lift?: SuspensionLift | null loader: Loader | null loader_version: string | null mc_version: string | null @@ -662,6 +680,13 @@ export namespace Archon { medal_expires?: string } + export type SuspensionLift = { + /** When the server is suspended again. */ + until: string + /** Whether the server is prevented from starting while the suspension is lifted. */ + restrict_power: boolean + } + export type UserScope = number export type Net = { @@ -970,9 +995,31 @@ export namespace Archon { history: BackupQueueOperation[] } + /** A backup scheduled for deletion. Only listed for support agents. */ + export type DeletedBackupQueueBackup = { + id: string + name: string + created_at: string + status: BackupStatus + automated: boolean + created_by?: UserInfo | null + /** When the backup's data becomes eligible for permanent deletion. */ + scheduled_delete: string + /** Until when the backup can be undeleted. May be in the past. */ + undelete_deadline: string + deleted_suspension: boolean + can_undelete: boolean + repository_id: number + restic_snapshot_id?: string | null + /** Whether the backup's snapshot was forgotten from its repository. */ + permanently_deleted: boolean + } + export type BackupsQueueResponse = { active_operations: ActiveOperation[] backups: BackupQueueBackup[] + /** Only present for support agents. */ + deleted_backups?: DeletedBackupQueueBackup[] | null } export type WorldBackupOptions = { diff --git a/packages/ui/src/components/base/ErrorInformationCard.vue b/packages/ui/src/components/base/ErrorInformationCard.vue index f183cca2fee..485c8ac0016 100644 --- a/packages/ui/src/components/base/ErrorInformationCard.vue +++ b/packages/ui/src/components/base/ErrorInformationCard.vue @@ -64,6 +64,8 @@ {{ action.label }} + + + + {{ formatMessage(messages.suspendedAgain, { time: liftEndsIn }) }} + + + + {{ + formatMessage( + suspensionLift.restrict_power + ? messages.powerActionsBlocked + : messages.powerActionsAllowed, + ) + }} + + +
{{ formatMessage(messages.resetToOnboardingButton) }} @@ -78,11 +116,14 @@ diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/backups/index.vue b/packages/ui/src/layouts/wrapped/hosting/manage/backups/index.vue index c5fc30f5cf0..d458d4c9307 100644 --- a/packages/ui/src/layouts/wrapped/hosting/manage/backups/index.vue +++ b/packages/ui/src/layouts/wrapped/hosting/manage/backups/index.vue @@ -112,6 +112,13 @@ @delete="(backup) => deleteModal?.show(backup)" /> + +
serverId), - worldId, - ) +const { + query, + backups, + deletedBackups, + options, + nextAutoBackupAt, + hasActiveCreate, + hasActiveRestore, + invalidate, +} = useServerBackupsQueue( + computed(() => serverId), + worldId, +) const error = computed(() => query.error.value) const readyPending = computed( @@ -345,6 +361,14 @@ const filteredBackups = computed(() => { }) const selectedIds = ref([]) +const selectedDeletedIds = ref([]) + +watch(selectedIds, (ids) => { + if (ids.length > 0) selectedDeletedIds.value = [] +}) +watch(selectedDeletedIds, (ids) => { + if (ids.length > 0) selectedIds.value = [] +}) const selectedBackups = computed(() => completedBackups.value.filter((backup) => selectedIds.value.includes(backup.id)), ) diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/root.vue b/packages/ui/src/layouts/wrapped/hosting/manage/root.vue index d96c14d4ae8..2f60cffebde 100644 --- a/packages/ui/src/layouts/wrapped/hosting/manage/root.vue +++ b/packages/ui/src/layouts/wrapped/hosting/manage/root.vue @@ -51,6 +51,19 @@ :icon="LockIcon" icon-color="orange" :action="suspendedAction" + > + + +
@@ -351,6 +364,7 @@ import { LinkIcon, LoaderCircleIcon, LockIcon, + LockOpenIcon, MoreVerticalIcon, ServerIcon as ServerAssetIcon, SettingsIcon, @@ -368,7 +382,7 @@ import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router' import Admonition from '#ui/components/base/Admonition.vue' import Avatar from '#ui/components/base/Avatar.vue' -import { IconButton, TeleportOverflowMenu } from '#ui/components/base/buttons' +import { Button, IconButton, TeleportOverflowMenu } from '#ui/components/base/buttons' import ErrorInformationCard from '#ui/components/base/ErrorInformationCard.vue' import IntlFormatted from '#ui/components/base/IntlFormatted.vue' import NavTabs from '#ui/components/base/NavTabs.vue' @@ -383,6 +397,7 @@ import { Tooltip } from '#ui/components/floating' import ConfirmLeaveModal from '#ui/components/modal/ConfirmLeaveModal.vue' import ServerPanelAdmonitions from '#ui/components/servers/admonitions/ServerPanelAdmonitions.vue' import ServerIcon from '#ui/components/servers/icons/ServerIcon.vue' +import LiftSuspensionModal from '#ui/components/servers/LiftSuspensionModal.vue' import MedalServerCountdown from '#ui/components/servers/marketing/MedalServerCountdown.vue' import { PanelServerActionButton } from '#ui/components/servers/server-header' import ServerSettingsModal from '#ui/components/servers/ServerSettingsModal.vue' @@ -438,6 +453,7 @@ const props = withDefaults( products?: Labrinth.Billing.Internal.Product[] authUser?: { id: string; username: string; email: string; created: string } navigateToBilling?: () => void + navigateToAdminBilling?: (ownerId: string) => void navigateToServers?: () => void browseModpacks?: (args: { serverId: string @@ -462,6 +478,7 @@ const props = withDefaults( products: () => [], authUser: undefined, navigateToBilling: undefined, + navigateToAdminBilling: undefined, navigateToServers: undefined, browseModpacks: undefined, browseContent: undefined, @@ -529,9 +546,32 @@ function dismissSettingsHint() { } const serverSettingsModal = ref | null>(null) +const liftSuspensionModal = ref | null>(null) +const viewerRole = ref(null) +const isSiteAdmin = computed(() => viewerRole.value === 'admin') +let viewerPromise: ReturnType | null = null + +function resolveViewerOnce() { + viewerPromise ??= props.resolveViewer().catch((error) => { + viewerPromise = null + throw error + }) + return viewerPromise +} const confirmLeaveModal = ref>() const formatDateTime = useFormatDateTime({ dateStyle: 'long', timeStyle: 'short' }) +const suspensionMessages = defineMessages({ + liftSuspension: { + id: 'servers.manage.suspended.lift-suspension', + defaultMessage: 'Lift suspension', + }, + goToAdminBilling: { + id: 'servers.manage.suspended.go-to-admin-billing', + defaultMessage: 'Go to admin billing', + }, +}) + const lockMessages = defineMessages({ lockedBadge: { id: 'servers.manage.locked.badge', @@ -642,6 +682,7 @@ const { server: serverData, serverFull, content: serverContent, + isSiteAdmin, extraBusyReasons: backupsBusy, setDisconnectedOnAuthIncorrect: false, syncUptimeFromState: true, @@ -833,6 +874,16 @@ watch(serverData, (data) => { log('serverData changed', !!data) }) +watch( + () => serverData.value?.status, + (status, previousStatus) => { + if (!status || !previousStatus) return + if ((status === 'suspended') === (previousStatus === 'suspended')) return + isLoading.value = true + props.reloadPage() + }, +) + const navLinks = computed(() => [ { label: 'Overview', @@ -1281,11 +1332,21 @@ const generalErrorDetails = computed(() => [ : []), ]) -const suspendedAction = computed(() => ({ - label: 'Go to billing settings', - onClick: () => props.navigateToBilling?.(), - color: 'brand' as const, -})) +const suspendedAction = computed(() => { + const ownerId = serverData.value?.owner_id + if (isSiteAdmin.value && ownerId) { + return { + label: formatMessage(suspensionMessages.goToAdminBilling), + onClick: () => props.navigateToAdminBilling?.(ownerId), + color: 'brand' as const, + } + } + return { + label: 'Go to billing settings', + onClick: () => props.navigateToBilling?.(), + color: 'brand' as const, + } +}) const generalErrorAction = computed(() => ({ label: 'Go back to all servers', @@ -1404,6 +1465,12 @@ const cleanup = () => { onMounted(() => { isMounted.value = true + void resolveViewerOnce() + .then(({ userRole }) => { + viewerRole.value = userRole + }) + .catch((error) => console.error('Failed to resolve viewer:', error)) + if (serverData.value) { initializeServer() } else { diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json index f47ea96f51c..b7a37f1566b 100644 --- a/packages/ui/src/locales/en-US/index.json +++ b/packages/ui/src/locales/en-US/index.json @@ -5018,12 +5018,24 @@ "servers.access-page.activity-log-filter.action.server-restarted": { "defaultMessage": "Restarted server" }, + "servers.access-page.activity-log-filter.action.server-resuspended": { + "defaultMessage": "Server suspended again" + }, "servers.access-page.activity-log-filter.action.server-started": { "defaultMessage": "Started server" }, "servers.access-page.activity-log-filter.action.server-stopped": { "defaultMessage": "Stopped server" }, + "servers.access-page.activity-log-filter.action.server-suspended": { + "defaultMessage": "Server suspended" + }, + "servers.access-page.activity-log-filter.action.server-suspension-lifted": { + "defaultMessage": "Suspension temporarily lifted" + }, + "servers.access-page.activity-log-filter.action.server-unsuspended": { + "defaultMessage": "Server unsuspended" + }, "servers.access-page.activity-log-filter.action.sftp-credentials-rolled": { "defaultMessage": "Rotated SFTP credentials" }, @@ -5399,6 +5411,12 @@ "servers.audit-log.event.server-restarted": { "defaultMessage": "Restarted server" }, + "servers.audit-log.event.server-resuspended": { + "defaultMessage": "Ended suspension lift early, server suspended again" + }, + "servers.audit-log.event.server-resuspended-expired": { + "defaultMessage": "Suspension lift expired, server suspended again" + }, "servers.audit-log.event.server-started": { "defaultMessage": "Started server" }, @@ -5408,6 +5426,18 @@ "servers.audit-log.event.server-subdomain-changed": { "defaultMessage": "Changed server subdomain to " }, + "servers.audit-log.event.server-suspended": { + "defaultMessage": "{reason, select, moderated {Server suspended by moderation action} paymentfailed {Server suspended due to a failed payment} cancelled {Server suspended due to a cancelled subscription} other {Server suspended}}" + }, + "servers.audit-log.event.server-suspension-lifted": { + "defaultMessage": "Temporarily lifted suspension until " + }, + "servers.audit-log.event.server-suspension-lifted-restricted": { + "defaultMessage": "Temporarily lifted suspension until with power actions blocked" + }, + "servers.audit-log.event.server-unsuspended": { + "defaultMessage": "Server unsuspended" + }, "servers.audit-log.event.sftp-credentials-rolled": { "defaultMessage": "Rotated SFTP credentials" }, @@ -5549,6 +5579,84 @@ "servers.backups.delete.error": { "defaultMessage": "Failed to delete {count, plural, one {backup} other {# backups}}" }, + "servers.backups.deleted.auto-badge": { + "defaultMessage": "Auto" + }, + "servers.backups.deleted.bulk-bar.none-recoverable": { + "defaultMessage": "None of the selected backups can be undeleted" + }, + "servers.backups.deleted.bulk-bar.selected-count": { + "defaultMessage": "{count, plural, one {# backup selected} other {# backups selected}}" + }, + "servers.backups.deleted.bulk-bar.some-unrecoverable": { + "defaultMessage": "Only {recoverable} of {count, plural, one {# selected backup} other {# selected backups}} can be undeleted" + }, + "servers.backups.deleted.column.created": { + "defaultMessage": "Created" + }, + "servers.backups.deleted.column.name": { + "defaultMessage": "Name" + }, + "servers.backups.deleted.column.reason": { + "defaultMessage": "Reason" + }, + "servers.backups.deleted.column.recoverable-until": { + "defaultMessage": "Recoverable until" + }, + "servers.backups.deleted.filter.add": { + "defaultMessage": "Add filter" + }, + "servers.backups.deleted.filter.clear": { + "defaultMessage": "Clear filters" + }, + "servers.backups.deleted.filter.reason": { + "defaultMessage": "Reason" + }, + "servers.backups.deleted.filter.recoverable": { + "defaultMessage": "Recoverability" + }, + "servers.backups.deleted.filter.recoverable.yes": { + "defaultMessage": "Recoverable" + }, + "servers.backups.deleted.filter.type": { + "defaultMessage": "Type" + }, + "servers.backups.deleted.filter.type.auto": { + "defaultMessage": "Auto" + }, + "servers.backups.deleted.filter.type.manual": { + "defaultMessage": "Manual" + }, + "servers.backups.deleted.no-results": { + "defaultMessage": "No deleted backups match your search." + }, + "servers.backups.deleted.reason.deleted": { + "defaultMessage": "Manually deleted" + }, + "servers.backups.deleted.reason.suspension": { + "defaultMessage": "Suspension" + }, + "servers.backups.deleted.search-label": { + "defaultMessage": "Search deleted backups" + }, + "servers.backups.deleted.search-placeholder": { + "defaultMessage": "Search {count, plural, one {# deleted backup} other {# deleted backups}}..." + }, + "servers.backups.deleted.title": { + "defaultMessage": "Deleted backups" + }, + "servers.backups.deleted.undelete": { + "defaultMessage": "Undelete" + }, + "servers.backups.deleted.undelete.error": { + "defaultMessage": "Failed to undelete {count, plural, one {backup} other {# backups}}" + }, + "servers.backups.deleted.undelete.success": { + "defaultMessage": "{count, plural, one {Backup undeleted} other {# backups undeleted}}" + }, + "servers.backups.deleted.unrecoverable": { + "defaultMessage": "Unrecoverable" + }, "servers.backups.empty.description": { "defaultMessage": "Create your first backup" }, @@ -5801,6 +5909,63 @@ "servers.installing-banner.preparing.header": { "defaultMessage": "Preparing your server" }, + "servers.lift-suspension.duration": { + "defaultMessage": "Duration" + }, + "servers.lift-suspension.duration.custom": { + "defaultMessage": "Custom" + }, + "servers.lift-suspension.duration.days": { + "defaultMessage": "{count, plural, one {# day} other {# days}}" + }, + "servers.lift-suspension.duration.hours": { + "defaultMessage": "{count, plural, one {# hour} other {# hours}}" + }, + "servers.lift-suspension.duration.minutes": { + "defaultMessage": "{count, plural, one {# minute} other {# minutes}}" + }, + "servers.lift-suspension.ends-at": { + "defaultMessage": "The server will be suspended again around {date}." + }, + "servers.lift-suspension.error.title": { + "defaultMessage": "Failed to lift suspension" + }, + "servers.lift-suspension.header": { + "defaultMessage": "Lift suspension of {name}" + }, + "servers.lift-suspension.lift-button": { + "defaultMessage": "Lift suspension" + }, + "servers.lift-suspension.minutes": { + "defaultMessage": "Minutes" + }, + "servers.lift-suspension.minutes.invalid": { + "defaultMessage": "Enter a whole number of minutes between 1 and {max} (3 days)." + }, + "servers.lift-suspension.minutes.placeholder": { + "defaultMessage": "Enter a number of minutes" + }, + "servers.lift-suspension.restrict-power.description": { + "defaultMessage": "The server can't be started, so its panel and files can be used without the server being played." + }, + "servers.lift-suspension.restrict-power.label": { + "defaultMessage": "Block power actions while the suspension is lifted" + }, + "servers.lift-suspension.success.text": { + "defaultMessage": "{name} is unsuspended until {date}." + }, + "servers.lift-suspension.success.text-restricted": { + "defaultMessage": "{name} is unsuspended until {date}, with power actions blocked." + }, + "servers.lift-suspension.success.title": { + "defaultMessage": "Suspension lifted" + }, + "servers.lift-suspension.temporary.body": { + "defaultMessage": "The server will be suspended again automatically once the lift ends. Its subdomain stays unassigned while the suspension is lifted." + }, + "servers.lift-suspension.temporary.header": { + "defaultMessage": "This lift is temporary" + }, "servers.list-empty.already-have-server-label": { "defaultMessage": "Already have a server?" }, @@ -6047,6 +6212,12 @@ "servers.manage.shared-servers-title": { "defaultMessage": "Shared servers" }, + "servers.manage.suspended.go-to-admin-billing": { + "defaultMessage": "Go to admin billing" + }, + "servers.manage.suspended.lift-suspension": { + "defaultMessage": "Lift suspension" + }, "servers.manage.unlock-server": { "defaultMessage": "Unlock server" }, @@ -6344,6 +6515,36 @@ "servers.settings.support.clear-action-log-title": { "defaultMessage": "Clear action log?" }, + "servers.settings.support.resuspend.button": { + "defaultMessage": "Unlift now" + }, + "servers.settings.support.resuspend.description": { + "defaultMessage": "This ends the temporary lift of {name}'s suspension early. The server will be suspended again right away." + }, + "servers.settings.support.resuspend.error-title": { + "defaultMessage": "Failed to unlift the suspension" + }, + "servers.settings.support.resuspend.success-text": { + "defaultMessage": "{name} has been suspended again." + }, + "servers.settings.support.resuspend.success-title": { + "defaultMessage": "Server suspended" + }, + "servers.settings.support.resuspend.title": { + "defaultMessage": "Unlift suspension now?" + }, + "servers.settings.support.suspension-lifted.power-allowed": { + "defaultMessage": "Power actions are allowed while the suspension is lifted." + }, + "servers.settings.support.suspension-lifted.power-blocked": { + "defaultMessage": "Power actions are blocked while the suspension is lifted." + }, + "servers.settings.support.suspension-lifted.suspended-again": { + "defaultMessage": "Suspended again {time}" + }, + "servers.settings.support.suspension-lifted.title": { + "defaultMessage": "Suspension lifted" + }, "servers.setup.onboarding.installation-failed.text": { "defaultMessage": "An unexpected error occurred while installing. Please try again later." }, diff --git a/packages/ui/src/providers/server-context.ts b/packages/ui/src/providers/server-context.ts index 334df47dac9..ba2699fd78a 100644 --- a/packages/ui/src/providers/server-context.ts +++ b/packages/ui/src/providers/server-context.ts @@ -44,6 +44,8 @@ export interface ModrinthServerContext { readonly server: Ref readonly serverFull: ComputedRef readonly currentUserPermissions: ComputedRef + /** Whether the viewer is a Modrinth admin, same as the server settings support tab. */ + readonly isSiteAdmin: ComputedRef // Websocket state readonly isConnected: Ref diff --git a/packages/ui/src/stories/servers/AuditLogTable.stories.ts b/packages/ui/src/stories/servers/AuditLogTable.stories.ts index 66a70b18d4a..d1cc3350038 100644 --- a/packages/ui/src/stories/servers/AuditLogTable.stories.ts +++ b/packages/ui/src/stories/servers/AuditLogTable.stories.ts @@ -237,6 +237,29 @@ const actionLogResponse: Archon.Actions.v1.ActionLogResponse = { worldId: null, minutesAgo: 25, }), + rawEntry({ + action: 'server_suspended', + metadata: { reason: 'paymentfailed' }, + worldId: null, + minutesAgo: 26, + }), + rawEntry({ + action: 'server_suspension_lifted', + metadata: { + until: new Date(Date.now() + 60 * 60 * 1000).toISOString(), + restrict_power: true, + }, + actor: { type: 'support', user_id: userIds.prospector }, + worldId: null, + minutesAgo: 27, + }), + rawEntry({ + action: 'server_resuspended', + metadata: { expired: true }, + worldId: null, + minutesAgo: 28, + }), + rawEntry({ action: 'server_unsuspended', worldId: null, minutesAgo: 29 }), rawEntry({ action: 'user_invited', metadata: { user_id: userIds.modmuss, permissions: -4611686018427387904 }, diff --git a/packages/ui/src/stories/servers/EditServerIcon.stories.ts b/packages/ui/src/stories/servers/EditServerIcon.stories.ts index acdb0703531..d01f8b44cbb 100644 --- a/packages/ui/src/stories/servers/EditServerIcon.stories.ts +++ b/packages/ui/src/stories/servers/EditServerIcon.stories.ts @@ -64,6 +64,7 @@ const meta = { server, serverFull: computed(() => null), currentUserPermissions: computed(() => 0), + isSiteAdmin: computed(() => false), isConnected: ref(true), isWsAuthIncorrect: ref(false), powerState: ref('running'), diff --git a/packages/ui/src/stories/servers/InstallingBanner.stories.ts b/packages/ui/src/stories/servers/InstallingBanner.stories.ts index 4921477280c..9654197e327 100644 --- a/packages/ui/src/stories/servers/InstallingBanner.stories.ts +++ b/packages/ui/src/stories/servers/InstallingBanner.stories.ts @@ -27,6 +27,7 @@ function renderInstallation(state: ServerInstallationState) { } as Archon.Servers.v0.Server), serverFull: computed(() => null), currentUserPermissions: computed(() => 0), + isSiteAdmin: computed(() => false), isConnected: ref(true), isWsAuthIncorrect: ref(false), powerState: ref('stopped'), diff --git a/packages/ui/src/stories/servers/ServerPanelAdmonitions.stories.ts b/packages/ui/src/stories/servers/ServerPanelAdmonitions.stories.ts index 22da1a80c15..f5111264c63 100644 --- a/packages/ui/src/stories/servers/ServerPanelAdmonitions.stories.ts +++ b/packages/ui/src/stories/servers/ServerPanelAdmonitions.stories.ts @@ -90,6 +90,7 @@ const meta = { server, serverFull: computed(() => null), currentUserPermissions: computed(() => 0), + isSiteAdmin: computed(() => false), isConnected: ref(true), isWsAuthIncorrect: ref(false), powerState: ref('running'),