mirror of
https://github.com/unraid/api.git
synced 2026-01-02 14:40:01 -06:00
feat(unraid-plugins): add query for installed Unraid OS plugins and enhance plugin management
- Introduced a new GraphQL query `installedUnraidPlugins` to list installed Unraid OS plugins by their .plg filenames. - Updated the `UnraidPluginsResolver` to include the new query and implemented the corresponding service method to read plugin files from the filesystem. - Enhanced the `ActivationPluginsStep` component to utilize the new query, improving the user experience by dynamically displaying installed plugins. - Added a new GraphQL query file for `INSTALLED_UNRAID_PLUGINS_QUERY` to facilitate fetching installed plugins in the frontend. These changes enhance the plugin management capabilities, providing users with better visibility and control over installed plugins.
This commit is contained in:
@@ -2909,6 +2909,9 @@ type Query {
|
||||
"""List all tracked plugin installation operations"""
|
||||
pluginInstallOperations: [PluginInstallOperation!]!
|
||||
|
||||
"""List installed Unraid OS plugins by .plg filename"""
|
||||
installedUnraidPlugins: [String!]!
|
||||
|
||||
"""List all installed plugins with their metadata"""
|
||||
plugins: [Plugin!]!
|
||||
remoteAccess: RemoteAccess!
|
||||
|
||||
@@ -38,6 +38,17 @@ export class UnraidPluginsResolver {
|
||||
return this.pluginsService.listOperations();
|
||||
}
|
||||
|
||||
@Query(() => [String], {
|
||||
description: 'List installed Unraid OS plugins by .plg filename',
|
||||
})
|
||||
@UsePermissions({
|
||||
action: AuthAction.READ_ANY,
|
||||
resource: Resource.CONFIG,
|
||||
})
|
||||
async installedUnraidPlugins(): Promise<string[]> {
|
||||
return this.pluginsService.listInstalledPlugins();
|
||||
}
|
||||
|
||||
@Subscription(() => PluginInstallEvent, {
|
||||
name: 'pluginInstallUpdates',
|
||||
resolve: (payload: { pluginInstallUpdates: PluginInstallEvent }) => payload.pluginInstallUpdates,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import EventEmitter from 'node:events';
|
||||
import { PassThrough } from 'node:stream';
|
||||
|
||||
@@ -24,7 +25,7 @@ describe('UnraidPluginsService', () => {
|
||||
let currentProcess: MockExecaProcess;
|
||||
|
||||
beforeEach(() => {
|
||||
service = new UnraidPluginsService();
|
||||
service = new UnraidPluginsService(new ConfigService());
|
||||
currentProcess = new MockExecaProcess();
|
||||
currentProcess.all.setEncoding('utf-8');
|
||||
mockExeca.mockReset();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import type { ExecaError } from 'execa';
|
||||
import { execa } from 'execa';
|
||||
@@ -40,6 +43,8 @@ export class UnraidPluginsService {
|
||||
private readonly operations = new Map<string, OperationState>();
|
||||
private readonly MAX_OUTPUT_LINES = 500;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
async installPlugin(input: InstallPluginInput): Promise<PluginInstallOperation> {
|
||||
const id = randomUUID();
|
||||
const createdAt = new Date();
|
||||
@@ -103,6 +108,27 @@ export class UnraidPluginsService {
|
||||
return this.toGraphqlOperation(operation);
|
||||
}
|
||||
|
||||
async listInstalledPlugins(): Promise<string[]> {
|
||||
const paths = this.configService.get<Record<string, string>>('store.paths', {});
|
||||
const dynamixBase = paths?.['dynamix-base'] ?? '/boot/config/plugins/dynamix';
|
||||
const pluginsDir = path.resolve(dynamixBase, '..');
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
|
||||
return entries
|
||||
.filter((entry) => entry.isFile() && entry.name.endsWith('.plg'))
|
||||
.map((entry) => entry.name);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
||||
this.logger.warn(`Plugin directory not found at ${pluginsDir}.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
this.logger.error('Failed to read plugin directory.', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getOperation(id: string): PluginInstallOperation | null {
|
||||
const operation = this.operations.get(id);
|
||||
if (!operation) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ref } from 'vue';
|
||||
import { flushPromises, mount } from '@vue/test-utils';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
@@ -7,6 +8,7 @@ import { PluginInstallStatus } from '~/composables/gql/graphql';
|
||||
import { createTestI18n } from '../../utils/i18n';
|
||||
|
||||
const installPluginMock = vi.fn();
|
||||
const useQueryMock = vi.fn();
|
||||
|
||||
vi.mock('@unraid/ui', () => ({
|
||||
BrandButton: {
|
||||
@@ -22,9 +24,23 @@ vi.mock('~/components/Activation/usePluginInstaller', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@vue/apollo-composable', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('@vue/apollo-composable')>('@vue/apollo-composable');
|
||||
return {
|
||||
...actual,
|
||||
useQuery: useQueryMock,
|
||||
};
|
||||
});
|
||||
|
||||
describe('ActivationPluginsStep', () => {
|
||||
beforeEach(() => {
|
||||
installPluginMock.mockReset();
|
||||
useQueryMock.mockReturnValue({
|
||||
result: ref({ installedUnraidPlugins: [] }),
|
||||
loading: ref(false),
|
||||
error: ref(null),
|
||||
});
|
||||
});
|
||||
|
||||
const mountComponent = (overrides: Record<string, unknown> = {}) => {
|
||||
|
||||
@@ -18,6 +18,7 @@ defineProps<{
|
||||
allowSkip?: boolean;
|
||||
showKeyfileHint?: boolean;
|
||||
showActivationCodeHint?: boolean;
|
||||
isSavingStep?: boolean;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -33,12 +34,20 @@ const { t } = useI18n();
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="mx-auto mb-10 flex gap-4">
|
||||
<BrandButton v-if="canGoBack" :text="t('common.back')" variant="outline" @click="onBack?.()" />
|
||||
<BrandButton
|
||||
v-if="canGoBack"
|
||||
:text="t('common.back')"
|
||||
variant="outline"
|
||||
:disabled="isSavingStep"
|
||||
@click="onBack?.()"
|
||||
/>
|
||||
<BrandButton
|
||||
:text="t('activation.activationModal.activateNow')"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:href="activateHref"
|
||||
:external="activateExternal"
|
||||
:disabled="isSavingStep"
|
||||
:loading="isSavingStep"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -63,9 +72,15 @@ const { t } = useI18n();
|
||||
v-if="allowSkip"
|
||||
:text="t('activation.skipForNow')"
|
||||
variant="underline"
|
||||
:disabled="isSavingStep"
|
||||
@click="onComplete?.()"
|
||||
/>
|
||||
<BrandButton v-for="button in docsButtons" :key="button.text" v-bind="button" />
|
||||
<BrandButton
|
||||
v-for="button in docsButtons"
|
||||
:key="button.text"
|
||||
v-bind="button"
|
||||
:disabled="isSavingStep"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -275,6 +275,7 @@ const currentStepConfig = computed(() => {
|
||||
});
|
||||
|
||||
const isCurrentStepSaved = computed(() => currentStepConfig.value?.completed ?? false);
|
||||
const isStepSaving = computed(() => stepSaveState.value === 'saving');
|
||||
|
||||
const currentStepProps = computed<Record<string, unknown>>(() => {
|
||||
const step = currentStep.value;
|
||||
@@ -291,6 +292,7 @@ const currentStepProps = computed<Record<string, unknown>>(() => {
|
||||
onBack: goToPreviousStep,
|
||||
showBack: canGoBack.value,
|
||||
isCompleted: isCurrentStepCompleted,
|
||||
isSavingStep: isStepSaving.value,
|
||||
};
|
||||
|
||||
switch (step) {
|
||||
@@ -409,7 +411,7 @@ watch(
|
||||
<ActivationSteps
|
||||
:steps="allUpgradeSteps"
|
||||
:active-step-index="currentDynamicStepIndex"
|
||||
:on-step-click="goToStep"
|
||||
:on-step-click="isStepSaving ? undefined : goToStep"
|
||||
class="mt-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuery } from '@vue/apollo-composable';
|
||||
|
||||
import { BrandButton } from '@unraid/ui';
|
||||
|
||||
import { INSTALLED_UNRAID_PLUGINS_QUERY } from '~/components/Activation/graphql/installedPlugins.query';
|
||||
import usePluginInstaller from '~/components/Activation/usePluginInstaller';
|
||||
import { PluginInstallStatus } from '~/composables/gql/graphql';
|
||||
|
||||
@@ -14,6 +16,7 @@ export interface Props {
|
||||
showSkip?: boolean;
|
||||
showBack?: boolean;
|
||||
isRequired?: boolean;
|
||||
isSavingStep?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@@ -26,6 +29,13 @@ interface Plugin {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const normalizePluginFileName = (value: string) => value.trim().toLowerCase();
|
||||
|
||||
const getPluginFileName = (url: string) => {
|
||||
const parts = url.split('/');
|
||||
return parts[parts.length - 1] ?? url;
|
||||
};
|
||||
|
||||
const availablePlugins: Plugin[] = [
|
||||
{
|
||||
id: 'community-apps',
|
||||
@@ -67,14 +77,71 @@ const pluginStates = reactive<Record<string, PluginState>>(
|
||||
);
|
||||
|
||||
const selectedPlugins = ref<Set<string>>(new Set());
|
||||
const installedPluginIds = ref<Set<string>>(new Set());
|
||||
const isInstalling = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const installationFinished = ref(false);
|
||||
|
||||
const { result: installedPluginsResult } = useQuery(INSTALLED_UNRAID_PLUGINS_QUERY, null, {
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
const { installPlugin } = usePluginInstaller();
|
||||
|
||||
const INSTALL_TIMEOUT_MS = 60000;
|
||||
|
||||
const installablePlugins = computed(() =>
|
||||
availablePlugins.filter(
|
||||
(plugin) => selectedPlugins.value.has(plugin.id) && !installedPluginIds.value.has(plugin.id)
|
||||
)
|
||||
);
|
||||
|
||||
const hasInstallableSelection = computed(() => installablePlugins.value.length > 0);
|
||||
|
||||
const isPluginInstalled = (pluginId: string) => installedPluginIds.value.has(pluginId);
|
||||
const isBusy = computed(() => isInstalling.value || (props.isSavingStep ?? false));
|
||||
|
||||
const applyInstalledPlugins = (installedPlugins: string[] | null | undefined) => {
|
||||
if (!Array.isArray(installedPlugins)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const installedFiles = new Set(installedPlugins.map((name) => normalizePluginFileName(name)));
|
||||
const nextInstalledIds = new Set<string>();
|
||||
|
||||
for (const plugin of availablePlugins) {
|
||||
const fileName = normalizePluginFileName(getPluginFileName(plugin.url));
|
||||
if (installedFiles.has(fileName)) {
|
||||
nextInstalledIds.add(plugin.id);
|
||||
}
|
||||
}
|
||||
|
||||
installedPluginIds.value = nextInstalledIds;
|
||||
|
||||
if (nextInstalledIds.size > 0) {
|
||||
const nextSelected = new Set(selectedPlugins.value);
|
||||
for (const id of nextInstalledIds) {
|
||||
nextSelected.add(id);
|
||||
}
|
||||
selectedPlugins.value = nextSelected;
|
||||
}
|
||||
|
||||
for (const id of nextInstalledIds) {
|
||||
const state = pluginStates[id];
|
||||
if (state && state.status !== 'installing') {
|
||||
state.status = 'success';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => installedPluginsResult.value?.installedUnraidPlugins,
|
||||
(installedPlugins) => {
|
||||
applyInstalledPlugins(installedPlugins);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -113,6 +180,10 @@ const resetCompletionState = () => {
|
||||
};
|
||||
|
||||
const togglePlugin = (pluginId: string) => {
|
||||
if (installedPluginIds.value.has(pluginId) || isBusy.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const next = new Set(selectedPlugins.value);
|
||||
if (next.has(pluginId)) {
|
||||
next.delete(pluginId);
|
||||
@@ -130,7 +201,8 @@ const togglePlugin = (pluginId: string) => {
|
||||
};
|
||||
|
||||
const handleInstall = async () => {
|
||||
if (selectedPlugins.value.size === 0) {
|
||||
const pluginsToInstall = installablePlugins.value;
|
||||
if (pluginsToInstall.length === 0) {
|
||||
installationFinished.value = true;
|
||||
return;
|
||||
}
|
||||
@@ -141,8 +213,6 @@ const handleInstall = async () => {
|
||||
let hadError = false;
|
||||
|
||||
try {
|
||||
const pluginsToInstall = availablePlugins.filter((p) => selectedPlugins.value.has(p.id));
|
||||
|
||||
for (const plugin of pluginsToInstall) {
|
||||
const state = pluginStates[plugin.id];
|
||||
state.status = 'installing';
|
||||
@@ -190,6 +260,9 @@ const handleInstall = async () => {
|
||||
t('activation.pluginsStep.pluginInstalledMessage', { name: plugin.name })
|
||||
);
|
||||
state.status = 'success';
|
||||
const nextInstalled = new Set(installedPluginIds.value);
|
||||
nextInstalled.add(plugin.id);
|
||||
installedPluginIds.value = nextInstalled;
|
||||
}
|
||||
|
||||
installationFinished.value = pluginsToInstall.every((plugin) =>
|
||||
@@ -216,12 +289,12 @@ const handleBack = () => {
|
||||
};
|
||||
|
||||
const handlePrimaryAction = async () => {
|
||||
if (installationFinished.value || selectedPlugins.value.size === 0) {
|
||||
if (installationFinished.value || !hasInstallableSelection.value) {
|
||||
props.onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isInstalling.value) {
|
||||
if (!isBusy.value) {
|
||||
await handleInstall();
|
||||
}
|
||||
};
|
||||
@@ -230,14 +303,14 @@ const primaryButtonText = computed(() => {
|
||||
if (installationFinished.value) {
|
||||
return t('common.continue');
|
||||
}
|
||||
if (selectedPlugins.value.size > 0) {
|
||||
if (hasInstallableSelection.value) {
|
||||
return t('activation.pluginsStep.installSelected');
|
||||
}
|
||||
return t('common.continue');
|
||||
});
|
||||
|
||||
const isPrimaryActionDisabled = computed(() => {
|
||||
if (isInstalling.value) {
|
||||
if (isBusy.value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -279,7 +352,7 @@ const isPrimaryActionDisabled = computed(() => {
|
||||
:id="plugin.id"
|
||||
type="checkbox"
|
||||
:checked="selectedPlugins.has(plugin.id)"
|
||||
:disabled="isInstalling"
|
||||
:disabled="isBusy || isPluginInstalled(plugin.id)"
|
||||
@change="() => togglePlugin(plugin.id)"
|
||||
class="text-primary focus:ring-primary h-5 w-5 cursor-pointer rounded border-gray-300 focus:ring-2"
|
||||
/>
|
||||
@@ -329,7 +402,7 @@ const isPrimaryActionDisabled = computed(() => {
|
||||
v-if="onBack && showBack"
|
||||
:text="t('common.back')"
|
||||
variant="outline"
|
||||
:disabled="isInstalling"
|
||||
:disabled="isBusy"
|
||||
@click="handleBack"
|
||||
/>
|
||||
<div class="flex-1" />
|
||||
@@ -337,13 +410,13 @@ const isPrimaryActionDisabled = computed(() => {
|
||||
v-if="onSkip && showSkip"
|
||||
:text="t('common.skip')"
|
||||
variant="outline"
|
||||
:disabled="isInstalling"
|
||||
:disabled="isBusy"
|
||||
@click="handleSkip"
|
||||
/>
|
||||
<BrandButton
|
||||
:text="primaryButtonText"
|
||||
:disabled="isPrimaryActionDisabled"
|
||||
:loading="isInstalling"
|
||||
:loading="isBusy"
|
||||
@click="handlePrimaryAction"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface Props {
|
||||
onBack?: () => void;
|
||||
showSkip?: boolean;
|
||||
showBack?: boolean;
|
||||
isSavingStep?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@@ -135,6 +136,8 @@ const handleSkip = () => {
|
||||
const handleBack = () => {
|
||||
props.onBack?.();
|
||||
};
|
||||
|
||||
const isBusy = computed(() => isSaving.value || (props.isSavingStep ?? false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -162,7 +165,7 @@ const handleBack = () => {
|
||||
v-if="onBack && showBack"
|
||||
:text="t('common.back')"
|
||||
variant="outline"
|
||||
:disabled="isSaving"
|
||||
:disabled="isBusy"
|
||||
@click="handleBack"
|
||||
/>
|
||||
<div class="flex-1" />
|
||||
@@ -170,13 +173,13 @@ const handleBack = () => {
|
||||
v-if="onSkip && showSkip"
|
||||
:text="t('common.skip')"
|
||||
variant="outline"
|
||||
:disabled="isSaving"
|
||||
:disabled="isBusy"
|
||||
@click="handleSkip"
|
||||
/>
|
||||
<BrandButton
|
||||
:text="t('common.continue')"
|
||||
:disabled="!selectedTimeZone || isSaving"
|
||||
:loading="isSaving"
|
||||
:disabled="!selectedTimeZone || isBusy"
|
||||
:loading="isBusy"
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface Props {
|
||||
showBack?: boolean;
|
||||
// For redirecting to login page after welcome
|
||||
redirectToLogin?: boolean;
|
||||
isSavingStep?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@@ -59,6 +60,8 @@ const buttonText = computed<string>(() => {
|
||||
return t('activation.welcomeModal.getStarted');
|
||||
});
|
||||
|
||||
const isBusy = computed(() => props.isSavingStep ?? false);
|
||||
|
||||
const handleComplete = () => {
|
||||
if (props.redirectToLogin) {
|
||||
// Redirect to login page for password creation
|
||||
@@ -78,11 +81,23 @@ const handleComplete = () => {
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-4">
|
||||
<BrandButton v-if="showBack" :text="t('common.back')" variant="outline" @click="onBack" />
|
||||
<BrandButton
|
||||
v-if="showBack"
|
||||
:text="t('common.back')"
|
||||
variant="outline"
|
||||
:disabled="isBusy"
|
||||
@click="onBack"
|
||||
/>
|
||||
|
||||
<BrandButton v-if="showSkip" :text="t('common.skip')" variant="outline" @click="onSkip" />
|
||||
<BrandButton
|
||||
v-if="showSkip"
|
||||
:text="t('common.skip')"
|
||||
variant="outline"
|
||||
:disabled="isBusy"
|
||||
@click="onSkip"
|
||||
/>
|
||||
|
||||
<BrandButton :text="buttonText" @click="handleComplete" />
|
||||
<BrandButton :text="buttonText" :disabled="isBusy" :loading="isBusy" @click="handleComplete" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { graphql } from '@/composables/gql/gql';
|
||||
|
||||
export const INSTALLED_UNRAID_PLUGINS_QUERY = graphql(/* GraphQL */ `
|
||||
query InstalledUnraidPlugins {
|
||||
installedUnraidPlugins
|
||||
}
|
||||
`);
|
||||
@@ -20,6 +20,7 @@ type Documents = {
|
||||
"\n query PublicWelcomeData {\n publicPartnerInfo {\n hasPartnerLogo\n partnerName\n partnerUrl\n partnerLogoUrl\n }\n isInitialSetup\n }\n": typeof types.PublicWelcomeDataDocument,
|
||||
"\n query ActivationCode {\n customization {\n activationCode {\n code\n partnerName\n serverName\n sysModel\n comment\n header\n headermetacolor\n background\n showBannerGradient\n theme\n }\n partnerInfo {\n hasPartnerLogo\n partnerName\n partnerUrl\n partnerLogoUrl\n }\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n isInitialSetup\n hasActivationCode\n activationRequired\n }\n }\n }\n": typeof types.ActivationCodeDocument,
|
||||
"\n mutation InstallPlugin($input: InstallPluginInput!) {\n unraidPlugins {\n installPlugin(input: $input) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n }\n": typeof types.InstallPluginDocument,
|
||||
"\n query InstalledUnraidPlugins {\n installedUnraidPlugins\n }\n": typeof types.InstalledUnraidPluginsDocument,
|
||||
"\n query PluginInstallOperation($operationId: ID!) {\n pluginInstallOperation(operationId: $operationId) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n": typeof types.PluginInstallOperationDocument,
|
||||
"\n subscription PluginInstallUpdates($operationId: ID!) {\n pluginInstallUpdates(operationId: $operationId) {\n operationId\n status\n output\n timestamp\n }\n }\n": typeof types.PluginInstallUpdatesDocument,
|
||||
"\n query TimeZoneOptions {\n timeZoneOptions {\n value\n label\n }\n }\n": typeof types.TimeZoneOptionsDocument,
|
||||
@@ -97,6 +98,7 @@ const documents: Documents = {
|
||||
"\n query PublicWelcomeData {\n publicPartnerInfo {\n hasPartnerLogo\n partnerName\n partnerUrl\n partnerLogoUrl\n }\n isInitialSetup\n }\n": types.PublicWelcomeDataDocument,
|
||||
"\n query ActivationCode {\n customization {\n activationCode {\n code\n partnerName\n serverName\n sysModel\n comment\n header\n headermetacolor\n background\n showBannerGradient\n theme\n }\n partnerInfo {\n hasPartnerLogo\n partnerName\n partnerUrl\n partnerLogoUrl\n }\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n isInitialSetup\n hasActivationCode\n activationRequired\n }\n }\n }\n": types.ActivationCodeDocument,
|
||||
"\n mutation InstallPlugin($input: InstallPluginInput!) {\n unraidPlugins {\n installPlugin(input: $input) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n }\n": types.InstallPluginDocument,
|
||||
"\n query InstalledUnraidPlugins {\n installedUnraidPlugins\n }\n": types.InstalledUnraidPluginsDocument,
|
||||
"\n query PluginInstallOperation($operationId: ID!) {\n pluginInstallOperation(operationId: $operationId) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n": types.PluginInstallOperationDocument,
|
||||
"\n subscription PluginInstallUpdates($operationId: ID!) {\n pluginInstallUpdates(operationId: $operationId) {\n operationId\n status\n output\n timestamp\n }\n }\n": types.PluginInstallUpdatesDocument,
|
||||
"\n query TimeZoneOptions {\n timeZoneOptions {\n value\n label\n }\n }\n": types.TimeZoneOptionsDocument,
|
||||
@@ -206,6 +208,10 @@ export function graphql(source: "\n query ActivationCode {\n customization {
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation InstallPlugin($input: InstallPluginInput!) {\n unraidPlugins {\n installPlugin(input: $input) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n }\n"): (typeof documents)["\n mutation InstallPlugin($input: InstallPluginInput!) {\n unraidPlugins {\n installPlugin(input: $input) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query InstalledUnraidPlugins {\n installedUnraidPlugins\n }\n"): (typeof documents)["\n query InstalledUnraidPlugins {\n installedUnraidPlugins\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -2116,6 +2116,8 @@ export type Query = {
|
||||
/** Get the actual permissions that would be granted by a set of roles */
|
||||
getPermissionsForRoles: Array<Permission>;
|
||||
info: Info;
|
||||
/** List installed Unraid OS plugins by .plg filename */
|
||||
installedUnraidPlugins: Array<Scalars['String']['output']>;
|
||||
isInitialSetup: Scalars['Boolean']['output'];
|
||||
isSSOEnabled: Scalars['Boolean']['output'];
|
||||
logFile: LogFileContent;
|
||||
@@ -3185,6 +3187,11 @@ export type InstallPluginMutationVariables = Exact<{
|
||||
|
||||
export type InstallPluginMutation = { __typename?: 'Mutation', unraidPlugins: { __typename?: 'UnraidPluginsMutations', installPlugin: { __typename?: 'PluginInstallOperation', id: string, url: string, name?: string | null, status: PluginInstallStatus, createdAt: string, updatedAt?: string | null, finishedAt?: string | null, output: Array<string> } } };
|
||||
|
||||
export type InstalledUnraidPluginsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type InstalledUnraidPluginsQuery = { __typename?: 'Query', installedUnraidPlugins: Array<string> };
|
||||
|
||||
export type PluginInstallOperationQueryVariables = Exact<{
|
||||
operationId: Scalars['ID']['input'];
|
||||
}>;
|
||||
@@ -3662,6 +3669,7 @@ export const PartnerInfoDocument = {"kind":"Document","definitions":[{"kind":"Op
|
||||
export const PublicWelcomeDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PublicWelcomeData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publicPartnerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasPartnerLogo"}},{"kind":"Field","name":{"kind":"Name","value":"partnerName"}},{"kind":"Field","name":{"kind":"Name","value":"partnerUrl"}},{"kind":"Field","name":{"kind":"Name","value":"partnerLogoUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"isInitialSetup"}}]}}]} as unknown as DocumentNode<PublicWelcomeDataQuery, PublicWelcomeDataQueryVariables>;
|
||||
export const ActivationCodeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActivationCode"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customization"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activationCode"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"partnerName"}},{"kind":"Field","name":{"kind":"Name","value":"serverName"}},{"kind":"Field","name":{"kind":"Name","value":"sysModel"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"header"}},{"kind":"Field","name":{"kind":"Name","value":"headermetacolor"}},{"kind":"Field","name":{"kind":"Name","value":"background"}},{"kind":"Field","name":{"kind":"Name","value":"showBannerGradient"}},{"kind":"Field","name":{"kind":"Name","value":"theme"}}]}},{"kind":"Field","name":{"kind":"Name","value":"partnerInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasPartnerLogo"}},{"kind":"Field","name":{"kind":"Name","value":"partnerName"}},{"kind":"Field","name":{"kind":"Name","value":"partnerUrl"}},{"kind":"Field","name":{"kind":"Name","value":"partnerLogoUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"onboardingState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"registrationState"}},{"kind":"Field","name":{"kind":"Name","value":"isRegistered"}},{"kind":"Field","name":{"kind":"Name","value":"isFreshInstall"}},{"kind":"Field","name":{"kind":"Name","value":"isInitialSetup"}},{"kind":"Field","name":{"kind":"Name","value":"hasActivationCode"}},{"kind":"Field","name":{"kind":"Name","value":"activationRequired"}}]}}]}}]}}]} as unknown as DocumentNode<ActivationCodeQuery, ActivationCodeQueryVariables>;
|
||||
export const InstallPluginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InstallPlugin"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InstallPluginInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unraidPlugins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"installPlugin"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"output"}}]}}]}}]}}]} as unknown as DocumentNode<InstallPluginMutation, InstallPluginMutationVariables>;
|
||||
export const InstalledUnraidPluginsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"InstalledUnraidPlugins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"installedUnraidPlugins"}}]}}]} as unknown as DocumentNode<InstalledUnraidPluginsQuery, InstalledUnraidPluginsQueryVariables>;
|
||||
export const PluginInstallOperationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PluginInstallOperation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"operationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pluginInstallOperation"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"operationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"operationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"output"}}]}}]}}]} as unknown as DocumentNode<PluginInstallOperationQuery, PluginInstallOperationQueryVariables>;
|
||||
export const PluginInstallUpdatesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"PluginInstallUpdates"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"operationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pluginInstallUpdates"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"operationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"operationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"operationId"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"output"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}}]}}]}}]} as unknown as DocumentNode<PluginInstallUpdatesSubscription, PluginInstallUpdatesSubscriptionVariables>;
|
||||
export const TimeZoneOptionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"TimeZoneOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timeZoneOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}}]}}]} as unknown as DocumentNode<TimeZoneOptionsQuery, TimeZoneOptionsQueryVariables>;
|
||||
|
||||
Reference in New Issue
Block a user