Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src-tauri/src/cmd/http_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ use tauri::State;
pub async fn get_process_list(_state: State<'_, AppState>) -> Result<Vec<ProcessStatus>, String> {
let api_key = get_api_key();
let port = get_server_port();
println!("API Key: {api_key}");
println!("Server Port: {port}");
let client = reqwest::Client::new();
let response = client
.get(format!("http://127.0.0.1:{port}/api/v1/processes"))
Expand Down
46 changes: 45 additions & 1 deletion src-tauri/src/cmd/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,50 @@ pub async fn get_logs(source: Option<String>) -> Result<Vec<String>, String> {
}

#[tauri::command]
pub async fn clear_logs() -> Result<bool, String> {
pub async fn clear_logs(source: Option<String>) -> Result<bool, String> {
let app_dir = env::current_exe().unwrap().parent().unwrap().to_path_buf();

let log_files = match source.as_deref() {
Some("openlist") => vec![app_dir.join("data/log/log.log")],
Some("app") => vec![app_dir.join("logs/app.log")],
Some("rclone") => vec![app_dir.join("logs/process_rclone.log")],
Some("openlist_core") => vec![app_dir.join("logs/process_openlist_core.log")],
None => vec![
app_dir.join("data/log/log.log"),
app_dir.join("logs/app.log"),
app_dir.join("logs/process_rclone.log"),
app_dir.join("logs/process_openlist_core.log"),
],
_ => return Err("Invalid log source".to_string()),
};

let mut cleared_count = 0;
let mut errors = Vec::new();

for log_file in log_files {
if log_file.exists() {
match std::fs::write(&log_file, "") {
Ok(_) => {
cleared_count += 1;
}
Err(e) => {
let error_msg = format!("Failed to clear {log_file:?}: {e}");
errors.push(error_msg);
}
}
}
}

if !errors.is_empty() {
return Err(format!(
"Some log files could not be cleared: {}",
errors.join(", ")
));
}

if cleared_count == 0 {
return Err("No log files found to clear".to_string());
}

Ok(true)
}
1 change: 0 additions & 1 deletion src-tauri/src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ pub fn update_tray_menu_delayed(
service_running: bool,
) -> tauri::Result<()> {
let app_handle_clone = app_handle.clone();
println!("Scheduling delayed tray menu update...");
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(3000));
if let Err(e) = update_tray_menu(&app_handle_clone, service_running) {
Expand Down
4 changes: 2 additions & 2 deletions src/api/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ export class TauriAPI {
return await invoke('get_logs', { source })
}

static async clearLogs(): Promise<boolean> {
return await invoke('clear_logs')
static async clearLogs(source?: 'openlist' | 'rclone' | 'app' | 'openlist_core'): Promise<boolean> {
return await invoke('clear_logs', { source })
}

static async getAdminPassword(): Promise<string> {
Expand Down
9 changes: 7 additions & 2 deletions src/composables/useLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,13 @@ export function useLogs() {
}
}

const clearLogs = async () => {
await store.clearLogs()
const clearLogs = async (source?: 'openlist' | 'rclone' | 'app') => {
try {
await store.clearLogs(source)
} catch (error) {
console.error('Failed to clear logs:', error)
throw error
}
}

const copyLogsToClipboard = async () => {
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,13 @@
"search": {
"placeholder": "Search logs... (Ctrl+F)"
},
"notifications": {
"copySuccess": "Successfully copied {count} logs entries to clipboard",
"copyFailed": "Failed to copy logs to clipboard",
"exportSuccess": "Successfully exported {count} logs entries to file",
"clearSuccess": "Logs cleared successfully",
"clearFailed": "Failed to clear logs"
},
"toolbar": {
"pause": "Pause (Space)",
"resume": "Resume (Space)",
Expand Down Expand Up @@ -368,7 +375,6 @@
"fontSize": "Font Size:",
"maxLines": "Max Lines:",
"showTimestamp": "Show Timestamp",
"showLevel": "Show Level",
"showSource": "Show Source",
"compactMode": "Compact Mode",
"stripAnsiColors": "Strip ANSI Colors"
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,13 @@
"search": {
"placeholder": "搜索日志... (Ctrl+F)"
},
"notifications": {
"copySuccess": "成功复制 {count} 条日志到剪贴板",
"copyFailed": "复制日志到剪贴板失败",
"exportSuccess": "成功导出 {count} 条日志到文件",
"clearSuccess": "日志清理成功",
"clearFailed": "清理日志失败"
},
"toolbar": {
"pause": "暂停 (Space)",
"resume": "恢复 (Space)",
Expand Down Expand Up @@ -368,7 +375,6 @@
"fontSize": "字体大小:",
"maxLines": "最大行数:",
"showTimestamp": "显示时间戳",
"showLevel": "显示级别",
"showSource": "显示来源",
"compactMode": "紧凑模式",
"stripAnsiColors": "去除 ANSI 颜色"
Expand Down
16 changes: 12 additions & 4 deletions src/stores/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,12 +543,20 @@ export const useAppStore = defineStore('app', () => {
}
}

async function clearLogs() {
async function clearLogs(source?: 'openlist' | 'rclone' | 'app') {
try {
await TauriAPI.clearLogs()
logs.value = []
loading.value = true
const result = await TauriAPI.clearLogs(source)
if (result) {
logs.value = []
} else {
throw new Error('Failed to clear logs - backend returned false')
}
} catch (err) {
console.error('Failed to clear logs:', err)
error.value = 'Failed to clear logs'
throw err
} finally {
loading.value = false
}
}

Expand Down
79 changes: 66 additions & 13 deletions src/views/LogView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
ArrowUp,
ArrowDown,
Maximize2,
Minimize2
Minimize2,
AlertCircle,
Info,
AlertTriangle
} from 'lucide-vue-next'
import * as chrono from 'chrono-node'

Expand All @@ -33,10 +36,12 @@ const showFilters = ref(false)
const showSettings = ref(false)
const fontSize = ref(13)
const maxLines = ref(1000)
const showLevel = ref(true)
const isCompactMode = ref(false)
const isFullscreen = ref(false)
const stripAnsiColors = ref(true)
const showNotification = ref(false)
const notificationMessage = ref('')
const notificationType = ref<'success' | 'info' | 'warning' | 'error'>('success')

watch(
filterSource,
Expand All @@ -48,6 +53,16 @@ watch(

let logRefreshInterval: NodeJS.Timeout | null = null

const showNotificationMessage = (message: string, type: 'success' | 'info' | 'warning' | 'error' = 'success') => {
notificationMessage.value = message
notificationType.value = type
showNotification.value = true

setTimeout(() => {
showNotification.value = false
}, 3000)
}

const stripAnsiCodes = (text: string): string => {
return text.replace(/\u001b\[[0-9;]*[mGKHF]/g, '')
}
Expand Down Expand Up @@ -173,25 +188,51 @@ const scrollToTop = () => {

const clearLogs = async () => {
if (confirm(t('logs.messages.confirmClear'))) {
await store.clearLogs()
selectedEntries.value.clear()
try {
await store.clearLogs(
(filterSource.value !== 'all' && filterSource.value !== 'gin' ? filterSource.value : 'openlist') as
| 'openlist'
| 'rclone'
| 'app'
)
selectedEntries.value.clear()
showNotificationMessage(t('logs.notifications.clearSuccess'), 'success')
} catch (error) {
console.error('Failed to clear logs:', error)
showNotificationMessage(t('logs.notifications.clearFailed'), 'error')
}
}
}

const copyLogsToClipboard = async () => {
const logsText = filteredLogs.value
let logsToExport = filteredLogs.value

if (selectedEntries.value.size > 0) {
logsToExport = filteredLogs.value.filter((_, index) => selectedEntries.value.has(index))
}

const logsText = logsToExport
.map((log: any) => `[${log.timestamp || 'N/A'}] [${log.level.toUpperCase()}] [${log.source}] ${log.message}`)
.join('\n')

try {
await navigator.clipboard.writeText(logsText)
const count = selectedEntries.value.size > 0 ? selectedEntries.value.size : filteredLogs.value.length
showNotificationMessage(t('logs.notifications.copySuccess', { count }), 'success')
} catch (error) {
console.error('Failed to copy logs:', error)
showNotificationMessage(t('logs.notifications.copyFailed'), 'error')
}
}

const exportLogs = () => {
const logsText = filteredLogs.value
let logsToExport = filteredLogs.value

if (selectedEntries.value.size > 0) {
logsToExport = filteredLogs.value.filter((_, index) => selectedEntries.value.has(index))
}

const logsText = logsToExport
.map((log: any) => `[${log.timestamp || 'N/A'}] [${log.level.toUpperCase()}] [${log.source}] ${log.message}`)
.join('\n')

Expand All @@ -204,6 +245,9 @@ const exportLogs = () => {
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)

const count = selectedEntries.value.size > 0 ? selectedEntries.value.size : filteredLogs.value.length
showNotificationMessage(t('logs.notifications.exportSuccess', { count }), 'success')
}

const toggleSelectEntry = (index: number) => {
Expand Down Expand Up @@ -497,11 +541,6 @@ onUnmounted(() => {
<input v-model="maxLines" type="number" min="100" max="10000" step="100" class="number-input" />
</div>
<div class="setting-group">
<label class="checkbox-label">
<input v-model="showLevel" type="checkbox" class="checkbox" />
{{ t('logs.settings.showLevel') }}
</label>

<label class="checkbox-label">
<input v-model="isCompactMode" type="checkbox" class="checkbox" />
{{ t('logs.settings.compactMode') }}
Expand All @@ -516,7 +555,7 @@ onUnmounted(() => {
<div class="log-container">
<div class="log-header">
<div class="log-col timestamp">{{ t('logs.headers.timestamp') }}</div>
<div v-if="showLevel" class="log-col level">{{ t('logs.headers.level') }}</div>
<div class="log-col level">{{ t('logs.headers.level') }}</div>
<div class="log-col source">{{ t('logs.headers.source') }}</div>
<div class="log-col message">{{ t('logs.headers.message') }}</div>
<div class="log-col actions">
Expand Down Expand Up @@ -552,7 +591,7 @@ onUnmounted(() => {
<div class="log-col timestamp">
{{ log.timestamp || '--:--:--' }}
</div>
<div v-if="showLevel" class="log-col level">
<div class="log-col level">
<span class="level-badge" :class="log.level">
{{ log.level.toUpperCase() }}
</span>
Expand Down Expand Up @@ -583,6 +622,20 @@ onUnmounted(() => {
</span>
</div>
</div>

<Transition name="notification">
<div v-if="showNotification" class="notification-toast" :class="[`notification-${notificationType}`]">
<div class="notification-content">
<div class="notification-icon">
<Copy v-if="notificationType === 'success'" :size="20" />
<AlertCircle v-else-if="notificationType === 'error'" :size="20" />
<Info v-else-if="notificationType === 'info'" :size="20" />
<AlertTriangle v-else-if="notificationType === 'warning'" :size="20" />
</div>
<span class="notification-message">{{ notificationMessage }}</span>
</div>
</div>
</Transition>
</div>
</template>

Expand Down
Loading