Add panel feature updates across API, daemon, and web
This commit is contained in:
@@ -31,9 +31,30 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
|
||||
interface PluginInstallField {
|
||||
key: string;
|
||||
label: string;
|
||||
type: 'text' | 'number' | 'boolean' | 'select';
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
defaultValue?: unknown;
|
||||
options?: Array<{ label: string; value: string }>;
|
||||
min?: number;
|
||||
max?: number;
|
||||
pattern?: string;
|
||||
secret?: boolean;
|
||||
}
|
||||
|
||||
interface InstalledPluginRelease {
|
||||
id: string;
|
||||
version: string;
|
||||
installSchema: PluginInstallField[];
|
||||
}
|
||||
|
||||
interface InstalledPlugin {
|
||||
id: string;
|
||||
pluginId: string;
|
||||
releaseId: string | null;
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
@@ -41,7 +62,17 @@ interface InstalledPlugin {
|
||||
externalId: string | null;
|
||||
installedVersion: string | null;
|
||||
isActive: boolean;
|
||||
installOptions: Record<string, unknown>;
|
||||
autoUpdateChannel: 'stable' | 'beta' | 'alpha';
|
||||
isPinned: boolean;
|
||||
status: 'installed' | 'updating' | 'failed';
|
||||
lastError: string | null;
|
||||
updateAvailable: boolean;
|
||||
latestReleaseId: string | null;
|
||||
latestVersion: string | null;
|
||||
latestChannel: 'stable' | 'beta' | 'alpha' | null;
|
||||
installedAt: string;
|
||||
currentRelease: InstalledPluginRelease | null;
|
||||
}
|
||||
|
||||
interface SpigetResult {
|
||||
@@ -68,7 +99,19 @@ interface MarketplacePlugin {
|
||||
installId: string | null;
|
||||
installedVersion: string | null;
|
||||
isActive: boolean;
|
||||
isPinned: boolean;
|
||||
autoUpdateChannel: 'stable' | 'beta' | 'alpha';
|
||||
installedAt: string | null;
|
||||
releaseId: string | null;
|
||||
updateAvailable: boolean;
|
||||
latestRelease: {
|
||||
id: string;
|
||||
version: string;
|
||||
channel: 'stable' | 'beta' | 'alpha';
|
||||
artifactType: 'file' | 'zip';
|
||||
artifactUrl: string;
|
||||
installSchema: PluginInstallField[];
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface MarketplaceResponse {
|
||||
@@ -90,6 +133,91 @@ function extractApiMessage(error: unknown, fallback: string): string {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function buildInstallOptionsState(
|
||||
fields: PluginInstallField[],
|
||||
current: Record<string, unknown> = {},
|
||||
): Record<string, unknown> {
|
||||
const next: Record<string, unknown> = {};
|
||||
|
||||
for (const field of fields) {
|
||||
if (field.defaultValue !== undefined) {
|
||||
next[field.key] = field.defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(current)) {
|
||||
next[key] = value;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function PluginInstallSchemaFields({
|
||||
fields,
|
||||
values,
|
||||
onChange,
|
||||
}: {
|
||||
fields: PluginInstallField[];
|
||||
values: Record<string, unknown>;
|
||||
onChange: (next: Record<string, unknown>) => void;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{fields.map((field) => (
|
||||
<div className="space-y-2" key={field.key}>
|
||||
<Label>{field.label}</Label>
|
||||
{field.type === 'select' ? (
|
||||
<select
|
||||
className="h-10 w-full rounded-md border bg-background px-3 text-sm"
|
||||
value={String(values[field.key] ?? '')}
|
||||
onChange={(e) => onChange({ ...values, [field.key]: e.target.value })}
|
||||
>
|
||||
<option value="">Select...</option>
|
||||
{(field.options ?? []).map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : field.type === 'boolean' ? (
|
||||
<label className="inline-flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(values[field.key])}
|
||||
onChange={(e) => onChange({ ...values, [field.key]: e.target.checked })}
|
||||
/>
|
||||
Enabled
|
||||
</label>
|
||||
) : (
|
||||
<Input
|
||||
type={field.type === 'number' ? 'number' : field.secret ? 'password' : 'text'}
|
||||
value={String(values[field.key] ?? '')}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
...values,
|
||||
[field.key]:
|
||||
field.type === 'number'
|
||||
? e.target.value === ''
|
||||
? ''
|
||||
: Number(e.target.value)
|
||||
: e.target.value,
|
||||
})
|
||||
}
|
||||
min={field.type === 'number' ? field.min : undefined}
|
||||
max={field.type === 'number' ? field.max : undefined}
|
||||
pattern={field.type === 'text' ? field.pattern : undefined}
|
||||
required={Boolean(field.required)}
|
||||
/>
|
||||
)}
|
||||
{field.description && (
|
||||
<p className="text-xs text-muted-foreground">{field.description}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function PluginsPage() {
|
||||
const { orgId, serverId } = useParams();
|
||||
const { server } = useOutletContext<{ server?: { gameSlug: string } }>();
|
||||
@@ -161,6 +289,11 @@ function MarketplacePlugins({ orgId, serverId }: { orgId: string; serverId: stri
|
||||
const [downloadUrl, setDownloadUrl] = useState('');
|
||||
const [version, setVersion] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [installDialogOpen, setInstallDialogOpen] = useState(false);
|
||||
const [installTarget, setInstallTarget] = useState<MarketplacePlugin | null>(null);
|
||||
const [installOptions, setInstallOptions] = useState<Record<string, unknown>>({});
|
||||
const [installPinVersion, setInstallPinVersion] = useState(false);
|
||||
const [installAutoUpdateChannel, setInstallAutoUpdateChannel] = useState<'stable' | 'beta' | 'alpha'>('stable');
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['plugin-marketplace', orgId, serverId, searchTerm],
|
||||
@@ -172,10 +305,24 @@ function MarketplacePlugins({ orgId, serverId }: { orgId: string; serverId: stri
|
||||
});
|
||||
|
||||
const installMutation = useMutation({
|
||||
mutationFn: (pluginId: string) =>
|
||||
api.post(`/organizations/${orgId}/servers/${serverId}/plugins/install/${pluginId}`),
|
||||
mutationFn: ({
|
||||
pluginId,
|
||||
payload,
|
||||
}: {
|
||||
pluginId: string;
|
||||
payload?: {
|
||||
releaseId?: string;
|
||||
options?: Record<string, unknown>;
|
||||
pinVersion?: boolean;
|
||||
autoUpdateChannel?: 'stable' | 'beta' | 'alpha';
|
||||
};
|
||||
}) =>
|
||||
api.post(`/organizations/${orgId}/servers/${serverId}/plugins/install/${pluginId}`, payload ?? {}),
|
||||
onSuccess: () => {
|
||||
toast.success('Plugin installed');
|
||||
setInstallDialogOpen(false);
|
||||
setInstallTarget(null);
|
||||
setInstallOptions({});
|
||||
queryClient.invalidateQueries({ queryKey: ['plugins', orgId, serverId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['plugin-marketplace', orgId, serverId] });
|
||||
},
|
||||
@@ -197,6 +344,19 @@ function MarketplacePlugins({ orgId, serverId }: { orgId: string; serverId: stri
|
||||
},
|
||||
});
|
||||
|
||||
const updateInstallMutation = useMutation({
|
||||
mutationFn: ({ installId }: { installId: string }) =>
|
||||
api.post(`/organizations/${orgId}/servers/${serverId}/plugins/${installId}/update`),
|
||||
onSuccess: () => {
|
||||
toast.success('Plugin updated');
|
||||
queryClient.invalidateQueries({ queryKey: ['plugins', orgId, serverId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['plugin-marketplace', orgId, serverId] });
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractApiMessage(error, 'Plugin update failed'));
|
||||
},
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (body: {
|
||||
name: string;
|
||||
@@ -278,6 +438,26 @@ function MarketplacePlugins({ orgId, serverId }: { orgId: string; serverId: stri
|
||||
setEditOpen(true);
|
||||
};
|
||||
|
||||
const openInstallDialog = (plugin: MarketplacePlugin) => {
|
||||
const fields = plugin.latestRelease?.installSchema ?? [];
|
||||
setInstallTarget(plugin);
|
||||
setInstallOptions(buildInstallOptionsState(fields));
|
||||
setInstallPinVersion(false);
|
||||
setInstallAutoUpdateChannel('stable');
|
||||
setInstallDialogOpen(true);
|
||||
};
|
||||
|
||||
const installDirect = (plugin: MarketplacePlugin) => {
|
||||
installMutation.mutate({
|
||||
pluginId: plugin.id,
|
||||
payload: {
|
||||
releaseId: plugin.latestRelease?.id ?? undefined,
|
||||
pinVersion: false,
|
||||
autoUpdateChannel: 'stable',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const plugins = data?.plugins ?? [];
|
||||
const gameName = data?.game.name ?? 'Game';
|
||||
|
||||
@@ -453,36 +633,67 @@ function MarketplacePlugins({ orgId, serverId }: { orgId: string; serverId: stri
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<p className="font-medium">{plugin.name}</p>
|
||||
<Badge variant="outline">{plugin.source}</Badge>
|
||||
{plugin.version && <Badge variant="secondary">v{plugin.version}</Badge>}
|
||||
{plugin.latestRelease?.version && (
|
||||
<Badge variant="secondary">v{plugin.latestRelease.version}</Badge>
|
||||
)}
|
||||
{plugin.isInstalled && <Badge>Installed</Badge>}
|
||||
{plugin.updateAvailable && <Badge variant="destructive">Update Available</Badge>}
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<p className="text-sm text-muted-foreground">{plugin.description}</p>
|
||||
)}
|
||||
{plugin.downloadUrl && (
|
||||
<p className="line-clamp-1 text-xs text-muted-foreground">{plugin.downloadUrl}</p>
|
||||
{plugin.latestRelease?.artifactUrl && (
|
||||
<p className="line-clamp-1 text-xs text-muted-foreground">
|
||||
{plugin.latestRelease.artifactUrl}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{plugin.isInstalled && plugin.installId ? (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => uninstallMutation.mutate(plugin.installId!)}
|
||||
disabled={uninstallMutation.isPending}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Kaldır
|
||||
</Button>
|
||||
<>
|
||||
{plugin.updateAvailable && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => updateInstallMutation.mutate({ installId: plugin.installId! })}
|
||||
disabled={updateInstallMutation.isPending}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Güncelle
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => uninstallMutation.mutate(plugin.installId!)}
|
||||
disabled={uninstallMutation.isPending}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Kaldır
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => installMutation.mutate(plugin.id)}
|
||||
disabled={installMutation.isPending}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Kur
|
||||
</Button>
|
||||
<>
|
||||
{(plugin.latestRelease?.installSchema?.length ?? 0) > 0 ? (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => openInstallDialog(plugin)}
|
||||
disabled={installMutation.isPending}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Ayarla ve Kur
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => installDirect(plugin)}
|
||||
disabled={installMutation.isPending}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Kur
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
@@ -508,6 +719,70 @@ function MarketplacePlugins({ orgId, serverId }: { orgId: string; serverId: stri
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Dialog open={installDialogOpen} onOpenChange={setInstallDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Install Plugin
|
||||
{installTarget ? ` - ${installTarget.name}` : ''}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form
|
||||
className="space-y-4"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (!installTarget) return;
|
||||
|
||||
installMutation.mutate({
|
||||
pluginId: installTarget.id,
|
||||
payload: {
|
||||
releaseId: installTarget.latestRelease?.id ?? undefined,
|
||||
options: installOptions,
|
||||
pinVersion: installPinVersion,
|
||||
autoUpdateChannel: installAutoUpdateChannel,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PluginInstallSchemaFields
|
||||
fields={installTarget?.latestRelease?.installSchema ?? []}
|
||||
values={installOptions}
|
||||
onChange={setInstallOptions}
|
||||
/>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Auto Update Channel</Label>
|
||||
<select
|
||||
className="h-10 w-full rounded-md border bg-background px-3 text-sm"
|
||||
value={installAutoUpdateChannel}
|
||||
onChange={(e) =>
|
||||
setInstallAutoUpdateChannel(e.target.value as 'stable' | 'beta' | 'alpha')
|
||||
}
|
||||
>
|
||||
<option value="stable">stable</option>
|
||||
<option value="beta">beta</option>
|
||||
<option value="alpha">alpha</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label className="inline-flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={installPinVersion}
|
||||
onChange={(e) => setInstallPinVersion(e.target.checked)}
|
||||
/>
|
||||
Pin this release version
|
||||
</label>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="submit" disabled={installMutation.isPending || !installTarget}>
|
||||
{installMutation.isPending ? 'Installing...' : 'Install'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -522,6 +797,9 @@ function InstalledPlugins({
|
||||
serverId: string;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const [configureDialogOpen, setConfigureDialogOpen] = useState(false);
|
||||
const [configureTarget, setConfigureTarget] = useState<InstalledPlugin | null>(null);
|
||||
const [configureOptions, setConfigureOptions] = useState<Record<string, unknown>>({});
|
||||
|
||||
const toggleMutation = useMutation({
|
||||
mutationFn: (id: string) =>
|
||||
@@ -545,6 +823,50 @@ function InstalledPlugins({
|
||||
},
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (id: string) =>
|
||||
api.post(`/organizations/${orgId}/servers/${serverId}/plugins/${id}/update`),
|
||||
onSuccess: () => {
|
||||
toast.success('Plugin güncellendi');
|
||||
queryClient.invalidateQueries({ queryKey: ['plugins', orgId, serverId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['plugin-marketplace', orgId, serverId] });
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractApiMessage(error, 'Plugin güncellenemedi'));
|
||||
},
|
||||
});
|
||||
|
||||
const configureMutation = useMutation({
|
||||
mutationFn: ({
|
||||
id,
|
||||
payload,
|
||||
}: {
|
||||
id: string;
|
||||
payload: {
|
||||
releaseId: string;
|
||||
options: Record<string, unknown>;
|
||||
};
|
||||
}) => api.post(`/organizations/${orgId}/servers/${serverId}/plugins/${id}/update`, payload),
|
||||
onSuccess: () => {
|
||||
toast.success('Plugin ayarları güncellendi');
|
||||
setConfigureDialogOpen(false);
|
||||
setConfigureTarget(null);
|
||||
setConfigureOptions({});
|
||||
queryClient.invalidateQueries({ queryKey: ['plugins', orgId, serverId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['plugin-marketplace', orgId, serverId] });
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractApiMessage(error, 'Plugin ayarları güncellenemedi'));
|
||||
},
|
||||
});
|
||||
|
||||
const openConfigureDialog = (plugin: InstalledPlugin) => {
|
||||
const fields = plugin.currentRelease?.installSchema ?? [];
|
||||
setConfigureTarget(plugin);
|
||||
setConfigureOptions(buildInstallOptionsState(fields, plugin.installOptions));
|
||||
setConfigureDialogOpen(true);
|
||||
};
|
||||
|
||||
if (installed.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
@@ -560,48 +882,138 @@ function InstalledPlugins({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{installed.map((plugin) => (
|
||||
<Card key={plugin.id}>
|
||||
<CardContent className="flex items-center justify-between p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Puzzle className="h-5 w-5 text-primary" />
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="font-medium">{plugin.name}</p>
|
||||
<Badge variant="outline">{plugin.source}</Badge>
|
||||
{!plugin.isActive && <Badge variant="secondary">Disabled</Badge>}
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
{installed.map((plugin) => (
|
||||
<Card key={plugin.id}>
|
||||
<CardContent className="flex flex-col gap-3 p-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<Puzzle className="h-5 w-5 text-primary" />
|
||||
<div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<p className="font-medium">{plugin.name}</p>
|
||||
<Badge variant="outline">{plugin.source}</Badge>
|
||||
{plugin.installedVersion && <Badge variant="secondary">v{plugin.installedVersion}</Badge>}
|
||||
{!plugin.isActive && <Badge variant="secondary">Disabled</Badge>}
|
||||
{plugin.status !== 'installed' && (
|
||||
<Badge variant={plugin.status === 'failed' ? 'destructive' : 'outline'}>
|
||||
{plugin.status}
|
||||
</Badge>
|
||||
)}
|
||||
{plugin.updateAvailable && <Badge variant="destructive">Update Available</Badge>}
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<p className="text-sm text-muted-foreground">{plugin.description}</p>
|
||||
)}
|
||||
{plugin.latestVersion && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Latest: v{plugin.latestVersion}
|
||||
{plugin.latestChannel ? ` (${plugin.latestChannel})` : ''}
|
||||
</p>
|
||||
)}
|
||||
{plugin.lastError && (
|
||||
<p className="text-xs text-destructive">{plugin.lastError}</p>
|
||||
)}
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<p className="text-sm text-muted-foreground">{plugin.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={() => toggleMutation.mutate(plugin.id)}
|
||||
title={plugin.isActive ? 'Disable' : 'Enable'}
|
||||
>
|
||||
{plugin.isActive ? (
|
||||
<ToggleRight className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<ToggleLeft className="h-4 w-4 text-muted-foreground" />
|
||||
<div className="flex items-center gap-2">
|
||||
{plugin.currentRelease && plugin.currentRelease.installSchema.length > 0 && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={() => openConfigureDialog(plugin)}
|
||||
title="Ayarları düzenle"
|
||||
disabled={configureMutation.isPending}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</Button>
|
||||
{plugin.updateAvailable && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={() => updateMutation.mutate(plugin.id)}
|
||||
title="Update"
|
||||
disabled={updateMutation.isPending}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={() => toggleMutation.mutate(plugin.id)}
|
||||
title={plugin.isActive ? 'Disable' : 'Enable'}
|
||||
>
|
||||
{plugin.isActive ? (
|
||||
<ToggleRight className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<ToggleLeft className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={() => uninstallMutation.mutate(plugin.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
open={configureDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
setConfigureDialogOpen(open);
|
||||
if (!open) {
|
||||
setConfigureTarget(null);
|
||||
setConfigureOptions({});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Plugin Ayarları
|
||||
{configureTarget ? ` - ${configureTarget.name}` : ''}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form
|
||||
className="space-y-4"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (!configureTarget?.currentRelease) return;
|
||||
|
||||
configureMutation.mutate({
|
||||
id: configureTarget.id,
|
||||
payload: {
|
||||
releaseId: configureTarget.currentRelease.id,
|
||||
options: configureOptions,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PluginInstallSchemaFields
|
||||
fields={configureTarget?.currentRelease?.installSchema ?? []}
|
||||
values={configureOptions}
|
||||
onChange={setConfigureOptions}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={() => uninstallMutation.mutate(plugin.id)}
|
||||
type="submit"
|
||||
disabled={configureMutation.isPending || !configureTarget?.currentRelease}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
{configureMutation.isPending ? 'Kaydediliyor...' : 'Kaydet'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -696,18 +1108,18 @@ function SpigetSearch({ orgId, serverId }: { orgId: string; serverId: string })
|
||||
function ManualInstall({ orgId, serverId }: { orgId: string; serverId: string }) {
|
||||
const queryClient = useQueryClient();
|
||||
const [name, setName] = useState('');
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [filePath, setFilePath] = useState('');
|
||||
const [version, setVersion] = useState('');
|
||||
|
||||
const installMutation = useMutation({
|
||||
mutationFn: (body: { name: string; fileName: string; version?: string }) =>
|
||||
mutationFn: (body: { name: string; filePath: string; version?: string }) =>
|
||||
api.post(`/organizations/${orgId}/servers/${serverId}/plugins/install/manual`, body),
|
||||
onSuccess: () => {
|
||||
toast.success('Plugin registered');
|
||||
queryClient.invalidateQueries({ queryKey: ['plugins', orgId, serverId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['plugin-marketplace', orgId, serverId] });
|
||||
setName('');
|
||||
setFileName('');
|
||||
setFilePath('');
|
||||
setVersion('');
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -727,7 +1139,7 @@ function ManualInstall({ orgId, serverId }: { orgId: string; serverId: string })
|
||||
e.preventDefault();
|
||||
installMutation.mutate({
|
||||
name,
|
||||
fileName,
|
||||
filePath,
|
||||
version: version || undefined,
|
||||
});
|
||||
}}
|
||||
@@ -737,15 +1149,15 @@ function ManualInstall({ orgId, serverId }: { orgId: string; serverId: string })
|
||||
<Input value={name} onChange={(e) => setName(e.target.value)} required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>File Name</Label>
|
||||
<Label>File Path</Label>
|
||||
<Input
|
||||
value={fileName}
|
||||
onChange={(e) => setFileName(e.target.value)}
|
||||
placeholder="plugin.jar"
|
||||
value={filePath}
|
||||
onChange={(e) => setFilePath(e.target.value)}
|
||||
placeholder="plugins/plugin.jar"
|
||||
required
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Upload the file to the correct plugin directory via Files tab first.
|
||||
Files sekmesinden dosyayı önce sunucuya yükleyin. Relative path girerseniz oyunun varsayılan plugin dizinine göre çözülür.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user