Ver código fonte

完成page6应用管理页面的全部功能开发,前端js后端服务接口都已简单联调通过

niujiuru 3 dias atrás
pai
commit
94dc4c81f8

+ 2 - 0
app-install.service/rpc_handler.go

@@ -75,6 +75,8 @@ func pkgImport(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
 		return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, "missing or empty 'file' parameter")
 	}
 
+	defer os.Remove(file)
+
 	if !strings.HasSuffix(strings.ToLower(filepath.Base(file)), ".tar.gz") {
 		return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, "invalid install package format")
 	}

+ 148 - 1
web-ui.service/page6_handler.go

@@ -109,6 +109,11 @@ func getUserAppStatus() (string, string) {
 }
 
 func appInstallStatusHandler(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
 	var info *struct {
 		Pkg struct {
 			FileName   string `json:"fileName"`
@@ -180,7 +185,7 @@ func appInstallStatusHandler(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(data)
 }
 
-func uploadUserAPP(w http.ResponseWriter, r *http.Request) {
+func uploadUserAppPkg(w http.ResponseWriter, r *http.Request) {
 	if r.Method != http.MethodPost {
 		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
 		return
@@ -236,3 +241,145 @@ func saveUploadFile(src io.Reader, path string) error {
 
 	return nil
 }
+
+func appPkgImportHandler(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	var params struct {
+		File string `json:"file"`
+	}
+
+	if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	if params.File == "" {
+		http.Error(w, "missing file", http.StatusBadRequest)
+		return
+	}
+
+	callErr := callRPCResult(r.Context(), 7004, "core.package.import",
+		map[string]string{"file": params.File}, nil)
+	if callErr != nil {
+		http.Error(w, callErr.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	json.NewEncoder(w).Encode(map[string]string{"result": "success"})
+}
+
+func appPkgRemoveHandler(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	callErr := callRPCResult(r.Context(), 7004, "core.package.remove", nil, nil)
+	if callErr != nil {
+		http.Error(w, callErr.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	json.NewEncoder(w).Encode(map[string]string{"result": "success"})
+}
+
+func appInstallHandler(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	callErr := callRPCResult(r.Context(), 7004, "core.app.install", nil, nil)
+	if callErr != nil {
+		http.Error(w, callErr.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	json.NewEncoder(w).Encode(map[string]string{"result": "success"})
+}
+
+func appUninstallHandler(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	callErr := callRPCResult(r.Context(), 7004, "core.app.uninstall", nil, nil)
+	if callErr != nil {
+		http.Error(w, callErr.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	json.NewEncoder(w).Encode(map[string]string{"result": "success"})
+}
+
+func restartUserAppService(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	cmd := exec.Command("systemctl", "restart", "yfkj-user-app.service")
+
+	if out, err := cmd.CombinedOutput(); err != nil {
+		http.Error(w, string(out)+err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.Write([]byte(`{"status":"ok"}`))
+}
+
+var userAppPkgDir = "/opt/yfkj/app-install.service/user-app-pkg"
+
+func downloadUserAppPkg(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	var info struct {
+		Pkg struct {
+			FileName string `json:"fileName"`
+		} `json:"pkg"`
+	}
+
+	data, err := os.ReadFile(filepath.Join(userAppPkgDir, "appins.json"))
+	if err != nil {
+		http.Error(w, "用户应用安装包不存在", http.StatusNotFound)
+		return
+	}
+
+	if err := json.Unmarshal(data, &info); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	if info.Pkg.FileName == "" {
+		http.Error(w, "用户应用安装包不存在", http.StatusNotFound)
+		return
+	}
+
+	filename := filepath.Base(info.Pkg.FileName)
+	file := filepath.Join(userAppPkgDir, filename)
+
+	stat, err := os.Stat(file)
+	if err != nil || !stat.Mode().IsRegular() {
+		http.Error(w, "用户应用安装包不存在", http.StatusNotFound)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/gzip")
+	w.Header().Set("Content-Disposition",
+		fmt.Sprintf(`attachment; filename="%s"`, filename))
+
+	http.ServeFile(w, r, file)
+}

+ 9 - 5
web-ui.service/web/pages/page6.html

@@ -35,10 +35,12 @@
           <div class="card-header">
             <h2>包管理器</h2>
             <div class="log-actions">
-              <input type="file" id="userAppFile" style="display:none" accept=".tar.gz" onchange="uploadUserAppFile(this)">
-              <button class="btn" id="btn1" title="上传用户应用安装包" onclick="onBtn1Click()">上传</button>
+              <input type="file" id="userAppPkg" style="display:none" accept=".tar.gz" onchange="uploadUserAppPkg(this)">
+              <button class="btn" id="btn1" title="从本地选择用户应用安装包并上传" onclick="onBtn1Click()">上传</button>
               <span style="margin:0 1px;"></span>
-              <button class="btn" id="btn2" title="删除当前已上传的用户应用安装包" onclick="onBtn2Click()">移除</button>
+              <button class="btn" id="btn2" title="下载当前已上传的用户应用安装包" onclick="onBtn2Click()">下载</button>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" id="btn3" title="删除当前已上传的用户应用安装包" onclick="onBtn3Click()">移除</button>
             </div>
           </div>
           <div class="card-body">
@@ -68,9 +70,11 @@
           <div class="card-header">
             <h2>用户应用</h2>
             <div class="log-actions">
-              <button class="btn" id="btn3" title="使用当前安装包安装用户应用,安装完成后自动启动,并设置为开机自启动" onclick="onBtn3Click()">安装</button>
+              <button class="btn" id="btn4" title="使用当前安装包安装用户应用" onclick="onBtn4Click()">安装</button>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" id="btn5" title="卸载当前已经安装的用户应用" onclick="onBtn5Click()">卸载</button>
               <span style="margin:0 1px;"></span>
-              <button class="btn" id="btn4" title="卸载当前已安装的用户应用程序" onclick="onBtn4Click()">卸载</button>
+              <button class="btn" id="btn6" title="重启当前已经安装的用户应用" onclick="onBtn6Click()">重启</button>
             </div>
           </div>
           <div class="card-body">

+ 170 - 10
web-ui.service/web/static/js/page6.js

@@ -114,7 +114,12 @@ function restartAppInstallService(event) {
 }
 
 let appInsStatusTimer = null;
+let userAppOperating = false;
 function getAppInstallStatus() {
+  if (userAppOperating) {
+    return;
+  }
+
   fetch("/api/app-install/status")
     .then(async r => {
       if (!r.ok) {
@@ -177,24 +182,40 @@ function updateAppInstallButtons(state) {
     setBtnStatus("btn2", false, "page6")
     setBtnStatus("btn3", false, "page6")
     setBtnStatus("btn4", false, "page6")
+    setBtnStatus("btn5", false, "page6")
+    setBtnStatus("btn6", false, "page6")
     break;
    case 1: // 有安装包, 未安装时
     setBtnStatus("btn1", false, "page6")
     setBtnStatus("btn2", true,  "page6")
     setBtnStatus("btn3", true,  "page6")
-    setBtnStatus("btn4", false, "page6")
+    setBtnStatus("btn4", true,  "page6")
+    setBtnStatus("btn5", false, "page6")
+    setBtnStatus("btn6", false, "page6")
     break;
    case 2: // 有安装包, 已安装时
     setBtnStatus("btn1", false, "page6")
+    setBtnStatus("btn2", true,  "page6")
+    setBtnStatus("btn3", false, "page6")
+    setBtnStatus("btn4", false, "page6")
+    setBtnStatus("btn5", true,  "page6")
+    setBtnStatus("btn6", true,  "page6")
+    break;
+   case 4: // 禁用所有, 特殊操作
+    setBtnStatus("btn1", false, "page6")
     setBtnStatus("btn2", false, "page6")
     setBtnStatus("btn3", false, "page6")
-    setBtnStatus("btn4", true,  "page6")
+    setBtnStatus("btn4", false, "page6")
+    setBtnStatus("btn5", false, "page6")
+    setBtnStatus("btn6", false, "page6")
     break;
    default:
     setBtnStatus("btn1", true,  "page6")
     setBtnStatus("btn2", true,  "page6")
     setBtnStatus("btn3", true,  "page6")
     setBtnStatus("btn4", true,  "page6")
+    setBtnStatus("btn5", true,  "page6")
+    setBtnStatus("btn6", true,  "page6")
     break;
   }
 }
@@ -205,10 +226,10 @@ function onBtn1Click() {
   if (userAppUploading) {
     return;
   }
-  document.getElementById("userAppFile").click();
+  document.getElementById("userAppPkg").click();
 }
 
-function uploadUserAppFile(input) {
+function uploadUserAppPkg(input) {
   let file = input.files[0];
 
   if (!file) {
@@ -227,7 +248,7 @@ function uploadUserAppFile(input) {
   formData.append("file", file);
 
   let xhr = new XMLHttpRequest();
-  xhr.open("POST", "/api/app-install/uploadUserAPP", true);
+  xhr.open("POST", "/api/app-install/uploadUserAppPkg", true);
   xhr.timeout = 600000; // 10分钟
 
   xhr.upload.onprogress = function(e) {
@@ -239,16 +260,17 @@ function uploadUserAppFile(input) {
     setText("bvar5", "正在上传 " + file.name + " (" + percent + "%)", "page6");
   };
 
-  xhr.onload = function() {
-    userAppUploading = false;
-    input.value = "";
-
+  xhr.onload = async function() {
     if (xhr.status !== 200) {
+      userAppUploading = false;
+      input.value = "";
       setText("bvar5", "上传失败: " + xhr.responseText, "page6");
       return;
     }
 
-    setText("bvar5", "上传完成,等待下一步处理", "page6");
+    await importUserAppPkg("/tmp/" + file.name);
+    userAppUploading = false;
+    input.value = "";
   };
 
   xhr.onerror = function() {
@@ -268,6 +290,144 @@ function uploadUserAppFile(input) {
   xhr.send(formData);
 }
 
+async function importUserAppPkg(file) {
+  userAppOperating = true; updateAppInstallButtons(4);
+  setText("bvar5", "正在导入当前上传的用户应用安装包,请稍候...", "page6");
+
+  try {
+    let res = await fetch("/api/app-install/importUserAppPkg", {
+      method:"POST",
+      headers:{"Content-Type":"application/json"},
+      body:JSON.stringify({file:file})
+    });
+
+    if (!res.ok) {
+      throw new Error(await res.text());
+    }
+
+    let data = await res.json();
+    if (data.result !== "success") {
+      throw new Error("返回结果异常");
+    }
+
+    userAppOperating = false;
+    return true;
+  } catch (err) {
+    setText("bvar5", "导入过程中发生错误: " + err.message, "page6");
+    userAppOperating = false;
+    return false;
+  }
+}
+
+function onBtn2Click() {
+  window.location.href = "/api/app-install/downloadUserAppPkg";
+}
+
+async function onBtn3Click() {
+  if (!confirm("确定删除当前已上传的用户应用安装包吗?")) {
+    return;
+  }
+
+  userAppOperating = true; updateAppInstallButtons(4);
+  setText("bvar5", "正在删除当前已上传的用户应用安装包,请稍候...", "page6");
+
+  try {
+    let res = await fetch("/api/app-install/removeUserAppPkg", {
+      method:"POST"
+    });
+
+    if (!res.ok) {
+      throw new Error(await res.text());
+    }
+
+    let data = await res.json();
+    if (data.result !== "success") {
+      throw new Error("返回结果异常");
+    }
+  } catch (err) {
+    setText("bvar5", "删除过程中发生错误: " + err.message, "page6");
+  }
+
+  userAppOperating = false;
+}
+
+async function onBtn4Click() {
+  userAppOperating = true; updateAppInstallButtons(4);
+  setText("bvar5", "正在使用当前安装包安装用户应用,请稍候...", "page6");
+
+  try {
+    let res = await fetch("/api/app-install/installUserApp", {
+      method:"POST"
+    });
+
+    if (!res.ok) {
+      throw new Error(await res.text());
+    }
+
+    let data = await res.json();
+    if (data.result !== "success") {
+      throw new Error("返回结果异常");
+    }
+  } catch (err) {
+    setText("bvar5", "安装过程中发生错误: " + err.message, "page6");
+  }
+
+  userAppOperating = false;
+}
+
+async function onBtn5Click() {
+  if (!confirm("确定卸载当前已经安装的用户应用吗?")) {
+    return;
+  }
+
+  userAppOperating = true; updateAppInstallButtons(4);
+  setText("bvar5", "正在卸载当前已经安装的用户应用,请稍候...", "page6");
+
+  try {
+    let res = await fetch("/api/app-install/uninstallUserApp", {
+      method:"POST"
+    });
+
+    if (!res.ok) {
+      throw new Error(await res.text());
+    }
+
+    let data = await res.json();
+    if (data.result !== "success") {
+      throw new Error("返回结果异常");
+    }
+  } catch (err) {
+    setText("bvar5", "卸载过程中发生错误: " + err.message, "page6");
+  }
+
+  userAppOperating = false;
+}
+
+function onBtn6Click() {
+  if (!confirm("确定重启当前已经安装的用户应用吗?")) {
+    return;
+  }
+
+  setBtnStatus("btn6", false, "page6")
+
+  fetch("/api/user-app/restart", {
+    method: "POST"
+  })
+  .then(async r => {
+    if (!r.ok) {
+      throw new Error(await r.text());
+    }
+    alert("重启成功");
+  })
+  .catch(e => {
+    console.error("restart failed:", e);
+    alert("重启失败: " + e.message);
+  })
+  .finally(() => {
+    setBtnStatus("btn6", true, "page6")
+  });
+}
+
 function initPage6() {
   startAppInstallStatusRefresh()
   connectLog("yfkj-app-install.service", "text1");

+ 14 - 1
web-ui.service/web_handler.go

@@ -286,6 +286,14 @@ func callRPCResult(ctx context.Context, port int, method string, params any, res
 
 	baseapp.Logger.Debugf("[发送RPC应答] %s result=%s err=%v\n", method, string(resp.Result), resp.Error)
 
+	if resp.Error != nil {
+		return fmt.Errorf("%s", resp.Error.Message)
+	}
+
+	if result == nil {
+		return nil
+	}
+
 	return json.Unmarshal(resp.Result, result)
 }
 
@@ -309,8 +317,13 @@ func callRPCResponse(w http.ResponseWriter, r *http.Request, port int, method st
 
 	baseapp.Logger.Debugf("[发送RPC应答] %s result=%s err=%v\n", method, string(resp.Result), resp.Error)
 
+	if resp.Error != nil {
+		http.Error(w, resp.Error.Message, http.StatusBadRequest)
+		return
+	}
+
 	w.Header().Set("Content-Type", "application/json; charset=utf-8")
-	json.NewEncoder(w).Encode(resp.Result)
+	w.Write(resp.Result)
 }
 
 func serviceLogLevel(w http.ResponseWriter, r *http.Request, port int) {

+ 35 - 3
web-ui.service/web_route.go

@@ -1,6 +1,8 @@
 package main
 
-import "net/http"
+import (
+	"net/http"
+)
 
 type Route struct {
 	Path    string
@@ -194,8 +196,38 @@ var routes = []Route{
 	},
 
 	{
-		"/api/app-install/uploadUserAPP",
-		uploadUserAPP,
+		"/api/app-install/uploadUserAppPkg",
+		uploadUserAppPkg,
+	},
+
+	{
+		"/api/app-install/importUserAppPkg",
+		appPkgImportHandler,
+	},
+
+	{
+		"/api/app-install/downloadUserAppPkg",
+		downloadUserAppPkg,
+	},
+
+	{
+		"/api/app-install/removeUserAppPkg",
+		appPkgRemoveHandler,
+	},
+
+	{
+		"/api/app-install/installUserApp",
+		appInstallHandler,
+	},
+
+	{
+		"/api/app-install/uninstallUserApp",
+		appUninstallHandler,
+	},
+
+	{
+		"/api/user-app/restart",
+		restartUserAppService,
 	},
 
 	{