feat(apps): add "enable on guest dashboard option"
This commit is contained in:
parent
fd6c5afe2c
commit
5830d16382
14 changed files with 79 additions and 40 deletions
1
next-env.d.ts
vendored
1
next-env.d.ts
vendored
|
@ -1,6 +1,5 @@
|
||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
/// <reference types="next/navigation-types/compat/navigation" />
|
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||||
|
|
|
@ -116,8 +116,7 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app, localDomain }) => {
|
||||||
const handleInstallSubmit = async (values: FormValues) => {
|
const handleInstallSubmit = async (values: FormValues) => {
|
||||||
setCustomStatus('installing');
|
setCustomStatus('installing');
|
||||||
installDisclosure.close();
|
installDisclosure.close();
|
||||||
const { exposed, domain } = values;
|
installMutation.execute({ id: app.id, form: values });
|
||||||
installMutation.execute({ id: app.id, form: values, exposed, domain });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnistallSubmit = () => {
|
const handleUnistallSubmit = () => {
|
||||||
|
@ -139,8 +138,7 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app, localDomain }) => {
|
||||||
|
|
||||||
const handleUpdateSettingsSubmit = async (values: FormValues) => {
|
const handleUpdateSettingsSubmit = async (values: FormValues) => {
|
||||||
updateSettingsDisclosure.close();
|
updateSettingsDisclosure.close();
|
||||||
const { exposed, domain } = values;
|
updateConfigMutation.execute({ id: app.id, form: values });
|
||||||
updateConfigMutation.execute({ id: app.id, form: values, exposed, domain });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateSubmit = async () => {
|
const handleUpdateSubmit = async () => {
|
||||||
|
@ -185,8 +183,6 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app, localDomain }) => {
|
||||||
onClose={updateSettingsDisclosure.close}
|
onClose={updateSettingsDisclosure.close}
|
||||||
info={app.info}
|
info={app.info}
|
||||||
config={castAppConfig(app?.config)}
|
config={castAppConfig(app?.config)}
|
||||||
exposed={app?.exposed}
|
|
||||||
domain={app?.domain || ''}
|
|
||||||
/>
|
/>
|
||||||
<div className="card-header d-flex flex-column flex-md-row">
|
<div className="card-header d-flex flex-column flex-md-row">
|
||||||
<AppLogo id={app.id} size={130} alt={app.info.name} />
|
<AppLogo id={app.id} size={130} alt={app.info.name} />
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { validateAppConfig } from '../../utils/validators';
|
||||||
interface IProps {
|
interface IProps {
|
||||||
formFields: FormField[];
|
formFields: FormField[];
|
||||||
onSubmit: (values: FormValues) => void;
|
onSubmit: (values: FormValues) => void;
|
||||||
initalValues?: { exposed?: boolean; domain?: string } & { [key: string]: string | boolean | undefined };
|
initalValues?: { [key: string]: unknown };
|
||||||
info: AppInfo;
|
info: AppInfo;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ interface IProps {
|
||||||
export type FormValues = {
|
export type FormValues = {
|
||||||
exposed?: boolean;
|
exposed?: boolean;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
|
isVisibleOnGuestDashboard?: boolean;
|
||||||
[key: string]: string | boolean | undefined;
|
[key: string]: string | boolean | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ export const InstallForm: React.FC<IProps> = ({ formFields, info, onSubmit, init
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initalValues && !isDirty) {
|
if (initalValues && !isDirty) {
|
||||||
Object.entries(initalValues).forEach(([key, value]) => {
|
Object.entries(initalValues).forEach(([key, value]) => {
|
||||||
setValue(key, value);
|
setValue(key, value as string);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [initalValues, isDirty, setValue]);
|
}, [initalValues, isDirty, setValue]);
|
||||||
|
@ -153,6 +154,14 @@ export const InstallForm: React.FC<IProps> = ({ formFields, info, onSubmit, init
|
||||||
return (
|
return (
|
||||||
<form className="flex flex-col" onSubmit={handleSubmit(validate)}>
|
<form className="flex flex-col" onSubmit={handleSubmit(validate)}>
|
||||||
{formFields.filter(typeFilter).map(renderField)}
|
{formFields.filter(typeFilter).map(renderField)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isVisibleOnGuestDashboard"
|
||||||
|
defaultValue={false}
|
||||||
|
render={({ field: { onChange, value, ref, ...props } }) => (
|
||||||
|
<Switch className="mb-3" disabled={info.force_expose} ref={ref} checked={value} onCheckedChange={onChange} {...props} label={t('display-on-guest-dashboard')} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
{info.exposable && renderExposeForm()}
|
{info.exposable && renderExposeForm()}
|
||||||
<Button loading={loading} type="submit" className="btn-success">
|
<Button loading={loading} type="submit" className="btn-success">
|
||||||
{initalValues ? t('submit-update') : t('sumbit-install')}
|
{initalValues ? t('submit-update') : t('sumbit-install')}
|
||||||
|
|
|
@ -9,13 +9,11 @@ interface IProps {
|
||||||
info: AppInfo;
|
info: AppInfo;
|
||||||
config: Record<string, unknown>;
|
config: Record<string, unknown>;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
exposed?: boolean;
|
|
||||||
domain?: string;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (values: FormValues) => void;
|
onSubmit: (values: FormValues) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, onClose, onSubmit, exposed, domain }) => {
|
export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, onClose, onSubmit }) => {
|
||||||
const t = useTranslations('apps.app-details.update-settings-form');
|
const t = useTranslations('apps.app-details.update-settings-form');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -26,7 +24,7 @@ export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, on
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<ScrollArea maxHeight={500}>
|
<ScrollArea maxHeight={500}>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<InstallForm onSubmit={onSubmit} formFields={info.form_fields} info={info} initalValues={{ ...config, exposed, domain }} />
|
<InstallForm onSubmit={onSubmit} formFields={info.form_fields} info={info} initalValues={{ ...config }} />
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
@ -6,8 +6,9 @@ import React from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { AppCategory } from '@runtipi/shared';
|
import { AppCategory } from '@runtipi/shared';
|
||||||
import { AppLogo } from '@/components/AppLogo';
|
import { AppLogo } from '@/components/AppLogo';
|
||||||
|
import { limitText } from '@/lib/helpers/text-helpers';
|
||||||
import styles from './AppStoreTile.module.scss';
|
import styles from './AppStoreTile.module.scss';
|
||||||
import { colorSchemeForCategory, limitText } from '../../helpers/table.helpers';
|
import { colorSchemeForCategory } from '../../helpers/table.helpers';
|
||||||
|
|
||||||
type App = {
|
type App = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { limitText } from '@/lib/helpers/text-helpers';
|
||||||
import { createAppConfig } from '../../../../../server/tests/apps.factory';
|
import { createAppConfig } from '../../../../../server/tests/apps.factory';
|
||||||
import { limitText, sortTable } from '../table.helpers';
|
import { sortTable } from '../table.helpers';
|
||||||
import { AppTableData } from '../table.types';
|
import { AppTableData } from '../table.types';
|
||||||
|
|
||||||
describe('sortTable function', () => {
|
describe('sortTable function', () => {
|
||||||
|
|
|
@ -46,8 +46,6 @@ export const sortTable = (params: SortParams) => {
|
||||||
return sortedData.filter((app) => app.name.toLowerCase().includes(search.toLowerCase()));
|
return sortedData.filter((app) => app.name.toLowerCase().includes(search.toLowerCase()));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const limitText = (text: string, limit: number) => (text.length > limit ? `${text.substring(0, limit)}...` : text);
|
|
||||||
|
|
||||||
export const colorSchemeForCategory: Record<AppCategory, string> = {
|
export const colorSchemeForCategory: Record<AppCategory, string> = {
|
||||||
network: 'blue',
|
network: 'blue',
|
||||||
media: 'azure',
|
media: 'azure',
|
||||||
|
|
|
@ -12,18 +12,16 @@ const formSchema = z.object({}).catchall(z.any());
|
||||||
const input = z.object({
|
const input = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
form: formSchema,
|
form: formSchema,
|
||||||
exposed: z.boolean().optional(),
|
|
||||||
domain: z.string().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an app id, installs the app.
|
* Given an app id, installs the app.
|
||||||
*/
|
*/
|
||||||
export const installAppAction = action(input, async ({ id, form, domain, exposed }) => {
|
export const installAppAction = action(input, async ({ id, form }) => {
|
||||||
try {
|
try {
|
||||||
const appsService = new AppServiceClass(db);
|
const appsService = new AppServiceClass(db);
|
||||||
|
|
||||||
await appsService.installApp(id, form, exposed, domain);
|
await appsService.installApp(id, form);
|
||||||
|
|
||||||
revalidatePath('/apps');
|
revalidatePath('/apps');
|
||||||
revalidatePath(`/app/${id}`);
|
revalidatePath(`/app/${id}`);
|
||||||
|
|
|
@ -19,11 +19,11 @@ const input = z.object({
|
||||||
/**
|
/**
|
||||||
* Given an app id and form, updates the app config
|
* Given an app id and form, updates the app config
|
||||||
*/
|
*/
|
||||||
export const updateAppConfigAction = action(input, async ({ id, form, domain, exposed }) => {
|
export const updateAppConfigAction = action(input, async ({ id, form }) => {
|
||||||
try {
|
try {
|
||||||
const appsService = new AppServiceClass(db);
|
const appsService = new AppServiceClass(db);
|
||||||
|
|
||||||
await appsService.updateAppConfig(id, form, exposed, domain);
|
await appsService.updateAppConfig(id, form);
|
||||||
|
|
||||||
revalidatePath('/apps');
|
revalidatePath('/apps');
|
||||||
revalidatePath(`/app/${id}`);
|
revalidatePath(`/app/${id}`);
|
||||||
|
|
|
@ -177,6 +177,7 @@
|
||||||
"install-form": {
|
"install-form": {
|
||||||
"title": "Install {name}",
|
"title": "Install {name}",
|
||||||
"expose-app": "Expose app",
|
"expose-app": "Expose app",
|
||||||
|
"display-on-guest-dashboard": "Display on guest dashboard",
|
||||||
"domain-name": "Domain name",
|
"domain-name": "Domain name",
|
||||||
"domain-name-hint": "Make sure this exact domain contains an A record pointing to your IP.",
|
"domain-name-hint": "Make sure this exact domain contains an A record pointing to your IP.",
|
||||||
"choose-option": "Choose an option...",
|
"choose-option": "Choose an option...",
|
||||||
|
@ -239,6 +240,8 @@
|
||||||
"invalid-ip": "Invalid IP address",
|
"invalid-ip": "Invalid IP address",
|
||||||
"invalid-url": "Invalid URL",
|
"invalid-url": "Invalid URL",
|
||||||
"invalid-domain": "Invalid domain",
|
"invalid-domain": "Invalid domain",
|
||||||
|
"guest-dashboard": "Enable guest dashboard",
|
||||||
|
"guest-dashboard-hint": "This will allow non-authenticated users to see a limited dashboard and easily access the running apps on your instance.",
|
||||||
"domain-name": "Domain name",
|
"domain-name": "Domain name",
|
||||||
"domain-name-hint": "Make sure this exact domain contains an A record pointing to your IP.",
|
"domain-name-hint": "Make sure this exact domain contains an A record pointing to your IP.",
|
||||||
"dns-ip": "DNS IP",
|
"dns-ip": "DNS IP",
|
||||||
|
@ -291,10 +294,15 @@
|
||||||
"app-store": "App Store",
|
"app-store": "App Store",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
|
"login": "Login",
|
||||||
"dark-mode": "Dark Mode",
|
"dark-mode": "Dark Mode",
|
||||||
"light-mode": "Light Mode",
|
"light-mode": "Light Mode",
|
||||||
"sponsor": "Sponsor",
|
"sponsor": "Sponsor",
|
||||||
"source-code": "Source code",
|
"source-code": "Source code",
|
||||||
"update-available": "Update available"
|
"update-available": "Update available"
|
||||||
}
|
},
|
||||||
|
"runtipi": "Runtipi",
|
||||||
|
"guest-dashboard": "Guest dashboard",
|
||||||
|
"guest-dashboard-no-apps": "No apps to display",
|
||||||
|
"guest-dashboard-no-apps-subtitle": "Ask your administrator to add apps to the guest dashboard or login to see your apps."
|
||||||
}
|
}
|
||||||
|
|
1
src/lib/helpers/text-helpers.ts
Normal file
1
src/lib/helpers/text-helpers.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const limitText = (text: string, limit: number) => (text.length > limit ? `${text.substring(0, limit)}...` : text);
|
|
@ -48,6 +48,7 @@ export const appTable = pgTable('app', {
|
||||||
version: integer('version').default(1).notNull(),
|
version: integer('version').default(1).notNull(),
|
||||||
exposed: boolean('exposed').notNull(),
|
exposed: boolean('exposed').notNull(),
|
||||||
domain: varchar('domain'),
|
domain: varchar('domain'),
|
||||||
|
isVisibleOnGuestDashboard: boolean('is_visible_on_guest_dashboard').default(false).notNull(),
|
||||||
});
|
});
|
||||||
export type App = InferModel<typeof appTable>;
|
export type App = InferModel<typeof appTable>;
|
||||||
export type NewApp = InferModel<typeof appTable, 'insert'>;
|
export type NewApp = InferModel<typeof appTable, 'insert'>;
|
||||||
|
|
|
@ -76,7 +76,7 @@ describe('Install app', () => {
|
||||||
const appConfig = createAppConfig({ exposable: true });
|
const appConfig = createAppConfig({ exposable: true });
|
||||||
|
|
||||||
// act & assert
|
// act & assert
|
||||||
await expect(AppsService.installApp(appConfig.id, {}, true)).rejects.toThrowError('server-messages.errors.domain-required-if-expose-app');
|
await expect(AppsService.installApp(appConfig.id, { exposed: true })).rejects.toThrowError('server-messages.errors.domain-required-if-expose-app');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if app is exposed and config does not allow it', async () => {
|
it('Should throw if app is exposed and config does not allow it', async () => {
|
||||||
|
@ -84,7 +84,7 @@ describe('Install app', () => {
|
||||||
const appConfig = createAppConfig({ exposable: false });
|
const appConfig = createAppConfig({ exposable: false });
|
||||||
|
|
||||||
// act & assert
|
// act & assert
|
||||||
await expect(AppsService.installApp(appConfig.id, {}, true, 'test.com')).rejects.toThrowError('server-messages.errors.app-not-exposable');
|
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain: 'test.com' })).rejects.toThrowError('server-messages.errors.app-not-exposable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if app is exposed and domain is not valid', async () => {
|
it('Should throw if app is exposed and domain is not valid', async () => {
|
||||||
|
@ -92,7 +92,7 @@ describe('Install app', () => {
|
||||||
const appConfig = createAppConfig({ exposable: true });
|
const appConfig = createAppConfig({ exposable: true });
|
||||||
|
|
||||||
// act & assert
|
// act & assert
|
||||||
await expect(AppsService.installApp(appConfig.id, {}, true, 'test')).rejects.toThrowError('server-messages.errors.domain-not-valid');
|
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain: 'test' })).rejects.toThrowError('server-messages.errors.domain-not-valid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if app is exposed and domain is already used by another exposed app', async () => {
|
it('Should throw if app is exposed and domain is already used by another exposed app', async () => {
|
||||||
|
@ -103,7 +103,7 @@ describe('Install app', () => {
|
||||||
await insertApp({ domain, exposed: true }, appConfig2, db);
|
await insertApp({ domain, exposed: true }, appConfig2, db);
|
||||||
|
|
||||||
// act & assert
|
// act & assert
|
||||||
await expect(AppsService.installApp(appConfig.id, {}, true, domain)).rejects.toThrowError('server-messages.errors.domain-already-in-use');
|
await expect(AppsService.installApp(appConfig.id, { exposed: true, domain })).rejects.toThrowError('server-messages.errors.domain-already-in-use');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if architecure is not supported', async () => {
|
it('Should throw if architecure is not supported', async () => {
|
||||||
|
@ -308,7 +308,7 @@ describe('Update app config', () => {
|
||||||
await insertApp({}, appConfig, db);
|
await insertApp({}, appConfig, db);
|
||||||
|
|
||||||
// act & assert
|
// act & assert
|
||||||
expect(AppsService.updateAppConfig(appConfig.id, {}, true)).rejects.toThrowError('server-messages.errors.domain-required-if-expose-app');
|
expect(AppsService.updateAppConfig(appConfig.id, { exposed: true })).rejects.toThrowError('server-messages.errors.domain-required-if-expose-app');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if app is exposed and domain is not valid', async () => {
|
it('Should throw if app is exposed and domain is not valid', async () => {
|
||||||
|
@ -317,7 +317,7 @@ describe('Update app config', () => {
|
||||||
await insertApp({}, appConfig, db);
|
await insertApp({}, appConfig, db);
|
||||||
|
|
||||||
// act & assert
|
// act & assert
|
||||||
expect(AppsService.updateAppConfig(appConfig.id, {}, true, 'test')).rejects.toThrowError('server-messages.errors.domain-not-valid');
|
expect(AppsService.updateAppConfig(appConfig.id, { exposed: true, domain: 'test' })).rejects.toThrowError('server-messages.errors.domain-not-valid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if app is exposed and domain is already used', async () => {
|
it('Should throw if app is exposed and domain is already used', async () => {
|
||||||
|
@ -329,7 +329,7 @@ describe('Update app config', () => {
|
||||||
await insertApp({}, appConfig2, db);
|
await insertApp({}, appConfig2, db);
|
||||||
|
|
||||||
// act & assert
|
// act & assert
|
||||||
await expect(AppsService.updateAppConfig(appConfig2.id, {}, true, domain)).rejects.toThrowError('server-messages.errors.domain-already-in-use');
|
await expect(AppsService.updateAppConfig(appConfig2.id, { exposed: true, domain })).rejects.toThrowError('server-messages.errors.domain-already-in-use');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if app is not exposed and config has force_expose set to true', async () => {
|
it('should throw if app is not exposed and config has force_expose set to true', async () => {
|
||||||
|
@ -347,7 +347,7 @@ describe('Update app config', () => {
|
||||||
await insertApp({}, appConfig, db);
|
await insertApp({}, appConfig, db);
|
||||||
|
|
||||||
// act & assert
|
// act & assert
|
||||||
await expect(AppsService.updateAppConfig(appConfig.id, {}, true, 'test.com')).rejects.toThrowError('server-messages.errors.app-not-exposable');
|
await expect(AppsService.updateAppConfig(appConfig.id, { exposed: true, domain: 'test.com' })).rejects.toThrowError('server-messages.errors.app-not-exposable');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,12 @@ import { getConfig } from '../../core/TipiConfig';
|
||||||
import { Logger } from '../../core/Logger';
|
import { Logger } from '../../core/Logger';
|
||||||
import { notEmpty } from '../../common/typescript.helpers';
|
import { notEmpty } from '../../common/typescript.helpers';
|
||||||
|
|
||||||
|
type AlwaysFields = {
|
||||||
|
isVisibleOnGuestDashboard?: boolean;
|
||||||
|
domain?: string;
|
||||||
|
exposed?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const sortApps = (a: AppInfo, b: AppInfo) => a.id.localeCompare(b.id);
|
const sortApps = (a: AppInfo, b: AppInfo) => a.id.localeCompare(b.id);
|
||||||
const filterApp = (app: AppInfo): boolean => {
|
const filterApp = (app: AppInfo): boolean => {
|
||||||
if (!app.supported_architectures) {
|
if (!app.supported_architectures) {
|
||||||
|
@ -101,12 +107,12 @@ export class AppServiceClass {
|
||||||
*
|
*
|
||||||
* @param {string} id - The id of the app to be installed
|
* @param {string} id - The id of the app to be installed
|
||||||
* @param {Record<string, string>} form - The form data submitted by the user
|
* @param {Record<string, string>} form - The form data submitted by the user
|
||||||
* @param {boolean} [exposed] - A flag indicating if the app will be exposed to the internet
|
|
||||||
* @param {string} [domain] - The domain name to expose the app to the internet, required if exposed is true
|
|
||||||
*/
|
*/
|
||||||
public installApp = async (id: string, form: Record<string, string>, exposed?: boolean, domain?: string) => {
|
public installApp = async (id: string, form: Record<string, unknown> & AlwaysFields) => {
|
||||||
const app = await this.queries.getApp(id);
|
const app = await this.queries.getApp(id);
|
||||||
|
|
||||||
|
const { exposed, domain, isVisibleOnGuestDashboard } = form;
|
||||||
|
|
||||||
if (app) {
|
if (app) {
|
||||||
await this.startApp(id);
|
await this.startApp(id);
|
||||||
} else {
|
} else {
|
||||||
|
@ -148,7 +154,15 @@ export class AppServiceClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.queries.createApp({ id, status: 'installing', config: form, version: appInfo.tipi_version, exposed: exposed || false, domain: domain || null });
|
await this.queries.createApp({
|
||||||
|
id,
|
||||||
|
status: 'installing',
|
||||||
|
config: form,
|
||||||
|
version: appInfo.tipi_version,
|
||||||
|
exposed: exposed || false,
|
||||||
|
domain: domain || null,
|
||||||
|
isVisibleOnGuestDashboard,
|
||||||
|
});
|
||||||
|
|
||||||
// Run script
|
// Run script
|
||||||
const eventDispatcher = new EventDispatcher('installApp');
|
const eventDispatcher = new EventDispatcher('installApp');
|
||||||
|
@ -181,10 +195,10 @@ export class AppServiceClass {
|
||||||
*
|
*
|
||||||
* @param {string} id - The ID of the app to update.
|
* @param {string} id - The ID of the app to update.
|
||||||
* @param {object} form - The new configuration of the app.
|
* @param {object} form - The new configuration of the app.
|
||||||
* @param {boolean} [exposed] - If the app should be exposed or not.
|
|
||||||
* @param {string} [domain] - The domain for the app if exposed is true.
|
|
||||||
*/
|
*/
|
||||||
public updateAppConfig = async (id: string, form: Record<string, string>, exposed?: boolean, domain?: string) => {
|
public updateAppConfig = async (id: string, form: Record<string, unknown> & AlwaysFields) => {
|
||||||
|
const { exposed, domain } = form;
|
||||||
|
|
||||||
if (exposed && !domain) {
|
if (exposed && !domain) {
|
||||||
throw new TranslatedError('server-messages.errors.domain-required-if-expose-app');
|
throw new TranslatedError('server-messages.errors.domain-required-if-expose-app');
|
||||||
}
|
}
|
||||||
|
@ -226,7 +240,7 @@ export class AppServiceClass {
|
||||||
await eventDispatcher.close();
|
await eventDispatcher.close();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
const updatedApp = await this.queries.updateApp(id, { exposed: exposed || false, domain: domain || null, config: form });
|
const updatedApp = await this.queries.updateApp(id, { exposed: exposed || false, domain: domain || null, config: form, isVisibleOnGuestDashboard: form.isVisibleOnGuestDashboard });
|
||||||
return updatedApp;
|
return updatedApp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,6 +383,21 @@ export class AppServiceClass {
|
||||||
})
|
})
|
||||||
.filter(notEmpty);
|
.filter(notEmpty);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public getGuestDashboardApps = async () => {
|
||||||
|
const apps = await this.queries.getGuestDashboardApps();
|
||||||
|
|
||||||
|
console.log(apps);
|
||||||
|
return apps
|
||||||
|
.map((app) => {
|
||||||
|
const info = getAppInfo(app.id, app.status);
|
||||||
|
if (info) {
|
||||||
|
return { ...app, info };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(notEmpty);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppService = InstanceType<typeof AppServiceClass>;
|
export type AppService = InstanceType<typeof AppServiceClass>;
|
||||||
|
|
Loading…
Reference in a new issue