Depot API & v4 (#298)

* feat: nginx + torrential basics & services system

* fix: lint + i18n

* fix: update torrential to remove openssl

* feat: add torrential to Docker build

* feat: move to self hosted runner

* fix: move off self-hosted runner

* fix: update nginx.conf

* feat: torrential cache invalidation

* fix: update torrential for cache invalidation

* feat: integrity check task

* fix: lint

* feat: move to version ids

* fix: client fixes and client-side checks

* feat: new depot apis and version id fixes

* feat: update torrential

* feat: droplet bump and remove unsafe update functions

* fix: lint

* feat: v4 featureset: emulators, multi-launch commands

* fix: lint

* fix: mobile ui for game editor

* feat: launch options

* fix: lint

* fix: remove axios, use $fetch

* feat: metadata and task api improvements

* feat: task actions

* fix: slight styling issue

* feat: fix style and lints

* feat: totp backend routes

* feat: oidc groups

* fix: update drop-base

* feat: creation of passkeys & totp

* feat: totp signin

* feat: webauthn mfa/signin

* feat: launch selecting ui

* fix: manually running tasks

* feat: update add company game modal to use new SelectorGame

* feat: executor selector

* fix(docker): update rust to rust nightly for torrential build (#305)

* feat: new version ui

* feat: move package lookup to build time to allow for deno dev

* fix: lint

* feat: localisation cleanup

* feat: apply localisation cleanup

* feat: potential i18n refactor logic

* feat: remove args from commands

* fix: lint

* fix: lockfile

---------

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
This commit is contained in:
DecDuck
2026-01-13 15:32:39 +11:00
committed by GitHub
parent b6701f50e6
commit 038507fa74
190 changed files with 5848 additions and 2309 deletions
+128 -428
View File
@@ -1,5 +1,5 @@
<template>
<div class="flex flex-col gap-y-4 max-w-lg">
<div class="flex flex-col gap-y-4 max-w-[35vw]">
<Listbox
as="div"
:model-value="currentlySelectedVersion"
@@ -73,139 +73,59 @@
</div>
</Listbox>
<div v-if="versionGuesses" class="flex flex-col gap-8">
<div v-if="versionGuesses" class="flex flex-col gap-4">
<!-- setup executable -->
<div>
<label
for="startup"
class="block text-sm font-medium leading-6 text-zinc-100"
>{{ $t("library.admin.import.version.setupCmd") }}</label
>
<p class="text-zinc-400 text-xs">
{{ $t("library.admin.import.version.setupDesc") }}
</p>
<div class="mt-2">
<div
class="flex w-fit rounded-md shadow-sm bg-zinc-950 ring-1 ring-inset ring-zinc-800 focus-within:ring-2 focus-within:ring-inset focus-within:ring-blue-600"
>
<span
class="flex select-none items-center pl-3 text-zinc-500 sm:text-sm"
>
{{ $t("library.admin.import.version.installDir") }}
</span>
<Combobox
as="div"
:value="versionSettings.setup"
nullable
@update:model-value="(v) => updateSetupCommand(v)"
>
<div class="relative">
<ComboboxInput
class="block flex-1 border-0 py-1.5 pl-1 bg-transparent text-zinc-100 placeholder:text-zinc-400 focus:ring-0 sm:text-sm sm:leading-6"
:placeholder="
$t('library.admin.import.version.setupPlaceholder')
"
@change="setupProcessQuery = $event.target.value"
@blur="setupProcessQuery = ''"
/>
<ComboboxButton
v-if="setupFilteredVersionGuesses?.length ?? 0 > 0"
class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none"
>
<ChevronUpDownIcon
class="size-5 text-gray-400"
aria-hidden="true"
/>
</ComboboxButton>
<ComboboxOptions
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-white/5 focus:outline-none sm:text-sm"
>
<ComboboxOption
v-for="guess in setupFilteredVersionGuesses"
:key="guess.filename"
v-slot="{ active, selected }"
:value="guess.filename"
as="template"
>
<li
:class="[
'relative cursor-default select-none py-2 pl-3 pr-9',
active
? 'bg-blue-600 text-white outline-none'
: 'text-zinc-100',
]"
>
<span
:class="[
'inline-flex items-center gap-x-2 block truncate',
selected && 'font-semibold',
]"
>
{{ guess.filename }}
<component
:is="PLATFORM_ICONS[guess.platform as PlatformClient]"
class="size-5"
/>
</span>
<span
v-if="selected"
:class="[
'absolute inset-y-0 right-0 flex items-center pr-4',
active ? 'text-white' : 'text-blue-600',
]"
>
<CheckIcon class="size-5" aria-hidden="true" />
</span>
</li>
</ComboboxOption>
<ComboboxOption
v-if="setupProcessQuery"
v-slot="{ active, selected }"
:value="setupProcessQuery"
>
<li
:class="[
'relative cursor-default select-none py-2 pl-3 pr-9',
active
? 'bg-blue-600 text-white outline-none'
: 'text-zinc-100',
]"
>
<span
:class="['block truncate', selected && 'font-semibold']"
>
{{ $t("chars.quoted", { text: setupProcessQuery }) }}
</span>
<span
v-if="selected"
:class="[
'absolute inset-y-0 right-0 flex items-center pr-4',
active ? 'text-white' : 'text-blue-600',
]"
>
<CheckIcon class="size-5" aria-hidden="true" />
</span>
</li>
</ComboboxOption>
</ComboboxOptions>
</div>
</Combobox>
<input
id="startup"
v-model="versionSettings.setupArgs"
type="text"
name="startup"
class="border-l border-zinc-700 block flex-1 border-0 py-1.5 pl-2 bg-transparent text-zinc-100 placeholder:text-zinc-400 focus:ring-0 sm:text-sm sm:leading-6"
placeholder="--setup"
/>
</div>
<div class="bg-zinc-800 p-4 rounded-xl relative flex flex-col gap-y-2">
<div>
<label class="block text-sm font-medium leading-6 text-zinc-100">{{
$t("library.admin.import.version.setupCmd")
}}</label>
<p class="text-zinc-400 text-xs">
{{ $t("library.admin.import.version.setupDesc") }}
</p>
</div>
<ol
v-if="versionSettings.setups.length > 0"
class="divide-y-1 divide-zinc-700"
>
<li
v-for="(launch, launchIdx) in versionSettings.setups"
:key="launchIdx"
class="py-2 inline-flex items-start gap-x-1"
>
<ImportVersionLaunchRow
v-model="versionSettings.setups[launchIdx]"
:version-guesses="versionGuesses"
:needs-name="false"
/>
<button
class="transition rounded p-1 bg-zinc-900/30 group hover:bg-red-600/30"
@click="() => versionSettings.setups.splice(launchIdx, 1)"
>
<TrashIcon
class="transition size-5 text-zinc-700 group-hover:text-red-700"
/>
</button>
</li>
</ol>
<span
v-else
class="text-sm text-zinc-700 uppercase font-display font-bold"
>{{ $t("library.admin.import.version.noSetups") }}</span
>
<LoadingButton
:loading="false"
class="w-fit"
@click="() => versionSettings.setups.push({} as any)"
>{{ $t("common.add") }}</LoadingButton
>
</div>
<!-- setup mode -->
<SwitchGroup as="div" class="flex items-center justify-between">
<SwitchGroup
as="div"
class="bg-zinc-800 p-4 rounded-xl flex items-center justify-between gap-4"
>
<span class="flex flex-grow flex-col">
<SwitchLabel
as="span"
@@ -220,7 +140,7 @@
<Switch
v-model="versionSettings.onlySetup"
:class="[
versionSettings.onlySetup ? 'bg-blue-600' : 'bg-zinc-800',
versionSettings.onlySetup ? 'bg-blue-600' : 'bg-zinc-900',
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2',
]"
>
@@ -233,143 +153,62 @@
/>
</Switch>
</SwitchGroup>
<div class="relative">
<label
for="startup"
class="block text-sm font-medium leading-6 text-zinc-100"
>{{ $t("library.admin.import.version.launchCmd") }}</label
>
<p class="text-zinc-400 text-xs">
{{ $t("library.admin.import.version.launchDesc") }}
</p>
<div class="mt-2">
<div
class="flex w-fit rounded-md shadow-sm bg-zinc-950 ring-1 ring-inset ring-zinc-800 focus-within:ring-2 focus-within:ring-inset focus-within:ring-blue-600"
>
<span
class="flex select-none items-center pl-3 text-zinc-500 sm:text-sm"
>{{ $t("library.admin.import.version.installDir") }}</span
>
<Combobox
as="div"
:value="versionSettings.launch"
nullable
@update:model-value="(v) => updateLaunchCommand(v)"
>
<div class="relative">
<ComboboxInput
class="block flex-1 border-0 py-1.5 pl-1 bg-transparent text-zinc-100 placeholder:text-zinc-400 focus:ring-0 sm:text-sm sm:leading-6"
:placeholder="
$t('library.admin.import.version.launchPlaceholder')
"
@change="launchProcessQuery = $event.target.value"
@blur="launchProcessQuery = ''"
/>
<ComboboxButton
v-if="launchFilteredVersionGuesses?.length ?? 0 > 0"
class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none"
>
<ChevronUpDownIcon
class="size-5 text-gray-400"
aria-hidden="true"
/>
</ComboboxButton>
<ComboboxOptions
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-white/5 focus:outline-none sm:text-sm"
>
<ComboboxOption
v-for="guess in launchFilteredVersionGuesses"
:key="guess.filename"
v-slot="{ active, selected }"
:value="guess.filename"
as="template"
>
<li
:class="[
'relative cursor-default select-none py-2 pl-3 pr-9',
active
? 'bg-blue-600 text-white outline-none'
: 'text-zinc-100',
]"
>
<span
:class="[
'inline-flex items-center gap-x-2 block truncate',
selected && 'font-semibold',
]"
>
{{ guess.filename }}
<component
:is="PLATFORM_ICONS[guess.platform as PlatformClient]"
class="size-5"
/>
</span>
<span
v-if="selected"
:class="[
'absolute inset-y-0 right-0 flex items-center pr-4',
active ? 'text-white' : 'text-blue-600',
]"
>
<CheckIcon class="size-5" aria-hidden="true" />
</span>
</li>
</ComboboxOption>
<ComboboxOption
v-if="launchProcessQuery"
v-slot="{ active, selected }"
:value="launchProcessQuery"
>
<li
:class="[
'relative cursor-default select-none py-2 pl-3 pr-9',
active
? 'bg-blue-600 text-white outline-none'
: 'text-zinc-100',
]"
>
<span
:class="['block truncate', selected && 'font-semibold']"
>
{{ $t("chars.quoted", { text: launchProcessQuery }) }}
</span>
<span
v-if="selected"
:class="[
'absolute inset-y-0 right-0 flex items-center pr-4',
active ? 'text-white' : 'text-blue-600',
]"
>
<CheckIcon class="size-5" aria-hidden="true" />
</span>
</li>
</ComboboxOption>
</ComboboxOptions>
</div>
</Combobox>
<input
id="startup"
v-model="versionSettings.launchArgs"
type="text"
name="startup"
class="border-l border-zinc-700 block flex-1 border-0 py-1.5 pl-2 bg-transparent text-zinc-100 placeholder:text-zinc-400 focus:ring-0 sm:text-sm sm:leading-6"
placeholder="--launch"
/>
</div>
<!-- launch executables -->
<div class="relative flex flex-col gap-y-2 bg-zinc-800 p-4 rounded-xl">
<div>
<label class="block text-sm font-medium leading-6 text-zinc-100">{{
$t("library.admin.import.version.launchCmd")
}}</label>
<p class="text-zinc-400 text-xs">
{{ $t("library.admin.import.version.launchDesc") }}
</p>
</div>
<ol
v-if="versionSettings.launches.length > 0"
class="divide-y-1 divide-zinc-700"
>
<li
v-for="(launch, launchIdx) in versionSettings.launches"
:key="launchIdx"
class="py-2 inline-flex items-start gap-x-1 w-full"
>
<ImportVersionLaunchRow
v-model="versionSettings.launches[launchIdx]"
:version-guesses="versionGuesses"
:needs-name="true"
/>
<button
class="transition rounded p-1 bg-zinc-900/30 group hover:bg-red-600/30"
@click="() => versionSettings.launches.splice(launchIdx, 1)"
>
<TrashIcon
class="transition size-5 text-zinc-700 group-hover:text-red-700"
/>
</button>
</li>
</ol>
<span
v-else
class="text-sm text-zinc-700 uppercase font-display font-bold"
>{{ $t("library.admin.import.version.noLaunches") }}</span
>
<LoadingButton
:loading="false"
class="w-fit"
@click="() => versionSettings.launches.push({} as any)"
>{{ $t("common.add") }}</LoadingButton
>
<div
v-if="versionSettings.onlySetup"
class="absolute inset-0 bg-zinc-900/50"
/>
</div>
<PlatformSelector v-model="versionSettings.platform">
{{ $t("library.admin.import.version.platform") }}
</PlatformSelector>
<SwitchGroup as="div" class="flex items-center justify-between">
<SwitchGroup
as="div"
class="bg-zinc-800 p-4 rounded-xl flex items-center gap-4 justify-between"
>
<span class="flex flex-grow flex-col">
<SwitchLabel
as="span"
@@ -385,7 +224,7 @@
<Switch
v-model="versionSettings.delta"
:class="[
versionSettings.delta ? 'bg-blue-600' : 'bg-zinc-800',
versionSettings.delta ? 'bg-blue-600' : 'bg-zinc-900',
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2',
]"
>
@@ -398,89 +237,9 @@
/>
</Switch>
</SwitchGroup>
<Disclosure v-slot="{ open }" as="div" class="py-2">
<dt>
<DisclosureButton
class="border-b border-zinc-600 pb-2 flex w-full items-start justify-between text-left text-zinc-100"
>
<span class="text-base/7 font-semibold">
{{ $t("library.admin.import.version.advancedOptions") }}
</span>
<span class="ml-6 flex h-7 items-center">
<ChevronUpIcon v-if="!open" class="size-6" aria-hidden="true" />
<ChevronDownIcon v-else class="size-6" aria-hidden="true" />
</span>
</DisclosureButton>
</dt>
<DisclosurePanel
as="dd"
class="bg-zinc-950/30 p-3 rounded-b-lg mt-2 flex flex-col gap-y-4"
>
<!-- UMU launcher configuration -->
<div
v-if="versionSettings.platform == PlatformClient.Windows"
class="flex flex-col gap-y-4"
>
<SwitchGroup as="div" class="flex items-center justify-between">
<span class="flex flex-grow flex-col">
<SwitchLabel
as="span"
class="text-sm font-medium leading-6 text-zinc-100"
passive
>
{{ $t("library.admin.import.version.umuOverride") }}
</SwitchLabel>
<SwitchDescription as="span" class="text-sm text-zinc-400">
{{ $t("library.admin.import.version.umuOverrideDesc") }}
</SwitchDescription>
</span>
<Switch
v-model="umuIdEnabled"
:class="[
umuIdEnabled ? 'bg-blue-600' : 'bg-zinc-800',
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2',
]"
>
<span
aria-hidden="true"
:class="[
umuIdEnabled ? 'translate-x-5' : 'translate-x-0',
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
]"
/>
</Switch>
</SwitchGroup>
<div>
<label
for="umu-id"
class="block text-sm font-medium leading-6 text-zinc-100"
>
{{ $t("library.admin.import.version.umuLauncherId") }}
</label>
<div class="mt-2">
<input
id="umu-id"
v-model="umuId"
name="umu-id"
type="text"
autocomplete="umu-id"
required
:disabled="!umuIdEnabled"
placeholder="umu-starcitizen"
class="block w-full rounded-md border-0 py-1.5 px-3 bg-zinc-950 disabled:bg-zinc-900/80 text-zinc-100 disabled:text-zinc-400 shadow-sm ring-1 ring-inset ring-zinc-700 disabled:ring-zinc-800 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
</div>
<div v-else class="text-zinc-400">
{{ $t("library.admin.import.version.noAdv") }}
</div>
</DisclosurePanel>
</Disclosure>
<LoadingButton
class="w-fit"
class="w-fit ml-auto"
:loading="importLoading"
@click="startImport_wrapper"
>
@@ -536,18 +295,15 @@ import {
SwitchDescription,
SwitchGroup,
SwitchLabel,
Disclosure,
DisclosureButton,
DisclosurePanel,
Combobox,
ComboboxButton,
ComboboxInput,
ComboboxOption,
ComboboxOptions,
} from "@headlessui/vue";
import { XCircleIcon } from "@heroicons/vue/16/solid";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/solid";
import {
CheckIcon,
ChevronUpDownIcon,
TrashIcon,
} from "@heroicons/vue/20/solid";
import type { Platform } from "~/prisma/client/enums";
import type { ImportVersion } from "~/server/api/v1/admin/import/version/index.post";
definePageMeta({
layout: "admin",
@@ -561,76 +317,16 @@ const versions = await $dropFetch(
`/api/v1/admin/import/version?id=${encodeURIComponent(gameId)}`,
);
const currentlySelectedVersion = ref(-1);
const versionSettings = ref<{
platform: PlatformClient | undefined;
onlySetup: boolean;
launch: string;
launchArgs: string;
setup: string;
setupArgs: string;
delta: boolean;
umuId: string;
}>({
platform: undefined,
launch: "",
launchArgs: "",
setup: "",
setupArgs: "",
const versionSettings = ref<typeof ImportVersion.infer>({
id: gameId,
version: "",
delta: false,
onlySetup: false,
umuId: "",
launches: [],
setups: [],
});
const versionGuesses =
ref<Array<{ platform: PlatformClient; filename: string }>>();
const launchProcessQuery = ref("");
const setupProcessQuery = ref("");
const launchFilteredVersionGuesses = computed(() =>
versionGuesses.value?.filter((e) =>
e.filename.toLowerCase().includes(launchProcessQuery.value.toLowerCase()),
),
);
const setupFilteredVersionGuesses = computed(() =>
versionGuesses.value?.filter((e) =>
e.filename.toLowerCase().includes(setupProcessQuery.value.toLowerCase()),
),
);
function updateLaunchCommand(value: string) {
versionSettings.value.launch = value;
autosetPlatform(value);
}
function updateSetupCommand(value: string) {
versionSettings.value.setup = value;
autosetPlatform(value);
}
function autosetPlatform(value: string) {
if (!versionGuesses.value) return;
if (versionSettings.value.platform) return;
const guessIndex = versionGuesses.value.findIndex(
(e) => e.filename === value,
);
if (guessIndex == -1) return;
versionSettings.value.platform = versionGuesses.value[guessIndex].platform;
}
const umuIdEnabled = ref(false);
const umuId = computed({
get() {
if (umuIdEnabled.value) return versionSettings.value.umuId;
return undefined;
},
set(v) {
if (umuIdEnabled.value && v) {
versionSettings.value.umuId = v;
}
},
});
const versionGuesses = ref<Array<{ platform: Platform; filename: string }>>();
const importLoading = ref(false);
const importError = ref<string | undefined>();
@@ -639,15 +335,19 @@ async function updateCurrentlySelectedVersion(value: number) {
if (currentlySelectedVersion.value == value) return;
currentlySelectedVersion.value = value;
const version = versions[currentlySelectedVersion.value];
const results = await $dropFetch(
`/api/v1/admin/import/version/preload?id=${encodeURIComponent(
gameId,
)}&version=${encodeURIComponent(version)}`,
);
versionGuesses.value = results.map((e) => ({
...e,
platform: e.platform as PlatformClient,
}));
try {
const results = await $dropFetch(
`/api/v1/admin/import/version/preload?id=${encodeURIComponent(
gameId,
)}&version=${encodeURIComponent(version)}`,
{
failTitle: "Failed to fetch version information",
},
);
versionGuesses.value = results as typeof versionGuesses.value;
} catch {
currentlySelectedVersion.value = -1;
}
}
async function startImport() {
@@ -655,9 +355,9 @@ async function startImport() {
const taskId = await $dropFetch("/api/v1/admin/import/version", {
method: "POST",
body: {
...versionSettings.value,
id: gameId,
version: versions[currentlySelectedVersion.value],
...versionSettings.value,
},
});
router.push(`/admin/task/${taskId.taskId}`);
+4 -4
View File
@@ -3,11 +3,11 @@
class="pt-8 lg:pt-0 lg:pl-20 fixed inset-0 flex flex-col overflow-auto bg-zinc-900"
>
<div
class="bg-zinc-950 w-full flex flex-col sm:flex-row items-center gap-2 justify-between pr-2"
class="bg-zinc-950 w-full flex flex-row items-center gap-2 justify-between px-2 pt-6 lg:pt-0"
>
<!--start-->
<div>
<Listbox v-if="false" v-model="currentMode" as="div">
<Listbox v-model="currentMode" as="div" class="sm:hidden mb-2">
<div class="relative mt-2">
<ListboxButton
class="min-w-[10vw] w-full cursor-default inline-flex items-center gap-x-2 rounded-md bg-zinc-900 py-1.5 pr-2 pl-3 text-left text-zinc-200 outline-1 -outline-offset-1 outline-zinc-700 focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6"
@@ -68,7 +68,7 @@
</div>
</Listbox>
<div class="pt-4 inline-flex gap-x-2">
<div class="hidden sm:inline-flex pt-4 gap-x-2">
<div
v-for="[value, { icon }] in Object.entries(components)"
:key="value"
@@ -93,7 +93,7 @@
<NuxtLink
:href="`/store/${game.id}`"
type="button"
class="inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
class="whitespace-nowrap inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
{{ $t("library.admin.openStore") }}
<ArrowTopRightOnSquareIcon
+7
View File
@@ -42,6 +42,7 @@
import {
BuildingStorefrontIcon,
CodeBracketIcon,
ServerIcon,
} from "@heroicons/vue/24/outline";
const navigation: Array<NavigationItem & { icon: Component }> = [
@@ -57,6 +58,12 @@ const navigation: Array<NavigationItem & { icon: Component }> = [
prefix: "/admin/settings/tokens",
icon: CodeBracketIcon,
},
{
label: "Services",
route: "/admin/settings/services",
prefix: "/admin/settings/services",
icon: ServerIcon,
},
];
// const notifications = useNotifications();
+91
View File
@@ -0,0 +1,91 @@
<template>
<div class="max-w-xl">
<div
class="divide-y divide-white/10 overflow-hidden rounded-lg bg-zinc-900 outline -outline-offset-1 outline-white/20 sm:grid sm:grid-cols-2 sm:divide-y-0"
>
<div
v-for="(service, serviceIdx) in services"
:key="service.name"
:class="[
serviceIdx === 0
? 'rounded-tl-lg rounded-tr-lg sm:rounded-tr-none'
: '',
serviceIdx === 1 ? 'sm:rounded-tr-lg' : '',
serviceIdx === services.length - 2 ? 'sm:rounded-bl-lg' : '',
serviceIdx === services.length - 1
? 'rounded-br-lg rounded-bl-lg sm:rounded-bl-none'
: '',
'group relative border-white/10 bg-zinc-800/50 p-6 focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-indigo-500 sm:odd:not-nth-last-2:border-b sm:even:border-l sm:even:not-last:border-b',
]"
>
<div>
<span
:class="[
serviceMetadata[service.name].iconBackground,
serviceMetadata[service.name].iconForeground,
'inline-flex rounded-lg p-3',
]"
>
<component
:is="serviceMetadata[service.name].icon"
class="size-6"
aria-hidden="true"
/>
</span>
</div>
<div class="mt-8">
<h3 class="text-base font-semibold text-white">
<a :href="service.href" class="focus:outline-hidden">
<!-- Extend touch target to entire panel -->
<span class="absolute inset-0" aria-hidden="true"></span>
{{ serviceMetadata[service.name].title }}
</a>
</h3>
<p class="mt-2 text-sm text-zinc-400">
{{ serviceMetadata[service.name].description }}
</p>
</div>
<span
class="pointer-events-none absolute top-6 right-6"
aria-hidden="true"
>
<CheckIcon
:class="[
'size-6',
service.healthy ? 'text-green-600' : 'text-zinc-500',
]"
/>
</span>
</div>
</div>
</div>
</template>
<script setup>
import { ArrowDownTrayIcon, CheckIcon } from "@heroicons/vue/24/outline";
definePageMeta({
layout: "admin",
});
const services = await $dropFetch("/api/v1/admin/services");
const { t } = useI18n();
const serviceMetadata = computed(() => ({
torrential: {
title: t("services.torrential.title"),
description: t("services.torrential.description"),
iconForeground: "text-blue-400",
iconBackground: "bg-blue-500/10",
icon: ArrowDownTrayIcon,
},
nginx: {
title: t("services.nginx.title"),
description: t("services.nginx.description"),
iconForeground: "text-green-400",
iconBackground: "bg-green-500/10",
icon: ArrowDownTrayIcon,
},
}));
</script>
-1
View File
@@ -206,7 +206,6 @@ async function createToken(
},
failTitle: "Failed to create API token.",
});
console.log(result);
newToken.value = result.token;
tokens.value.push(result);
} catch {
+18 -1
View File
@@ -44,8 +44,25 @@
</div>
{{ task.name }}
</h1>
<ul class="flex flex-row items-center h-12 gap-x-3">
<li
v-for="[name, link] in task.actions.map((v) => v.split(':'))"
:key="link"
>
<NuxtLink :href="link">
<LoadingButton :loading="false"> {{ name }} </LoadingButton>
</NuxtLink>
</li>
<li
v-if="task.actions.length == 0"
class="text-md uppercase font-display font-bold text-zinc-700"
>
No actions
</li>
</ul>
<div
class="bg-zinc-950 p-2 rounded-md h-[80vh] flex flex-col flex-col-reverse overflow-y-scroll gap-y-1"
class="bg-zinc-950 p-2 rounded-md h-[70vh] flex flex-col flex-col-reverse overflow-y-scroll gap-y-1"
>
<LogLine
v-for="(_, idx) in task.log"
+2 -2
View File
@@ -164,8 +164,8 @@ const scheduledTasks: {
description: "",
},
debug: {
name: "Debug Task",
description: "Does debugging things.",
name: "",
description: "",
},
};