Compare commits
2 Commits
8d4540de97
...
7954945966
| Author | SHA1 | Date | |
|---|---|---|---|
| 7954945966 | |||
|
|
90424bff3a |
@ -210,3 +210,62 @@ export async function listGhostDirectories(): Promise<{ ok: boolean; data?: Ghos
|
|||||||
return { ok: false, message: 'Network error while loading ghost directories.' };
|
return { ok: false, message: 'Network error while loading ghost directories.' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function downloadExoscaleArchive(): Promise<{ ok: boolean; fileName?: string; message?: string }> {
|
||||||
|
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
|
||||||
|
const url = `${BASE_URL}/api/admin/dev/exoscale/download-archive`;
|
||||||
|
|
||||||
|
log('🧪 Dev Exoscale: GET', url);
|
||||||
|
try {
|
||||||
|
const res = await authFetch(url, { method: 'GET', headers: { Accept: 'application/zip' } });
|
||||||
|
|
||||||
|
if (res.status === 401) {
|
||||||
|
return { ok: false, message: 'Unauthorized. Please log in.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: any = null;
|
||||||
|
if (res.status === 403 || !res.ok) {
|
||||||
|
try {
|
||||||
|
body = await res.clone().json();
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
body = await res.text();
|
||||||
|
} catch {
|
||||||
|
body = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 403) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
message: typeof body === 'string' ? body : body?.error || 'Forbidden. Admin access required.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
message: typeof body === 'string' ? body : body?.error || 'Failed to download Exoscale archive.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await res.blob();
|
||||||
|
const contentDisposition = res.headers.get('content-disposition') || '';
|
||||||
|
const fileNameMatch = contentDisposition.match(/filename="?([^";]+)"?/i);
|
||||||
|
const fileName = fileNameMatch?.[1] || `exoscale-storage-${Date.now()}.zip`;
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const anchor = document.createElement('a');
|
||||||
|
anchor.href = downloadUrl;
|
||||||
|
anchor.download = fileName;
|
||||||
|
document.body.appendChild(anchor);
|
||||||
|
anchor.click();
|
||||||
|
anchor.remove();
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
|
||||||
|
return { ok: true, fileName };
|
||||||
|
} catch (e: any) {
|
||||||
|
log('❌ Dev Exoscale: archive download error', e?.message || e);
|
||||||
|
return { ok: false, message: 'Network error while downloading Exoscale archive.' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,11 +7,11 @@ import React from 'react'
|
|||||||
import Header from '../../components/nav/Header'
|
import Header from '../../components/nav/Header'
|
||||||
import Footer from '../../components/Footer'
|
import Footer from '../../components/Footer'
|
||||||
import PageTransitionEffect from '../../components/animation/pageTransitionEffect'
|
import PageTransitionEffect from '../../components/animation/pageTransitionEffect'
|
||||||
import { CommandLineIcon, PlayIcon, TrashIcon, ExclamationTriangleIcon, ArrowUpTrayIcon, WrenchScrewdriverIcon, FolderOpenIcon, ArrowPathIcon } from '@heroicons/react/24/outline'
|
import { CommandLineIcon, PlayIcon, TrashIcon, ExclamationTriangleIcon, ArrowUpTrayIcon, WrenchScrewdriverIcon, FolderOpenIcon, ArrowPathIcon, ArrowDownTrayIcon } from '@heroicons/react/24/outline'
|
||||||
import useAuthStore from '../../store/authStore'
|
import useAuthStore from '../../store/authStore'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { importSqlDump, SqlExecutionData, SqlExecutionMeta } from './hooks/executeSql'
|
import { importSqlDump, SqlExecutionData, SqlExecutionMeta } from './hooks/executeSql'
|
||||||
import { createFolderStructure, listFolderStructureIssues, listLooseFiles, listGhostDirectories, moveLooseFilesToContract, FixResult, FolderStructureIssueUser, GhostDirectory, LooseFileUser } from './hooks/exoscaleMaintenance'
|
import { createFolderStructure, listFolderStructureIssues, listLooseFiles, listGhostDirectories, moveLooseFilesToContract, downloadExoscaleArchive, FixResult, FolderStructureIssueUser, GhostDirectory, LooseFileUser } from './hooks/exoscaleMaintenance'
|
||||||
|
|
||||||
export default function DevManagementPage() {
|
export default function DevManagementPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -70,6 +70,8 @@ export default function DevManagementPage() {
|
|||||||
const [structureStatus, setStructureStatus] = React.useState('')
|
const [structureStatus, setStructureStatus] = React.useState('')
|
||||||
const [looseStatus, setLooseStatus] = React.useState('')
|
const [looseStatus, setLooseStatus] = React.useState('')
|
||||||
const [ghostStatus, setGhostStatus] = React.useState('')
|
const [ghostStatus, setGhostStatus] = React.useState('')
|
||||||
|
const [archiveLoading, setArchiveLoading] = React.useState(false)
|
||||||
|
const [archiveStatus, setArchiveStatus] = React.useState('')
|
||||||
|
|
||||||
const formatNow = () => new Date().toLocaleString()
|
const formatNow = () => new Date().toLocaleString()
|
||||||
|
|
||||||
@ -153,6 +155,23 @@ export default function DevManagementPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runArchiveDownload = async () => {
|
||||||
|
setExoscaleError('')
|
||||||
|
setArchiveLoading(true)
|
||||||
|
setArchiveStatus('Preparing ZIP export...')
|
||||||
|
try {
|
||||||
|
const res = await downloadExoscaleArchive()
|
||||||
|
if (!res.ok) {
|
||||||
|
setExoscaleError(res.message || 'Failed to download Exoscale archive.')
|
||||||
|
setArchiveStatus('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setArchiveStatus(`Download started: ${res.fileName || formatNow()}`)
|
||||||
|
} finally {
|
||||||
|
setArchiveLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const runCreateStructure = async (userId?: number) => {
|
const runCreateStructure = async (userId?: number) => {
|
||||||
setExoscaleError('')
|
setExoscaleError('')
|
||||||
if (userId) {
|
if (userId) {
|
||||||
@ -283,6 +302,25 @@ export default function DevManagementPage() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
|
<section className="mb-6 rounded-2xl border border-blue-100 bg-white/90 p-4 shadow-lg">
|
||||||
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-blue-900">Exoscale Backup Export</h2>
|
||||||
|
<p className="text-sm text-gray-600">Ladet alle Dateien aus dem Exoscale Storage und liefert sie als ZIP-Download aus.</p>
|
||||||
|
{archiveStatus && (
|
||||||
|
<div className="mt-1 text-xs text-slate-500">{archiveStatus}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={runArchiveDownload}
|
||||||
|
disabled={archiveLoading}
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-lg bg-blue-900 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-800 disabled:opacity-60"
|
||||||
|
>
|
||||||
|
<ArrowDownTrayIcon className="h-4 w-4" /> {archiveLoading ? 'Creating ZIP...' : 'Download Exoscale ZIP'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div className="inline-flex w-full sm:w-auto rounded-xl border border-blue-100 bg-white/90 p-1 shadow-sm">
|
<div className="inline-flex w-full sm:w-auto rounded-xl border border-blue-100 bg-white/90 p-1 shadow-sm">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('sql')}
|
onClick={() => setActiveTab('sql')}
|
||||||
|
|||||||
2144
src/app/i18n/translations/kk.ts
Normal file
2144
src/app/i18n/translations/kk.ts
Normal file
File diff suppressed because it is too large
Load Diff
2144
src/app/i18n/translations/sl.ts
Normal file
2144
src/app/i18n/translations/sl.ts
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user