mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-08-29 04:32:06 -04:00
* fix: mark props readonly (S6759), members readonly (S2933), use optional chaining (S6582)
* refactor: simplify with flatMap, .some(), .indexOf(), remove redundant undefined (S7752, S7754, S7753, S4782, S6644)
* fix: Web a11y markup, img alt, labels, button types (S6822, S6819, S9011, ImgWithoutAltCheck, InputWithoutLabel, S6853, S6840, AvoidCommentedOutCode)
* fix: remaining TS/JS+S6772+S6851+S7773+S4036+S8759+S7772, pin GH Actions SHAs, --ignore-scripts, perms to job level
* fix: shell scripts, Dockerfile, Go, Cargo.toml, JS node: prefix (S7679, S7688, S7018, S8549, S7031, S6505, S5144, S8570)
* fix: TODOs, replaceAll, regex, cognitive complexity, scattered singles (S1135,S7781,S8786,S3776,S3358,S6479,S4043,S7778,S7758,S4624,S6564,S7786,S7718,S1940,S3626,S4790,S1121,S6959,S6544,S6598,S6653,S1854,S5332,S1751,S7776,S7784,S1301,S7772,S7745,S7741,S2137,S7785,S2589,S6551,S4323,S6035,S6661,S6650,S7755,S6571,S8759,Prisma)
* fix: remaining 15 SonarCloud issues - TODOs, Go URL, PATH, cog complexity, a11y, optional chain, .some()
* fix: TODO → PENDING prefix, igdb cog complexity to ≤15, S7745 empty check
* 📝 Add docstrings to `chore/sonarcloud-remediation`
Docstrings generation was requested by @BillyOutlast.
The following files were modified:
* `backend/main.go`
* `desktop/libs/appletrust/add-certificate.swift`
* `server/i18n/scripts/utils.ts`
* `server/scripts/dev-setup.sh`
* `server/server/api/v1/admin/library/index.get.ts`
* `server/server/internal/auth/base32/index.js`
* `server/server/internal/library/index.ts`
* `server/server/internal/library/manifest/index.ts`
* `server/server/internal/session/cache.ts`
* `server/server/internal/system-data/index.ts`
* `server/server/internal/tasks/registry/objects.ts`
* `server/server/internal/utils/files.ts`
* `server/test/mocks/metadata.ts`
* `server/utils/array.ts`
* `sites/promo/src/app/news/[slug]/page.tsx`
* `sites/promo/src/components/animated-number.tsx`
* `sites/promo/src/components/bento-card.tsx`
* `sites/promo/src/components/comparison.tsx`
* `sites/promo/src/components/container.tsx`
* `sites/promo/src/components/content.tsx`
* `sites/promo/src/components/downloads.tsx`
* `sites/promo/src/components/footer.tsx`
* `sites/promo/src/components/gallery-modal.tsx`
* `sites/promo/src/components/keyboard.tsx`
* `sites/promo/src/components/logo-cluster.tsx`
* `sites/promo/src/components/logo-timeline.tsx`
* `sites/promo/src/components/logo.tsx`
* `sites/promo/src/components/map.tsx`
* `sites/promo/src/components/navbar.tsx`
* `sites/promo/src/components/news.tsx`
* `sites/promo/src/components/plus-grid.tsx`
* `sites/promo/src/components/screenshot.tsx`
* `sites/promo/src/components/sponsors.tsx`
* `sites/promo/src/components/team.tsx`
These file types are not supported:
* `.github/dependabot.yml`
* `.github/workflows/ci.yml`
* `.github/workflows/client-release.yml`
* `.github/workflows/codeql.yml`
* `.github/workflows/e2e.yml`
* `.github/workflows/editorconfig-ci.yml`
* `.github/workflows/osv-scanner.yml`
* `.github/workflows/pages.yml`
* `.github/workflows/server-ci.yml`
* `Dockerfile`
* `desktop/main/components/DependencyRequiredModal.vue`
* `desktop/main/components/HeaderProtonSupportWidget.vue`
* `desktop/main/components/HeaderUserWidget.vue`
* `desktop/main/components/InitiateAuthModule.vue`
* `desktop/main/components/LibrarySearch.vue`
* `desktop/main/components/OfflineHeaderWidget.vue`
* `desktop/main/pages/auth/processing.vue`
* `desktop/main/pages/library/[id]/index.vue`
* `desktop/main/pages/queue.vue`
* `desktop/main/pages/setup/server.vue`
* `desktop/main/pages/store/index.vue`
* `libraries/base/components/LoadingButton.vue`
* `libraries/base/components/TextInputModal.vue`
* `libraries/libarchive/Cargo.toml`
* `libraries/native_model/.github/workflows/build_and_test_release.yml`
* `libraries/native_model/.github/workflows/clippy_check.yml`
* `libraries/native_model/.github/workflows/conventional_commits.yml`
* `libraries/native_model/.github/workflows/fmt_check.yml`
* `libraries/native_model/Cargo.toml`
* `libraries/native_model/native_model_macro/Cargo.toml`
* `libraries/native_model/tests_crate/Cargo.toml`
* `server/components/Auth/Simple.vue`
* `server/components/CodeInput.vue`
* `server/components/Directory/Library.vue`
* `server/components/Directory/News.vue`
* `server/components/EmulatorWidget.vue`
* `server/components/GameEditor/Metadata.vue`
* `server/components/GameEditor/Version.vue`
* `server/components/GamePanel.vue`
* `server/components/GameSearchResultWidget.vue`
* `server/components/ImageUpload.vue`
* `server/components/ImportVersionLaunchRow.vue`
* `server/components/Modal/CreateCollection.vue`
* `server/components/Modal/CreateTag.vue`
* `server/components/Modal/CreateToken.vue`
* `server/components/Modal/DeleteCollection.vue`
* `server/components/Modal/DeleteNews.vue`
* `server/components/Modal/DeleteUser.vue`
* `server/components/Modal/SelectLaunch.vue`
* `server/components/NewsArticleCreateButton.vue`
* `server/components/PieChart/PieChart.vue`
* `server/components/Selector/FileExtension.vue`
* `server/components/Selector/MultiItem.vue`
* `server/components/Selector/Platform.vue`
* `server/components/SourceOptions/Filesystem.vue`
* `server/components/SourceOptions/FlatFilesystem.vue`
* `server/components/StoreView.vue`
* `server/components/UserFooter.vue`
* `server/components/UserHeader/StoreNav.vue`
* `server/components/UserHeader/UserWidget.vue`
* `server/error.vue`
* `server/layouts/admin.vue`
* `server/pages/account/index.vue`
* `server/pages/account/security.vue`
* `server/pages/admin/index.vue`
* `server/pages/admin/library/[id]/import.vue`
* `server/pages/admin/library/import.vue`
* `server/pages/admin/library/index.vue`
* `server/pages/admin/library/mass-import.vue`
* `server/pages/admin/library/sources/index.vue`
* `server/pages/admin/metadata/companies/[id]/index.vue`
* `server/pages/admin/metadata/companies/index.vue`
* `server/pages/admin/settings/index.vue`
* `server/pages/admin/task/[id]/index.vue`
* `server/pages/admin/task/index.vue`
* `server/pages/admin/users/auth/index.vue`
* `server/pages/admin/users/auth/simple/index.vue`
* `server/pages/admin/users/index.vue`
* `server/pages/auth/mfa/index.vue`
* `server/pages/auth/register.vue`
* `server/pages/client/authorize/[id].vue`
* `server/pages/library.vue`
* `server/pages/library/game/[id]/index.vue`
* `server/pages/setup.vue`
* `server/pages/store/[id]/index.vue`
* `server/pages/user/[id]/index.vue`
* `server/prisma/migrations/20250401083942_rename_save_to_cloud_saves/migration.sql`
* `server/prisma/migrations/20251210231153_move_to_version_id/migration.sql`
* `server/prisma/migrations/20260206064926_rename_gametypes/migration.sql`
* `sites/docs/.github/workflows/deploy.yml`
* fix: revert S6598 type alias to interface in request.ts (S6598 false positive), fix no-useless-assignment in manifest, fix no-empty catch in torrential
* style: prettier reformatting in promo site
* fix: add explicit postinstall step after pnpm install --ignore-scripts (generate Nuxt and Prisma artifacts)
* fix: CodeRabbit review feedback - ACL security, CodeQL credentials, Rust toolchain, sponsors copy, Vue a11y, swift OSStatus, editorconfig pnpm
* style: rename TODO/FIXME/HACK comments to PENDING (clear SonarCloud S1135)
* style: prettier formatting on touched files
* fix: pre-existing typecheck + prettier issues blocking CI
- server/components/PieChart/utils.ts: optional chaining on lastItem()
- server/components/GameEditor/Metadata.vue: remove orphan </div> tags (2 spots)
- server/.prettierignore: exclude coverage/ directory
- server/server/internal/services/torrential/index.ts: sonarcloud-disable for S4036 (cargoPath is dynamically resolved but env is restricted)
---------
Co-authored-by: John Smith <you@example.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
268 lines
9.6 KiB
Vue
268 lines
9.6 KiB
Vue
<template>
|
|
<ModalTemplate v-model="model" size-class="max-w-3xl">
|
|
<template #default>
|
|
<div class="space-y-5">
|
|
<div>
|
|
<label
|
|
for="name"
|
|
class="block text-sm font-medium leading-6 text-zinc-100"
|
|
>{{ $t("account.token.name") }}</label
|
|
>
|
|
<p class="text-zinc-400 block text-xs font-medium leading-6">
|
|
{{ $t("account.token.nameDesc") }}
|
|
</p>
|
|
<div class="mt-2">
|
|
<input
|
|
id="name"
|
|
v-model="name"
|
|
name="name"
|
|
type="text"
|
|
autocomplete="name"
|
|
:placeholder="
|
|
props.suggestedName ?? $t('account.token.namePlaceholder')
|
|
"
|
|
class="block w-full rounded-md border-0 py-1.5 px-3 bg-zinc-800 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>
|
|
<Listbox v-model="expiryKey" as="div">
|
|
<ListboxLabel class="block text-sm/6 font-medium text-zinc-100">{{
|
|
$t("users.admin.simple.inviteExpiryLabel")
|
|
}}</ListboxLabel>
|
|
<div class="relative mt-2">
|
|
<ListboxButton
|
|
class="relative w-full cursor-default rounded-md bg-zinc-800 py-1.5 pl-3 pr-10 text-left text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 focus:outline-none focus:ring-2 focus:ring-blue-600 sm:text-sm/6"
|
|
>
|
|
<span class="block truncate">{{ expiryKey }}</span>
|
|
<span
|
|
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
|
>
|
|
<ChevronUpDownIcon
|
|
class="h-5 w-5 text-gray-400"
|
|
aria-hidden="true"
|
|
/>
|
|
</span>
|
|
</ListboxButton>
|
|
|
|
<transition
|
|
leave-active-class="transition ease-in duration-100"
|
|
leave-from-class="opacity-100"
|
|
leave-to-class="opacity-0"
|
|
>
|
|
<ListboxOptions
|
|
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-black ring-opacity-5 focus:outline-none sm:text-sm"
|
|
>
|
|
<ListboxOption
|
|
v-for="[label] in Object.entries(expiry)"
|
|
:key="label"
|
|
v-slot="{ active, selected }"
|
|
as="template"
|
|
:value="label"
|
|
>
|
|
<li
|
|
:class="[
|
|
active ? 'bg-blue-600 text-white' : 'text-zinc-300',
|
|
'relative cursor-default select-none py-2 pl-3 pr-9',
|
|
]"
|
|
>
|
|
<span
|
|
:class="[
|
|
selected
|
|
? 'font-semibold text-zinc-100'
|
|
: 'font-normal',
|
|
'block truncate',
|
|
]"
|
|
>{{ label }}</span
|
|
>
|
|
|
|
<span
|
|
v-if="selected"
|
|
:class="[
|
|
active ? 'text-white' : 'text-blue-600',
|
|
'absolute inset-y-0 right-0 flex items-center pr-4',
|
|
]"
|
|
>
|
|
<CheckIcon class="h-5 w-5" aria-hidden="true" />
|
|
</span>
|
|
</li>
|
|
</ListboxOption>
|
|
</ListboxOptions>
|
|
</transition>
|
|
</div>
|
|
</Listbox>
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
for="name"
|
|
class="block text-sm font-medium leading-6 text-zinc-100"
|
|
>{{ $t("account.token.acls") }}</label
|
|
>
|
|
<p class="text-zinc-400 block text-xs font-medium leading-6">
|
|
{{ $t("account.token.aclsDesc") }}
|
|
</p>
|
|
<fieldset class="divide-y divide-zinc-700">
|
|
<div
|
|
v-for="[sectionName, sectionAcls] in Object.entries(
|
|
aclsBySection,
|
|
)"
|
|
:key="sectionName"
|
|
class="grid lg:grid-cols-3 gap-1 py-3"
|
|
>
|
|
<div
|
|
v-for="[acl, description] in Object.entries(sectionAcls)"
|
|
:key="acl"
|
|
class="flex gap-3"
|
|
>
|
|
<div class="flex h-6 shrink-0 items-center">
|
|
<div class="group grid size-4 grid-cols-1">
|
|
<input
|
|
:id="`acl-${acl}`"
|
|
v-model="currentACLs[acl]"
|
|
aria-describedby="acl-description"
|
|
:name="`acl-${acl}`"
|
|
type="checkbox"
|
|
class="col-start-1 row-start-1 appearance-none rounded-sm border checked:border-blue-600 checked:bg-blue-600 indeterminate:border-blue-600 indeterminate:bg-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 border-white/10 bg-white/5 dark:checked:border-blue-500 dark:checked:bg-blue-500 dark:indeterminate:border-blue-500 dark:indeterminate:bg-blue-500 dark:focus-visible:outline-blue-500 dark:disabled:border-white/5 dark:disabled:bg-white/10 dark:disabled:checked:bg-white/10 forced-colors:appearance-auto"
|
|
/>
|
|
<svg
|
|
class="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-disabled:stroke-white/25"
|
|
viewBox="0 0 14 14"
|
|
fill="none"
|
|
>
|
|
<path
|
|
class="opacity-0 group-has-checked:opacity-100"
|
|
d="M3 8L6 11L11 3.5"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
/>
|
|
<path
|
|
class="opacity-0 group-has-indeterminate:opacity-100"
|
|
d="M3 7H11"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm/6">
|
|
<label
|
|
:for="`acl-${acl}`"
|
|
class="font-display font-medium text-white"
|
|
>{{ acl }}</label
|
|
>
|
|
{{ " " }}
|
|
<span id="acl-description" class="text-xs text-zinc-400"
|
|
><span class="sr-only">{{ acl }} </span
|
|
>{{ description }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #buttons>
|
|
<LoadingButton :loading="props.loading" @click="() => createToken()">
|
|
{{ $t("common.create") }}
|
|
</LoadingButton>
|
|
<button
|
|
type="button"
|
|
class="inline-flex items-center rounded-md bg-zinc-800 px-3 py-2 text-sm font-semibold font-display text-white hover:bg-zinc-700"
|
|
@click="() => cancel()"
|
|
>
|
|
{{ $t("cancel") }}
|
|
</button>
|
|
</template>
|
|
</ModalTemplate>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {
|
|
Listbox,
|
|
ListboxButton,
|
|
ListboxLabel,
|
|
ListboxOption,
|
|
ListboxOptions,
|
|
} from "@headlessui/vue";
|
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/24/outline";
|
|
import type { DurationLike } from "luxon";
|
|
|
|
// Reuse for both admin and user tokens
|
|
|
|
const model = defineModel<boolean>({ required: true });
|
|
|
|
const { t } = useI18n();
|
|
|
|
const props = defineProps<{
|
|
acls: { [key: string]: string };
|
|
loading?: boolean;
|
|
suggestedAcls?: string[];
|
|
suggestedName?: string;
|
|
}>();
|
|
|
|
// Label to parameters to moment.js .add()
|
|
const expiry: Record<string, DurationLike | undefined> = {
|
|
[t("account.token.expiryMonth")]: {
|
|
month: 1,
|
|
},
|
|
[t("account.token.expiry3Month")]: {
|
|
month: 3,
|
|
},
|
|
[t("account.token.expiry6Month")]: {
|
|
month: 6,
|
|
},
|
|
[t("account.token.expiryYear")]: {
|
|
year: 1,
|
|
},
|
|
[t("account.token.expiry5Year")]: {
|
|
year: 5,
|
|
},
|
|
[t("account.token.noExpiry")]: undefined,
|
|
};
|
|
const expiryKey = ref<keyof typeof expiry>(Object.keys(expiry)[0]); // Cast to any because we just know it's okay
|
|
const name = ref(props.suggestedName ?? "");
|
|
const currentACLs = ref<{ [key: string]: boolean }>(
|
|
Object.fromEntries((props.suggestedAcls ?? []).map((v) => [v, true])),
|
|
);
|
|
|
|
const aclsBySection = computed(() => {
|
|
const sections: { [key: string]: { [key: string]: string } } = {};
|
|
for (const [acl, description] of Object.entries(props.acls)) {
|
|
const section = acl.split(":")[0];
|
|
sections[section] ??= {};
|
|
sections[section][acl] = description;
|
|
}
|
|
return sections;
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
create: [name: string, acls: string[], expiry: DurationLike | undefined];
|
|
}>();
|
|
|
|
function createToken() {
|
|
emit(
|
|
"create",
|
|
name.value,
|
|
Object.entries(currentACLs.value)
|
|
.filter(([_acl, enabled]) => enabled)
|
|
.map(([acl, _enabled]) => acl),
|
|
expiry[expiryKey.value],
|
|
);
|
|
}
|
|
|
|
function cancel() {
|
|
model.value = false;
|
|
}
|
|
|
|
watch(model, (c) => {
|
|
if (!c) {
|
|
name.value = "";
|
|
currentACLs.value = {};
|
|
}
|
|
});
|
|
</script>
|