mirror of
https://github.com/unraid/api.git
synced 2026-01-06 00:30:22 -06:00
Compare commits
8 Commits
4.28.2-bui
...
renovate/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85b947dc40 | ||
|
|
9ef1cf1eca | ||
|
|
a0745e15ca | ||
|
|
c39b0b267c | ||
|
|
73135b8328 | ||
|
|
e42d619b6d | ||
|
|
560db880cc | ||
|
|
d6055f102b |
@@ -1 +1 @@
|
||||
{".":"4.28.2"}
|
||||
{".":"4.29.2"}
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## [4.29.2](https://github.com/unraid/api/compare/v4.29.1...v4.29.2) (2025-12-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* unraid-connect plugin not loaded when connect is installed ([#1856](https://github.com/unraid/api/issues/1856)) ([73135b8](https://github.com/unraid/api/commit/73135b832801f5c76d60020161492e4770958c3d))
|
||||
|
||||
## [4.29.1](https://github.com/unraid/api/compare/v4.29.0...v4.29.1) (2025-12-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* revert replace docker overview table with web component (7.3+) ([#1853](https://github.com/unraid/api/issues/1853)) ([560db88](https://github.com/unraid/api/commit/560db880cc138324f9ff8753f7209b683a84c045))
|
||||
|
||||
## [4.29.0](https://github.com/unraid/api/compare/v4.28.2...v4.29.0) (2025-12-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* replace docker overview table with web component (7.3+) ([#1764](https://github.com/unraid/api/issues/1764)) ([277ac42](https://github.com/unraid/api/commit/277ac420464379e7ee6739c4530271caf7717503))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle race condition between guid loading and license check ([#1847](https://github.com/unraid/api/issues/1847)) ([8b155d1](https://github.com/unraid/api/commit/8b155d1f1c99bb19efbc9614e000d852e9f0c12d))
|
||||
* resolve issue with "Continue" button when updating ([#1852](https://github.com/unraid/api/issues/1852)) ([d099e75](https://github.com/unraid/api/commit/d099e7521d2062bb9cf84f340e46b169dd2492c5))
|
||||
* update myservers config references to connect config references ([#1810](https://github.com/unraid/api/issues/1810)) ([e1e3ea7](https://github.com/unraid/api/commit/e1e3ea7eb68cc6840f67a8aec937fd3740e75b28))
|
||||
|
||||
## [4.28.2](https://github.com/unraid/api/compare/v4.28.1...v4.28.2) (2025-12-16)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "4.28.2",
|
||||
"version": "4.29.2",
|
||||
"extraOrigins": [],
|
||||
"sandbox": true,
|
||||
"ssoSubIds": [],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/api",
|
||||
"version": "4.28.2",
|
||||
"version": "4.29.2",
|
||||
"main": "src/cli/index.ts",
|
||||
"type": "module",
|
||||
"corepack": {
|
||||
@@ -53,8 +53,8 @@
|
||||
"unraid-api": "dist/cli.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "3.14.0",
|
||||
"@apollo/server": "4.12.2",
|
||||
"@apollo/client": "4.0.11",
|
||||
"@apollo/server": "5.2.0",
|
||||
"@as-integrations/fastify": "2.1.1",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
"@fastify/helmet": "13.0.1",
|
||||
|
||||
@@ -2,10 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
AutoStartEntry,
|
||||
DockerAutostartService,
|
||||
} from '@app/unraid-api/graph/resolvers/docker/docker-autostart.service.js';
|
||||
import { DockerAutostartService } from '@app/unraid-api/graph/resolvers/docker/docker-autostart.service.js';
|
||||
import { DockerContainer } from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
|
||||
|
||||
// Mock store getters
|
||||
|
||||
@@ -4,7 +4,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { AppError } from '@app/core/errors/app-error.js';
|
||||
import { DockerLogService } from '@app/unraid-api/graph/resolvers/docker/docker-log.service.js';
|
||||
import { DockerContainerLogs } from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
|
||||
|
||||
// Mock dependencies
|
||||
const mockExeca = vi.fn();
|
||||
|
||||
@@ -91,7 +91,6 @@ export class DockerTailscaleService {
|
||||
);
|
||||
|
||||
const dnsName = rawStatus.Self.DNSName;
|
||||
const actualHostname = dnsName ? dnsName.split('.')[0] : undefined;
|
||||
|
||||
let relayName: string | undefined;
|
||||
if (rawStatus.Self.Relay && derpMap) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
Field,
|
||||
Float,
|
||||
GraphQLISODateTime,
|
||||
ID,
|
||||
InputType,
|
||||
Int,
|
||||
ObjectType,
|
||||
|
||||
@@ -13,11 +13,7 @@ import { DockerLogService } from '@app/unraid-api/graph/resolvers/docker/docker-
|
||||
import { DockerManifestService } from '@app/unraid-api/graph/resolvers/docker/docker-manifest.service.js';
|
||||
import { DockerNetworkService } from '@app/unraid-api/graph/resolvers/docker/docker-network.service.js';
|
||||
import { DockerPortService } from '@app/unraid-api/graph/resolvers/docker/docker-port.service.js';
|
||||
import {
|
||||
ContainerPortType,
|
||||
ContainerState,
|
||||
DockerContainer,
|
||||
} from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
|
||||
import { ContainerState, DockerContainer } from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
|
||||
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
|
||||
import { NotificationsService } from '@app/unraid-api/graph/resolvers/notifications/notifications.service.js';
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ export class DockerService {
|
||||
await this.cacheManager.del(DockerService.CONTAINER_CACHE_KEY);
|
||||
this.logger.debug(`Invalidated container cache after pausing ${id}`);
|
||||
|
||||
let containers = await this.getContainers({ skipCache: true });
|
||||
let containers: DockerContainer[];
|
||||
let updatedContainer: DockerContainer | undefined;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await sleep(500);
|
||||
@@ -349,7 +349,7 @@ export class DockerService {
|
||||
await this.cacheManager.del(DockerService.CONTAINER_CACHE_KEY);
|
||||
this.logger.debug(`Invalidated container cache after unpausing ${id}`);
|
||||
|
||||
let containers = await this.getContainers({ skipCache: true });
|
||||
let containers: DockerContainer[];
|
||||
let updatedContainer: DockerContainer | undefined;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await sleep(500);
|
||||
|
||||
@@ -683,7 +683,7 @@ export interface MoveItemsToPositionParams {
|
||||
* Combines moveEntriesToFolder with position-based insertion.
|
||||
*/
|
||||
export function moveItemsToPosition(params: MoveItemsToPositionParams): OrganizerView {
|
||||
const { view, sourceEntryIds, destinationFolderId, position, resources } = params;
|
||||
const { view, sourceEntryIds, destinationFolderId, position } = params;
|
||||
|
||||
const movedView = moveEntriesToFolder({ view, sourceEntryIds, destinationFolderId });
|
||||
|
||||
@@ -743,7 +743,7 @@ export interface CreateFolderWithItemsParams {
|
||||
* Combines createFolder + moveItems + positioning in a single atomic operation.
|
||||
*/
|
||||
export function createFolderWithItems(params: CreateFolderWithItemsParams): OrganizerView {
|
||||
const { view, folderId, folderName, parentId, sourceEntryIds = [], position, resources } = params;
|
||||
const { view, folderId, folderName, parentId, sourceEntryIds = [], position } = params;
|
||||
|
||||
let newView = createFolderInView({
|
||||
view,
|
||||
|
||||
@@ -91,13 +91,9 @@ export class PluginService {
|
||||
return name;
|
||||
})
|
||||
);
|
||||
const { peerDependencies } = getPackageJson();
|
||||
// All api plugins must be installed as peer dependencies of the unraid-api package
|
||||
if (!peerDependencies) {
|
||||
PluginService.logger.warn('Unraid-API peer dependencies not found; skipping plugins.');
|
||||
return [];
|
||||
}
|
||||
const pluginTuples = Object.entries(peerDependencies).filter(
|
||||
const { peerDependencies = {}, dependencies = {} } = getPackageJson();
|
||||
const allDependencies = { ...peerDependencies, ...dependencies };
|
||||
const pluginTuples = Object.entries(allDependencies).filter(
|
||||
(entry): entry is [string, string] => {
|
||||
const [pkgName, version] = entry;
|
||||
return pluginNames.has(pkgName) && typeof version === 'string';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "unraid-monorepo",
|
||||
"private": true,
|
||||
"version": "4.28.2",
|
||||
"version": "4.29.2",
|
||||
"scripts": {
|
||||
"build": "pnpm -r build",
|
||||
"build:watch": "pnpm -r --parallel --filter '!@unraid/ui' build:watch",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"license": "GPL-2.0-or-later",
|
||||
"description": "Unraid Connect plugin for Unraid API",
|
||||
"devDependencies": {
|
||||
"@apollo/client": "3.14.0",
|
||||
"@apollo/client": "4.0.11",
|
||||
"@faker-js/faker": "10.0.0",
|
||||
"@graphql-codegen/cli": "6.0.0",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
@@ -73,7 +73,7 @@
|
||||
"node-cache": "5.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apollo/client": "3.14.0",
|
||||
"@apollo/client": "4.0.11",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@jsonforms/core": "3.6.0",
|
||||
"@nestjs/apollo": "13.1.0",
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"ws": "8.18.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apollo/client": "3.14.0",
|
||||
"@apollo/client": "4.0.11",
|
||||
"@graphql-tools/utils": "10.9.1",
|
||||
"@jsonforms/core": "3.6.0",
|
||||
"@nestjs/common": "11.1.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/connect-plugin",
|
||||
"version": "4.28.2",
|
||||
"version": "4.29.2",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"commander": "14.0.0",
|
||||
|
||||
675
pnpm-lock.yaml
generated
675
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/ui",
|
||||
"version": "4.28.2",
|
||||
"version": "4.29.2",
|
||||
"private": true,
|
||||
"license": "GPL-2.0-or-later",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/web",
|
||||
"version": "4.28.2",
|
||||
"version": "4.29.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "GPL-2.0-or-later",
|
||||
@@ -98,7 +98,7 @@
|
||||
"vue-tsc": "3.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "3.14.0",
|
||||
"@apollo/client": "4.0.11",
|
||||
"@floating-ui/dom": "1.7.4",
|
||||
"@floating-ui/utils": "0.2.10",
|
||||
"@floating-ui/vue": "1.1.9",
|
||||
|
||||
@@ -299,9 +299,7 @@ function createCellWrapper(
|
||||
});
|
||||
}
|
||||
|
||||
function wrapColumnHeaderRenderer(
|
||||
header: ColumnHeaderRenderer | undefined
|
||||
): ColumnHeaderRenderer | undefined {
|
||||
function wrapColumnHeaderRenderer(header: ColumnHeaderRenderer | undefined): ColumnHeaderRenderer {
|
||||
if (typeof header === 'function') {
|
||||
return function wrappedHeaderRenderer(this: unknown, ...args: unknown[]) {
|
||||
const result = (header as (...args: unknown[]) => unknown).apply(this, args);
|
||||
@@ -481,7 +479,7 @@ const processedColumns = computed<TableColumn<TreeRow<T>>[]>(() => {
|
||||
createSelectColumn(),
|
||||
...props.columns.map((col, colIndex) => {
|
||||
const originalHeader = col.header as ColumnHeaderRenderer | undefined;
|
||||
const header = wrapColumnHeaderRenderer(originalHeader) ?? originalHeader;
|
||||
const header = wrapColumnHeaderRenderer(originalHeader);
|
||||
const cell = (col as { cell?: unknown }).cell
|
||||
? ({ row }: { row: TableInstanceRow<T> }) => {
|
||||
const cellFn = (col as { cell: (args: unknown) => VNode | string | number }).cell;
|
||||
|
||||
@@ -68,6 +68,10 @@ function appendLogLines(newLines: Array<{ timestamp: string; message: string }>)
|
||||
|
||||
state.lines = [...state.lines, ...added];
|
||||
if (state.lines.length > MAX_LOG_LINES) {
|
||||
const removed = state.lines.slice(0, state.lines.length - MAX_LOG_LINES);
|
||||
for (const line of removed) {
|
||||
state.lineKeys.delete(`${line.timestamp}|${line.message}`);
|
||||
}
|
||||
state.lines = state.lines.slice(state.lines.length - MAX_LOG_LINES);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ const refreshLogContent = async () => {
|
||||
startLogSubscription();
|
||||
};
|
||||
|
||||
watch(() => props.logFilePath, refreshLogContent);
|
||||
watch(() => props.logFilePath, refreshLogContent, { immediate: true });
|
||||
defineExpose({ refreshLogContent });
|
||||
</script>
|
||||
|
||||
|
||||
@@ -142,14 +142,16 @@ const dismissNotification = async (notification: NotificationFragmentFragment) =
|
||||
const { onResult: onNotificationAdded } = useSubscription(notificationAddedSubscription);
|
||||
|
||||
onNotificationAdded(({ data }) => {
|
||||
if (!data) {
|
||||
if (!data?.notificationAdded) {
|
||||
return;
|
||||
}
|
||||
const notification = useFragment(NOTIFICATION_FRAGMENT, data.notificationAdded);
|
||||
|
||||
// Access raw subscription data directly - don't call useFragment in async callback
|
||||
const rawNotification = data.notificationAdded as unknown as NotificationFragmentFragment;
|
||||
if (
|
||||
!notification ||
|
||||
(notification.importance !== NotificationImportance.ALERT &&
|
||||
notification.importance !== NotificationImportance.WARNING)
|
||||
!rawNotification ||
|
||||
(rawNotification.importance !== NotificationImportance.ALERT &&
|
||||
rawNotification.importance !== NotificationImportance.WARNING)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -160,7 +162,7 @@ onNotificationAdded(({ data }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (notification.timestamp) {
|
||||
if (rawNotification.timestamp) {
|
||||
// Trigger the global toast in tandem with the subscription update.
|
||||
const funcMapping: Record<
|
||||
NotificationImportance,
|
||||
@@ -170,16 +172,16 @@ onNotificationAdded(({ data }) => {
|
||||
[NotificationImportance.WARNING]: globalThis.toast.warning,
|
||||
[NotificationImportance.INFO]: globalThis.toast.info,
|
||||
};
|
||||
const toast = funcMapping[notification.importance];
|
||||
const toast = funcMapping[rawNotification.importance];
|
||||
const createOpener = () => ({
|
||||
label: 'Open',
|
||||
onClick: () => notification.link && window.open(notification.link, '_blank', 'noopener'),
|
||||
onClick: () => rawNotification.link && window.open(rawNotification.link, '_blank', 'noopener'),
|
||||
});
|
||||
|
||||
requestAnimationFrame(() =>
|
||||
toast(notification.title, {
|
||||
description: notification.subject,
|
||||
action: notification.link ? createOpener() : undefined,
|
||||
toast(rawNotification.title, {
|
||||
description: rawNotification.subject,
|
||||
action: rawNotification.link ? createOpener() : undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ function ensurePortalRoot(): string | undefined {
|
||||
}
|
||||
|
||||
ensureUnapiScope(portalRoot);
|
||||
if (isDarkModeActive()) {
|
||||
portalRoot.classList.add('dark');
|
||||
}
|
||||
|
||||
return `#${PORTAL_ROOT_ID}`;
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@ export function useContainerActions<T = unknown>(options: ContainerActionOptions
|
||||
const confirmStartStopOpen = ref(false);
|
||||
const confirmToStart = ref<{ name: string }[]>([]);
|
||||
const confirmToStop = ref<{ name: string }[]>([]);
|
||||
let pendingStartStopIds: string[] = [];
|
||||
const pendingStartStopIds = ref<string[]>([]);
|
||||
|
||||
const confirmPauseResumeOpen = ref(false);
|
||||
const confirmToPause = ref<{ name: string }[]>([]);
|
||||
const confirmToResume = ref<{ name: string }[]>([]);
|
||||
let pendingPauseResumeIds: string[] = [];
|
||||
const pendingPauseResumeIds = ref<string[]>([]);
|
||||
|
||||
function classifyStartStop(ids: string[]) {
|
||||
const toStart: { id: string; containerId: string; name: string }[] = [];
|
||||
@@ -201,7 +201,7 @@ export function useContainerActions<T = unknown>(options: ContainerActionOptions
|
||||
const { toStart, toStop } = classifyStartStop(ids);
|
||||
const isMixed = toStart.length > 0 && toStop.length > 0;
|
||||
if (isMixed) {
|
||||
pendingStartStopIds = ids;
|
||||
pendingStartStopIds.value = ids;
|
||||
confirmToStart.value = toStart.map((i) => ({ name: i.name }));
|
||||
confirmToStop.value = toStop.map((i) => ({ name: i.name }));
|
||||
confirmStartStopOpen.value = true;
|
||||
@@ -216,15 +216,15 @@ export function useContainerActions<T = unknown>(options: ContainerActionOptions
|
||||
}
|
||||
|
||||
async function confirmStartStop(close: () => void) {
|
||||
const { toStart, toStop } = classifyStartStop(pendingStartStopIds);
|
||||
setRowsBusy(pendingStartStopIds, true);
|
||||
const { toStart, toStop } = classifyStartStop(pendingStartStopIds.value);
|
||||
setRowsBusy(pendingStartStopIds.value, true);
|
||||
try {
|
||||
await runStartStopBatch(toStart, toStop);
|
||||
onSuccess?.('Action completed');
|
||||
} finally {
|
||||
setRowsBusy(pendingStartStopIds, false);
|
||||
setRowsBusy(pendingStartStopIds.value, false);
|
||||
confirmStartStopOpen.value = false;
|
||||
pendingStartStopIds = [];
|
||||
pendingStartStopIds.value = [];
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -234,7 +234,7 @@ export function useContainerActions<T = unknown>(options: ContainerActionOptions
|
||||
const { toPause, toResume } = classifyPauseResume(ids);
|
||||
const isMixed = toPause.length > 0 && toResume.length > 0;
|
||||
if (isMixed) {
|
||||
pendingPauseResumeIds = ids;
|
||||
pendingPauseResumeIds.value = ids;
|
||||
confirmToPause.value = toPause.map((i) => ({ name: i.name }));
|
||||
confirmToResume.value = toResume.map((i) => ({ name: i.name }));
|
||||
confirmPauseResumeOpen.value = true;
|
||||
@@ -249,15 +249,15 @@ export function useContainerActions<T = unknown>(options: ContainerActionOptions
|
||||
}
|
||||
|
||||
async function confirmPauseResume(close: () => void) {
|
||||
const { toPause, toResume } = classifyPauseResume(pendingPauseResumeIds);
|
||||
setRowsBusy(pendingPauseResumeIds, true);
|
||||
const { toPause, toResume } = classifyPauseResume(pendingPauseResumeIds.value);
|
||||
setRowsBusy(pendingPauseResumeIds.value, true);
|
||||
try {
|
||||
await runPauseResumeBatch(toPause, toResume);
|
||||
onSuccess?.('Action completed');
|
||||
} finally {
|
||||
setRowsBusy(pendingPauseResumeIds, false);
|
||||
setRowsBusy(pendingPauseResumeIds.value, false);
|
||||
confirmPauseResumeOpen.value = false;
|
||||
pendingPauseResumeIds = [];
|
||||
pendingPauseResumeIds.value = [];
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user