mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-09-05 07:28:04 +08:00
fix: 修复日志页面自动滚动问题 (#382)
This commit is contained in:
@@ -5,4 +5,4 @@ mod response;
|
|||||||
mod routes;
|
mod routes;
|
||||||
mod wrapper;
|
mod wrapper;
|
||||||
|
|
||||||
pub use routes::{MpscWriter, router};
|
pub use routes::{MAX_HISTORY_LOGS, MpscWriter, router};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::response::sse::{Event, KeepAlive, Sse};
|
|||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::{Extension, Router};
|
use axum::{Extension, Router};
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
pub use mpsc::MpscWriter;
|
pub use mpsc::{MAX_HISTORY_LOGS, MpscWriter};
|
||||||
use tokio_stream::wrappers::BroadcastStream;
|
use tokio_stream::wrappers::BroadcastStream;
|
||||||
|
|
||||||
pub(super) fn router() -> Router {
|
pub(super) fn router() -> Router {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use parking_lot::Mutex;
|
|||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tracing_subscriber::fmt::MakeWriter;
|
use tracing_subscriber::fmt::MakeWriter;
|
||||||
|
|
||||||
const MAX_HISTORY_LOGS: usize = 20;
|
pub const MAX_HISTORY_LOGS: usize = 30;
|
||||||
|
|
||||||
pub struct MpscWriter {
|
pub struct MpscWriter {
|
||||||
pub sender: broadcast::Sender<String>,
|
pub sender: broadcast::Sender<String>,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ mod me;
|
|||||||
mod video_sources;
|
mod video_sources;
|
||||||
mod videos;
|
mod videos;
|
||||||
|
|
||||||
pub use logs::MpscWriter;
|
pub use logs::{MAX_HISTORY_LOGS, MpscWriter};
|
||||||
|
|
||||||
pub fn router() -> Router {
|
pub fn router() -> Router {
|
||||||
Router::new().route("/image-proxy", get(image_proxy)).nest(
|
Router::new().route("/image-proxy", get(image_proxy)).nest(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use task::{http_server, video_downloader};
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tokio_util::task::TaskTracker;
|
use tokio_util::task::TaskTracker;
|
||||||
|
|
||||||
use crate::api::MpscWriter;
|
use crate::api::{MAX_HISTORY_LOGS, MpscWriter};
|
||||||
use crate::config::{ARGS, VersionedConfig};
|
use crate::config::{ARGS, VersionedConfig};
|
||||||
use crate::database::setup_database;
|
use crate::database::setup_database;
|
||||||
use crate::utils::init_logger;
|
use crate::utils::init_logger;
|
||||||
@@ -79,7 +79,7 @@ fn spawn_task(
|
|||||||
/// 初始化日志系统、打印欢迎信息,初始化数据库连接和全局配置
|
/// 初始化日志系统、打印欢迎信息,初始化数据库连接和全局配置
|
||||||
async fn init() -> (Arc<DatabaseConnection>, MpscWriter) {
|
async fn init() -> (Arc<DatabaseConnection>, MpscWriter) {
|
||||||
let (tx, _rx) = tokio::sync::broadcast::channel(30);
|
let (tx, _rx) = tokio::sync::broadcast::channel(30);
|
||||||
let log_history = Arc::new(Mutex::new(VecDeque::with_capacity(20)));
|
let log_history = Arc::new(Mutex::new(VecDeque::with_capacity(MAX_HISTORY_LOGS + 1)));
|
||||||
let log_writer = MpscWriter::new(tx, log_history.clone());
|
let log_writer = MpscWriter::new(tx, log_history.clone());
|
||||||
|
|
||||||
init_logger(&ARGS.log_level, Some(log_writer.clone()));
|
init_logger(&ARGS.log_level, Some(log_writer.clone()));
|
||||||
|
|||||||
+5
-2
@@ -28,8 +28,8 @@ html {
|
|||||||
--border: oklch(0.929 0.013 255.508);
|
--border: oklch(0.929 0.013 255.508);
|
||||||
--input: oklch(0.929 0.013 255.508);
|
--input: oklch(0.929 0.013 255.508);
|
||||||
--ring: oklch(0.704 0.04 256.788);
|
--ring: oklch(0.704 0.04 256.788);
|
||||||
--chart-1: oklch(0.37 0.04 257);
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
--chart-2: oklch(0.13 0.04 265);
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
@@ -113,6 +113,9 @@ html {
|
|||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--font-mono:
|
||||||
|
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
||||||
|
monospace, ui-sans-serif, system-ui, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@
|
|||||||
<SheetTitle class="text-lg">编辑状态</SheetTitle>
|
<SheetTitle class="text-lg">编辑状态</SheetTitle>
|
||||||
<SheetDescription class="text-muted-foreground space-y-1 text-sm">
|
<SheetDescription class="text-muted-foreground space-y-1 text-sm">
|
||||||
<div>修改视频和分页的下载状态。可以将任务重置为未开始状态,或者标记为已完成。</div>
|
<div>修改视频和分页的下载状态。可以将任务重置为未开始状态,或者标记为已完成。</div>
|
||||||
<div class="font-medium text-red-600">
|
<div class="font-medium text-rose-600">
|
||||||
⚠️ 已完成任务被重置为未开始,任务重新执行时会覆盖现存文件。
|
⚠️ 已完成任务被重置为未开始,任务重新执行时会覆盖现存文件。
|
||||||
</div>
|
</div>
|
||||||
</SheetDescription>
|
</SheetDescription>
|
||||||
|
|||||||
@@ -11,10 +11,11 @@
|
|||||||
|
|
||||||
// 获取状态显示信息
|
// 获取状态显示信息
|
||||||
function getStatusInfo(value: number) {
|
function getStatusInfo(value: number) {
|
||||||
if (value === 7) return { label: '已完成', class: 'text-green-600', dotClass: 'bg-green-500' };
|
if (value === 7)
|
||||||
|
return { label: '已完成', class: 'text-emerald-600', dotClass: 'bg-emerald-600' };
|
||||||
if (value >= 1 && value <= 4)
|
if (value >= 1 && value <= 4)
|
||||||
return { label: `失败${value}次`, class: 'text-red-600', dotClass: 'bg-red-500' };
|
return { label: `失败${value}次`, class: 'text-rose-600', dotClass: 'bg-rose-600' };
|
||||||
return { label: '未开始', class: 'text-yellow-600', dotClass: 'bg-yellow-500' };
|
return { label: '未开始', class: 'text-yellow-600', dotClass: 'bg-yellow-600' };
|
||||||
}
|
}
|
||||||
|
|
||||||
$: statusInfo = getStatusInfo(currentStatus);
|
$: statusInfo = getStatusInfo(currentStatus);
|
||||||
@@ -71,8 +72,8 @@
|
|||||||
onclick={() => onStatusChange(7)}
|
onclick={() => onStatusChange(7)}
|
||||||
{disabled}
|
{disabled}
|
||||||
class="h-7 min-w-[60px] cursor-pointer px-3 text-xs {currentStatus === 7
|
class="h-7 min-w-[60px] cursor-pointer px-3 text-xs {currentStatus === 7
|
||||||
? 'border-green-600 bg-green-600 font-medium text-white hover:bg-green-700'
|
? 'border-emerald-600 bg-emerald-600 font-medium text-white hover:bg-emerald-700'
|
||||||
: 'hover:border-green-400 hover:bg-green-50 hover:text-green-700'}"
|
: 'hover:border-emerald-400 hover:bg-emerald-50 hover:text-emerald-700'}"
|
||||||
>
|
>
|
||||||
已完成
|
已完成
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -33,11 +33,11 @@
|
|||||||
|
|
||||||
function getSegmentColor(status: number): string {
|
function getSegmentColor(status: number): string {
|
||||||
if (status === 7) {
|
if (status === 7) {
|
||||||
return 'bg-emerald-500'; // 恢复更高对比度的绿色
|
return 'bg-emerald-500';
|
||||||
} else if (status === 0) {
|
} else if (status === 0) {
|
||||||
return 'bg-slate-400'; // 恢复更清晰的灰色 - 未开始
|
return 'bg-yellow-500';
|
||||||
} else {
|
} else {
|
||||||
return 'bg-rose-500'; // 恢复更清晰的红色 - 失败
|
return 'bg-rose-500';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
const failed = downloadStatus.filter((status) => status !== 7 && status !== 0).length;
|
const failed = downloadStatus.filter((status) => status !== 7 && status !== 0).length;
|
||||||
|
|
||||||
if (completed === total) {
|
if (completed === total) {
|
||||||
return { text: '完成', color: 'outline' }; // 更简洁的文案
|
return { text: '完成', color: 'outline' };
|
||||||
} else if (failed > 0) {
|
} else if (failed > 0) {
|
||||||
return { text: '失败', color: 'destructive' };
|
return { text: '失败', color: 'destructive' };
|
||||||
} else {
|
} else {
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
<div
|
<div
|
||||||
class="h-1.5 w-full cursor-help rounded-full transition-all {getSegmentColor(
|
class="h-1.5 w-full cursor-help rounded-full transition-all {getSegmentColor(
|
||||||
status
|
status
|
||||||
)} hover:opacity-80"
|
)}"
|
||||||
></div>
|
></div>
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Content>
|
<Tooltip.Content>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<BreadCrumb items={$breadcrumbStore} />
|
<BreadCrumb items={$breadcrumbStore} />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="w-full overflow-y-auto px-6 py-2" style="scrollbar-width: thin;">
|
<div class="w-full overflow-y-auto px-6 py-2" style="scrollbar-width: thin;" id="main">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</Sidebar.Inset>
|
</Sidebar.Inset>
|
||||||
|
|||||||
@@ -87,29 +87,29 @@
|
|||||||
const videoChartConfig = {
|
const videoChartConfig = {
|
||||||
videos: {
|
videos: {
|
||||||
label: '视频数量',
|
label: '视频数量',
|
||||||
color: 'var(--chart-1)'
|
color: 'var(--color-slate-700)'
|
||||||
}
|
}
|
||||||
} satisfies Chart.ChartConfig;
|
} satisfies Chart.ChartConfig;
|
||||||
|
|
||||||
const memoryChartConfig = {
|
const memoryChartConfig = {
|
||||||
used: {
|
used: {
|
||||||
label: '整体占用',
|
label: '整体占用',
|
||||||
color: 'var(--chart-1)'
|
color: 'var(--color-slate-700)'
|
||||||
},
|
},
|
||||||
process: {
|
process: {
|
||||||
label: '程序占用',
|
label: '程序占用',
|
||||||
color: 'var(--chart-2)'
|
color: 'var(--color-slate-950)'
|
||||||
}
|
}
|
||||||
} satisfies Chart.ChartConfig;
|
} satisfies Chart.ChartConfig;
|
||||||
|
|
||||||
const cpuChartConfig = {
|
const cpuChartConfig = {
|
||||||
used: {
|
used: {
|
||||||
label: '整体占用',
|
label: '整体占用',
|
||||||
color: 'var(--chart-1)'
|
color: 'var(--color-slate-700)'
|
||||||
},
|
},
|
||||||
process: {
|
process: {
|
||||||
label: '程序占用',
|
label: '程序占用',
|
||||||
color: 'var(--chart-2)'
|
color: 'var(--color-slate-950)'
|
||||||
}
|
}
|
||||||
} satisfies Chart.ChartConfig;
|
} satisfies Chart.ChartConfig;
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,18 @@
|
|||||||
let logEventSource: EventSource | null = null;
|
let logEventSource: EventSource | null = null;
|
||||||
let logs: Array<{ timestamp: string; level: string; message: string }> = [];
|
let logs: Array<{ timestamp: string; level: string; message: string }> = [];
|
||||||
let shouldAutoScroll = true;
|
let shouldAutoScroll = true;
|
||||||
|
let main: HTMLElement | null = null;
|
||||||
|
|
||||||
function checkScrollPosition() {
|
function checkScrollPosition() {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
|
if (main) {
|
||||||
shouldAutoScroll = scrollTop + clientHeight >= scrollHeight - 5;
|
const { scrollTop, scrollHeight, clientHeight } = main;
|
||||||
|
shouldAutoScroll = scrollTop + clientHeight >= scrollHeight - 5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
if (shouldAutoScroll) {
|
if (shouldAutoScroll && main) {
|
||||||
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' });
|
main.scrollTop = main.scrollHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,23 +48,24 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
setBreadcrumb([{ label: '日志' }]);
|
setBreadcrumb([{ label: '日志' }]);
|
||||||
window.addEventListener('scroll', checkScrollPosition);
|
main = document.getElementById('main');
|
||||||
|
main?.addEventListener('scroll', checkScrollPosition);
|
||||||
startLogStream();
|
startLogStream();
|
||||||
return () => {
|
return () => {
|
||||||
stopLogStream();
|
stopLogStream();
|
||||||
window.removeEventListener('scroll', checkScrollPosition);
|
main?.removeEventListener('scroll', checkScrollPosition);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function getLevelColor(level: string) {
|
function getLevelColor(level: string) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case 'ERROR':
|
case 'ERROR':
|
||||||
return 'text-red-600';
|
return 'text-rose-600';
|
||||||
case 'WARN':
|
case 'WARN':
|
||||||
return 'text-yellow-600';
|
return 'text-yellow-600';
|
||||||
case 'INFO':
|
case 'INFO':
|
||||||
default:
|
default:
|
||||||
return 'text-green-600';
|
return 'text-emerald-600';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -112,8 +112,8 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="h-2 w-2 rounded-full bg-green-500"></div>
|
<div class="h-2 w-2 rounded-full bg-emerald-500"></div>
|
||||||
<span class="text-sm text-green-600">已认证</span>
|
<span class="text-sm text-emerald-600">已认证</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
Reference in New Issue
Block a user