mirror of
https://github.com/meienberger/runtipi.git
synced 2024-11-20 13:41:44 +03:00
very wip
This commit is contained in:
parent
4a222c4fde
commit
e85f8eb642
@ -34,6 +34,7 @@ const appEventSchema = z.object({
|
||||
})
|
||||
.extend({})
|
||||
.catchall(z.unknown()),
|
||||
archiveName: z.string().optional(),
|
||||
});
|
||||
|
||||
export type AppEventFormInput = z.input<typeof appEventSchema>['form'];
|
||||
|
@ -472,12 +472,19 @@ export class AppExecutors {
|
||||
}
|
||||
};
|
||||
|
||||
public backupApp = async (appId: string, form: AppEventForm, skipEnvGeneration = false) => {
|
||||
public backupApp = async (
|
||||
appId: string,
|
||||
archiveName: string,
|
||||
form: AppEventForm,
|
||||
skipEnvGeneration = false,
|
||||
) => {
|
||||
try {
|
||||
await SocketManager.emit({ type: 'app', event: 'status_change', data: { appId } });
|
||||
|
||||
const { appDataDirPath, appDirPath } = this.getAppPaths(appId);
|
||||
const backupDir = path.join(DATA_DIR, 'backups', appId);
|
||||
const backupRootDir = path.join(DATA_DIR, 'backups', appId);
|
||||
const tmpData = path.join(backupRootDir, archiveName);
|
||||
const archive = `${archiveName}.tar.gz`;
|
||||
|
||||
this.logger.info('Backing up app...');
|
||||
|
||||
@ -497,29 +504,41 @@ export class AppExecutors {
|
||||
|
||||
this.logger.info('Copying files to backup location...');
|
||||
|
||||
// Remove old backup archive
|
||||
await fs.promises.rm(`${backupDir}.tar.gz`, { force: true, recursive: true });
|
||||
// Remove old backup archive if exists
|
||||
if (await pathExists(path.join(backupRootDir, archive))) {
|
||||
await fs.promises.rm(path.join(backupRootDir, archive), { force: true, recursive: true });
|
||||
}
|
||||
|
||||
// Create app backup directory
|
||||
await fs.promises.mkdir(backupDir, { recursive: true });
|
||||
// Create app backups directory if it doesn't exist
|
||||
if (!(await pathExists(backupRootDir))) {
|
||||
await fs.promises.mkdir(backupRootDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Remove old temp backup directory if exists
|
||||
if (await pathExists(tmpData)) {
|
||||
await fs.promises.rm(tmpData, { force: true, recursive: true });
|
||||
}
|
||||
|
||||
// Create app temp backup directory
|
||||
await fs.promises.mkdir(tmpData, { recursive: true });
|
||||
|
||||
// Move app data and app directories
|
||||
await fs.promises.cp(appDataDirPath, path.join(backupDir, 'data'), { recursive: true });
|
||||
await fs.promises.cp(appDirPath, path.join(backupDir, 'app'), { recursive: true });
|
||||
await fs.promises.cp(appDataDirPath, path.join(tmpData, 'data'), { recursive: true });
|
||||
await fs.promises.cp(appDirPath, path.join(tmpData, 'app'), { recursive: true });
|
||||
|
||||
// Check if the user config folder exists and if it does copy it too
|
||||
if (await pathExists(path.join(DATA_DIR, 'user-config', appId))) {
|
||||
await fs.promises.cp(
|
||||
path.join(DATA_DIR, 'user-config', appId),
|
||||
path.join(backupDir, 'user-config'),
|
||||
path.join(tmpData, 'user-config'),
|
||||
);
|
||||
}
|
||||
|
||||
// Create the archive
|
||||
await execAsync(`tar -czpf ${backupDir}.tar.gz -C ${backupDir} .`);
|
||||
await execAsync(`tar -czpf ${backupRootDir}/${archiveName}.tar.gz -C ${tmpData} .`);
|
||||
|
||||
// Remove the backup folder
|
||||
await fs.promises.rm(backupDir, { force: true, recursive: true });
|
||||
await fs.promises.rm(tmpData, { force: true, recursive: true });
|
||||
|
||||
this.logger.info('Backup completed!');
|
||||
|
||||
@ -538,20 +557,26 @@ export class AppExecutors {
|
||||
}
|
||||
};
|
||||
|
||||
public restoreApp = async (appId: string, form: AppEventForm, skipEnvGeneration = false) => {
|
||||
public restoreApp = async (
|
||||
appId: string,
|
||||
archiveName: string,
|
||||
form: AppEventForm,
|
||||
skipEnvGeneration = false,
|
||||
) => {
|
||||
try {
|
||||
await SocketManager.emit({ type: 'app', event: 'status_change', data: { appId } });
|
||||
|
||||
const { appDataDirPath, appDirPath } = this.getAppPaths(appId);
|
||||
const backupDir = path.join(DATA_DIR, 'backups', appId);
|
||||
const archive = path.join(DATA_DIR, 'backups', `${appId}.tar.gz`);
|
||||
const backupRootDir = path.join(DATA_DIR, 'backups', appId);
|
||||
const tmpData = path.join(backupRootDir, archiveName);
|
||||
const archive = `${archiveName}.tar.gz`;
|
||||
const client = await getDbClient();
|
||||
|
||||
this.logger.info('Restoring app from backup...');
|
||||
|
||||
// Verify the app has a backup
|
||||
if (!(await pathExists(archive))) {
|
||||
throw new Error('App does not have any backups!');
|
||||
// Verify the archive exists
|
||||
if (!(await pathExists(path.join(backupRootDir, archive)))) {
|
||||
throw new Error('Archive does not exist!');
|
||||
}
|
||||
|
||||
// Ensure app directory and generate env
|
||||
@ -577,25 +602,30 @@ export class AppExecutors {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
// Delete old tmp data directory if exists
|
||||
if (await pathExists(tmpData)) {
|
||||
await fs.promises.rm(tmpData, { force: true, recursive: true });
|
||||
}
|
||||
|
||||
// Unzip the archive
|
||||
await fs.promises.mkdir(backupDir, { recursive: true });
|
||||
await execAsync(`tar -xf ${archive} -C ${backupDir}`);
|
||||
await fs.promises.mkdir(tmpData, { recursive: true });
|
||||
await execAsync(`tar -xf ${path.join(backupRootDir, archive)} -C ${tmpData}`);
|
||||
|
||||
// Copy data from the backup folder
|
||||
await fs.promises.cp(path.join(backupDir, 'app'), appDirPath, { recursive: true });
|
||||
await fs.promises.cp(path.join(backupDir, 'data'), appDataDirPath, { recursive: true });
|
||||
await fs.promises.cp(path.join(tmpData, 'app'), appDirPath, { recursive: true });
|
||||
await fs.promises.cp(path.join(tmpData, 'data'), appDataDirPath, { recursive: true });
|
||||
|
||||
// Copy user config foler if it exists
|
||||
if (await pathExists(path.join(backupDir, 'user-config'))) {
|
||||
if (await pathExists(path.join(tmpData, 'user-config'))) {
|
||||
await fs.promises.cp(
|
||||
path.join(backupDir, 'user-config'),
|
||||
path.join(tmpData, 'user-config'),
|
||||
path.join(DATA_DIR, 'user-config', appId),
|
||||
{ recursive: true },
|
||||
);
|
||||
}
|
||||
|
||||
// Delete backup folder
|
||||
await fs.promises.rm(backupDir, { force: true, recursive: true });
|
||||
await fs.promises.rm(tmpData, { force: true, recursive: true });
|
||||
|
||||
// Set the version in the database
|
||||
const configFileRaw = await fs.promises.readFile(path.join(appDirPath, 'config.json'), {
|
||||
|
@ -64,11 +64,11 @@ const runCommand = async (jobData: unknown) => {
|
||||
}
|
||||
|
||||
if (data.command === 'backup') {
|
||||
({ success, message } = await backupApp(data.appid, data.form));
|
||||
({ success, message } = await backupApp(data.appid, data.archiveName || '', data.form));
|
||||
}
|
||||
|
||||
if (data.command === 'restore') {
|
||||
({ success, message } = await restoreApp(data.appid, data.form));
|
||||
({ success, message } = await restoreApp(data.appid, data.archiveName || '', data.form));
|
||||
}
|
||||
} else if (data.type === 'repo') {
|
||||
if (data.command === 'clone') {
|
||||
|
@ -34,12 +34,13 @@ type OpenType = 'local' | 'domain' | 'local_domain';
|
||||
|
||||
type AppDetailsContainerProps = {
|
||||
app: Awaited<ReturnType<GetAppCommand['execute']>>;
|
||||
backups: string[];
|
||||
localDomain?: string;
|
||||
optimisticStatus: AppStatusEnum;
|
||||
setOptimisticStatus: (status: AppStatusEnum) => void;
|
||||
};
|
||||
|
||||
export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, localDomain, optimisticStatus, setOptimisticStatus }) => {
|
||||
export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, backups, localDomain, optimisticStatus, setOptimisticStatus }) => {
|
||||
const t = useTranslations();
|
||||
|
||||
const installDisclosure = useDisclosure();
|
||||
@ -253,15 +254,16 @@ export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, l
|
||||
status={optimisticStatus}
|
||||
onBackup={openBackupModal}
|
||||
onRestore={openRestoreModal}
|
||||
backups={backups}
|
||||
/>
|
||||
<BackupModal
|
||||
onConfirm={() => backupMutation.execute({ id: app.info.id })}
|
||||
onSubmit={(values) => backupMutation.execute({ id: app.id, archiveName: values.archiveName })}
|
||||
isOpen={backupDisclosure.isOpen}
|
||||
onClose={backupDisclosure.close}
|
||||
info={app.info}
|
||||
/>
|
||||
<RestoreModal
|
||||
onConfirm={() => restoreMutation.execute({ id: app.info.id })}
|
||||
onConfirm={() => restoreMutation.execute({ id: app.info.id, archiveName: 'hi' })}
|
||||
isOpen={restoreDisclosure.isOpen}
|
||||
onClose={restoreDisclosure.close}
|
||||
info={app.info}
|
||||
|
@ -8,11 +8,12 @@ import { GetAppCommand } from '@/server/services/app-catalog/commands';
|
||||
|
||||
interface IProps {
|
||||
app: Awaited<ReturnType<GetAppCommand['execute']>>;
|
||||
backups: string[];
|
||||
localDomain?: string;
|
||||
}
|
||||
|
||||
export const AppDetailsWrapper = (props: IProps) => {
|
||||
const { app, localDomain } = props;
|
||||
const { app, localDomain, backups } = props;
|
||||
const [optimisticStatus, setOptimisticStatus] = useOptimistic<AppStatus>(app.status);
|
||||
|
||||
const changeStatus = (status: AppStatus) => {
|
||||
@ -90,5 +91,13 @@ export const AppDetailsWrapper = (props: IProps) => {
|
||||
selector: { type: 'app', data: { property: 'appId', value: app.id } },
|
||||
});
|
||||
|
||||
return <AppDetailsContainer localDomain={localDomain} app={app} optimisticStatus={optimisticStatus} setOptimisticStatus={setOptimisticStatus} />;
|
||||
return (
|
||||
<AppDetailsContainer
|
||||
localDomain={localDomain}
|
||||
app={app}
|
||||
backups={backups}
|
||||
optimisticStatus={optimisticStatus}
|
||||
setOptimisticStatus={setOptimisticStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -3,17 +3,25 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { AppInfo } from '@runtipi/shared';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
interface IProps {
|
||||
info: AppInfo;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
onSubmit: (values: { archiveName: string }) => void;
|
||||
}
|
||||
|
||||
export const BackupModal: React.FC<IProps> = ({ info, isOpen, onClose, onConfirm }) => {
|
||||
export const BackupModal: React.FC<IProps> = ({ info, isOpen, onClose, onSubmit }) => {
|
||||
const t = useTranslations();
|
||||
|
||||
type FormValues = {
|
||||
archiveName: string;
|
||||
};
|
||||
|
||||
const { register, handleSubmit } = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent size="sm">
|
||||
@ -21,13 +29,18 @@ export const BackupModal: React.FC<IProps> = ({ info, isOpen, onClose, onConfirm
|
||||
<DialogTitle>{t('APP_BACKUP_TITLE', { name: info.name })}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<span className="text-muted">{t('APP_BACKUP_SUBTITILE')}</span>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<p className="text-muted">{t('APP_BACKUP_SUBTITILE')}</p>
|
||||
<div className="mt-1 mb-3">
|
||||
<Input {...register('archiveName')} label="Backup" placeholder="mybackup"></Input>
|
||||
</div>
|
||||
<div className="d-flex justify-content-end">
|
||||
<Button type="submit" className="btn-success">
|
||||
{t('APP_BACKUP_SUBMIT')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Button onClick={onConfirm} className="btn-success">
|
||||
{t('APP_BACKUP_SUBMIT')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
@ -9,7 +9,6 @@ import { Tabs } from '@/components/ui/tabs';
|
||||
import { SettingsTabTriggers } from '../SettingsTabTriggers';
|
||||
import { TabsContent } from '@radix-ui/react-tabs';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { IconStackPop, IconStackPush } from '@tabler/icons-react';
|
||||
|
||||
interface IProps {
|
||||
info: AppInfo;
|
||||
@ -20,10 +19,11 @@ interface IProps {
|
||||
onReset: () => void;
|
||||
status?: AppStatus;
|
||||
onBackup: () => void;
|
||||
onRestore: () => void;
|
||||
onRestore: (backup: string) => void;
|
||||
backups: string[];
|
||||
}
|
||||
|
||||
export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, onClose, onSubmit, onReset, status, onBackup, onRestore }) => {
|
||||
export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, onClose, onSubmit, onReset, status, onBackup, onRestore, backups }) => {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
@ -47,18 +47,22 @@ export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, on
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="backups" className="p-3">
|
||||
<h3 className="mb-1">{t('APP_BACKUP_SUBMIT')}</h3>
|
||||
<p className="text-muted mb-2">{t('APP_BACKUP_SETTINGS_SUBTITLE')}</p>
|
||||
<Button onClick={onBackup}>
|
||||
{t('APP_BACKUP_SUBMIT')}
|
||||
<IconStackPush className="ms-1" size={14} />
|
||||
</Button>
|
||||
<h3 className="mb-1 mt-3">{t('APP_RESTORE_SUBMIT')}</h3>
|
||||
<p className="text-muted mb-2">{t('APP_RESTORE_SETTINGS_SUBTITILE')}</p>
|
||||
<Button onClick={onRestore}>
|
||||
{t('APP_RESTORE_SUBMIT')}
|
||||
<IconStackPop className="ms-1" size={14} />
|
||||
</Button>
|
||||
<h3 className="mb-0">{t('APP_BACKUP_SUBMIT')}</h3>
|
||||
<div className="d-flex justify-content-between mb-2 mt-0">
|
||||
<p className="text-muted my-auto">Manage backups for your app.</p>
|
||||
<Button onClick={onBackup}>{t('APP_BACKUP_SUBMIT')}</Button>
|
||||
</div>
|
||||
<pre>
|
||||
{backups.length !== 0 ? (
|
||||
<div className="card">
|
||||
{backups.map((backup) => (
|
||||
<RenderBackup backup={backup} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="mx-auto my-3 text-muted">No backups found! Why don't you create one?</p>
|
||||
)}
|
||||
</pre>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</DialogDescription>
|
||||
@ -67,3 +71,21 @@ export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, on
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
interface props {
|
||||
backup: string;
|
||||
// onRestore: () => void;
|
||||
// onDelete: () => void;
|
||||
}
|
||||
|
||||
const RenderBackup: React.FC<props> = ({ backup }) => {
|
||||
return (
|
||||
<div key={backup} className="card-body d-flex justify-content-between">
|
||||
<p className="my-auto">{backup}</p>
|
||||
<div>
|
||||
<Button className="btn-danger">Delete</Button>
|
||||
<Button className="ms-2">Restore</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -16,9 +16,10 @@ export async function generateMetadata({ params }: { params: { id: string } }):
|
||||
export default async function AppDetailsPage({ params }: { params: { id: string } }) {
|
||||
try {
|
||||
const app = await appCatalog.getApp(params.id);
|
||||
const appBackups = await appCatalog.getAppBackups(params.id);
|
||||
const settings = TipiConfig.getSettings();
|
||||
|
||||
return <AppDetailsWrapper app={app} localDomain={settings.localDomain} />;
|
||||
return <AppDetailsWrapper app={app} backups={appBackups} localDomain={settings.localDomain} />;
|
||||
} catch (e) {
|
||||
const translator = await getTranslatorFromCookie();
|
||||
|
||||
|
@ -9,16 +9,17 @@ import { ensureUser } from '../utils/ensure-user';
|
||||
|
||||
const input = z.object({
|
||||
id: z.string(),
|
||||
archiveName: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Given an app id, backs up the app.
|
||||
*/
|
||||
export const backupAppAction = action(input, async ({ id }) => {
|
||||
export const backupAppAction = action(input, async ({ id, archiveName }) => {
|
||||
try {
|
||||
await ensureUser();
|
||||
|
||||
await appLifecycle.executeCommand('backupApp', { appId: id });
|
||||
await appLifecycle.executeCommand('backupApp', { appId: id, archiveName });
|
||||
|
||||
revalidatePath('/apps');
|
||||
revalidatePath(`/app/${id}`);
|
||||
|
34
src/app/actions/app-actions/delete-backup-action.ts
Normal file
34
src/app/actions/app-actions/delete-backup-action.ts
Normal file
@ -0,0 +1,34 @@
|
||||
'use server';
|
||||
|
||||
import { z } from 'zod';
|
||||
import { action } from '@/lib/safe-action';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { handleActionError } from '../utils/handle-action-error';
|
||||
import { ensureUser } from '../utils/ensure-user';
|
||||
import fs from 'fs';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import path from 'path';
|
||||
|
||||
const input = z.object({
|
||||
id: z.string(),
|
||||
archiveName: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Given an app id, backs up the app.
|
||||
*/
|
||||
export const backupAppAction = action(input, async ({ id, archiveName }) => {
|
||||
try {
|
||||
await ensureUser();
|
||||
|
||||
await fs.promises.rm(path.join(DATA_DIR, 'backups', id, archiveName), { force: true, recursive: true });
|
||||
|
||||
revalidatePath('/apps');
|
||||
revalidatePath(`/app/${id}`);
|
||||
revalidatePath(`/app-store/${id}`);
|
||||
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
});
|
@ -9,16 +9,17 @@ import { ensureUser } from '../utils/ensure-user';
|
||||
|
||||
const input = z.object({
|
||||
id: z.string(),
|
||||
archiveName: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Given an app id, backs up the app.
|
||||
*/
|
||||
export const restoreAppAction = action(input, async ({ id }) => {
|
||||
export const restoreAppAction = action(input, async ({ id, archiveName }) => {
|
||||
try {
|
||||
await ensureUser();
|
||||
|
||||
await appLifecycle.executeCommand('restoreApp', { appId: id });
|
||||
await appLifecycle.executeCommand('restoreApp', { appId: id, archiveName });
|
||||
|
||||
revalidatePath('/apps');
|
||||
revalidatePath(`/app/${id}`);
|
||||
|
@ -2,6 +2,7 @@ import { AppCacheManager } from './app-cache-manager';
|
||||
import { AppQueries } from '@/server/queries/apps/apps.queries';
|
||||
import { GetInstalledAppsCommand, GetGuestDashboardApps, GetAppCommand } from './commands';
|
||||
import { IAppLifecycleCommand } from '../app-lifecycle/commands/types';
|
||||
import { GetAppBackups } from './commands/get-app-backups';
|
||||
|
||||
class CommandInvoker {
|
||||
public async execute<T>(command: IAppLifecycleCommand<T>) {
|
||||
@ -42,6 +43,11 @@ export class AppCatalogClass {
|
||||
const command = new GetAppCommand({ queries: this.queries, appId: id });
|
||||
return this.commandInvoker.execute(command);
|
||||
}
|
||||
|
||||
public async getAppBackups(id: string) {
|
||||
const command = new GetAppBackups({ appId: id });
|
||||
return this.commandInvoker.execute(command);
|
||||
}
|
||||
}
|
||||
|
||||
export type AppCatalog = InstanceType<typeof AppCatalogClass>;
|
||||
|
34
src/server/services/app-catalog/commands/get-app-backups.ts
Normal file
34
src/server/services/app-catalog/commands/get-app-backups.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { ICommand } from './types';
|
||||
import fs from 'fs';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import path from 'path';
|
||||
import { pathExists } from 'fs-extra';
|
||||
|
||||
type ReturnValue = Awaited<ReturnType<InstanceType<typeof GetAppBackups>['execute']>>;
|
||||
|
||||
export class GetAppBackups implements ICommand<ReturnValue> {
|
||||
private appId: string;
|
||||
|
||||
constructor(params: { appId: string }) {
|
||||
this.appId = params.appId;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
const backupsRootDir = path.join(DATA_DIR, 'backups', this.appId);
|
||||
|
||||
if (!(await pathExists(backupsRootDir))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const files = await fs.promises.readdir(path.join(DATA_DIR, 'backups', this.appId));
|
||||
const backups: string[] = [];
|
||||
|
||||
for (const file in files) {
|
||||
if (files[file]!.includes('.tar.gz')) {
|
||||
backups.push(files[file]!);
|
||||
}
|
||||
}
|
||||
|
||||
return backups;
|
||||
}
|
||||
}
|
@ -15,8 +15,14 @@ export class BackupAppCommand implements IAppLifecycleCommand {
|
||||
this.eventDispatcher = params.eventDispatcher;
|
||||
}
|
||||
|
||||
private async sendEvent(appId: string, form: AppEventFormInput): Promise<void> {
|
||||
const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'backup', appid: appId, form });
|
||||
private async sendEvent(appId: string, archiveName: string, form: AppEventFormInput): Promise<void> {
|
||||
const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({
|
||||
type: 'app',
|
||||
command: 'backup',
|
||||
appid: appId,
|
||||
archiveName,
|
||||
form,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
await this.queries.updateApp(appId, { status: 'running' });
|
||||
@ -26,8 +32,8 @@ export class BackupAppCommand implements IAppLifecycleCommand {
|
||||
}
|
||||
}
|
||||
|
||||
async execute(params: { appId: string }): Promise<void> {
|
||||
const { appId } = params;
|
||||
async execute(params: { appId: string; archiveName: string }): Promise<void> {
|
||||
const { appId, archiveName } = params;
|
||||
const app = await this.queries.getApp(appId);
|
||||
|
||||
if (!app) {
|
||||
@ -37,6 +43,6 @@ export class BackupAppCommand implements IAppLifecycleCommand {
|
||||
// Run script
|
||||
await this.queries.updateApp(appId, { status: 'backing_up' });
|
||||
|
||||
void this.sendEvent(appId, castAppConfig(app.config));
|
||||
void this.sendEvent(appId, archiveName, castAppConfig(app.config));
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ export class RestoreAppCommand implements IAppLifecycleCommand {
|
||||
this.eventDispatcher = params.eventDispatcher;
|
||||
}
|
||||
|
||||
private async sendEvent(appId: string, form: AppEventFormInput): Promise<void> {
|
||||
const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'restore', appid: appId, form });
|
||||
private async sendEvent(appId: string, archiveName: string, form: AppEventFormInput): Promise<void> {
|
||||
const { success, stdout } = await this.eventDispatcher.dispatchEventAsync({ type: 'app', command: 'restore', appid: appId, archiveName, form });
|
||||
|
||||
if (success) {
|
||||
await this.queries.updateApp(appId, { status: 'running' });
|
||||
@ -26,8 +26,8 @@ export class RestoreAppCommand implements IAppLifecycleCommand {
|
||||
}
|
||||
}
|
||||
|
||||
async execute(params: { appId: string }): Promise<void> {
|
||||
const { appId } = params;
|
||||
async execute(params: { appId: string; archiveName: string }): Promise<void> {
|
||||
const { appId, archiveName } = params;
|
||||
const app = await this.queries.getApp(appId);
|
||||
|
||||
if (!app) {
|
||||
@ -37,6 +37,6 @@ export class RestoreAppCommand implements IAppLifecycleCommand {
|
||||
// Run script
|
||||
await this.queries.updateApp(appId, { status: 'restoring' });
|
||||
|
||||
void this.sendEvent(appId, castAppConfig(app.config));
|
||||
void this.sendEvent(appId, archiveName, castAppConfig(app.config));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user