From 9b049d2c44802a08f8ac94df66f3f2d7408f19d0 Mon Sep 17 00:00:00 2001 From: "benjamin.747" Date: Wed, 21 Aug 2024 17:41:44 +0800 Subject: [PATCH] use libra in lunar app for clone and push code --- Cargo.toml | 1 + gateway/src/api/ztm_router.rs | 4 +- gemini/src/http/handler.rs | 2 +- lunar/.eslintrc.json | 7 +- lunar/src-tauri/Cargo.toml | 1 + lunar/src-tauri/build.rs | 4 + lunar/src-tauri/src/main.rs | 83 ++++++++++++++- lunar/src-tauri/tauri.conf.json | 7 +- lunar/src/app/api/fetcher.ts | 26 ++++- lunar/src/app/api/relay/repo_fork/route.ts | 16 --- lunar/src/app/application-layout.tsx | 18 +++- lunar/src/app/page.tsx | 47 +++----- lunar/src/app/repo/page.tsx | 18 +--- lunar/src/components/CodeTable.tsx | 2 +- lunar/src/components/MergeDetail.tsx | 2 +- .../components/{DataList.tsx => RepoList.tsx} | 100 ++++++++---------- lunar/src/components/RepoTree.tsx | 36 ++++--- 17 files changed, 224 insertions(+), 150 deletions(-) delete mode 100644 lunar/src/app/api/relay/repo_fork/route.ts rename lunar/src/components/{DataList.tsx => RepoList.tsx} (58%) diff --git a/Cargo.toml b/Cargo.toml index 1d3740784..6b556e99c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,3 +81,4 @@ ed25519-dalek = "2.1.1" ctrlc = "3.4.4" git2 = "0.19.0" tempfile = "3.10.1" +home = "0.5.9" diff --git a/gateway/src/api/ztm_router.rs b/gateway/src/api/ztm_router.rs index 62e6d3435..cd423f011 100644 --- a/gateway/src/api/ztm_router.rs +++ b/gateway/src/api/ztm_router.rs @@ -18,7 +18,7 @@ use crate::api::MegaApiServiceState; pub fn routers() -> Router { Router::new() .route("/ztm/repo_provide", post(repo_provide)) - .route("/ztm/repo_fork", get(repo_folk)) + .route("/ztm/repo_fork", get(repo_fork)) .route("/ztm/peer_id", get(peer_id)) .route("/ztm/alias_to_path", get(alias_to_path)) } @@ -59,7 +59,7 @@ async fn repo_provide( Ok(Json(res)) } -async fn repo_folk( +async fn repo_fork( Query(query): Query>, state: State, ) -> Result>, (StatusCode, String)> { diff --git a/gemini/src/http/handler.rs b/gemini/src/http/handler.rs index 42d0de474..d97b329c7 100644 --- a/gemini/src/http/handler.rs +++ b/gemini/src/http/handler.rs @@ -134,7 +134,7 @@ pub async fn repo_folk_alias(ztm_agent_port: u16, identifier: String) -> Result< Err(e) => return Err(e), } - let msg = format!("git clone http://localhost:{local_port}{path}.git"); + let msg = format!("http://localhost:{local_port}{path}.git"); Ok(msg) } diff --git a/lunar/.eslintrc.json b/lunar/.eslintrc.json index bffb357a7..a8dfdbea8 100644 --- a/lunar/.eslintrc.json +++ b/lunar/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": "next/core-web-vitals" -} + "extends": "next/core-web-vitals", + "rules": { + "@next/next/no-img-element": "off" // disable it due to we use static export + } +} \ No newline at end of file diff --git a/lunar/src-tauri/Cargo.toml b/lunar/src-tauri/Cargo.toml index 3f1cd1fc6..d4582db37 100644 --- a/lunar/src-tauri/Cargo.toml +++ b/lunar/src-tauri/Cargo.toml @@ -21,6 +21,7 @@ tauri = { version = "1.7.1", features = [ "process-command-api", ] } tokio = { workspace = true } +home = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["macros"] } diff --git a/lunar/src-tauri/build.rs b/lunar/src-tauri/build.rs index 9d5b5ce4c..11bc6c129 100644 --- a/lunar/src-tauri/build.rs +++ b/lunar/src-tauri/build.rs @@ -30,6 +30,7 @@ fn main() { .unwrap(); let sidecar_path = format!("./binaries/mega-{}{}", target_triple, extension); + let libra_path = format!("./binaries/libra-{}{}", target_triple, extension); let debug_path = if cfg!(debug_assertions) { "debug" @@ -40,6 +41,9 @@ fn main() { std::fs::copy(format!("../../target/{}/mega{}", debug_path, extension), sidecar_path) .expect("Run Cargo build for mega first"); + std::fs::copy(format!("../../target/{}/libra{}", debug_path, extension), libra_path) + .expect("Run Cargo build for libra first"); + // Copy libpipy due to target os #[cfg(target_os = "macos")] std::fs::copy( diff --git a/lunar/src-tauri/src/main.rs b/lunar/src-tauri/src/main.rs index 9d11d9c34..a9327b667 100644 --- a/lunar/src-tauri/src/main.rs +++ b/lunar/src-tauri/src/main.rs @@ -1,8 +1,9 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use std::env; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; +use std::{env, fs}; use serde::Deserialize; use tauri::api::process::{Command, CommandChild, CommandEvent}; @@ -129,11 +130,86 @@ fn restart_mega_service( } #[tauri::command] -async fn mega_service_status(state: State<'_, Arc>>) -> Result<(bool, bool), String> { +fn mega_service_status(state: State<'_, Arc>>) -> Result<(bool, bool), String> { let service_state = state.lock().unwrap(); Ok((service_state.child.is_some(), service_state.with_relay)) } +#[tauri::command] +fn clone_repository(repo_url: String, name: String) -> Result<(), String> { + let home = match home::home_dir() { + Some(path) if !path.as_os_str().is_empty() => path, + _ => { + println!("Unable to get your home dir!"); + PathBuf::new() + } + }; + let target_dir = home.join(".mega").join(name.clone()); + + if target_dir.exists() { + fs::remove_dir_all(&target_dir).unwrap(); + } + + let output = Command::new_sidecar("libra") + .expect("Failed to create `libra` binary command") + .args(["clone", &repo_url, (target_dir.to_str().unwrap())]) + .output() + .map_err(|e| format!("Failed to execute process: {}", e))?; + + if output.status.success() { + println!("{}", output.stdout); + } else { + eprintln!("{}", output.stderr); + } + change_remote_url(target_dir.clone(), name)?; + push_to_new_remote(target_dir)?; + Ok(()) +} + +fn change_remote_url(repo_path: PathBuf, name: String) -> Result<(), String> { + Command::new_sidecar("libra") + .expect("Failed to create `libra` binary command") + .args(["remote", "remove", "origin"]) + .current_dir(repo_path.clone()) + .output() + .map_err(|e| format!("Failed to execute process: {}", e))?; + + let output = Command::new_sidecar("libra") + .expect("Failed to create `libra` binary command") + .args([ + "remote", + "add", + "origin", + &format!("http://localhost:8000/third-part/{}", name), + ]) + .current_dir(repo_path.clone()) + .output() + .map_err(|e| format!("Failed to execute process: {}", e))?; + + if output.status.success() { + println!("{}", output.stdout); + } else { + eprintln!("{}", output.stderr); + } + Ok(()) +} + +fn push_to_new_remote(repo_path: PathBuf) -> Result<(), String> { + let output = Command::new_sidecar("libra") + .expect("Failed to create `libra` binary command") + .args(["push", "origin", "master"]) + .current_dir(repo_path) + .output() + .map_err(|e| format!("Failed to execute process: {}", e))?; + + if output.status.success() { + println!("{}", output.stdout); + } else { + eprintln!("{}", output.stderr); + } + Ok(()) +} + fn main() { let params = MegaStartParams::default(); tauri::Builder::default() @@ -142,7 +218,8 @@ fn main() { start_mega_service, stop_mega_service, restart_mega_service, - mega_service_status + mega_service_status, + clone_repository ]) .setup(|app| { let app_handle = app.handle().clone(); diff --git a/lunar/src-tauri/tauri.conf.json b/lunar/src-tauri/tauri.conf.json index 28c2b4d9d..ed494fba5 100644 --- a/lunar/src-tauri/tauri.conf.json +++ b/lunar/src-tauri/tauri.conf.json @@ -17,6 +17,10 @@ { "name": "binaries/mega", "sidecar": true + }, + { + "name": "binaries/libra", + "sidecar": true } ] }, @@ -35,7 +39,8 @@ "files": {} }, "externalBin": [ - "binaries/mega" + "binaries/mega", + "binaries/libra" ], "icon": [ "icons/32x32.png", diff --git a/lunar/src/app/api/fetcher.ts b/lunar/src/app/api/fetcher.ts index 14ad39bbf..8434e4bcb 100644 --- a/lunar/src/app/api/fetcher.ts +++ b/lunar/src/app/api/fetcher.ts @@ -4,6 +4,12 @@ import { invoke } from '@tauri-apps/api/tauri'; const endpoint = process.env.NEXT_PUBLIC_API_URL; const relay = process.env.NEXT_PUBLIC_RELAY_API_URL; +export interface ApiResult { + req_result: boolean, + data: T, + err_message: string +} + export class FetchError extends Error { info: any; status: number; @@ -86,9 +92,9 @@ export function useRepoList() { dedupingInterval: 30000, }) return { - data: data, - isLoading, - isError: error, + repo: data, + isRepoLoading: isLoading, + isRepoError: error, } } @@ -102,6 +108,18 @@ export function usePeerId() { isError: error, } } + +export function useRepoFork(identifier) { + const { data, error, isLoading } = useSWR(`${endpoint}/api/v1/mega/ztm/repo_fork?identifier=${identifier}`, { + dedupingInterval: 60000, + }) + return { + url: data, + isForkLoading: isLoading, + isForkError: error, + } +} + // export function usePublishRepo(path: string) { // const { data, error, isLoading } = useSWR(`${endpoint}/api/v1/mega/ztm/repo_provide?path=${path}`, fetcher) // return { @@ -144,4 +162,4 @@ export async function requestPublishRepo(data) { throw new Error(errorMessage); } return response.json(); -} \ No newline at end of file +} diff --git a/lunar/src/app/api/relay/repo_fork/route.ts b/lunar/src/app/api/relay/repo_fork/route.ts deleted file mode 100644 index 819299836..000000000 --- a/lunar/src/app/api/relay/repo_fork/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const revalidate = 0 - -import { NextRequest } from "next/server"; - -const endpoint = process.env.NEXT_PUBLIC_API_URL; - -export async function GET(request: NextRequest) { - const searchParams = request.nextUrl.searchParams - const identifier = searchParams.get('identifier') - const port = searchParams.get('port') - const res = await fetch(`${endpoint}/api/v1/mega/ztm/repo_fork?identifier=${identifier}&port=${port}`, { - }) - 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 525719f93..0e789080a 100644 --- a/lunar/src/app/application-layout.tsx +++ b/lunar/src/app/application-layout.tsx @@ -45,7 +45,7 @@ import { import { invoke } from '@tauri-apps/api/tauri' import { usePathname } from 'next/navigation' import { useState, useEffect } from 'react' -import { Badge } from 'antd/lib' +import { Badge, Alert, Skeleton } from 'antd/lib' function AccountDropdownMenu({ anchor }: { anchor: 'top start' | 'bottom end' }) { return ( @@ -82,11 +82,14 @@ export function ApplicationLayout({ let pathname = usePathname() const [mega_status, setMegaStatus] = useState(false) + const [ztm_status, setZtmStatus] = useState(true) + useEffect(() => { const fetchStatus = () => { invoke('mega_service_status') - .then((status: boolean) => { - setMegaStatus(status); + .then((status: boolean[]) => { + setMegaStatus(status[0]); + setZtmStatus(status[1]); console.log(`Service Status: ${status}`); }) .catch((error) => { @@ -214,6 +217,15 @@ export function ApplicationLayout({ } > + { + !ztm_status && + + } {children} ) diff --git a/lunar/src/app/page.tsx b/lunar/src/app/page.tsx index 1a508c11d..ad92a8ba1 100644 --- a/lunar/src/app/page.tsx +++ b/lunar/src/app/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { Flex, Layout, Skeleton, Alert } from "antd/lib"; +import { Flex, Layout, Skeleton } from "antd/lib"; import CodeTable from '@/components/CodeTable'; import MergeList from '@/components/MergeList'; import { useTreeCommitInfo, useBlobContent, useMRList, useMegaStatus } from '@/app/api/fetcher'; @@ -44,36 +44,19 @@ export default function HomePage() { if (isTreeLoading || isBlobLoading || isMRLoading || isLoading) return ; return ( -
- { - !status[1] && - - } - - { - - - { - (tree && blob) && - - } - - - {(isTreeLoading || isBlobLoading) && - - } - {(!isTreeLoading && !isBlobLoading) && - - } - {/* */} - - - } -
+ + + { + (tree && blob) && + + } + + + {mrList && + + } + {/* */} + + ) } diff --git a/lunar/src/app/repo/page.tsx b/lunar/src/app/repo/page.tsx index 3c3c9bd4c..dc33ec96b 100644 --- a/lunar/src/app/repo/page.tsx +++ b/lunar/src/app/repo/page.tsx @@ -1,23 +1,15 @@ 'use client' -import DataList from '@/components/DataList' -import { useRepoList } from '../api/fetcher'; +import RepoList from '@/components/RepoList' +import { useMegaStatus, useRepoList } from '@/app/api/fetcher'; import { Skeleton } from "antd"; export default function RepoPage() { - const { data, isLoading, isError } = useRepoList(); + const { repo, isRepoLoading, isRepoError } = useRepoList(); + if (isRepoLoading) return ; return ( -
- { - isLoading && - - } - { - !isLoading && - - } -
+ ) } \ No newline at end of file diff --git a/lunar/src/components/CodeTable.tsx b/lunar/src/components/CodeTable.tsx index 3cc1bb8e4..c73e158b3 100644 --- a/lunar/src/components/CodeTable.tsx +++ b/lunar/src/components/CodeTable.tsx @@ -24,7 +24,6 @@ export interface DataType { const CodeTable = ({ directory, readmeContent, with_ztm }) => { const [messageApi, contextHolder] = message.useMessage(); - const msg_error = (content: String) => { messageApi.open({ type: 'error', @@ -175,6 +174,7 @@ const CodeTable = ({ directory, readmeContent, with_ztm }) => { handleOk(modalText)} confirmLoading={confirmLoading} onCancel={handleCancel} diff --git a/lunar/src/components/MergeDetail.tsx b/lunar/src/components/MergeDetail.tsx index 97f5501e1..043f6be63 100644 --- a/lunar/src/components/MergeDetail.tsx +++ b/lunar/src/components/MergeDetail.tsx @@ -23,7 +23,7 @@ const MRDetailPage = ({ mrDetail }) => { setFileData(mrFiles.data); cancel_loading(2) } - }, [mrDetail, mrFiles]); + }, [mrDetail, mrFiles, isMRLoading]); const set_to_loading = (index: number) => { diff --git a/lunar/src/components/DataList.tsx b/lunar/src/components/RepoList.tsx similarity index 58% rename from lunar/src/components/DataList.tsx rename to lunar/src/components/RepoList.tsx index 2ff03d50d..58d48873f 100644 --- a/lunar/src/components/DataList.tsx +++ b/lunar/src/components/RepoList.tsx @@ -1,10 +1,13 @@ 'use client' -import { Input, Modal, Space, Table, TableProps, Badge, Button } from 'antd/lib' -import { useEffect, useState } from 'react' +import { Input, Modal, Space, Table, TableProps, Badge, Button, Skeleton } from 'antd/lib' +import { useState } from 'react' import { format, fromUnixTime } from 'date-fns' import { DownloadOutlined } from '@ant-design/icons'; +import { invoke } from '@tauri-apps/api/tauri'; +import { ApiResult, useMegaStatus } from '@/app/api/fetcher'; +const endpoint = process.env.NEXT_PUBLIC_API_URL; interface DataType { name: string; @@ -16,25 +19,27 @@ interface DataType { } const DataList = ({ data }) => { - const [open, setOpen] = useState(false); - const [confirmLoading, setConfirmLoading] = useState(false); - const [inputPort, setInputPort] = useState(""); - const [isOkButtonDisabled, setIsOkButtonDisabled] = useState(true); - const [modelRecord, setModalRecord] = useState({ - name: "", - identifier: "", - origin: "", - update_time: 0, - commit: "", - peer_online: true, - }); + const { status, isLoading, isError } = useMegaStatus(); + const [loadings, setLoadings] = useState([]); const [modal, contextHolder] = Modal.useModal(); - const handleInputChange = (e: React.ChangeEvent) => { - // console.log("value changes", e.target.value); - setInputPort(e.target.value); - setIsOkButtonDisabled(e.target.value.length < 4); - }; + if (isLoading) return ; + + const enterLoading = (index: number) => { + setLoadings((prevLoadings) => { + const newLoadings = [...prevLoadings]; + newLoadings[index] = true; + return newLoadings; + }); + } + + const exitLoading = (index: number) => { + setLoadings((prevLoadings) => { + const newLoadings = [...prevLoadings]; + newLoadings[index] = false; + return newLoadings; + }); + } const showSuccModel = (text) => { modal.success({ @@ -93,8 +98,8 @@ const DataList = ({ data }) => { key: 'action', render: (_, record) => ( - @@ -102,41 +107,28 @@ const DataList = ({ data }) => { }, ]; - - const showModal = (record) => { - setModalRecord(record); - setOpen(true); - }; - - const handleOk = () => { - setConfirmLoading(true); - - const repoFork = async () => { - try { - let text = await getRepoFork(modelRecord.identifier, inputPort); - setOpen(false); - setConfirmLoading(false); - showSuccModel(text); - } catch (error) { - console.error('Error fetching data:', error); + const handleClone = async (record) => { + enterLoading(1) + try { + let res: ApiResult = await getRepoFork(record.identifier); + // showSuccModel(text); + console.log("repo fork result", res); + if (res.req_result) { + invoke('clone_repository', { repoUrl: res.data, name: record.name }) + .catch((error) => { + console.error(`Failed to get service status: ${error}`); + }); } - }; - repoFork(); - - setTimeout(() => { - setOpen(false); - setConfirmLoading(false); - }, 5000); - }; - - const handleCancel = () => { - setOpen(false); + } catch (error) { + console.error('Error fetching data:', error); + } + exitLoading(1) }; return (
- { okButtonProps={{ disabled: isOkButtonDisabled }} > - + */} {contextHolder} ); }; -async function getRepoFork(identifier, port) { - const res = await fetch(`api/relay/repo_fork?identifier=${identifier}&port=${port}`); +async function getRepoFork(identifier) { + const res = await fetch(`${endpoint}/api/v1/mega/ztm/repo_fork?identifier=${identifier}`); const response = await res.json(); - return response.data.data + return response.data } export default DataList; diff --git a/lunar/src/components/RepoTree.tsx b/lunar/src/components/RepoTree.tsx index d95d5d701..11c2a0f6e 100644 --- a/lunar/src/components/RepoTree.tsx +++ b/lunar/src/components/RepoTree.tsx @@ -1,6 +1,6 @@ import 'github-markdown-css/github-markdown-light.css' import { DownOutlined } from '@ant-design/icons/lib' -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' import { Tree } from 'antd/lib' import styles from './RepoTree.module.css' @@ -11,9 +11,24 @@ const RepoTree = ({ directory }) => { const [updateTree, setUpdateTree] = useState(false); const [expandedKeys, setExpandedKeys] = useState([]); + const convertToTreeData = useCallback((directory) => { + return sortProjectsByType(directory).map(item => { + const treeItem = { + title: item.name, + key: item.id, + isLeaf: item.content_type !== 'directory', + path: item.path, + expanded: false, // initialize expanded state to false + children: [] // eneure every node having the children element + }; + return treeItem; + }); + }, []); + useEffect(() => { - setTreeData(convertToTreeData(directory)); - }, [directory]); + let data = convertToTreeData(directory); + setTreeData(data); + }, [directory, convertToTreeData]); useEffect(() => { @@ -24,20 +39,7 @@ const RepoTree = ({ directory }) => { - // convert the dir to tree data - const convertToTreeData = (responseData) => { - return sortProjectsByType(responseData).map(item => { - const treeItem = { - title: item.name, - key: item.id, - isLeaf: item.content_type !== 'directory', - path: item.path, - expanded: false, // initialize expanded state to false - children: [] // eneure every node having the children element - }; - return treeItem; - }); - }; + // sortProjectsByType function to sort projects by file type const sortProjectsByType = (projects) => {