feat(core): Added core startup and debugging features

- Introduced the tauri-plugin-os, tauri-plugin-shell, and tauri-plugin-process plugins
- Added the coreStart function to execute shell commands to start the core
- Added the coreLog page and MainConsole component for debugging output
- Introduced vue-web-terminal to implement a terminal interface
- Removed the old Exe and Group modules and related code
This commit is contained in:
2025-10-24 09:36:10 +08:00
parent 462f70f35d
commit e4f67f0f76
20 changed files with 179 additions and 163 deletions

View File

@@ -15,10 +15,14 @@
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-fs": "~2",
"@tauri-apps/plugin-opener": "^2",
"@tauri-apps/plugin-os": "~2",
"@tauri-apps/plugin-process": "~2",
"@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-sql": "~2",
"vue": "^3.5.21",
"vue-i18n": "^11.1.12",
"vue-router": "^4.5.1",
"vue-web-terminal": "^3.4.1",
"vuetify": "^3.10.1",
"zod": "^4.1.12"
},

View File

@@ -24,3 +24,6 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
tauri-plugin-fs = "2"
tauri-plugin-os = "2"
tauri-plugin-shell = "2"
tauri-plugin-process = "2"

View File

@@ -11,6 +11,9 @@
"sql:default",
"sql:allow-execute",
"fs:default",
"os:default",
"shell:default",
"process:default",
{
"identifier": "fs:allow-write-text-file",
"allow": [
@@ -26,6 +29,28 @@
"path": "$APPCONFIG/*"
}
]
},
{
"identifier": "fs:allow-exists",
"allow": [
{
"path": "$APPCONFIG/*"
}
]
},
{
"identifier": "shell:allow-execute",
"allow": [
{
"name": "exec-sh",
"cmd": "sh",
"args": [
"-c",
"echo 'Hello World!'"
],
"sidecar": false
}
]
}
]
}

View File

@@ -1,3 +0,0 @@
enum Core{
XRAY(String),
}

View File

@@ -1 +0,0 @@

View File

@@ -1,130 +0,0 @@
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
pub struct Exe {
path: PathBuf,
args: Vec<String>,
child: Arc<Mutex<Option<Child>>>,
pub pid: Arc<Mutex<Option<u32>>>,
}
impl Exe {
pub fn new<P: Into<PathBuf>, S: Into<String>>(path: P, args: Vec<S>) -> Self {
Self {
path: path.into(),
args: args.into_iter().map(|s| s.into()).collect(),
child: Arc::new(Mutex::new(None)),
pid: Arc::new(Mutex::new(None)),
}
}
pub fn start<F, E, X>(
&mut self,
mut on_stdout: F,
mut on_stderr: E,
mut on_exit: X,
) -> std::io::Result<()>
where
F: FnMut(String) + Send + 'static,
E: FnMut(String) + Send + 'static,
X: FnMut(i32) + Send + 'static,
{
let mut cmd = Command::new(&self.path);
cmd.args(&self.args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = cmd.spawn()?;
// ✅ 保存 PID
let pid = child.id();
{
*self.pid.lock().unwrap() = Some(pid);
}
// ✅ 异步读取 stdout
if let Some(out) = child.stdout.take() {
let mut reader = BufReader::new(out);
let mut line = String::new();
thread::spawn(move || {
while let Ok(n) = reader.read_line(&mut line) {
if n == 0 {
break;
}
on_stdout(line.clone());
line.clear();
}
});
}
if let Some(err) = child.stderr.take() {
let mut reader = BufReader::new(err);
let mut line = String::new();
thread::spawn(move || {
while let Ok(n) = reader.read_line(&mut line) {
if n == 0 {
break;
}
on_stderr(line.clone());
line.clear();
}
});
}
let child_arc = self.child.clone();
*child_arc.lock().unwrap() = Some(child);
let pid_clone = self.pid.clone();
thread::spawn(move || {
if let Some(mut c) = child_arc.lock().unwrap().take() {
match c.wait() {
Ok(status) => {
let code = status.code().unwrap_or(-1);
*pid_clone.lock().unwrap() = None;
on_exit(code);
}
Err(e) => {
eprintln!("Error waiting for process: {e}");
*pid_clone.lock().unwrap() = None;
on_exit(-1);
}
}
}
});
Ok(())
}
pub fn kill(&self) -> std::io::Result<()> {
let mut guard = self.child.lock().unwrap();
if let Some(child) = guard.as_mut() {
child.kill()?;
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"No running process",
))
}
}
pub fn is_alive(&self) -> bool {
let mut guard = self.child.lock().unwrap();
if let Some(child) = guard.as_mut() {
match child.try_wait() {
Ok(Some(_)) => false, // 已退出
Ok(None) => true, // 仍在运行
Err(_) => false,
}
} else {
false
}
}
pub fn get_pid(&self) -> Option<u32> {
*self.pid.lock().unwrap()
}
}

View File

@@ -1,14 +0,0 @@
#[tauri::command]
pub fn add_group(
group_name: String,
group_subscribe_url: Option<String>,
group_arguments: Option<String>,
) {
let message = match (&group_subscribe_url, &group_arguments) {
(Some(url), Some(args)) => format!("add_group: {} {} {}", group_name, url, args),
(Some(url), None) => format!("add_group: {} {}", group_name, url),
(None, Some(args)) => format!("add_group: {} {}", group_name, args),
(None, None) => format!("add_group: {}", group_name),
};
println!("{}", message);
}

View File

@@ -1,12 +1,8 @@
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
use crate::group::add_group;
use crate::spary::spary_switch;
use tauri_plugin_sql::{Migration, MigrationKind};
mod exe;
mod group;
mod spary;
mod cores;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
@@ -31,6 +27,9 @@ pub fn run() {
}
];
tauri::Builder::default()
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_fs::init())
.plugin(
tauri_plugin_sql::Builder::default()
@@ -38,7 +37,7 @@ pub fn run() {
.build(),
)
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![spary_switch, add_group])
.invoke_handler(tauri::generate_handler![spary_switch])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -1,7 +1,5 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod entity;
fn main() {
spary_lib::run()
}

1
src/components.d.ts vendored
View File

@@ -11,6 +11,7 @@ declare module 'vue' {
AddGroup: typeof import('./components/nodeEdit/addGroup.vue')['default']
AddNode: typeof import('./components/nodeEdit/addNode.vue')['default']
LanguageSwitcher: typeof import('./components/LanguageSwitcher.vue')['default']
MainConsole: typeof import('./components/index/mainConsole.vue')['default']
MainDrawer: typeof import('./components/index/mainDrawer.vue')['default']
NodeList: typeof import('./components/nodeEdit/nodeList.vue')['default']
NodesFloatButton: typeof import('./components/nodeEdit/nodesFloatButton.vue')['default']

View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
import { Message, Terminal } from 'vue-web-terminal';
const initLog: Message[] = [
{
content: 'Spary.S > '
}
]
</script>
<template>
<terminal
name="main-console"
theme="dark"
:show-header="false"
:enable-default-command="false"
cursor-style="block"
context=""
context-suffix=""
:init-log="initLog"
/>
</template>
<style scoped>
</style>

View File

@@ -51,6 +51,12 @@ useI18n()
<language-switcher/>
</template>
</v-list-item>
<v-list-item
prepend-icon="mdi-bug"
:title="$t('app.debug')"
value="coreLog"
@click="router.push('/coreLog')"
></v-list-item>
</v-list>
</v-navigation-drawer>
</template>

View File

@@ -13,18 +13,20 @@
<script setup lang="ts">
import {ref} from "vue";
import {invoke} from "@tauri-apps/api/core";
import {genConfigFile, removeConfigFile} from "@/utils/bootArgs.ts";
import {genConfigFile, removeConfigFile} from "@/utils/coreBootArgs.ts";
import {coreStart} from "@/utils/coreBoot.ts";
const functionStatus = ref<String>("Off");
function toggleFunctionStatus() {
async function toggleFunctionStatus() {
functionStatus.value = functionStatus.value === "Off" ? "On" : "Off";
spary_switch(functionStatus.value === "On")
await spary_switch(functionStatus.value === "On")
if (functionStatus.value === "On") {
genConfigFile()
await genConfigFile()
await coreStart()
} else {
removeConfigFile()
await removeConfigFile()
}
}

View File

@@ -6,7 +6,8 @@
"settings": "设置",
"add": "添加",
"group": "组",
"node": "节点"
"node": "节点",
"debug": "调试"
},
"spary": {
"functionStatus": {

View File

@@ -15,9 +15,11 @@ import { createApp } from 'vue'
// Styles
import 'unfonts.css'
import {createTerminal} from "vue-web-terminal";
const app = createApp(App)
registerPlugins(app)
app.use(createTerminal())
app.mount('#app')

11
src/pages/coreLog.vue Normal file
View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
<main-console />
</template>
<style scoped>
</style>

View File

@@ -21,6 +21,7 @@ declare module 'vue-router/auto-routes' {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/addGroup': RouteRecordInfo<'/addGroup', '/addGroup', Record<never, never>, Record<never, never>>,
'/addNode/[groupId]': RouteRecordInfo<'/addNode/[groupId]', '/addNode/:groupId', { groupId: ParamValue<true> }, { groupId: ParamValue<false> }>,
'/coreLog': RouteRecordInfo<'/coreLog', '/coreLog', Record<never, never>, Record<never, never>>,
'/nodes': RouteRecordInfo<'/nodes', '/nodes', Record<never, never>, Record<never, never>>,
'/settings': RouteRecordInfo<'/settings', '/settings', Record<never, never>, Record<never, never>>,
}
@@ -48,6 +49,10 @@ declare module 'vue-router/auto-routes' {
routes: '/addNode/[groupId]'
views: never
}
'src/pages/coreLog.vue': {
routes: '/coreLog'
views: never
}
'src/pages/nodes.vue': {
routes: '/nodes'
views: never

9
src/utils/coreBoot.ts Normal file
View File

@@ -0,0 +1,9 @@
import {Command} from '@tauri-apps/plugin-shell';
export async function coreStart() {
let result = await Command.create('exec-sh', [
'-c',
"echo 'Hello World!'",
]).execute();
console.log(result);
}

View File

@@ -1,6 +1,10 @@
import {writeTextFile, BaseDirectory, remove} from '@tauri-apps/plugin-fs';
import {writeTextFile, BaseDirectory, remove, exists} from '@tauri-apps/plugin-fs';
export async function removeConfigFile(){
try {
const tokenExists = await exists('config.json', { baseDir: BaseDirectory.AppConfig });
if (!tokenExists) {
return;
}
await remove('config.json', { baseDir: BaseDirectory.AppConfig });
} catch (error) {
// ignore

View File

@@ -697,6 +697,27 @@
dependencies:
"@tauri-apps/api" "^2.8.0"
"@tauri-apps/plugin-os@~2":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-os/-/plugin-os-2.3.1.tgz#2b773d03916fcc272dc23b502f945251bebe64f8"
integrity sha512-ty5V8XDUIFbSnrk3zsFoP3kzN+vAufYzalJSlmrVhQTImIZa1aL1a03bOaP2vuBvfR+WDRC6NgV2xBl8G07d+w==
dependencies:
"@tauri-apps/api" "^2.8.0"
"@tauri-apps/plugin-process@~2":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-process/-/plugin-process-2.3.0.tgz#43cb71f655ab774314c17344b2948e84d5331f8f"
integrity sha512-0DNj6u+9csODiV4seSxxRbnLpeGYdojlcctCuLOCgpH9X3+ckVZIEj6H7tRQ7zqWr7kSTEWnrxtAdBb0FbtrmQ==
dependencies:
"@tauri-apps/api" "^2.6.0"
"@tauri-apps/plugin-shell@~2":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-shell/-/plugin-shell-2.3.1.tgz#e92fb07e7bcf48ba647e5c8ef78e2ea8b469db15"
integrity sha512-jjs2WGDO/9z2pjNlydY/F5yYhNsscv99K5lCmU5uKjsVvQ3dRlDhhtVYoa4OLDmktLtQvgvbQjCFibMl6tgGfw==
dependencies:
"@tauri-apps/api" "^2.8.0"
"@tauri-apps/plugin-sql@~2":
version "2.3.0"
resolved "https://registry.npmjs.org/@tauri-apps/plugin-sql/-/plugin-sql-2.3.0.tgz"
@@ -1166,6 +1187,15 @@ clean-regexp@^1.0.0:
dependencies:
escape-string-regexp "^1.0.5"
clipboard@^2.0.4:
version "2.0.11"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5"
integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
@@ -1246,6 +1276,11 @@ deep-is@^0.1.3:
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz"
@@ -1703,6 +1738,13 @@ globals@^16.3.0, globals@^16.4.0:
resolved "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz"
integrity sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
integrity sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==
dependencies:
delegate "^3.1.2"
graphemer@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz"
@@ -2430,6 +2472,11 @@ scule@^1.3.0:
resolved "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz"
integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==
semver@^7.3.5, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3, semver@^7.7.2:
version "7.7.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
@@ -2505,6 +2552,11 @@ sync-message-port@^1.0.0:
dependencies:
"@pkgr/core" "^0.2.9"
tiny-emitter@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tinyexec@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz"
@@ -2725,6 +2777,13 @@ vue-i18n@^11.1.12:
"@intlify/shared" "11.1.12"
"@vue/devtools-api" "^6.5.0"
vue-json-viewer@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/vue-json-viewer/-/vue-json-viewer-3.0.4.tgz#c1d65515e57d4036defbbc18fa942d7fd5fb9a8b"
integrity sha512-pnC080rTub6YjccthVSNQod2z9Sl5IUUq46srXtn6rxwhW8QM4rlYn+CTSLFKXWfw+N3xv77Cioxw7B4XUKIbQ==
dependencies:
clipboard "^2.0.4"
vue-router@^4.5.1:
version "4.5.1"
resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz"
@@ -2740,7 +2799,15 @@ vue-tsc@^3.0.7:
"@volar/typescript" "2.4.23"
"@vue/language-core" "3.1.1"
vue@^3.5.21:
vue-web-terminal@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/vue-web-terminal/-/vue-web-terminal-3.4.1.tgz#f62b33f5df5dc789e86509340286d94d2632d757"
integrity sha512-+gU28qClqvIZQlzokcvDS2tbFpGfIJKIPc6dvLm2VYX110c6NOh7mV1YrcUESnaE5VQ9DgxqtIbr1YraEA/GRQ==
dependencies:
vue "^3.3.4"
vue-json-viewer "^3.0.4"
vue@^3.3.4, vue@^3.5.21:
version "3.5.22"
resolved "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz"
integrity sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==