feat: add OpenList upload support and upload tasks monitoring page

Backend:
- Add OpenListRecordArtifactUploader implementing AList-compatible API upload
- Add Uploading status to RecordArtifactUploadStatus enum
- Add MarkUploadStarted() to RecordResult entity for upload progress tracking
- Add ListUploadStatus API endpoint with pagination and status filtering
- Add UploadTaskItemDto and UploadTaskListResponse models
- Add upload segment count stats (uploaded/failed/uploading) to session DTO
- Add OpenListUploadSettingsDto and upload target type OpenList

Frontend:
- Add UploadTasksView page with route /upload-tasks
- Add upload status labels and UploadTaskItem types
- Refactor MainLayout navigation and clean up main.css
- Polish DashboardView, MetricCard, StatusBadge, RightDrawer components
- Update SettingsView to support OpenList upload configuration

Build:
- Add frontend/Dockerfile.arm64 for ARM64 frontend image
- Update build-arm64-image.sh script

Other:
- Add segment_completed_openlist.sh trigger script
- Add prototype/ directory with UI mockups
- Add frontend .dockerignore refinements

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 21:14:50 +08:00
co-authored by Claude Fable 5
parent f8abae0a7a
commit 9df174bb0b
44 changed files with 3962 additions and 1827 deletions
@@ -27,6 +27,7 @@ internal static class RecordModelMapper
StartedAt = recordTask.StartedAt,
EndedAt = recordTask.EndedAt,
DurationSeconds = recordTask.DurationSeconds,
UploadStatus = recordTask.Result?.UploadStatus,
PostProcessStage = runtimeState?.Stage,
PostProcessProgressPercent = runtimeState?.ProgressPercent,
PostProcessDetail = runtimeState?.Detail
@@ -87,6 +88,9 @@ internal static class RecordModelMapper
EndedAt = recordSession.EndedAt,
TotalFileSizeBytes = totalFileSizeBytes,
TotalDanmakuMessageCount = totalDanmakuMessageCount,
UploadedSegmentCount = orderedTasks.Count(item => item.Result?.UploadStatus == RecordArtifactUploadStatus.Succeeded),
FailedUploadSegmentCount = orderedTasks.Count(item => item.Result?.UploadStatus == RecordArtifactUploadStatus.Failed),
UploadingSegmentCount = orderedTasks.Count(item => item.Result?.UploadStatus == RecordArtifactUploadStatus.Uploading),
Tasks = orderedTasks.Select(item => MapTaskWithFallback(
item,
recordSession,
@@ -122,6 +126,7 @@ internal static class RecordModelMapper
StartedAt = recordTask.StartedAt,
EndedAt = recordTask.EndedAt,
DurationSeconds = recordTask.DurationSeconds,
UploadStatus = recordTask.Result?.UploadStatus,
PostProcessStage = runtimeState?.Stage,
PostProcessProgressPercent = runtimeState?.ProgressPercent,
PostProcessDetail = runtimeState?.Detail
@@ -54,6 +54,10 @@ public sealed class SystemSettingsService : ISystemSettingsService
private const string S3SecretKeyKey = "upload.s3.secret_key";
private const string S3PrefixKey = "upload.s3.prefix";
private const string S3ForcePathStyleKey = "upload.s3.force_path_style";
private const string OpenListBaseUrlKey = "upload.openlist.base_url";
private const string OpenListUsernameKey = "upload.openlist.username";
private const string OpenListPasswordKey = "upload.openlist.password";
private const string OpenListBasePathKey = "upload.openlist.base_path";
private const string DouyinProxyEnabledKey = "platform_proxy.douyin.enabled";
private const string DouyinProxyUrlKey = "platform_proxy.douyin.url";
private const string BilibiliProxyEnabledKey = "platform_proxy.bilibili.enabled";
@@ -180,6 +184,13 @@ public sealed class SystemSettingsService : ISystemSettingsService
Prefix = GetValue(lookup, S3PrefixKey, string.Empty),
ForcePathStyle = bool.TryParse(GetValue(lookup, S3ForcePathStyleKey, "false"), out var s3ForcePathStyle) && s3ForcePathStyle
},
OpenListUpload = new OpenListUploadSettingsDto
{
BaseUrl = GetValue(lookup, OpenListBaseUrlKey, string.Empty),
Username = GetValue(lookup, OpenListUsernameKey, string.Empty),
Password = GetValue(lookup, OpenListPasswordKey, string.Empty),
BasePath = GetValue(lookup, OpenListBasePathKey, string.Empty)
},
EnableEventScripts = bool.TryParse(GetValue(lookup, EnableEventScriptsKey, "false"), out var enableEventScripts) && enableEventScripts,
EnableLiveStartedScript = GetEventScriptEnabled(
lookup,
@@ -348,6 +359,11 @@ public sealed class SystemSettingsService : ISystemSettingsService
await UpsertAsync(S3SecretKeyKey, s3Upload.SecretKey, now, cancellationToken);
await UpsertAsync(S3PrefixKey, s3Upload.Prefix.Trim(), now, cancellationToken);
await UpsertAsync(S3ForcePathStyleKey, s3Upload.ForcePathStyle.ToString(), now, cancellationToken);
var openListUpload = request.OpenListUpload ?? new OpenListUploadSettingsDto();
await UpsertAsync(OpenListBaseUrlKey, openListUpload.BaseUrl.Trim(), now, cancellationToken);
await UpsertAsync(OpenListUsernameKey, openListUpload.Username.Trim(), now, cancellationToken);
await UpsertAsync(OpenListPasswordKey, openListUpload.Password, now, cancellationToken);
await UpsertAsync(OpenListBasePathKey, openListUpload.BasePath.Trim(), now, cancellationToken);
foreach (var platformDefinition in LivePlatformCatalog.All)
{
var platformRequestSettings = request.GetPlatformRequestSettings(platformDefinition.Type);