diff --git a/lunar/.env.local b/lunar/.env.local index b6366b0a6..8a5e7944f 100644 --- a/lunar/.env.local +++ b/lunar/.env.local @@ -1,2 +1,2 @@ -RELAY_API_URL=http://34.84.172.121 +NEXT_PUBLIC_RELAY_API_URL=http://34.84.172.121 NEXT_PUBLIC_API_URL=http://localhost:8000 diff --git a/lunar/src-tauri/Cargo.toml b/lunar/src-tauri/Cargo.toml index 8bacc4626..133d09eb6 100644 --- a/lunar/src-tauri/Cargo.toml +++ b/lunar/src-tauri/Cargo.toml @@ -16,8 +16,9 @@ tauri-build = { version = "1.5.3", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.7.0", features = [] } +tauri = { version = "1.7.0", features = ["process-command-api"] } mega = { workspace = true } +tokio = { workspace = true } [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. diff --git a/lunar/src-tauri/src/main.rs b/lunar/src-tauri/src/main.rs index 90a777aee..cbed908ca 100644 --- a/lunar/src-tauri/src/main.rs +++ b/lunar/src-tauri/src/main.rs @@ -1,24 +1,111 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use std::str::FromStr; +use std::sync::Arc; + +use serde::Deserialize; +use tauri::api::process::{Command, CommandChild, CommandEvent}; +use tauri::State; +use tokio::sync::Mutex; + +struct ServiceState { + child: Option, +} + +#[derive(Debug, Deserialize, Clone)] +struct MegaStartParams { + pub bootstrap_node: String, +} + +impl Default for MegaStartParams { + fn default() -> Self { + Self { + bootstrap_node: String::from_str("http://34.84.172.121/relay").unwrap(), + } + } +} + +#[tauri::command] +async fn start_mega_service( + state: State<'_, Arc>>, + params: MegaStartParams, +) -> Result<(), String> { + let mut service_state = state.lock().await; + if service_state.child.is_some() { + return Err("Service is already running".into()); + } + + let (mut rx, child) = Command::new_sidecar("mega") + .expect("Failed to create `mega` binary command") + .args([ + "service", + "http", + "--bootstrap-node", + ¶ms.bootstrap_node, + ]) + .spawn() + .expect("Failed to spawn `Mega service`"); + + service_state.child = Some(child); + + tauri::async_runtime::spawn(async move { + while let Some(event) = rx.recv().await { + if let CommandEvent::Stdout(line) = event { + print!("{}", line); + } + } + }); + Ok(()) +} + #[tauri::command] -fn hello_string(name: &str) -> String { - format!("Hello from Rust, {}!", name) +async fn stop_mega_service(state: State<'_, Arc>>) -> Result<(), String> { + let mut service_state = state.lock().await; + if let Some(child) = service_state.child.take() { + child.kill().map_err(|e| e.to_string())?; + } else { + println!("Mega Service is not running"); + } + Ok(()) } -fn start_mega() { - let args_str = "service http --ztm agent --bootstrap-node http://34.84.172.121/relay".to_string(); - let args = args_str.split(' ').collect(); - mega::cli::parse(Some(args)).expect("failed to start mega"); +#[tauri::command] +async fn restart_mega_service( + state: State<'_, Arc>>, + params: MegaStartParams, +) -> Result<(), String> { + stop_mega_service(state.clone()).await?; + start_mega_service(state, params).await?; + Ok(()) +} + +#[tauri::command] +async fn mega_service_status(state: State<'_, Arc>>) -> Result { + let service_state = state.lock().await; + Ok(service_state.child.is_some()) } fn main() { + // let params = MegaStartParams::default(); tauri::Builder::default() - .invoke_handler(tauri::generate_handler![hello_string]) + .manage(Arc::new(Mutex::new(ServiceState { child: None }))) + .invoke_handler(tauri::generate_handler![ + start_mega_service, + stop_mega_service, + restart_mega_service, + mega_service_status + ]) .setup(|_| { - std::thread::spawn(move || { - start_mega(); - }); + // let app_handle = app.handle(); + // let state = app.state::>>().clone(); + // tauri::async_runtime::spawn(async move { + // if let Err(e) = start_mega_service(state, params).await { + // eprintln!("Failed to restart rust_service: {}", e); + // } else { + // println!("Rust service restarted successfully"); + // } + // }); Ok(()) }) .run(tauri::generate_context!()) diff --git a/lunar/src-tauri/tauri.conf.json b/lunar/src-tauri/tauri.conf.json index 5b20b8e3c..c8becffcf 100644 --- a/lunar/src-tauri/tauri.conf.json +++ b/lunar/src-tauri/tauri.conf.json @@ -64,10 +64,10 @@ "windows": [ { "fullscreen": false, - "height": 600, + "height": 768, "resizable": true, "title": "lunar-app", - "width": 800 + "width": 1366 } ] } diff --git a/lunar/src/app/api/fetcher.ts b/lunar/src/app/api/fetcher.ts index c6ac2113f..6f79b469d 100644 --- a/lunar/src/app/api/fetcher.ts +++ b/lunar/src/app/api/fetcher.ts @@ -1,21 +1,7 @@ import useSWR from "swr"; const endpoint = process.env.NEXT_PUBLIC_API_URL; - -const fetchWithToken = async (url, token) => { - - return fetch(url, { - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - } - }).then(res => { - if (!res.ok) { - throw new Error('An error occurred while fetching the data.'); - } - return res.json(); - }); -}; +const relay = process.env.NEXT_PUBLIC_RELAY_API_URL; export class FetchError extends Error { info: any; @@ -38,20 +24,10 @@ const fetcher = async url => { return res.json() } -export function useUser(token) { - const { data, error, isLoading } = useSWR(token ? [`${endpoint}/auth/github/user`, token] : null, ([url, token]) => fetchWithToken(url, token), { - dedupingInterval: 300000, // The request will not be repeated for 5 minutes - }) - return { - user: data, - isLoading, - isError: error, - } -} export function useTreeCommitInfo(path) { const { data, error, isLoading } = useSWR(`${endpoint}/api/v1/tree/commit-info?path=${path}`, fetcher, { - dedupingInterval: 300000, + dedupingInterval: 60000, }) return { tree: data, @@ -62,7 +38,7 @@ export function useTreeCommitInfo(path) { export function useBlobContent(path) { const { data, error, isLoading } = useSWR(`${endpoint}/api/v1/blob?path=${path}`, fetcher, { - dedupingInterval: 300000, + dedupingInterval: 60000, }) return { blob: data, @@ -70,3 +46,14 @@ export function useBlobContent(path) { isBlobError: error, } } + +export function useRepoList() { + const { data, error, isLoading } = useSWR(`${relay}/relay/api/v1/repo_list`, fetcher, { + dedupingInterval: 30000, + }) + return { + data: data, + isLoading, + isError: error, + } +} diff --git a/lunar/src/app/api/relay/repo_list/route.ts b/lunar/src/app/api/relay/repo_list/route.ts deleted file mode 100644 index f6e8032ff..000000000 --- a/lunar/src/app/api/relay/repo_list/route.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const revalidate = 0 - -const endpoint = process.env.RELAY_API_URL; - -export async function GET(request: Request, ) { - const res = await fetch(`${endpoint}/relay/repo_list`, { - }) - const data = await res.json() - return Response.json({ data }) -} \ No newline at end of file diff --git a/lunar/src/app/application-layout.tsx b/lunar/src/app/application-layout.tsx index 65f447be9..a9f615bb3 100644 --- a/lunar/src/app/application-layout.tsx +++ b/lunar/src/app/application-layout.tsx @@ -43,8 +43,10 @@ import { CodeBracketSquareIcon, ArchiveBoxArrowDownIcon, } from '@heroicons/react/20/solid' +import { invoke } from '@tauri-apps/api' import { usePathname } from 'next/navigation' import { useState, useEffect } from 'react' +import { Badge } from 'antd/lib' function AccountDropdownMenu({ anchor }: { anchor: 'top start' | 'bottom end' }) { return ( @@ -79,32 +81,41 @@ export function ApplicationLayout({ children: React.ReactNode }) { let pathname = usePathname() - const [token, setToken] = useState("") + + const [mega_status, setMegaStatus] = useState(false) useEffect(() => { - if (typeof window !== 'undefined') { - setToken(localStorage.getItem("access_token") || "") - } + const fetchStatus = () => { + invoke('mega_service_status') + .then((status: boolean) => { + setMegaStatus(status); + console.log(`Service Status: ${status}`); + }) + .catch((error) => { + console.error(`Failed to get service status: ${error}`); + }); + }; + fetchStatus(); + // Set up interval to fetch status every 10 seconds + const interval = setInterval(fetchStatus, 10000); + // Clean up interval on unmount + return () => clearInterval(interval); }, []) - // const { user, isLoading, isError } = useUser(token); - // if (isLoading) return ; - return ( - {/* { - token && - - - - - - - - - } */} + + + + + + + + + + } sidebar={ @@ -113,7 +124,8 @@ export function ApplicationLayout({ - Mega + Mega Status: + diff --git a/lunar/src/app/page.tsx b/lunar/src/app/page.tsx index 3df4d7095..d3e4f6743 100644 --- a/lunar/src/app/page.tsx +++ b/lunar/src/app/page.tsx @@ -59,7 +59,7 @@ export default function HomePage() { } { - (!isTreeLoading && !isBlobLoading) && + (tree && blob) && } diff --git a/lunar/src/app/repo/page.tsx b/lunar/src/app/repo/page.tsx index 273db7085..3c3c9bd4c 100644 --- a/lunar/src/app/repo/page.tsx +++ b/lunar/src/app/repo/page.tsx @@ -1,34 +1,23 @@ 'use client' -import React, { useEffect, useState } from 'react' import DataList from '@/components/DataList' +import { useRepoList } from '../api/fetcher'; +import { Skeleton } from "antd"; -export default function HomePage() { - const [repo_list, setRepoList] = useState([]); - - useEffect(() => { - const fetchData = async () => { - try { - let repo_list = await getRepoList(); - setRepoList(repo_list); - } catch (error) { - console.error('Error fetching data:', error); - } - }; - fetchData(); - }, []); +export default function RepoPage() { + const { data, isLoading, isError } = useRepoList(); return (
- + { + isLoading && + + } + { + !isLoading && + + }
) -} - -async function getRepoList() { - const res = await fetch(`api/relay/repo_list`); - const response = await res.json(); - const repo_list = response.data; - return repo_list } \ No newline at end of file diff --git a/lunar/src/app/settings/page.tsx b/lunar/src/app/settings/page.tsx index 4b43dffb4..c410732e0 100644 --- a/lunar/src/app/settings/page.tsx +++ b/lunar/src/app/settings/page.tsx @@ -1,12 +1,75 @@ 'use client' -import { Button } from '@/components/catalyst/button' import { Divider } from '@/components/catalyst/divider' import { Heading, Subheading } from '@/components/catalyst/heading' import { Input } from '@/components/catalyst/input' import { Text } from '@/components/catalyst/text' +import { invoke } from '@tauri-apps/api/tauri' +import { useState } from 'react' +import { Button } from "antd"; + +interface MegaStartParams { + bootstrap_node: string, +} export default function Settings() { + + const [loadings, setLoadings] = useState([]); + const [params, setParams] = useState({ + bootstrap_node: "", + }); + + const enterLoading = (index: number) => { + setLoadings((prevLoadings) => { + const newLoadings = [...prevLoadings]; + newLoadings[index] = true; + return newLoadings; + }); + setTimeout(() => { + setLoadings((prevLoadings) => { + const newLoadings = [...prevLoadings]; + newLoadings[index] = false; + return newLoadings; + }); + }, 6000); + } + + const startMega = async () => { + try { + await invoke('start_mega_service', { params: { params } }); + console.log('Mega service started successfully'); + } catch (error) { + console.error('Failed to start Mega service', error); + } + }; + + const stopMega = async () => { + try { + await invoke('stop_mega_service'); + console.log('Mega service stopped successfully'); + } catch (error) { + console.error('Failed to stop Mega service', error); + } + }; + + const restartMega = async () => { + enterLoading(1); + try { + await invoke('restart_mega_service', { params: params }); + console.log('Mega service restarted successfully'); + } catch (error) { + console.error('Failed to restart Mega service', error); + } + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setParams((prevParams) => ({ + ...prevParams, + [name]: value, + })); + }; + return (
Settings @@ -15,20 +78,23 @@ export default function Settings() {
ZTM Server IP Address - This will be restart ZTM server. + This will be restart Mega server.
- +
- - +
)