mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 15:06:36 +08:00
feat(pages): configurable limits, multi-format packages, and dual versioning
Make Pages package size and history retention system-configurable, support zip/tar.gz/tar.xz/tar.bz2/tar/7z uploads, prune history with clear keep-N semantics, and rebind agent config to the live active Pages deployment so main-config rollback never depends on pruned packages.
This commit is contained in:
@@ -20,6 +20,8 @@ export type OpenFlareOpsFields = {
|
||||
uptime_kuma_timeout: string;
|
||||
database_auto_cleanup_enabled: boolean;
|
||||
database_auto_cleanup_retention_days: string;
|
||||
pages_max_package_size_mb: string;
|
||||
pages_max_history_count: string;
|
||||
};
|
||||
|
||||
export const defaultOpenFlareOpsFields: OpenFlareOpsFields = {
|
||||
@@ -42,6 +44,8 @@ export const defaultOpenFlareOpsFields: OpenFlareOpsFields = {
|
||||
uptime_kuma_timeout: '48',
|
||||
database_auto_cleanup_enabled: false,
|
||||
database_auto_cleanup_retention_days: '30',
|
||||
pages_max_package_size_mb: '100',
|
||||
pages_max_history_count: '20',
|
||||
};
|
||||
|
||||
export const INSTALLER_SCRIPT_URL =
|
||||
@@ -90,6 +94,8 @@ export function mapOptionsToOpsFields(
|
||||
),
|
||||
database_auto_cleanup_retention_days:
|
||||
optionMap.database_auto_cleanup_retention_days ?? '30',
|
||||
pages_max_package_size_mb: optionMap.pages_max_package_size_mb ?? '100',
|
||||
pages_max_history_count: optionMap.pages_max_history_count ?? '20',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,3 +232,30 @@ export function databaseAutoCleanupEntries(
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function validatePagesFields(fields: OpenFlareOpsFields) {
|
||||
const packageSize = Number.parseInt(fields.pages_max_package_size_mb, 10);
|
||||
const historyCount = Number.parseInt(fields.pages_max_history_count, 10);
|
||||
if (Number.isNaN(packageSize) || packageSize < 1 || packageSize > 2048) {
|
||||
throw new Error('Pages 部署包大小上限必须为 1~2048 MiB。');
|
||||
}
|
||||
if (Number.isNaN(historyCount) || historyCount < 0) {
|
||||
throw new Error(
|
||||
'Pages 历史保留数必须为大于等于 0 的整数(0 表示不限制)。',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function pagesOptionEntries(fields: OpenFlareOpsFields): OptionItem[] {
|
||||
validatePagesFields(fields);
|
||||
return [
|
||||
{
|
||||
key: 'pages_max_package_size_mb',
|
||||
value: String(Number.parseInt(fields.pages_max_package_size_mb, 10)),
|
||||
},
|
||||
{
|
||||
key: 'pages_max_history_count',
|
||||
value: String(Number.parseInt(fields.pages_max_history_count, 10)),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
mapOptionsToOpsFields,
|
||||
type OpenFlareOpsFields,
|
||||
optionsToMap,
|
||||
pagesOptionEntries,
|
||||
uptimeKumaOptionEntries,
|
||||
} from './openflare-ops-utils';
|
||||
import { UptimeKumaSiteSelectModal } from './uptimekuma-site-modal';
|
||||
@@ -258,6 +259,17 @@ export function OpenFlareOpsSettings() {
|
||||
}
|
||||
};
|
||||
|
||||
const savePagesSettings = () => {
|
||||
try {
|
||||
saveMutation.mutate({
|
||||
section: 'pages',
|
||||
entries: pagesOptionEntries(fields),
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : '参数校验失败');
|
||||
}
|
||||
};
|
||||
|
||||
if (optionsQuery.isLoading) {
|
||||
return (
|
||||
<LoadingStateWithBorder
|
||||
@@ -413,6 +425,55 @@ export function OpenFlareOpsSettings() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className='border-dashed shadow-none'>
|
||||
<CardHeader className='flex flex-row items-center justify-between gap-4'>
|
||||
<div>
|
||||
<CardTitle className='text-base'>Pages 静态托管</CardTitle>
|
||||
<CardDescription>
|
||||
配置部署包上传体积上限与每个项目的历史部署保留数量。
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
size='sm'
|
||||
disabled={savingSection === 'pages'}
|
||||
onClick={savePagesSettings}
|
||||
>
|
||||
{savingSection === 'pages' ? (
|
||||
<Loader2 className='size-4 animate-spin mr-1' />
|
||||
) : (
|
||||
<Save className='size-3.5 mr-1' />
|
||||
)}
|
||||
保存
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4'>
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<FieldInput
|
||||
label='部署包大小上限 (MiB)'
|
||||
value={fields.pages_max_package_size_mb}
|
||||
type='number'
|
||||
onChange={(value) =>
|
||||
updateField('pages_max_package_size_mb', value)
|
||||
}
|
||||
placeholder='100'
|
||||
/>
|
||||
<FieldInput
|
||||
label='历史部署保留数(0 不限制)'
|
||||
value={fields.pages_max_history_count}
|
||||
type='number'
|
||||
onChange={(value) =>
|
||||
updateField('pages_max_history_count', value)
|
||||
}
|
||||
placeholder='20'
|
||||
/>
|
||||
</div>
|
||||
<p className='text-xs text-muted-foreground'>
|
||||
每个项目最多保留 N 条部署:激活部署始终保留,其余按从新到旧填充;超出的非激活部署会在上传成功后自动清理。支持
|
||||
zip、tar.gz、tar.xz、tar.bz2、tar、7z 格式。
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className='border-dashed shadow-none'>
|
||||
<CardHeader className='flex flex-row items-center justify-between gap-4'>
|
||||
<div>
|
||||
|
||||
@@ -27,6 +27,27 @@ import {
|
||||
projectsQueryKey,
|
||||
} from './pages-utils';
|
||||
|
||||
const PAGES_PACKAGE_ACCEPT =
|
||||
'.zip,.tar.gz,.tgz,.tar.xz,.txz,.tar.bz2,.tbz2,.tbz,.tar,.7z';
|
||||
|
||||
const PAGES_PACKAGE_EXTENSIONS = [
|
||||
'.zip',
|
||||
'.tar.gz',
|
||||
'.tgz',
|
||||
'.tar.xz',
|
||||
'.txz',
|
||||
'.tar.bz2',
|
||||
'.tbz2',
|
||||
'.tbz',
|
||||
'.tar',
|
||||
'.7z',
|
||||
] as const;
|
||||
|
||||
function isSupportedPagesPackage(fileName: string) {
|
||||
const lower = fileName.toLowerCase();
|
||||
return PAGES_PACKAGE_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
||||
}
|
||||
|
||||
interface DeploymentUploadDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
@@ -58,7 +79,7 @@ export function DeploymentUploadDialog({
|
||||
|
||||
const uploadMutation = useMutation({
|
||||
mutationFn: () => {
|
||||
if (!file) throw new Error('请选择 zip 部署包');
|
||||
if (!file) throw new Error('请选择部署包');
|
||||
return PagesService.uploadDeployment(projectId, {
|
||||
file,
|
||||
onProgress: setUploadProgress,
|
||||
@@ -83,8 +104,8 @@ export function DeploymentUploadDialog({
|
||||
|
||||
const handleFileSelect = (selected: File | null) => {
|
||||
if (!selected) return;
|
||||
if (!selected.name.toLowerCase().endsWith('.zip')) {
|
||||
toast.error('仅支持 zip 格式的文件');
|
||||
if (!isSupportedPagesPackage(selected.name)) {
|
||||
toast.error('仅支持 zip、tar.gz、tar.xz、tar.bz2、tar、7z 格式的部署包');
|
||||
return;
|
||||
}
|
||||
setFile(selected);
|
||||
@@ -96,7 +117,8 @@ export function DeploymentUploadDialog({
|
||||
<DialogHeader>
|
||||
<DialogTitle>上传部署包</DialogTitle>
|
||||
<DialogDescription>
|
||||
上传已构建的 zip 静态资源包,部署后可在列表中激活。
|
||||
上传已构建的静态资源压缩包(zip / tar.gz / tar.xz / tar.bz2 / tar /
|
||||
7z),部署后可在列表中激活。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -121,7 +143,10 @@ export function DeploymentUploadDialog({
|
||||
}}
|
||||
>
|
||||
<UploadCloud className='size-8 mx-auto text-muted-foreground' />
|
||||
<p className='mt-3 text-sm'>拖拽 zip 文件到此处,或点击选择文件</p>
|
||||
<p className='mt-3 text-sm'>拖拽部署包到此处,或点击选择文件</p>
|
||||
<p className='mt-1 text-xs text-muted-foreground'>
|
||||
支持 zip、tar.gz、tar.xz、tar.bz2、tar、7z
|
||||
</p>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
@@ -134,7 +159,7 @@ export function DeploymentUploadDialog({
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type='file'
|
||||
accept='.zip'
|
||||
accept={PAGES_PACKAGE_ACCEPT}
|
||||
className='hidden'
|
||||
onChange={(e) => handleFileSelect(e.target.files?.[0] ?? null)}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,8 @@ export default function PagesPage() {
|
||||
<div>
|
||||
<h1 className='text-2xl font-semibold tracking-tight'>Pages</h1>
|
||||
<p className='text-sm text-muted-foreground'>
|
||||
边缘静态站点托管,上传 zip 部署包并在代理规则中选择 Pages 上游。
|
||||
边缘静态站点托管,上传静态资源部署包并在代理规则中选择 Pages
|
||||
上游。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user