runtipi/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx
2023-10-07 10:38:42 +02:00

138 lines
5.1 KiB
TypeScript

import { Icon, IconDownload, IconExternalLink, IconLock, IconLockOff, IconPlayerPause, IconPlayerPlay, IconSettings, IconTrash, IconX } from '@tabler/icons-react';
import clsx from 'clsx';
import React from 'react';
import type { AppStatus } from '@/server/db/schema';
import { useTranslations } from 'next-intl';
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from '@/components/ui/DropdownMenu';
import { Button } from '@/components/ui/Button';
import type { AppService } from '@/server/services/apps/apps.service';
interface IProps {
app: Awaited<ReturnType<AppService['getApp']>>;
status?: AppStatus;
updateAvailable: boolean;
localDomain?: string;
onInstall: () => void;
onUninstall: () => void;
onStart: () => void;
onStop: () => void;
onOpen: (url: OpenType) => void;
onUpdate: () => void;
onUpdateSettings: () => void;
onCancel: () => void;
}
interface BtnProps {
IconComponent?: Icon;
onClick?: () => void;
width?: number | null;
title?: string;
color?: string;
loading?: boolean;
}
const ActionButton: React.FC<BtnProps> = (props) => {
const { IconComponent, onClick, title, loading, color, width = 140 } = props;
const testId = loading ? 'action-button-loading' : undefined;
return (
<Button loading={loading} data-testid={testId} onClick={onClick} width={width} className={clsx('me-2 px-4 mt-2', [`btn-${color}`])}>
{title}
{IconComponent && <IconComponent className="ms-1" size={14} />}
</Button>
);
};
type OpenType = 'local' | 'domain' | 'local_domain';
export const AppActions: React.FC<IProps> = ({ app, status, localDomain, onInstall, onUninstall, onStart, onStop, onOpen, onUpdate, onCancel, updateAvailable, onUpdateSettings }) => {
const { info } = app;
const t = useTranslations('apps.app-details');
const hasSettings = Object.keys(info.form_fields).length > 0 || info.exposable;
const hostname = typeof window !== 'undefined' ? window.location.hostname : '';
const buttons: JSX.Element[] = [];
const StartButton = <ActionButton key="start" IconComponent={IconPlayerPlay} onClick={onStart} title={t('actions.start')} color="success" />;
const RemoveButton = <ActionButton key="remove" IconComponent={IconTrash} onClick={onUninstall} title={t('actions.remove')} color="danger" />;
const SettingsButton = <ActionButton key="settings" IconComponent={IconSettings} onClick={onUpdateSettings} title={t('actions.settings')} />;
const StopButton = <ActionButton key="stop" IconComponent={IconPlayerPause} onClick={onStop} title={t('actions.stop')} color="danger" />;
const LoadingButtion = <ActionButton key="loading" loading color="success" title={t('actions.loading')} />;
const CancelButton = <ActionButton key="cancel" IconComponent={IconX} onClick={onCancel} title={t('actions.cancel')} />;
const InstallButton = <ActionButton key="install" onClick={onInstall} title={t('actions.install')} color="success" />;
const UpdateButton = <ActionButton key="update" IconComponent={IconDownload} onClick={onUpdate} width={null} title={t('actions.update')} color="success" />;
const OpenButton = (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button width={140} className={clsx('me-2 px-4 mt-2')}>
{t('actions.open')}
<IconExternalLink className="ms-1" size={14} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>{t('choose-open-method')}</DropdownMenuLabel>
<DropdownMenuGroup>
{app.exposed && app.domain && (
<DropdownMenuItem onClick={() => onOpen('domain')}>
<IconLock className="text-green me-2" size={16} />
{app.domain}
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={() => onOpen('local_domain')}>
<IconLock className="text-muted me-2" size={16} />
{app.id}.{localDomain}
</DropdownMenuItem>
{!app.info.force_expose && (
<DropdownMenuItem onClick={() => onOpen('local')}>
<IconLockOff className="text-muted me-2" size={16} />
{hostname}:{app.info.port}
</DropdownMenuItem>
)}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
switch (status) {
case 'stopped':
buttons.push(StartButton, RemoveButton);
if (hasSettings) {
buttons.push(SettingsButton);
}
if (updateAvailable) {
buttons.push(UpdateButton);
}
break;
case 'running':
buttons.push(StopButton);
if (!info.no_gui) {
buttons.push(OpenButton);
}
if (hasSettings) {
buttons.push(SettingsButton);
}
if (updateAvailable) {
buttons.push(UpdateButton);
}
break;
case 'installing':
case 'uninstalling':
case 'starting':
case 'stopping':
case 'updating':
buttons.push(LoadingButtion, CancelButton);
break;
case 'missing':
buttons.push(InstallButton);
break;
default:
break;
}
return <div className="d-flex justify-content-center flex-wrap">{buttons.map((button) => button)}</div>;
};