mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 10:16:37 +08:00
refactor(pages): 公开部署源任务并默认每日扫描
移除 Pages 部署源任务的 InternalOnly 限制,任务管理可查看与调度; 将 scanner cron 与 GitHub latest 默认检查间隔调整为每天一次, 并优化部署历史列表展示。
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { ChevronDown, ChevronRight, Upload } from 'lucide-react';
|
import { ChevronDown, ChevronRight, Rocket, Upload } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import { EmptyStateWithBorder } from '@/components/layout/empty';
|
import { EmptyStateWithBorder } from '@/components/layout/empty';
|
||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
} from '@/components/ui/card';
|
} from '@/components/ui/card';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
import { type PagesDeployment, PagesService } from '@/lib/services/openflare';
|
import { type PagesDeployment, PagesService } from '@/lib/services/openflare';
|
||||||
import { formatDateTime } from '@/lib/utils';
|
import { cn, formatDateTime } from '@/lib/utils';
|
||||||
|
|
||||||
import { DeploymentUploadDialog } from '../../components/deployment-upload-dialog';
|
import { DeploymentUploadDialog } from '../../components/deployment-upload-dialog';
|
||||||
import {
|
import {
|
||||||
@@ -69,6 +69,15 @@ type PendingAction = {
|
|||||||
deployment: PagesDeployment;
|
deployment: PagesDeployment;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isActiveDeployment(
|
||||||
|
deployment: PagesDeployment,
|
||||||
|
activeDeploymentId?: number | null,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
deployment.id === activeDeploymentId || deployment.status === 'active'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function deploymentSnapshot(deployment: PagesDeployment) {
|
function deploymentSnapshot(deployment: PagesDeployment) {
|
||||||
return [
|
return [
|
||||||
SOURCE_LABELS[deployment.source_type],
|
SOURCE_LABELS[deployment.source_type],
|
||||||
@@ -79,6 +88,107 @@ function deploymentSnapshot(deployment: PagesDeployment) {
|
|||||||
.join(' · ');
|
.join(' · ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DeploymentMeta({ deployment }: { deployment: PagesDeployment }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='truncate text-xs text-muted-foreground'>
|
||||||
|
{deployment.checksum.slice(0, 16)} · {deployment.file_count} 个文件 ·{' '}
|
||||||
|
{formatBytes(deployment.total_size)}
|
||||||
|
</p>
|
||||||
|
<p className='text-xs text-muted-foreground'>
|
||||||
|
创建于 {formatDateTime(deployment.created_at)}
|
||||||
|
{deployment.activated_at
|
||||||
|
? ` · 激活于 ${formatDateTime(deployment.activated_at)}`
|
||||||
|
: ''}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeploymentRowProps {
|
||||||
|
deployment: PagesDeployment;
|
||||||
|
active: boolean;
|
||||||
|
expanded: boolean;
|
||||||
|
actionPending: boolean;
|
||||||
|
showActions: boolean;
|
||||||
|
onToggleExpand: () => void;
|
||||||
|
onActivate: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
projectId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeploymentRow({
|
||||||
|
deployment,
|
||||||
|
active,
|
||||||
|
expanded,
|
||||||
|
actionPending,
|
||||||
|
showActions,
|
||||||
|
onToggleExpand,
|
||||||
|
onActivate,
|
||||||
|
onDelete,
|
||||||
|
projectId,
|
||||||
|
}: DeploymentRowProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'rounded-lg border border-dashed',
|
||||||
|
active && 'border-l-4 border-l-solid border-l-primary',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='flex flex-col gap-4 p-4 md:flex-row md:items-center md:justify-between'>
|
||||||
|
<div className='flex min-w-0 items-start gap-2'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
variant='ghost'
|
||||||
|
size='icon-sm'
|
||||||
|
aria-label={expanded ? '收起文件清单' : '展开文件清单'}
|
||||||
|
onClick={onToggleExpand}
|
||||||
|
>
|
||||||
|
{expanded ? <ChevronDown /> : <ChevronRight />}
|
||||||
|
</Button>
|
||||||
|
<div className='flex min-w-0 flex-col gap-2'>
|
||||||
|
<div className='flex flex-wrap items-center gap-2'>
|
||||||
|
<span className='text-sm font-medium'>
|
||||||
|
部署 #{deployment.deployment_number}
|
||||||
|
</span>
|
||||||
|
<Badge variant='secondary'>{deploymentSnapshot(deployment)}</Badge>
|
||||||
|
</div>
|
||||||
|
<DeploymentMeta deployment={deployment} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{showActions ? (
|
||||||
|
<div className='flex gap-2 md:ml-10'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
variant='outline'
|
||||||
|
size='sm'
|
||||||
|
disabled={active || actionPending}
|
||||||
|
onClick={onActivate}
|
||||||
|
>
|
||||||
|
激活
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
variant='destructive'
|
||||||
|
size='sm'
|
||||||
|
disabled={active || actionPending}
|
||||||
|
onClick={onDelete}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{expanded ? (
|
||||||
|
<DeploymentFilesPanel
|
||||||
|
projectId={projectId}
|
||||||
|
deploymentId={deployment.id}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function DeploymentHistory({
|
export function DeploymentHistory({
|
||||||
projectId,
|
projectId,
|
||||||
activeDeploymentId,
|
activeDeploymentId,
|
||||||
@@ -99,16 +209,20 @@ export function DeploymentHistory({
|
|||||||
queryFn: () => PagesService.listDeployments(projectId),
|
queryFn: () => PagesService.listDeployments(projectId),
|
||||||
});
|
});
|
||||||
|
|
||||||
const deployments = useMemo(() => {
|
const { productionDeployment, allDeployments } = useMemo(() => {
|
||||||
const records = [...(deploymentsQuery.data ?? [])];
|
const records = [...(deploymentsQuery.data ?? [])];
|
||||||
return records.sort((left, right) => {
|
records.sort(
|
||||||
const leftActive =
|
(left, right) => right.deployment_number - left.deployment_number,
|
||||||
left.id === activeDeploymentId || left.status === 'active';
|
);
|
||||||
const rightActive =
|
|
||||||
right.id === activeDeploymentId || right.status === 'active';
|
const production =
|
||||||
if (leftActive !== rightActive) return leftActive ? -1 : 1;
|
records.find((item) => isActiveDeployment(item, activeDeploymentId)) ??
|
||||||
return right.deployment_number - left.deployment_number;
|
null;
|
||||||
});
|
|
||||||
|
return {
|
||||||
|
productionDeployment: production,
|
||||||
|
allDeployments: records,
|
||||||
|
};
|
||||||
}, [activeDeploymentId, deploymentsQuery.data]);
|
}, [activeDeploymentId, deploymentsQuery.data]);
|
||||||
|
|
||||||
const invalidateDeploymentState = async () => {
|
const invalidateDeploymentState = async () => {
|
||||||
@@ -153,132 +267,14 @@ export function DeploymentHistory({
|
|||||||
|
|
||||||
const actionPending = activateMutation.isPending || deleteMutation.isPending;
|
const actionPending = activateMutation.isPending || deleteMutation.isPending;
|
||||||
|
|
||||||
return (
|
const toggleExpand = (deploymentId: number) => {
|
||||||
|
setExpandedDeploymentId((current) =>
|
||||||
|
current === deploymentId ? null : deploymentId,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = (
|
||||||
<>
|
<>
|
||||||
<Card className='border-dashed shadow-none'>
|
|
||||||
<CardHeader className='pb-3'>
|
|
||||||
<CardTitle className='text-base'>部署历史</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
部署记录不可变,来源信息是创建部署时的安全快照。
|
|
||||||
</CardDescription>
|
|
||||||
<CardAction>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
size='sm'
|
|
||||||
className='whitespace-nowrap'
|
|
||||||
onClick={() => setUploadOpen(true)}
|
|
||||||
>
|
|
||||||
<Upload data-icon='inline-start' />
|
|
||||||
手动上传
|
|
||||||
</Button>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className='space-y-3'>
|
|
||||||
{deploymentsQuery.isLoading ? (
|
|
||||||
<LoadingStateWithBorder description='加载部署历史...' />
|
|
||||||
) : deploymentsQuery.isError ? (
|
|
||||||
<div className='rounded-lg border p-4'>
|
|
||||||
<ErrorInline
|
|
||||||
message={
|
|
||||||
deploymentsQuery.error instanceof Error
|
|
||||||
? deploymentsQuery.error.message
|
|
||||||
: '部署历史加载失败'
|
|
||||||
}
|
|
||||||
onRetry={() => void deploymentsQuery.refetch()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : deployments.length === 0 ? (
|
|
||||||
<EmptyStateWithBorder
|
|
||||||
title='暂无部署'
|
|
||||||
description='上传本地部署包,或配置 Remote URL / GitHub Release 来源后同步发布。'
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
deployments.map((deployment) => {
|
|
||||||
const active =
|
|
||||||
deployment.id === activeDeploymentId ||
|
|
||||||
deployment.status === 'active';
|
|
||||||
const expanded = expandedDeploymentId === deployment.id;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={deployment.id}
|
|
||||||
className='rounded-lg border border-dashed'
|
|
||||||
>
|
|
||||||
<div className='flex flex-col gap-4 p-4 md:flex-row md:items-center md:justify-between'>
|
|
||||||
<div className='flex min-w-0 items-start gap-2'>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
variant='ghost'
|
|
||||||
size='icon-sm'
|
|
||||||
aria-label={expanded ? '收起文件清单' : '展开文件清单'}
|
|
||||||
onClick={() =>
|
|
||||||
setExpandedDeploymentId(
|
|
||||||
expanded ? null : deployment.id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{expanded ? <ChevronDown /> : <ChevronRight />}
|
|
||||||
</Button>
|
|
||||||
<div className='flex min-w-0 flex-col gap-2'>
|
|
||||||
<div className='flex flex-wrap items-center gap-2'>
|
|
||||||
<span className='text-sm font-medium'>
|
|
||||||
部署 #{deployment.deployment_number}
|
|
||||||
</span>
|
|
||||||
<Badge variant={active ? 'default' : 'outline'}>
|
|
||||||
{active ? 'Production' : '历史部署'}
|
|
||||||
</Badge>
|
|
||||||
<Badge variant='secondary'>
|
|
||||||
{deploymentSnapshot(deployment)}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<p className='truncate text-xs text-muted-foreground'>
|
|
||||||
{deployment.checksum.slice(0, 16)} ·{' '}
|
|
||||||
{deployment.file_count} 个文件 ·{' '}
|
|
||||||
{formatBytes(deployment.total_size)}
|
|
||||||
</p>
|
|
||||||
<p className='text-xs text-muted-foreground'>
|
|
||||||
创建于 {formatDateTime(deployment.created_at)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex gap-2 md:ml-10'>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
variant='outline'
|
|
||||||
size='sm'
|
|
||||||
disabled={active || actionPending}
|
|
||||||
onClick={() =>
|
|
||||||
setPendingAction({ type: 'activate', deployment })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
激活
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
variant='destructive'
|
|
||||||
size='sm'
|
|
||||||
disabled={active || actionPending}
|
|
||||||
onClick={() =>
|
|
||||||
setPendingAction({ type: 'delete', deployment })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{expanded ? (
|
|
||||||
<DeploymentFilesPanel
|
|
||||||
projectId={projectId}
|
|
||||||
deploymentId={deployment.id}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
open={pendingAction !== null}
|
open={pendingAction !== null}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
@@ -326,4 +322,117 @@ export function DeploymentHistory({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (deploymentsQuery.isLoading) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<LoadingStateWithBorder description='加载部署历史...' />
|
||||||
|
{dialogs}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deploymentsQuery.isError) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='rounded-lg border p-4'>
|
||||||
|
<ErrorInline
|
||||||
|
message={
|
||||||
|
deploymentsQuery.error instanceof Error
|
||||||
|
? deploymentsQuery.error.message
|
||||||
|
: '部署历史加载失败'
|
||||||
|
}
|
||||||
|
onRetry={() => void deploymentsQuery.refetch()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{dialogs}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-6'>
|
||||||
|
<Card className='border-dashed shadow-none'>
|
||||||
|
<CardHeader className='pb-3'>
|
||||||
|
<CardTitle className='text-base'>Production</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
当前对外生效的生产部署;切换激活状态后会立即生效。
|
||||||
|
</CardDescription>
|
||||||
|
<CardAction>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
size='sm'
|
||||||
|
className='whitespace-nowrap'
|
||||||
|
onClick={() => setUploadOpen(true)}
|
||||||
|
>
|
||||||
|
<Upload data-icon='inline-start' />
|
||||||
|
手动上传
|
||||||
|
</Button>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-3'>
|
||||||
|
{productionDeployment ? (
|
||||||
|
<DeploymentRow
|
||||||
|
deployment={productionDeployment}
|
||||||
|
active
|
||||||
|
expanded={expandedDeploymentId === productionDeployment.id}
|
||||||
|
actionPending={actionPending}
|
||||||
|
showActions={false}
|
||||||
|
onToggleExpand={() => toggleExpand(productionDeployment.id)}
|
||||||
|
onActivate={() => undefined}
|
||||||
|
onDelete={() => undefined}
|
||||||
|
projectId={projectId}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyStateWithBorder
|
||||||
|
icon={Rocket}
|
||||||
|
title='暂无 Production 部署'
|
||||||
|
description='上传本地部署包,或配置 Remote URL / GitHub Release 来源后同步发布。'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className='border-dashed shadow-none'>
|
||||||
|
<CardHeader className='pb-3'>
|
||||||
|
<CardTitle className='text-base'>All deployments</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
部署记录不可变,来源信息是创建部署时的安全快照。可从历史部署激活或删除。
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-3'>
|
||||||
|
{allDeployments.length === 0 ? (
|
||||||
|
<EmptyStateWithBorder
|
||||||
|
title='暂无部署'
|
||||||
|
description='上传本地部署包,或配置 Remote URL / GitHub Release 来源后同步发布。'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
allDeployments.map((deployment) => {
|
||||||
|
const active = isActiveDeployment(deployment, activeDeploymentId);
|
||||||
|
return (
|
||||||
|
<DeploymentRow
|
||||||
|
key={deployment.id}
|
||||||
|
deployment={deployment}
|
||||||
|
active={active}
|
||||||
|
expanded={expandedDeploymentId === deployment.id}
|
||||||
|
actionPending={actionPending}
|
||||||
|
showActions
|
||||||
|
onToggleExpand={() => toggleExpand(deployment.id)}
|
||||||
|
onActivate={() =>
|
||||||
|
setPendingAction({ type: 'activate', deployment })
|
||||||
|
}
|
||||||
|
onDelete={() =>
|
||||||
|
setPendingAction({ type: 'delete', deployment })
|
||||||
|
}
|
||||||
|
projectId={projectId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{dialogs}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ interface PagesSourceDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_GITHUB_ASSET = 'dist.zip';
|
const DEFAULT_GITHUB_ASSET = 'dist.zip';
|
||||||
const DEFAULT_GITHUB_CHECK_INTERVAL = 60;
|
const DEFAULT_GITHUB_CHECK_INTERVAL = 1440;
|
||||||
const EMPTY_GITHUB_ERRORS: PagesGitHubSourceFormErrors = {
|
const EMPTY_GITHUB_ERRORS: PagesGitHubSourceFormErrors = {
|
||||||
repository: '',
|
repository: '',
|
||||||
releaseTag: '',
|
releaseTag: '',
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ describe('PagesService', () => {
|
|||||||
release_tag: '',
|
release_tag: '',
|
||||||
asset_name: 'dist.zip',
|
asset_name: 'dist.zip',
|
||||||
auto_update_enabled: false,
|
auto_update_enabled: false,
|
||||||
check_interval_minutes: 60,
|
check_interval_minutes: 1440,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(apiClient.post).toHaveBeenCalledWith(
|
expect(apiClient.post).toHaveBeenCalledWith(
|
||||||
@@ -117,7 +117,7 @@ describe('PagesService', () => {
|
|||||||
release_tag: '',
|
release_tag: '',
|
||||||
asset_name: 'dist.zip',
|
asset_name: 'dist.zip',
|
||||||
auto_update_enabled: false,
|
auto_update_enabled: false,
|
||||||
check_interval_minutes: 60,
|
check_interval_minutes: 1440,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const githubLatestSource: PagesGitHubReleaseSource = {
|
|||||||
release_selector: 'latest',
|
release_selector: 'latest',
|
||||||
asset_name: 'dist.zip',
|
asset_name: 'dist.zip',
|
||||||
auto_update_enabled: false,
|
auto_update_enabled: false,
|
||||||
check_interval_minutes: 60,
|
check_interval_minutes: 1440,
|
||||||
sync_status: 'update_available',
|
sync_status: 'update_available',
|
||||||
update_available: true,
|
update_available: true,
|
||||||
last_seen: {
|
last_seen: {
|
||||||
@@ -172,7 +172,7 @@ describe('Pages source UI', () => {
|
|||||||
|
|
||||||
await user.click(screen.getByRole('radio', { name: 'GitHub Release' }));
|
await user.click(screen.getByRole('radio', { name: 'GitHub Release' }));
|
||||||
expect(screen.getByRole('switch', { name: '自动更新' })).not.toBeChecked();
|
expect(screen.getByRole('switch', { name: '自动更新' })).not.toBeChecked();
|
||||||
expect(screen.getByLabelText('检查间隔(分钟)')).toHaveValue(60);
|
expect(screen.getByLabelText('检查间隔(分钟)')).toHaveValue(1440);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submits the GitHub latest automatic update settings', async () => {
|
it('submits the GitHub latest automatic update settings', async () => {
|
||||||
@@ -391,7 +391,7 @@ describe('Pages source UI', () => {
|
|||||||
expect(screen.getByText('自动更新')).toBeVisible();
|
expect(screen.getByText('自动更新')).toBeVisible();
|
||||||
expect(screen.getByText('已关闭')).toBeVisible();
|
expect(screen.getByText('已关闭')).toBeVisible();
|
||||||
expect(screen.getByText('检查间隔')).toBeVisible();
|
expect(screen.getByText('检查间隔')).toBeVisible();
|
||||||
expect(screen.getByText('60 分钟')).toBeVisible();
|
expect(screen.getByText('1440 分钟')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('dispatches a GitHub check and starts TaskExecution polling', async () => {
|
it('dispatches a GitHub check and starts TaskExecution polling', async () => {
|
||||||
@@ -632,7 +632,10 @@ describe('Pages source UI', () => {
|
|||||||
<DeploymentHistory projectId={9} activeDeploymentId={51} />,
|
<DeploymentHistory projectId={9} activeDeploymentId={51} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await screen.findByText('GitHub · v1.2.3 · 手动同步')).toBeVisible();
|
expect(
|
||||||
|
await screen.findAllByText('GitHub · v1.2.3 · 手动同步'),
|
||||||
|
).toHaveLength(2);
|
||||||
expect(screen.getByText('Production')).toBeVisible();
|
expect(screen.getByText('Production')).toBeVisible();
|
||||||
|
expect(screen.getByText('All deployments')).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const (
|
|||||||
|
|
||||||
defaultRemoteAssetLabel = "pages-package"
|
defaultRemoteAssetLabel = "pages-package"
|
||||||
defaultGitHubAssetName = "dist.zip"
|
defaultGitHubAssetName = "dist.zip"
|
||||||
defaultCheckInterval = 60
|
defaultCheckInterval = 1440
|
||||||
minimumCheckInterval = 5
|
minimumCheckInterval = 5
|
||||||
maximumCheckInterval = 1440
|
maximumCheckInterval = 1440
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ const (
|
|||||||
pagesSourceScanBatchSize = 20
|
pagesSourceScanBatchSize = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
// PagesSourceScanMeta is available to the scheduler registry but hidden from
|
// PagesSourceScanMeta describes the periodic Pages source scanner schedule.
|
||||||
// generic Admin task dispatch and schedule mutation APIs.
|
|
||||||
var PagesSourceScanMeta = task.TaskMeta{
|
var PagesSourceScanMeta = task.TaskMeta{
|
||||||
Type: TaskTypePagesSourceScan,
|
Type: TaskTypePagesSourceScan,
|
||||||
AsynqTask: PagesSourceScanTask,
|
AsynqTask: PagesSourceScanTask,
|
||||||
@@ -38,7 +37,6 @@ var PagesSourceScanMeta = task.TaskMeta{
|
|||||||
MaxRetry: 0,
|
MaxRetry: 0,
|
||||||
Queue: task.QueueDefault,
|
Queue: task.QueueDefault,
|
||||||
Retryable: false,
|
Retryable: false,
|
||||||
InternalOnly: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type pagesSourceScanPayload struct{}
|
type pagesSourceScanPayload struct{}
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ func TestPagesSourceScannerIncludesOrphanCleanupSummary(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPagesSourceScanPayloadAndMetaAreInternalOnly(t *testing.T) {
|
func TestPagesSourceScanPayloadAndMeta(t *testing.T) {
|
||||||
handler := &SourceScanHandler{}
|
handler := &SourceScanHandler{}
|
||||||
if normalized, err := handler.ValidatePayload(nil); err != nil || string(normalized) != "{}" {
|
if normalized, err := handler.ValidatePayload(nil); err != nil || string(normalized) != "{}" {
|
||||||
t.Errorf("ValidatePayload(nil) = %s, %v; want {}, nil", normalized, err)
|
t.Errorf("ValidatePayload(nil) = %s, %v; want {}, nil", normalized, err)
|
||||||
@@ -485,9 +485,9 @@ func TestPagesSourceScanPayloadAndMetaAreInternalOnly(t *testing.T) {
|
|||||||
if _, err := handler.ValidatePayload([]byte(`{"unexpected":true}`)); err == nil {
|
if _, err := handler.ValidatePayload([]byte(`{"unexpected":true}`)); err == nil {
|
||||||
t.Error("ValidatePayload(unknown field) error = nil, want non-nil")
|
t.Error("ValidatePayload(unknown field) error = nil, want non-nil")
|
||||||
}
|
}
|
||||||
if !PagesSourceScanMeta.InternalOnly || PagesSourceScanMeta.Type != TaskTypePagesSourceScan ||
|
if PagesSourceScanMeta.InternalOnly || PagesSourceScanMeta.Type != TaskTypePagesSourceScan ||
|
||||||
PagesSourceScanMeta.AsynqTask != PagesSourceScanTask || PagesSourceScanMeta.MaxRetry != 0 {
|
PagesSourceScanMeta.AsynqTask != PagesSourceScanTask || PagesSourceScanMeta.MaxRetry != 0 {
|
||||||
t.Errorf("PagesSourceScanMeta = %+v, want internal bounded scheduled scanner", PagesSourceScanMeta)
|
t.Errorf("PagesSourceScanMeta = %+v, want public bounded scheduled scanner", PagesSourceScanMeta)
|
||||||
}
|
}
|
||||||
if PagesSourceScanMeta.SupportsTime {
|
if PagesSourceScanMeta.SupportsTime {
|
||||||
t.Error("PagesSourceScanMeta.SupportsTime = true, want empty scanner payload")
|
t.Error("PagesSourceScanMeta.SupportsTime = true, want empty scanner payload")
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const (
|
|||||||
|
|
||||||
var errUnexpectedJSONTrailingValue = errors.New("unexpected trailing JSON value")
|
var errUnexpectedJSONTrailingValue = errors.New("unexpected trailing JSON value")
|
||||||
|
|
||||||
// PagesSourceActionMeta is intentionally hidden from generic Admin task APIs.
|
// PagesSourceActionMeta describes check/sync work for Pages deployment sources.
|
||||||
var PagesSourceActionMeta = task.TaskMeta{
|
var PagesSourceActionMeta = task.TaskMeta{
|
||||||
Type: TaskTypePagesSourceAction,
|
Type: TaskTypePagesSourceAction,
|
||||||
AsynqTask: PagesSourceActionTask,
|
AsynqTask: PagesSourceActionTask,
|
||||||
@@ -42,7 +42,6 @@ var PagesSourceActionMeta = task.TaskMeta{
|
|||||||
MaxRetry: 2,
|
MaxRetry: 2,
|
||||||
Queue: task.QueueDefault,
|
Queue: task.QueueDefault,
|
||||||
Retryable: false,
|
Retryable: false,
|
||||||
InternalOnly: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SourceActionPayload is the credential-free internal queue contract.
|
// SourceActionPayload is the credential-free internal queue contract.
|
||||||
|
|||||||
@@ -165,9 +165,9 @@ func TestRemoteCheckActionIsPermanentWithoutExposingURL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPagesSourceActionMetaIsInternalOnly(t *testing.T) {
|
func TestPagesSourceActionMeta(t *testing.T) {
|
||||||
if !PagesSourceActionMeta.InternalOnly {
|
if PagesSourceActionMeta.InternalOnly {
|
||||||
t.Error("PagesSourceActionMeta.InternalOnly = false, want true")
|
t.Error("PagesSourceActionMeta.InternalOnly = true, want false")
|
||||||
}
|
}
|
||||||
if PagesSourceActionMeta.Type != TaskTypePagesSourceAction {
|
if PagesSourceActionMeta.Type != TaskTypePagesSourceAction {
|
||||||
t.Errorf("PagesSourceActionMeta.Type = %q, want %q", PagesSourceActionMeta.Type, TaskTypePagesSourceAction)
|
t.Errorf("PagesSourceActionMeta.Type = %q, want %q", PagesSourceActionMeta.Type, TaskTypePagesSourceAction)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ INSERT INTO w_schedules (name, task_type, cron, payload, is_active, created_at,
|
|||||||
SELECT
|
SELECT
|
||||||
'OpenFlare Pages 部署源扫描',
|
'OpenFlare Pages 部署源扫描',
|
||||||
'of_pages_source_scan',
|
'of_pages_source_scan',
|
||||||
'*/5 * * * *',
|
'0 0 * * *',
|
||||||
'{}',
|
'{}',
|
||||||
TRUE,
|
TRUE,
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
@@ -33,6 +33,6 @@ WHERE NOT EXISTS (
|
|||||||
DELETE FROM w_schedules
|
DELETE FROM w_schedules
|
||||||
WHERE task_type = 'of_pages_source_scan'
|
WHERE task_type = 'of_pages_source_scan'
|
||||||
AND name = 'OpenFlare Pages 部署源扫描'
|
AND name = 'OpenFlare Pages 部署源扫描'
|
||||||
AND cron = '*/5 * * * *'
|
AND cron = '0 0 * * *'
|
||||||
AND payload = '{}'
|
AND payload = '{}'
|
||||||
AND is_active = TRUE;
|
AND is_active = TRUE;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ INSERT INTO w_schedules (name, task_type, cron, payload, is_active, created_at,
|
|||||||
SELECT
|
SELECT
|
||||||
'OpenFlare Pages 部署源扫描',
|
'OpenFlare Pages 部署源扫描',
|
||||||
'of_pages_source_scan',
|
'of_pages_source_scan',
|
||||||
'*/5 * * * *',
|
'0 0 * * *',
|
||||||
'{}',
|
'{}',
|
||||||
1,
|
1,
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
@@ -16,6 +16,6 @@ WHERE NOT EXISTS (
|
|||||||
DELETE FROM w_schedules
|
DELETE FROM w_schedules
|
||||||
WHERE task_type = 'of_pages_source_scan'
|
WHERE task_type = 'of_pages_source_scan'
|
||||||
AND name = 'OpenFlare Pages 部署源扫描'
|
AND name = 'OpenFlare Pages 部署源扫描'
|
||||||
AND cron = '*/5 * * * *'
|
AND cron = '0 0 * * *'
|
||||||
AND payload = '{}'
|
AND payload = '{}'
|
||||||
AND is_active = 1;
|
AND is_active = 1;
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func assertPagesSourceScanSchedule(t *testing.T, gormDB *gorm.DB) model.Schedule
|
|||||||
require.Len(t, schedules, 1)
|
require.Len(t, schedules, 1)
|
||||||
schedule := schedules[0]
|
schedule := schedules[0]
|
||||||
assert.Equal(t, "OpenFlare Pages 部署源扫描", schedule.Name)
|
assert.Equal(t, "OpenFlare Pages 部署源扫描", schedule.Name)
|
||||||
assert.Equal(t, "*/5 * * * *", schedule.Cron)
|
assert.Equal(t, "0 0 * * *", schedule.Cron)
|
||||||
assert.Equal(t, "{}", schedule.Payload)
|
assert.Equal(t, "{}", schedule.Payload)
|
||||||
assert.True(t, schedule.IsActive)
|
assert.True(t, schedule.IsActive)
|
||||||
return schedule
|
return schedule
|
||||||
|
|||||||
Reference in New Issue
Block a user