Publish curated Unraid-AppOps-Gateway source from ad0a390e431166c3198a2ac3bec010c2b44f2dea
This commit is contained in:
1 parent
29c1e3d5a1
commit
98d31477f9
35 files changed
+7264
-185
No files matched your search
@@ -0,0 +1,277 @@
|
||||
import type {
|
||||
PreflightPreview,
|
||||
EnrollmentStorageRootSelection,
|
||||
ManagedStorageStatus,
|
||||
TrustedAppdataRoot,
|
||||
TrustedAppdataRootReadinessStatus,
|
||||
} from '@/types/appops';
|
||||
|
||||
export const LEGACY_APPDATA_ROOT: TrustedAppdataRoot = '/srv/example/appdata';
|
||||
export const MANAGED_APPDATA_ROOT: TrustedAppdataRoot = '/var/lib/docker/volumes/itworx-managed-appdata/_data';
|
||||
export const MANAGED_STORAGE_INITIALIZE_ACTION = 'controller.managed-storage.initialize';
|
||||
export const MANAGED_STORAGE_TARGET = 'tower/deployment-controller';
|
||||
|
||||
export interface EnrollmentStorageView {
|
||||
configured: boolean;
|
||||
trustStatus: 'ready' | 'blocked' | 'needs_setup' | 'unknown';
|
||||
currentRoot: TrustedAppdataRoot | null;
|
||||
backupAcknowledged: boolean;
|
||||
rootReadiness: Record<TrustedAppdataRoot, TrustedAppdataRootReadinessStatus>;
|
||||
managedStatus: ManagedStorageStatus;
|
||||
managedBlocker: string | null;
|
||||
managedCapabilityValid: boolean;
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> => (
|
||||
typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
);
|
||||
|
||||
const isRoot = (value: unknown): value is TrustedAppdataRoot => (
|
||||
value === LEGACY_APPDATA_ROOT || value === MANAGED_APPDATA_ROOT
|
||||
);
|
||||
|
||||
const isRootReadiness = (value: unknown): value is TrustedAppdataRootReadinessStatus => (
|
||||
value === 'ready' || value === 'needs_initialization' || value === 'unavailable'
|
||||
);
|
||||
|
||||
const isManagedStatus = (value: unknown): value is ManagedStorageStatus => (
|
||||
value === 'not_selected' || value === 'blocked' || value === 'ready' || value === 'initialization_required' || value === 'unavailable'
|
||||
);
|
||||
|
||||
export const buildEnrollmentStorageView = (trust: unknown, capabilities: unknown): EnrollmentStorageView => {
|
||||
const safeTrust = isRecord(trust) ? trust : {};
|
||||
const safeCapabilities = isRecord(capabilities) ? capabilities : {};
|
||||
const trustedRoot = isRoot(safeTrust.trustedAppdataRoot) ? safeTrust.trustedAppdataRoot : null;
|
||||
const managed = isRecord(safeCapabilities.managedStorage) ? safeCapabilities.managedStorage : null;
|
||||
const reportedRoots = safeCapabilities.availableTrustedAppdataRoots;
|
||||
let rootReadiness: Record<TrustedAppdataRoot, TrustedAppdataRootReadinessStatus> = {
|
||||
[LEGACY_APPDATA_ROOT]: 'unavailable',
|
||||
[MANAGED_APPDATA_ROOT]: 'unavailable',
|
||||
};
|
||||
let rootsValid = false;
|
||||
|
||||
if (Array.isArray(reportedRoots) && reportedRoots.length === 2) {
|
||||
const entries = new Map<TrustedAppdataRoot, TrustedAppdataRootReadinessStatus>();
|
||||
const valid = reportedRoots.every((entry) => {
|
||||
if (!isRecord(entry) || !isRoot(entry.root) || !isRootReadiness(entry.status) || entries.has(entry.root)) return false;
|
||||
entries.set(entry.root, entry.status);
|
||||
return true;
|
||||
});
|
||||
if (valid && entries.has(LEGACY_APPDATA_ROOT) && entries.has(MANAGED_APPDATA_ROOT)) {
|
||||
rootsValid = true;
|
||||
rootReadiness = {
|
||||
[LEGACY_APPDATA_ROOT]: entries.get(LEGACY_APPDATA_ROOT)!,
|
||||
[MANAGED_APPDATA_ROOT]: entries.get(MANAGED_APPDATA_ROOT)!,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const backupAcknowledged = safeTrust.managedAppdataBackupAcknowledged;
|
||||
const trustSelectionConsistent = typeof backupAcknowledged === 'boolean' && (
|
||||
trustedRoot === MANAGED_APPDATA_ROOT
|
||||
? backupAcknowledged && managed?.root === MANAGED_APPDATA_ROOT && managed.backupAcknowledged === backupAcknowledged
|
||||
: trustedRoot === LEGACY_APPDATA_ROOT && !backupAcknowledged
|
||||
);
|
||||
const managedStatusConsistent = Boolean(
|
||||
managed && (
|
||||
(managed.status !== 'ready' || rootReadiness[MANAGED_APPDATA_ROOT] === 'ready') &&
|
||||
(managed.status !== 'initialization_required' || rootReadiness[MANAGED_APPDATA_ROOT] === 'needs_initialization') &&
|
||||
(trustedRoot !== MANAGED_APPDATA_ROOT || managed.status !== 'not_selected') &&
|
||||
(trustedRoot !== MANAGED_APPDATA_ROOT || managed.backupAcknowledged === backupAcknowledged)
|
||||
)
|
||||
);
|
||||
const managedCapabilityValid = Boolean(
|
||||
rootsValid &&
|
||||
trustSelectionConsistent &&
|
||||
managed &&
|
||||
managed.root === MANAGED_APPDATA_ROOT &&
|
||||
managed.backupScope === 'dockerRootDir' &&
|
||||
typeof managed.backupAcknowledged === 'boolean' &&
|
||||
isManagedStatus(managed.status) &&
|
||||
managedStatusConsistent,
|
||||
);
|
||||
const rawTrustStatus = safeTrust.status === 'ready' || safeTrust.status === 'blocked' || safeTrust.status === 'needs_setup'
|
||||
? safeTrust.status
|
||||
: 'unknown';
|
||||
const configured = safeTrust.configured === true && trustedRoot !== null && trustSelectionConsistent;
|
||||
const trustStatus = rawTrustStatus === 'needs_setup' || rawTrustStatus === 'blocked'
|
||||
? rawTrustStatus
|
||||
: configured && rawTrustStatus === 'ready' ? 'ready' : 'unknown';
|
||||
|
||||
return {
|
||||
configured,
|
||||
trustStatus,
|
||||
currentRoot: configured ? trustedRoot : null,
|
||||
backupAcknowledged: backupAcknowledged === true,
|
||||
rootReadiness,
|
||||
managedStatus: managedCapabilityValid ? managed!.status as ManagedStorageStatus : 'unavailable',
|
||||
managedBlocker: managedCapabilityValid && typeof managed!.blockerCode === 'string' && managed!.blockerCode.length <= 100
|
||||
? managed!.blockerCode || null
|
||||
: null,
|
||||
managedCapabilityValid,
|
||||
};
|
||||
};
|
||||
|
||||
const isBoundedStringArray = (value: unknown): value is string[] => (
|
||||
Array.isArray(value) && value.length <= 100 && value.every((item) => typeof item === 'string' && item.length <= 500)
|
||||
);
|
||||
|
||||
export const normalizeManagedStoragePreview = (value: unknown): PreflightPreview | null => {
|
||||
if (!isRecord(value)) return null;
|
||||
const rollbackAvailable = value.rollbackAvailable;
|
||||
if (
|
||||
typeof value.action !== 'string' || value.action.length > 120 ||
|
||||
typeof value.target !== 'string' || value.target.length > 200 ||
|
||||
(value.risk !== 'low' && value.risk !== 'medium' && value.risk !== 'high' && value.risk !== 'critical') ||
|
||||
(value.policyDecision !== 'allowed' && value.policyDecision !== 'approval_required' && value.policyDecision !== 'blocked') ||
|
||||
typeof value.auditPreview !== 'string' || value.auditPreview.length > 2000 ||
|
||||
!isBoundedStringArray(value.affectedApps) ||
|
||||
!isBoundedStringArray(value.dependencies) ||
|
||||
!isBoundedStringArray(value.preChecks) ||
|
||||
!isBoundedStringArray(value.postChecks) ||
|
||||
(rollbackAvailable !== undefined && typeof rollbackAvailable !== 'boolean')
|
||||
) return null;
|
||||
|
||||
const normalizedRollbackAvailable: boolean | undefined = typeof rollbackAvailable === 'boolean'
|
||||
? rollbackAvailable
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
action: value.action,
|
||||
target: value.target,
|
||||
risk: value.risk,
|
||||
policyDecision: value.policyDecision,
|
||||
auditPreview: value.auditPreview,
|
||||
affectedApps: value.affectedApps,
|
||||
dependencies: value.dependencies,
|
||||
preChecks: value.preChecks,
|
||||
postChecks: value.postChecks,
|
||||
...(normalizedRollbackAvailable === undefined ? {} : { rollbackAvailable: normalizedRollbackAvailable }),
|
||||
};
|
||||
};
|
||||
|
||||
export const buildStorageRootSelection = (
|
||||
root: TrustedAppdataRoot,
|
||||
backupAcknowledged: boolean,
|
||||
): EnrollmentStorageRootSelection | null => {
|
||||
if (root === MANAGED_APPDATA_ROOT && !backupAcknowledged) return null;
|
||||
return {
|
||||
trusted_appdata_root: root,
|
||||
managed_appdata_backup_acknowledged: root === MANAGED_APPDATA_ROOT,
|
||||
};
|
||||
};
|
||||
|
||||
export const canInitializeManagedStorage = (
|
||||
view: EnrollmentStorageView,
|
||||
preview: unknown,
|
||||
): boolean => (
|
||||
view.configured &&
|
||||
view.currentRoot === MANAGED_APPDATA_ROOT &&
|
||||
view.backupAcknowledged &&
|
||||
view.managedCapabilityValid &&
|
||||
view.managedStatus === 'initialization_required' &&
|
||||
isRecord(preview) &&
|
||||
preview.action === MANAGED_STORAGE_INITIALIZE_ACTION &&
|
||||
preview.target === MANAGED_STORAGE_TARGET &&
|
||||
preview.risk === 'high' &&
|
||||
preview.policyDecision === 'allowed'
|
||||
);
|
||||
|
||||
export interface ManagedStorageInitializationGate {
|
||||
canManage: boolean;
|
||||
view: EnrollmentStorageView | null | undefined;
|
||||
preview: unknown;
|
||||
dirty: boolean;
|
||||
loadError: string;
|
||||
conflict: string;
|
||||
refreshing: boolean;
|
||||
saving: boolean;
|
||||
initializing: boolean;
|
||||
}
|
||||
|
||||
export const canRequestManagedStorageInitialization = (gate: ManagedStorageInitializationGate): boolean => (
|
||||
gate.canManage &&
|
||||
Boolean(gate.view && canInitializeManagedStorage(gate.view, gate.preview)) &&
|
||||
!gate.dirty &&
|
||||
!gate.loadError &&
|
||||
!gate.conflict &&
|
||||
!gate.refreshing &&
|
||||
!gate.saving &&
|
||||
!gate.initializing
|
||||
);
|
||||
|
||||
export interface StorageRootSaveGate {
|
||||
canManage: boolean;
|
||||
configured: boolean;
|
||||
payloadAvailable: boolean;
|
||||
dirty: boolean;
|
||||
loadError: string;
|
||||
refreshing: boolean;
|
||||
saving: boolean;
|
||||
initializing: boolean;
|
||||
}
|
||||
|
||||
export const canSaveStorageRootSelection = (gate: StorageRootSaveGate): boolean => (
|
||||
gate.canManage &&
|
||||
gate.configured &&
|
||||
gate.payloadAvailable &&
|
||||
gate.dirty &&
|
||||
!gate.loadError &&
|
||||
!gate.refreshing &&
|
||||
!gate.saving &&
|
||||
!gate.initializing
|
||||
);
|
||||
|
||||
export interface ManagedStorageActionOutcome {
|
||||
tone: 'status' | 'alert';
|
||||
message: string;
|
||||
}
|
||||
|
||||
const UNCONFIRMED_INITIALIZATION_MESSAGE = 'Het initialisatieverzoek is niet bevestigd met geldige referenties.';
|
||||
const isBoundedIdentifier = (value: unknown): value is string => (
|
||||
typeof value === 'string' && value.trim().length > 0 && value.length <= 200
|
||||
);
|
||||
|
||||
export const normalizeManagedStorageActionOutcome = (value: unknown): ManagedStorageActionOutcome => {
|
||||
if (!isRecord(value) || typeof value.message !== 'string' || value.message.trim().length === 0 || value.message.length > 500) {
|
||||
return { tone: 'alert', message: UNCONFIRMED_INITIALIZATION_MESSAGE };
|
||||
}
|
||||
|
||||
if (value.success === true && isBoundedIdentifier(value.jobId) && isBoundedIdentifier(value.auditId)) {
|
||||
return {
|
||||
tone: 'status',
|
||||
message: `Initialisatieverzoek geregistreerd. Job ${value.jobId.trim()}; audit ${value.auditId.trim()}. De opslag wordt pas als gereed getoond nadat de controller dat bevestigt.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (value.success === false) {
|
||||
return { tone: 'alert', message: value.message.trim() };
|
||||
}
|
||||
|
||||
return { tone: 'alert', message: UNCONFIRMED_INITIALIZATION_MESSAGE };
|
||||
};
|
||||
|
||||
export const rootReadinessLabel = (status: TrustedAppdataRootReadinessStatus): string => {
|
||||
if (status === 'ready') return 'Controller confirms ready';
|
||||
if (status === 'needs_initialization') return 'Controller reports initialization needed';
|
||||
return 'Not currently confirmed';
|
||||
};
|
||||
|
||||
export const managedStorageStatusLabel = (status: ManagedStorageStatus): string => {
|
||||
switch (status) {
|
||||
case 'not_selected': return 'Not selected for future targets';
|
||||
case 'blocked': return 'Blocked by a required safety condition';
|
||||
case 'ready': return 'Controller reports ready';
|
||||
case 'initialization_required': return 'Controller requires one-time initialization';
|
||||
default: return 'Unavailable or not confirmed';
|
||||
}
|
||||
};
|
||||
|
||||
export const managedStorageBlockerLabel = (blocker: string | null): string | null => {
|
||||
switch (blocker) {
|
||||
case 'managed_appdata_backup_acknowledgement_required': return 'Confirm the Docker-root backup responsibility before continuing.';
|
||||
case 'managed_appdata_initialization_required': return 'The controller reports that the managed storage volume needs initialization.';
|
||||
case 'managed_appdata_volume_unavailable': return 'The controller cannot currently verify the managed storage volume.';
|
||||
default: return blocker ? 'The controller reports an additional storage blocker.' : null;
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user