fix(appstore): выбор хранилища в мастере установки + видимость реальной ошибки
- Установка: поле «Хранилище» стало выпадающим списком доступных rootdir-хранилищ ноды (useLXCStorages) с автовыбором валидного вместо ручного ввода - Диагностика сбоя: увеличен захват вывода упавшей команды (400 → 2000 символов) и добавлен 2>&1 к docker compose pull/up — реальная причина (напр. отсутствие тега образа) больше не обрезается за строками warning Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -113,7 +113,7 @@ def _run(ssh: SSHClient, cmd: str, *, what: str, timeout: int = _CMD_TIMEOUT) ->
|
||||
if exit_code == -1:
|
||||
raise PocError(f"{what}: таймаут (>{timeout}с) или обрыв SSH")
|
||||
if exit_code != 0:
|
||||
raise PocError(f"{what}: exit={exit_code}: {(output or '').strip()[:400]}")
|
||||
raise PocError(f"{what}: exit={exit_code}: {(output or '').strip()[:2000]}")
|
||||
return output or ""
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ def poc_install(db: Session, spec: InstallSpec, on_step: StepCb = _noop) -> Inst
|
||||
on_step("Запуск docker compose up -d...", 85)
|
||||
_run(
|
||||
ssh,
|
||||
_pct(vmid, f"cd {APP_DIR} && docker compose --env-file .env up -d"),
|
||||
_pct(vmid, f"cd {APP_DIR} && docker compose --env-file .env up -d 2>&1"),
|
||||
what="docker compose up",
|
||||
timeout=_DOCKER_TIMEOUT, # тянет образы — может идти минуты
|
||||
)
|
||||
@@ -378,8 +378,8 @@ def poc_update(server, client, vmid: int, node: str, compose_yaml: str,
|
||||
on_step("docker compose pull && up -d...", 65)
|
||||
_run(
|
||||
ssh,
|
||||
_pct(vmid, f"cd {APP_DIR} && docker compose --env-file .env pull && "
|
||||
f"docker compose --env-file .env up -d"),
|
||||
_pct(vmid, f"cd {APP_DIR} && docker compose --env-file .env pull 2>&1 && "
|
||||
f"docker compose --env-file .env up -d 2>&1"),
|
||||
what="docker compose pull/up",
|
||||
timeout=_DOCKER_TIMEOUT, # pull больших образов — может идти минуты
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
|
||||
} from '@/components/ui/select';
|
||||
import { useServers } from '@/hooks/use-nodes';
|
||||
import { useLXCStorages } from '@/hooks/use-lxc-templates';
|
||||
import { useIPAMNetworks, useIPAMPools } from '@/hooks/use-ipam';
|
||||
import { useInstallApp, useInstalledApp, useOperationProgress, useAppOperations } from '@/hooks/use-appstore';
|
||||
import type { AppOperation, CatalogApp } from '@/types/appstore';
|
||||
@@ -48,6 +49,21 @@ export default function InstallWizard({ app, open, onOpenChange }: Props) {
|
||||
const { data: ipamNetworks = [] } = useIPAMNetworks();
|
||||
const { data: ipamPools = [] } = useIPAMPools(ipamNetworkId ? Number(ipamNetworkId) : undefined);
|
||||
|
||||
// Хранилища ноды под rootfs LXC (для выбора вместо ручного ввода).
|
||||
const { data: storages = [] } = useLXCStorages(
|
||||
serverId ? Number(serverId) : undefined,
|
||||
node || undefined,
|
||||
'rootdir',
|
||||
);
|
||||
// Автовыбор валидного хранилища, когда список загрузился (предпочитаем текущее/local-lvm).
|
||||
useEffect(() => {
|
||||
if (storages.length && !storages.some((s) => s.storage === storage)) {
|
||||
const preferred = storages.find((s) => s.storage === 'local-lvm') ?? storages[0];
|
||||
setStorage(preferred.storage);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [storages]);
|
||||
|
||||
const [phase, setPhase] = useState<Phase>('form');
|
||||
const [installedAppId, setInstalledAppId] = useState<number | null>(null);
|
||||
const [op, setOp] = useState<AppOperation | null>(null);
|
||||
@@ -206,7 +222,15 @@ export default function InstallWizard({ app, open, onOpenChange }: Props) {
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label>{t('appstore.storage')}</Label>
|
||||
<Input value={storage} onChange={(e) => setStorage(e.target.value)} />
|
||||
<Select value={storage} onValueChange={(v) => { if (v) setStorage(v); }}>
|
||||
<SelectTrigger><SelectValue placeholder={t('appstore.storage')} /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{storages.length === 0 && <SelectItem value={storage}>{storage}</SelectItem>}
|
||||
{storages.map((s) => (
|
||||
<SelectItem key={s.storage} value={s.storage}>{s.storage} ({s.type})</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label>{t('appstore.bridge')}</Label>
|
||||
|
||||
Reference in New Issue
Block a user