fix(appstore): выбор хранилища в мастере установки + видимость реальной ошибки
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 57s
Deploy to Production / Trigger production update (push) Successful in 8s

- Установка: поле «Хранилище» стало выпадающим списком доступных 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:
markmorado
2026-07-08 19:28:39 +05:00
parent 6d5df8caa7
commit 23d3f95f54
2 changed files with 29 additions and 5 deletions

View File

@@ -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 больших образов — может идти минуты
)

View File

@@ -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>