diff --git a/src-tauri/icons/1024x1024.png b/src-tauri/icons/1024x1024.png
new file mode 100644
index 0000000..312c394
Binary files /dev/null and b/src-tauri/icons/1024x1024.png differ
diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png
index 3ebb6c0..8bab508 100644
Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ
diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png
index a12e82b..054a9b0 100644
Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ
diff --git a/src-tauri/icons/16x16.png b/src-tauri/icons/16x16.png
new file mode 100644
index 0000000..cf51a7f
Binary files /dev/null and b/src-tauri/icons/16x16.png differ
diff --git a/src-tauri/icons/16x16@2x.png b/src-tauri/icons/16x16@2x.png
new file mode 100644
index 0000000..1bbe063
Binary files /dev/null and b/src-tauri/icons/16x16@2x.png differ
diff --git a/src-tauri/icons/256x256.png b/src-tauri/icons/256x256.png
new file mode 100644
index 0000000..054a9b0
Binary files /dev/null and b/src-tauri/icons/256x256.png differ
diff --git a/src-tauri/icons/256x256@2x.png b/src-tauri/icons/256x256@2x.png
new file mode 100644
index 0000000..287d956
Binary files /dev/null and b/src-tauri/icons/256x256@2x.png differ
diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png
index aa859f0..1bbe063 100644
Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ
diff --git a/src-tauri/icons/32x32@2x.png b/src-tauri/icons/32x32@2x.png
new file mode 100644
index 0000000..ea7bb57
Binary files /dev/null and b/src-tauri/icons/32x32@2x.png differ
diff --git a/src-tauri/icons/512x512.png b/src-tauri/icons/512x512.png
new file mode 100644
index 0000000..287d956
Binary files /dev/null and b/src-tauri/icons/512x512.png differ
diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png
new file mode 100644
index 0000000..ea7bb57
Binary files /dev/null and b/src-tauri/icons/64x64.png differ
diff --git a/src-tauri/icons/64x64@2x.png b/src-tauri/icons/64x64@2x.png
new file mode 100644
index 0000000..8bab508
Binary files /dev/null and b/src-tauri/icons/64x64@2x.png differ
diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns
index 5f0f92e..4c601f5 100644
Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ
diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico
index 10843a3..16f2b53 100644
Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ
diff --git a/src-tauri/icons/icon.iconset/icon_128x128.png b/src-tauri/icons/icon.iconset/icon_128x128.png
index 3ebb6c0..8bab508 100644
Binary files a/src-tauri/icons/icon.iconset/icon_128x128.png and b/src-tauri/icons/icon.iconset/icon_128x128.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_128x128@2x.png b/src-tauri/icons/icon.iconset/icon_128x128@2x.png
index a12e82b..054a9b0 100644
Binary files a/src-tauri/icons/icon.iconset/icon_128x128@2x.png and b/src-tauri/icons/icon.iconset/icon_128x128@2x.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_16x16.png b/src-tauri/icons/icon.iconset/icon_16x16.png
index a5350eb..cf51a7f 100644
Binary files a/src-tauri/icons/icon.iconset/icon_16x16.png and b/src-tauri/icons/icon.iconset/icon_16x16.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_16x16@2x.png b/src-tauri/icons/icon.iconset/icon_16x16@2x.png
index 5de0597..1bbe063 100644
Binary files a/src-tauri/icons/icon.iconset/icon_16x16@2x.png and b/src-tauri/icons/icon.iconset/icon_16x16@2x.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_256x256.png b/src-tauri/icons/icon.iconset/icon_256x256.png
index a12e82b..054a9b0 100644
Binary files a/src-tauri/icons/icon.iconset/icon_256x256.png and b/src-tauri/icons/icon.iconset/icon_256x256.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_256x256@2x.png b/src-tauri/icons/icon.iconset/icon_256x256@2x.png
index 3a81290..287d956 100644
Binary files a/src-tauri/icons/icon.iconset/icon_256x256@2x.png and b/src-tauri/icons/icon.iconset/icon_256x256@2x.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_32x32.png b/src-tauri/icons/icon.iconset/icon_32x32.png
index 5de0597..1bbe063 100644
Binary files a/src-tauri/icons/icon.iconset/icon_32x32.png and b/src-tauri/icons/icon.iconset/icon_32x32.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_32x32@2x.png b/src-tauri/icons/icon.iconset/icon_32x32@2x.png
index 016f5a1..ea7bb57 100644
Binary files a/src-tauri/icons/icon.iconset/icon_32x32@2x.png and b/src-tauri/icons/icon.iconset/icon_32x32@2x.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_512x512.png b/src-tauri/icons/icon.iconset/icon_512x512.png
new file mode 100644
index 0000000..287d956
Binary files /dev/null and b/src-tauri/icons/icon.iconset/icon_512x512.png differ
diff --git a/src-tauri/icons/icon.iconset/icon_512x512@2x.png b/src-tauri/icons/icon.iconset/icon_512x512@2x.png
new file mode 100644
index 0000000..312c394
Binary files /dev/null and b/src-tauri/icons/icon.iconset/icon_512x512@2x.png differ
diff --git a/src-tauri/icons/icon.svg b/src-tauri/icons/icon.svg
new file mode 100644
index 0000000..556f4ea
--- /dev/null
+++ b/src-tauri/icons/icon.svg
@@ -0,0 +1,48 @@
+
+
diff --git a/src-tauri/src/commands/extensions.rs b/src-tauri/src/commands/extensions.rs
index 013c380..219b317 100644
--- a/src-tauri/src/commands/extensions.rs
+++ b/src-tauri/src/commands/extensions.rs
@@ -194,3 +194,77 @@ pub fn get_clawapp_status() -> Result {
result.insert("url".into(), Value::String("http://localhost:3210".into()));
Ok(Value::Object(result))
}
+
+/// 一键安装 cftunnel
+#[tauri::command]
+pub async fn install_cftunnel(app: tauri::AppHandle) -> Result {
+ use std::process::Stdio;
+ use std::io::{BufRead, BufReader};
+ use tauri::Emitter;
+
+ let _ = app.emit("install-log", "开始安装 cftunnel...");
+ let _ = app.emit("install-progress", 10);
+
+ // 下载并安装脚本
+ let install_script = r#"
+#!/bin/bash
+set -e
+cd /tmp
+echo "下载 cftunnel..."
+curl -fsSL https://raw.githubusercontent.com/qingchencloud/cftunnel/main/install.sh -o cftunnel-install.sh
+chmod +x cftunnel-install.sh
+echo "执行安装..."
+./cftunnel-install.sh
+echo "安装完成"
+"#;
+
+ let _ = app.emit("install-log", "下载安装脚本...");
+ let _ = app.emit("install-progress", 30);
+
+ let mut child = Command::new("bash")
+ .arg("-c")
+ .arg(install_script)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()
+ .map_err(|e| format!("启动安装进程失败: {e}"))?;
+
+ let stderr = child.stderr.take();
+ let stdout = child.stdout.take();
+
+ // 读取 stderr
+ let app2 = app.clone();
+ let handle = std::thread::spawn(move || {
+ if let Some(pipe) = stderr {
+ for line in BufReader::new(pipe).lines().map_while(Result::ok) {
+ let _ = app2.emit("install-log", &line);
+ }
+ }
+ });
+
+ // 读取 stdout
+ let mut progress = 40;
+ if let Some(pipe) = stdout {
+ for line in BufReader::new(pipe).lines().map_while(Result::ok) {
+ let _ = app.emit("install-log", &line);
+ if progress < 90 {
+ progress += 5;
+ let _ = app.emit("install-progress", progress);
+ }
+ }
+ }
+
+ let _ = handle.join();
+ let _ = app.emit("install-progress", 95);
+
+ let status = child.wait().map_err(|e| format!("等待安装进程失败: {e}"))?;
+ let _ = app.emit("install-progress", 100);
+
+ if !status.success() {
+ let _ = app.emit("install-log", "❌ 安装失败");
+ return Err("安装失败,请查看日志".into());
+ }
+
+ let _ = app.emit("install-log", "✅ cftunnel 安装成功");
+ Ok("安装成功".into())
+}
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 3022685..11652d7 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -46,6 +46,7 @@ pub fn run() {
extensions::cftunnel_action,
extensions::get_cftunnel_logs,
extensions::get_clawapp_status,
+ extensions::install_cftunnel,
])
.run(tauri::generate_context!())
.expect("启动 ClawPanel 失败");
diff --git a/src/lib/tauri-api.js b/src/lib/tauri-api.js
index 1f12670..4879048 100644
--- a/src/lib/tauri-api.js
+++ b/src/lib/tauri-api.js
@@ -178,4 +178,5 @@ export const api = {
cftunnelAction: (action) => invoke('cftunnel_action', { action }),
getCftunnelLogs: (lines = 20) => invoke('get_cftunnel_logs', { lines }),
getClawappStatus: () => invoke('get_clawapp_status'),
+ installCftunnel: () => invoke('install_cftunnel'),
}
diff --git a/src/pages/extensions.js b/src/pages/extensions.js
index 5dc276f..af0b7a0 100644
--- a/src/pages/extensions.js
+++ b/src/pages/extensions.js
@@ -63,8 +63,12 @@ async function loadCftunnel(page) {
function renderCftunnel(el, s) {
if (!s.installed) {
el.innerHTML = `
- cftunnel 未安装
- 前往安装
+ cftunnel 未安装
+
+
`
return
}
@@ -199,6 +203,9 @@ function bindEvents(page) {
case 'clawapp-refresh':
await loadClawapp(page)
break
+ case 'install-cftunnel':
+ await handleInstallCftunnel(page)
+ break
}
})
}
@@ -238,3 +245,58 @@ async function handleCftunnelLogs(page) {
area.innerHTML = `读取日志失败: ${e}
`
}
}
+
+async function handleInstallCftunnel(page) {
+ const area = page.querySelector('#install-progress-area')
+ if (!area) return
+
+ // 显示进度条
+ area.innerHTML = `
+
+ `
+
+ const progressFill = area.querySelector('#install-progress-fill')
+ const progressText = area.querySelector('#install-progress-text')
+ const logBox = area.querySelector('#install-log-box')
+
+ let unlistenLog, unlistenProgress
+ try {
+ const { listen } = await import('@tauri-apps/api/event')
+
+ unlistenLog = await listen('install-log', (e) => {
+ logBox.textContent += e.payload + '\n'
+ logBox.scrollTop = logBox.scrollHeight
+ })
+
+ unlistenProgress = await listen('install-progress', (e) => {
+ const progress = e.payload
+ progressFill.style.width = progress + '%'
+ progressText.textContent = `安装中... ${progress}%`
+ })
+
+ await api.installCftunnel()
+
+ progressFill.classList.add('done')
+ progressText.textContent = '✅ 安装完成'
+ toast('cftunnel 安装成功', 'success')
+
+ // 3 秒后刷新状态
+ setTimeout(() => loadCftunnel(page), 3000)
+ } catch (e) {
+ progressFill.classList.add('error')
+ progressText.textContent = '❌ 安装失败'
+ logBox.textContent += '\n错误: ' + e
+ toast('安装失败: ' + e, 'error')
+ } finally {
+ unlistenLog?.()
+ unlistenProgress?.()
+ }
+}