change store
This commit is contained in:
parent
d8839f9138
commit
6f7313b770
20 changed files with 942 additions and 387 deletions
|
|
@ -11,7 +11,7 @@ An efficient intranet office platform that includes various tools such as Word,
|
|||
|
||||
English | [简体中文](README.md)
|
||||
|
||||
[FAQ](./docs/Faq.md) | [API](./docs/Api.md)
|
||||
[FAQ](./docs/Faq.md) | [API](./docs/Api.md) | [AppStore](./docs/Store.md)
|
||||
|
||||
### Install
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</p>
|
||||
|
||||
<h1 align="center">GodoOS</h1>
|
||||
一款高效的内网办公平台,内含word/excel/ppt/pdf/内网聊天/白板/思维导图等多个办公系统工具,支持原生文件存储。平台界面精仿windows风格,操作简便,同时保持低资源消耗和高性能运行。无需注册即可自动连接内网用户,实现即时通讯和文件共享。
|
||||
一款高效的内网办公平台,内含word/excel/ppt/pdf/内网聊天/白板/思维导图等多个办公系统工具,支持原生文件存储。平台界面精仿windows风格,操作简便,同时保持低资源消耗和高性能运行。无需注册即可自动连接内网用户,实现即时通讯和文件共享。灵活高配置的应用商店,可无限扩展。
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
[English](README.en.md) | 简体中文
|
||||
|
||||
[FAQ](./docs/Faq.md) | [API](./docs/Api.md)
|
||||
[FAQ](./docs/Faq.md) | [API](./docs/Api.md) | [应用开发](./docs/Store.md)
|
||||
|
||||
|
||||
|
||||
|
|
@ -48,6 +48,8 @@
|
|||
- ***精小,打包后仅65M,确包含了所有的办公套件***
|
||||
- ***可无限扩展,支持自定义应用***
|
||||
- ***golang开发后端,低资源消耗和高性能***
|
||||
- ***支持多平台,Windows、Linux、MacOS***
|
||||
- ***只会html和拥有可执行的应用,简单学习一下应用商店配置即可开发出复杂的应用***
|
||||
|
||||
## 🚧 开发进程
|
||||
- 2024年7月,发布v1.0.0版本,发布后,项目进入第二阶段。
|
||||
|
|
|
|||
91
docs/Api.md
91
docs/Api.md
|
|
@ -16,6 +16,11 @@
|
|||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 目录内容列表
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
PATH_PARAM="/path/to/directory"
|
||||
curl -X GET "http://localhost:56780/files/read?path=$PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 获取文件或目录状态
|
||||
|
|
@ -33,6 +38,12 @@
|
|||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 文件或目录的状态信息
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 获取文件或目录状态
|
||||
PATH_PARAM="/path/to/file_or_directory"
|
||||
curl -X GET "http://localhost:56780/files/stat?path=$PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 更改文件权限
|
||||
|
|
@ -51,6 +62,12 @@
|
|||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 更改文件权限
|
||||
JSON='{"path":"/path/to/file","mode":"0644"}'
|
||||
curl -X POST -H "Content-Type: application/json" -d "$JSON" "http://localhost:56780/files/chmod"
|
||||
```
|
||||
---
|
||||
|
||||
### 检查文件或目录是否存在
|
||||
|
|
@ -67,7 +84,12 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 存在性检查结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 检查文件或目录是否存在
|
||||
PATH_PARAM="/path/to/file_or_directory"
|
||||
curl -X GET "http://localhost:56780/files/exists?path=$PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 读取文件内容
|
||||
|
|
@ -84,7 +106,12 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 文件内容
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 读取文件内容
|
||||
PATH_PARAM="/path/to/file"
|
||||
curl -X GET "http://localhost:56780/files/readfile?path=$PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 删除文件
|
||||
|
|
@ -101,7 +128,12 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 删除文件
|
||||
PATH_PARAM="/path/to/file"
|
||||
curl -X GET "http://localhost:56780/files/unlink?path=$PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 清空文件系统
|
||||
|
|
@ -115,7 +147,11 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 清空文件系统
|
||||
curl -X GET "http://localhost:56780/files/clear"
|
||||
```
|
||||
---
|
||||
|
||||
### 重命名文件或目录
|
||||
|
|
@ -132,7 +168,13 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 重命名文件或目录
|
||||
OLD_PATH_PARAM="/path/to/old_file_or_directory"
|
||||
NEW_PATH_PARAM="/path/to/new_file_or_directory"
|
||||
curl -X GET "http://localhost:56780/files/rename?oldPath=$OLD_PATH_PARAM&newPath=$NEW_PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 创建目录
|
||||
|
|
@ -149,7 +191,12 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 创建目录
|
||||
DIR_PATH_PARAM="/path/to/new_directory"
|
||||
curl -X POST "http://localhost:56780/files/mkdir?dirPath=$DIR_PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 删除目录
|
||||
|
|
@ -166,7 +213,12 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 删除目录
|
||||
DIR_PATH_PARAM="/path/to/directory"
|
||||
curl -X GET "http://localhost:56780/files/rmdir?dirPath=$DIR_PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 复制文件
|
||||
|
|
@ -183,7 +235,13 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 复制文件
|
||||
SRC_PATH_PARAM="/path/to/source_file"
|
||||
DST_PATH_PARAM="/path/to/destination_file"
|
||||
curl -X GET "http://localhost:56780/files/copyfile?srcPath=$SRC_PATH_PARAM&dstPath=$DST_PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 写入文件
|
||||
|
|
@ -204,7 +262,13 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 写入文件
|
||||
FILE_PATH_PARAM="/path/to/file"
|
||||
CONTENT="This is the content to be written."
|
||||
curl -X POST -F "content=@-$CONTENT" "http://localhost:56780/files/writefile?filePath=$FILE_PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 追加文件内容
|
||||
|
|
@ -225,7 +289,13 @@
|
|||
#### 响应
|
||||
- **Content-Type**: `application/json`
|
||||
- **响应体**: 操作结果
|
||||
|
||||
#### 示例
|
||||
```bash
|
||||
# 追加文件内容
|
||||
FILE_PATH_PARAM="/path/to/file"
|
||||
CONTENT="This is the content to be appended."
|
||||
curl -X POST -F "content=@-$CONTENT" "http://localhost:56780/files/appendfile?filePath=$FILE_PATH_PARAM"
|
||||
```
|
||||
---
|
||||
|
||||
### 文件系统事件监听
|
||||
|
|
@ -237,3 +307,4 @@
|
|||
- **path** (监听的文件或目录路径)
|
||||
- **callback** (事件回调函数)
|
||||
- **errback** (错误回调函数)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
title: Markdown编辑器
|
||||
icon: circle-info
|
||||
---
|
||||
|
||||
## 概览
|
||||
Markdown 编辑器允许您使用丰富的文本编辑体验创建和编辑 Markdown 文件。它包括实时保存、文件导入/导出以及访问最近的 Markdown 文件等功能。
|
||||
|
|
|
|||
159
docs/Store.md
Normal file
159
docs/Store.md
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
## 应用管理
|
||||
|
||||
### 如何添加一个应用
|
||||
|
||||
1. 在用户目录.godoos/run下建立一个应用文件夹。
|
||||
2. 一个应用需要包含两个配置文件,在应用根目录下创建`install.json`和`store.json`两个文件。 配置文件格式如下:
|
||||
- install.json
|
||||
```json
|
||||
{
|
||||
"name": "", // string, 应用程序名称。
|
||||
"url": "", // string, 应用程序下载地址。
|
||||
"webUrl":"", // string, 如何设置,应用程序将显示到桌面。
|
||||
"isDev": true, // boolean, 是否为开发环境。
|
||||
"needDownload": true, // boolean, 是否需要下载。
|
||||
"needInstall": true, // boolean, 是否需要安装。
|
||||
"version": "1.0.0", // string, 应用程序版本。
|
||||
"icon": "", // string, 应用程序图标,为可访问的网络地址。
|
||||
"checkProgress": true, // boolean, 表示是否显示启动和停止。
|
||||
"hasRestart": true, // boolean, 是否需要重启。
|
||||
"setting": true // boolean, 是否需要设置。
|
||||
}
|
||||
|
||||
```
|
||||
如果不需要后端进程的web应用,store.json可以不用配置。
|
||||
|
||||
install.json的struct为:
|
||||
```json
|
||||
type InstallInfo struct {
|
||||
Name string `json:"name"` // 应用程序名称。重要,必须和应用的目录名称一致。
|
||||
URL string `json:"url"` // 应用程序下载地址。
|
||||
WebUrl string `json:"webUrl"` // 应用程序的网页地址。
|
||||
IsDev bool `json:"isDev"` // 标志位,表示是否为开发者版本。
|
||||
NeedDownload bool `json:"needDownload"` // 标志位,表示是否需要下载。
|
||||
NeedInstall bool `json:"needInstall"` // 标志位,表示是否需要安装。
|
||||
Version string `json:"version"` // 应用程序的版本号。
|
||||
Desc string `json:"desc"` // 应用程序的描述信息。
|
||||
Icon string `json:"icon"` // 应用程序的图标路径。
|
||||
CheckProgress bool `json:"checkProgress"` // 标志位,表示是否显示启动和停止。
|
||||
HasRestart bool `json:"hasRestart"` // 标志位,表示安装后是否需要重启。
|
||||
Setting bool `json:"setting"` // 标志位,表示是否需要配置。
|
||||
}
|
||||
```
|
||||
|
||||
- store.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "mysql5.7", // string, 应用程序名称。可不设置,会继承自install.json
|
||||
"icon": "mysql.png", // string, 应用程序图标,一般放在应用程序static目录下。
|
||||
"setting": {
|
||||
"binPath": "{exePath}/bin/mysqld.exe", // string, 重要,必须设置。为启动程序路径。
|
||||
"confPath": "{exePath}/my.ini",// string, 重要,必须设置。为配置文件路径。
|
||||
"progressName": "mysqld.exe",// string, 进程名称。如果为单线程可不设置。
|
||||
"isOn": true // boolean, 是否启动守护进程
|
||||
},
|
||||
"config": { // object, 配置文件。里面的配置可以任意填写,和cmds配合使用。
|
||||
},
|
||||
"cmds": {},// object, 命令集。
|
||||
"install": { // object, 安装配置。
|
||||
"envs": [],// object[], 环境变量。
|
||||
"cmds": []// object[], 启动命令。
|
||||
},
|
||||
"start": {// object, 启动配置。
|
||||
"envs": [],// object[], 环境变量。
|
||||
"cmds": []// object[], 启动命令。
|
||||
}
|
||||
}
|
||||
```
|
||||
store.json的struct为
|
||||
```json
|
||||
type StoreInfo struct {
|
||||
Name string `json:"name"` // 应用程序的名称。
|
||||
Icon string `json:"icon"` // 应用程序的图标路径。
|
||||
Setting Setting `json:"setting"` // 应用程序的配置信息。
|
||||
Config map[string]any `json:"config"` // 应用程序的配置信息映射。
|
||||
Cmds map[string][]Cmd `json:"cmds"` // 应用程序的命令集合。
|
||||
Install Install `json:"install"` // 安装应用程序的信息。
|
||||
Start Install `json:"start"` // 启动应用程序的信息。
|
||||
}
|
||||
```
|
||||
Setting的结构体为:
|
||||
```json
|
||||
// 包含应用程序的二进制文件路径、配置文件路径等关键设置信息。
|
||||
type Setting struct {
|
||||
BinPath string `json:"binPath"` // 应用程序二进制文件的路径。
|
||||
ConfPath string `json:"confPath"` // 应用程序配置文件的路径。
|
||||
ProgressName string `json:"progressName"` // 进程的名称。
|
||||
IsOn bool `json:"isOn"` //是否守护进程运行。
|
||||
}
|
||||
```
|
||||
Cmd的结构体为:
|
||||
```json
|
||||
type Cmd struct {
|
||||
Name string `json:"name"` // 命令的名称。
|
||||
FilePath string `json:"filePath,omitempty"` // 命令文件的路径。
|
||||
Content string `json:"content,omitempty"` // 命令的内容。
|
||||
BinPath string `json:"binPath,omitempty"` // 执行命令的二进制文件路径。
|
||||
TplPath string `json:"tplPath,omitempty"` // 命令的模板路径。
|
||||
Cmds []string `json:"cmds,omitempty"` // 要执行的子命令列表。
|
||||
Waiting int `json:"waiting"` // 等待的时间。
|
||||
Kill bool `json:"kill"` // 标志位,表示是否需要终止之前的命令。如果setting中设置了progressName会优先杀死整个进程
|
||||
Envs []Item `json:"envs"` // 命令执行时的环境变量。
|
||||
}
|
||||
```
|
||||
Install的struct为:
|
||||
```json
|
||||
// Install 描述了安装过程中的环境变量和命令列表。
|
||||
type Install struct {
|
||||
Envs []Item `json:"envs"` // 安装过程中需要的环境变量。
|
||||
Cmds []string `json:"cmds"` // 安装过程中需要执行的命令列表。
|
||||
}
|
||||
// Item 是一个通用的键值对结构体,用于表示配置项或环境变量等。
|
||||
type Item struct {
|
||||
Name string `json:"name"` // 配置项的名称。
|
||||
Value any `json:"value"` // 配置项的值。
|
||||
}
|
||||
```
|
||||
|
||||
3. 在应用商店添加应用,选择本地添加,输入应用的目录名字(不需要填写整个目录)。
|
||||
|
||||
### 配置文件`store.json`说明
|
||||
|
||||
1. `install`和`start`配置可以调用`cmds`里的命令。
|
||||
2. `cmds`里的命令也可以调用自身的命令。
|
||||
3. 所有的命令都可以串联使用。
|
||||
|
||||
### 如何设置配置
|
||||
1. 在应用目录下建立`static`目录,并创建`index.html`文件。`install.json`中`setting`设置为`true`。
|
||||
前端配置样列
|
||||
```js
|
||||
const postData = {
|
||||
dataDir: dataDir,// 此处对应store.json中的config配置项
|
||||
logDir: logDir,// 此处对应store.json中的config配置项
|
||||
port: port, // 此处对应store.json中的config配置项
|
||||
name: "mysql5.7",// 应用名称
|
||||
cmdKey: "setting"// 命令键,cmds的name
|
||||
}
|
||||
const comp = await fetch('http://localhost:56780/store/setting', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
});
|
||||
```
|
||||
关键要设置好`name`和`cmdKey`,`cmdKey`对应`store.json`中的`cmds`数组配置项中的`name`,一个数组中又可以配置一系列的命令,可以参考Cmd的结构体
|
||||
|
||||
### 内置应用说明
|
||||
- 系列封装了一些用于处理进程控制和文件操作的功能函数,以下是各函数的详细描述:
|
||||
1. `start` 启动应用
|
||||
2. `stop` 停止应用
|
||||
3. `restart` 重启应用
|
||||
4. `exec` 执行命令 必须设置binPath和cmds
|
||||
5. `writeFile` 写入文件 必须设置filePath和content,以config为依据,对content进行替换
|
||||
6. `changeFile` 修改文件 必须设置filePath和tplPath,以config为依据,在模板文件中进行替换
|
||||
7. `deleteFile` 删除文件
|
||||
8. `unzip` 解压文件 必须设置filePath和content,content为解压目录
|
||||
9. `zip` 压缩文件 必须设置filePath和content,filePath为将要压缩的文件夹,content为压缩后的文件名
|
||||
10. `mkdir` 创建文件夹 必须设置FilePath,FilePath为创建的文件夹路径
|
||||
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
|
|
@ -44,6 +44,7 @@ declare module 'vue' {
|
|||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
|
|
|
|||
|
|
@ -4,19 +4,12 @@
|
|||
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7/mysql5.7.zip",
|
||||
"needDownload": true,
|
||||
"needInstall": true,
|
||||
"isDev":true,
|
||||
"version": "5.7.37",
|
||||
"icon": "https://img0.baidu.com/it/u=1087008535,3349487719&fm=253&fmt=auto&app=138&f=PNG?w=969&h=500",
|
||||
"checkProgress": true,
|
||||
"hasRestart": true,
|
||||
"setting": [
|
||||
{
|
||||
"path": "setting.html",
|
||||
"lang":{
|
||||
"cn-zh": "设置",
|
||||
"en": "Setting"
|
||||
}
|
||||
}
|
||||
]
|
||||
"setting": true
|
||||
},
|
||||
{
|
||||
"name": "nginx",
|
||||
|
|
|
|||
|
|
@ -221,6 +221,7 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 10px;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { OpenDirDialog } from "@/util/goutil";
|
||||
import { getSystemKey, parseJson } from "@/system/config";
|
||||
const apiUrl = getSystemKey("apiUrl");
|
||||
const installed = getSystemKey("intstalledPlugins");
|
||||
import { notifySuccess, notifyError } from "@/util/msg";
|
||||
import { t } from "@/i18n";
|
||||
const formData = ref({
|
||||
importType: 'download',
|
||||
url: '',
|
||||
devPath:''
|
||||
devPath: ''
|
||||
})
|
||||
const progress = ref(0)
|
||||
const addType = [
|
||||
{
|
||||
'name': '远程下载',
|
||||
|
|
@ -21,12 +27,46 @@ const addType = [
|
|||
}
|
||||
]
|
||||
function selectFile() {
|
||||
OpenDirDialog().then((res: string) => {
|
||||
formData.value.devPath = res;
|
||||
});
|
||||
OpenDirDialog().then((res: string) => {
|
||||
formData.value.devPath = res;
|
||||
});
|
||||
}
|
||||
async function addAppByDownload() {
|
||||
|
||||
//console.log(item)
|
||||
if (formData.value.url == "") {
|
||||
notifyError(t("store.urlEmpty"))
|
||||
return;
|
||||
}
|
||||
const completion = await fetch(apiUrl + '/store/download?url=' + formData.value.url)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.downloadError"))
|
||||
}
|
||||
//console.log(completion)
|
||||
const reader: any = completion.body?.getReader();
|
||||
if (!reader) {
|
||||
notifyError(t("store.cantStream"));
|
||||
}
|
||||
while (true) {
|
||||
const { done, value } = await reader?.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
// console.log(value)
|
||||
const json = await new TextDecoder().decode(value);
|
||||
//console.log(json)
|
||||
const res = parseJson(json)
|
||||
//console.log(res)
|
||||
if (res) {
|
||||
if (res.progress) {
|
||||
progress.value = res.progress
|
||||
}
|
||||
if (res.done) {
|
||||
notifySuccess(t("store.downloadSuccess"))
|
||||
progress.value = 0
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async function addAppByImport() {
|
||||
|
||||
|
|
@ -46,6 +86,9 @@ async function addAppByDev() {
|
|||
<el-form-item label="下载地址">
|
||||
<el-input v-model="formData.url" style="width: 280px;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下载进度" v-if="progress > 0">
|
||||
<el-progress :text-inside="true" :stroke-width="20" :percentage="progress" />
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="addAppByDownload()">添加</el-button>
|
||||
</template>
|
||||
<template v-if="formData.importType == 'local'">
|
||||
|
|
@ -53,7 +96,8 @@ async function addAppByDev() {
|
|||
</template>
|
||||
<template v-if="formData.importType == 'dev'">
|
||||
<el-form-item label="本地路径">
|
||||
<el-input v-model="formData.devPath" style="width: 280px;" @click="selectFile()" />
|
||||
<el-input v-model="formData.devPath" style="width: 280px;" placeholder="用户目录下.godoos/run,只填目录名称"
|
||||
@click="selectFile()" />
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="addAppByDev()">添加</el-button>
|
||||
</template>
|
||||
|
|
@ -61,6 +105,6 @@ async function addAppByDev() {
|
|||
</template>
|
||||
<style scoped>
|
||||
.el-button {
|
||||
margin:20px 100px
|
||||
margin: 20px 100px
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,8 +4,9 @@
|
|||
<div class="app-content">
|
||||
<div class="app-title">{{ item?.name }}</div>
|
||||
<div class="app-desc">
|
||||
<span>{{ item?.desc ?? item?.name }}</span>
|
||||
<el-progress :text-inside="true" style="margin-top: 3px;" v-if="item?.progress" :stroke-width="20"
|
||||
:percentage="item?.progress" />
|
||||
:percentage="item?.progress" />
|
||||
</div>
|
||||
<div class="app-button">
|
||||
|
||||
|
|
@ -17,17 +18,20 @@
|
|||
<div v-if="item!.hasRestart && item?.isRuning">
|
||||
<button @click="restart?.(item)">重启</button>
|
||||
</div>
|
||||
<button @click="setting?.(item)" v-if="!item?.isRuning && item?.setting">配置</button>
|
||||
<button @click="uninstall?.(item)" v-if="!item?.isRuning">卸载</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button v-if="item?.url" @click="install?.(item)" :disabled="item?.progress">安装</button>
|
||||
|
||||
<button @click="install?.(item)" :disabled="item?.progress">安装</button>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { BrowserWindow } from "@/system";
|
||||
defineProps({
|
||||
item: Object,
|
||||
installedList: Array,
|
||||
|
|
@ -37,11 +41,26 @@ defineProps({
|
|||
start: Function,
|
||||
restart: Function
|
||||
});
|
||||
|
||||
function setting(item: any) {
|
||||
const win = new BrowserWindow({
|
||||
title: "配置",
|
||||
url: `http://localhost:56780/static/${item.name}/index.html`,
|
||||
icon: "gallery",
|
||||
width: 500,
|
||||
height: 500,
|
||||
center: true,
|
||||
minimizable: false,
|
||||
resizable: true,
|
||||
});
|
||||
win.show()
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.app-item {
|
||||
width: 350px;
|
||||
height: 100px;
|
||||
width: calc(100% - 20px);
|
||||
min-height: 100px;
|
||||
min-width:200px;
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -49,15 +68,23 @@ defineProps({
|
|||
align-items: center;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #cccccc79;
|
||||
border: 1px solid rgba(204, 204, 204, 0.79);
|
||||
transition: all 0.2s;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-item {
|
||||
flex-direction: column;
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
}
|
||||
|
||||
.app-img {
|
||||
padding: 10px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #cccccc7b;
|
||||
border: 1px solid rgba(204, 204, 204, 0.79);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
|
@ -67,8 +94,7 @@ defineProps({
|
|||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
height: 80px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
|
|
@ -76,31 +102,47 @@ defineProps({
|
|||
font-weight: bold;
|
||||
padding-bottom: 6px;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-desc {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
padding: 5px; /* 添加内边距 */
|
||||
/* 可选:添加边框 */
|
||||
/* border: 1px solid #ddd; */
|
||||
/* 可选:添加背景色 */
|
||||
/* background-color: #f9f9f9; */
|
||||
}
|
||||
@media (min-width: 769px) {
|
||||
.app-desc {
|
||||
font-size: 14px; /* 在大屏幕上使用更大的字体大小 */
|
||||
}
|
||||
}
|
||||
|
||||
.app-item:hover {
|
||||
box-shadow: 0 0 20px 2px #dddddd5b;
|
||||
box-shadow: 0 0 20px 2px rgba(221, 221, 221, 0.5);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.app-item:active {
|
||||
box-shadow: 0 0 0px #ccc;
|
||||
transform: translateY(0px);
|
||||
box-shadow: none;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.app-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.app-button button {
|
||||
|
|
@ -111,15 +153,14 @@ defineProps({
|
|||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.app-button button:hover {
|
||||
box-shadow: 0 0 10px #ccc;
|
||||
/* transform: translateY(-2px); */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.app-button button:active {
|
||||
box-shadow: 0 0 0px #ccc;
|
||||
transform: translateY(0px);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -24,15 +24,17 @@ onMounted(async () => {
|
|||
async function getList() {
|
||||
if (currentCate.value == 'add') return;
|
||||
storeList.value = storeInitList
|
||||
|
||||
// const apiUrl = getSystemKey("apiUrl");
|
||||
// const storeUrl = apiUrl + '/store/storelist?cate=' + currentCate.value
|
||||
// fetch(storeUrl).then(res => {
|
||||
// res.json().then(data => {
|
||||
// storeList.value = data
|
||||
// })
|
||||
// }).catch(() => {
|
||||
// notifyError(t("store.errorList"))
|
||||
// })
|
||||
// const res = await fetch(storeUrl)
|
||||
// if (!res.ok) {
|
||||
// notifyError(t('store.getStoreListError'))
|
||||
// return
|
||||
// }
|
||||
// const data = await res.json()
|
||||
// //console.log(data)
|
||||
// storeList.value = data
|
||||
await checkProgress()
|
||||
isready.value = true;
|
||||
}
|
||||
|
|
@ -41,8 +43,8 @@ async function checkProgress() {
|
|||
if (!completion.ok) {
|
||||
return
|
||||
}
|
||||
let res:any = await completion.json()
|
||||
if(!res || res.length < 1){
|
||||
let res: any = await completion.json()
|
||||
if (!res || res.length < 1) {
|
||||
res = []
|
||||
}
|
||||
storeList.value.forEach((item: any, index: number) => {
|
||||
|
|
@ -50,7 +52,7 @@ async function checkProgress() {
|
|||
console.log(pitem)
|
||||
if (pitem) {
|
||||
storeList.value[index].isRuning = pitem.running
|
||||
}else{
|
||||
} else {
|
||||
storeList.value[index].isRuning = false
|
||||
}
|
||||
})
|
||||
|
|
@ -69,7 +71,7 @@ function setCache() {
|
|||
}
|
||||
async function install(item: any) {
|
||||
|
||||
if (item.needDownload) {
|
||||
if (item.needDownload && !item.isDev) {
|
||||
await download(item)
|
||||
}
|
||||
if (item.needInstall) {
|
||||
|
|
@ -84,8 +86,11 @@ async function install(item: any) {
|
|||
notifyError(res.message)
|
||||
return
|
||||
}
|
||||
if(res.data){
|
||||
item.icon = res.data
|
||||
}
|
||||
}
|
||||
if(item.isOut){
|
||||
if (item.isOut) {
|
||||
item.progress = 0
|
||||
const completion = await fetch(apiUrl + '/store/installOut?url=' + item.url)
|
||||
if (!completion.ok) {
|
||||
|
|
@ -98,10 +103,10 @@ async function install(item: any) {
|
|||
return
|
||||
}
|
||||
}
|
||||
if (item.isWeb) {
|
||||
if (item.webUrl) {
|
||||
sys.fs.writeFile(
|
||||
`${sys._options.userLocation}Desktop/${item.name}.url`,
|
||||
`link::url::${item.url}::${item.icon}`
|
||||
`link::url::${item.webUrl}::${item.icon}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -165,26 +170,26 @@ async function download(item: any) {
|
|||
}
|
||||
}
|
||||
async function pauseApp(item: any) {
|
||||
const res:any = await fetch(apiUrl + '/store/stop/' + item.name)
|
||||
if(!res.ok){
|
||||
const res: any = await fetch(apiUrl + '/store/stop/' + item.name)
|
||||
if (!res.ok) {
|
||||
const msg = await res.text()
|
||||
notifyError(msg)
|
||||
return
|
||||
}
|
||||
setTimeout(async() => {
|
||||
setTimeout(async () => {
|
||||
await checkProgress()
|
||||
}, 1000)
|
||||
|
||||
|
||||
}
|
||||
async function restartApp(item: any) {
|
||||
await fetch(apiUrl + '/store/restart/' + item.name)
|
||||
setTimeout(async() => {
|
||||
setTimeout(async () => {
|
||||
await checkProgress()
|
||||
}, 1000)
|
||||
}
|
||||
async function startApp(item: any) {
|
||||
await fetch(apiUrl + '/store/start/' + item.name)
|
||||
setTimeout(async() => {
|
||||
setTimeout(async () => {
|
||||
await checkProgress()
|
||||
}, 1000)
|
||||
}
|
||||
|
|
@ -204,25 +209,10 @@ async function startApp(item: any) {
|
|||
</div>
|
||||
<div class="store">
|
||||
<div v-if="isready" class="store-top">
|
||||
<!-- <div class="left-bar"></div> -->
|
||||
<div class="right-main">
|
||||
<div class="main-title">
|
||||
<span class="sub-title">{{ currentTitle }} </span>
|
||||
</div>
|
||||
<!-- <div class="swiper">
|
||||
<div class="swiper-txt">主页</div>
|
||||
<div class="swiper-inner">
|
||||
<div class="swiper-tab">
|
||||
<img src="/image/store/banner1.jpg" />
|
||||
</div>
|
||||
<div class="swiper-tab">
|
||||
<img src="/image/store/banner2.jpg" />
|
||||
</div>
|
||||
<div class="swiper-tab">
|
||||
<img src="/image/store/banner3.jpg" />
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="main-app">
|
||||
<div v-for="item in storeList" v-if="currentCate != 'add'" class="store-item" :key="item.name">
|
||||
<AppItem :item="item" :installed-list="installedList" :install="install" :uninstall="uninstall"
|
||||
|
|
|
|||
|
|
@ -41,11 +41,12 @@ func OsStart() {
|
|||
progressRouter.HandleFunc("/listporgress", store.Status).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/listport", store.ListPortsHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/killport", store.KillPortHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/storelist", store.GetStoreInfoHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/storelist", store.GetStoreListHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/download", store.DownloadHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/install", store.InstallHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/installOut", store.RunOutHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/uninstall", store.UnInstallHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/setting", store.StoreSettingHandler).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CheckActive(ctx context.Context) {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
activeCheckWg := sync.WaitGroup{}
|
||||
|
|
@ -38,12 +37,15 @@ func checkInactiveProcesses() {
|
|||
defer processesMu.RUnlock()
|
||||
|
||||
for name, p := range processes {
|
||||
pid := p.Cmd.Process.Pid
|
||||
pid := p.Pid
|
||||
_, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
p.Running = false
|
||||
log.Printf("Process %s (PID: %d) not found.", name, pid)
|
||||
if p.IsOn {
|
||||
go ExecuteScript(name)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Error finding process %s (PID: %d): %v", name, pid, err)
|
||||
}
|
||||
|
|
@ -70,11 +72,14 @@ func IsProcessRunning(appName string) (bool, error) {
|
|||
|
||||
var output []byte
|
||||
var err error
|
||||
if cmd.SysProcAttr == nil {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
// if cmd.SysProcAttr == nil {
|
||||
// cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
// }
|
||||
//cmd.SysProcAttr.HideWindow = true // For Windows to hide the console window
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = SetHideConsoleCursor(cmd)
|
||||
}
|
||||
if output, err = cmd.CombinedOutput(); err != nil {
|
||||
return false, fmt.Errorf("error checking process: %w", err)
|
||||
}
|
||||
|
|
|
|||
149
godo/store/cmds.go
Normal file
149
godo/store/cmds.go
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"godo/files"
|
||||
"godo/libs"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func runStop(storeInfo StoreInfo) error {
|
||||
return StopCmd(storeInfo.Name)
|
||||
}
|
||||
func runStart(storeInfo StoreInfo) error {
|
||||
err := SetEnvs(storeInfo.Start.Envs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
if !libs.PathExists(storeInfo.Setting.BinPath) {
|
||||
return fmt.Errorf("script file %s does not exist", storeInfo.Setting.BinPath)
|
||||
}
|
||||
var cmd *exec.Cmd
|
||||
if len(storeInfo.Start.Cmds) > 0 {
|
||||
cmd = exec.Command(storeInfo.Setting.BinPath, storeInfo.Start.Cmds...)
|
||||
} else {
|
||||
cmd = exec.Command(storeInfo.Setting.BinPath)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = SetHideConsoleCursor(cmd)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
//log.Println("Error starting script:", err)
|
||||
return fmt.Errorf("error starting script: %v", err)
|
||||
}
|
||||
|
||||
// 启动脚本命令并返回可能的错误
|
||||
go func(cmd *exec.Cmd) {
|
||||
RegisterProcess(storeInfo.Name, storeInfo.Setting.ProgressName, storeInfo.Setting.IsOn, cmd)
|
||||
if err := cmd.Wait(); err != nil {
|
||||
log.Println("Error starting script:", err)
|
||||
return
|
||||
}
|
||||
|
||||
}(cmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
func runRestart(storeInfo StoreInfo) error {
|
||||
err := runStop(storeInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
return runStart(storeInfo)
|
||||
}
|
||||
func runExec(storeInfo StoreInfo, cmdParam Cmd) error {
|
||||
err := SetEnvs(cmdParam.Envs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
log.Printf("bin path:%v", cmdParam.BinPath)
|
||||
log.Printf("cmds:%v", cmdParam.Cmds)
|
||||
cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...)
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = SetHideConsoleCursor(cmd)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
go func() {
|
||||
if cmdParam.Waiting > 0 {
|
||||
if err = cmd.Wait(); err != nil {
|
||||
log.Printf("failed to wait exec process %s: %s", storeInfo.Name, err.Error())
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second * time.Duration(cmdParam.Waiting))
|
||||
}
|
||||
if cmdParam.Kill {
|
||||
if storeInfo.Setting.ProgressName != "" {
|
||||
if err := KillProcessByName(storeInfo.Setting.ProgressName); err != nil {
|
||||
log.Printf("failed to kill process %s: %s", storeInfo.Setting.ProgressName, err.Error())
|
||||
}
|
||||
} else {
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
log.Printf("failed to kill process %s: %s", storeInfo.Name, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//log.Printf("run exec process %s, name is %s", storeInfo.Name, cmdParam.Name)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
func WriteFile(cmd Cmd) error {
|
||||
if cmd.FilePath != "" {
|
||||
content := cmd.Content
|
||||
if content != "" {
|
||||
err := os.WriteFile(cmd.FilePath, []byte(content), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func ChangeFile(storeInfo StoreInfo, cmd Cmd) error {
|
||||
if cmd.FilePath != "" && cmd.TplPath != "" && libs.PathExists(cmd.TplPath) && libs.PathExists(cmd.FilePath) {
|
||||
content, err := os.ReadFile(cmd.TplPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read tpl file: %w", err)
|
||||
}
|
||||
exePath := GetExePath(storeInfo.Name)
|
||||
contentstr := strings.ReplaceAll(string(content), "{exePath}", exePath)
|
||||
contentstr = ChangeConfig(storeInfo, contentstr)
|
||||
if err := os.WriteFile(cmd.FilePath, []byte(contentstr), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func DeleteFile(cmd Cmd) error {
|
||||
if cmd.FilePath != "" {
|
||||
err := os.Remove(cmd.FilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete file: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func MkDir(cmd Cmd) error {
|
||||
err := os.MkdirAll(cmd.FilePath, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make dir: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func Unzip(cmd Cmd) error {
|
||||
return files.Decompress(cmd.FilePath, cmd.Content)
|
||||
}
|
||||
func Zip(cmd Cmd) error {
|
||||
return files.Encompress(cmd.FilePath, cmd.Content)
|
||||
}
|
||||
|
|
@ -3,79 +3,49 @@ package store
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"godo/files"
|
||||
"godo/libs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StoreInfo struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
NeedDownload bool `json:"needDownload"`
|
||||
Icon string `json:"icon"`
|
||||
Setting Setting `json:"setting"`
|
||||
Config map[string]any `json:"config"`
|
||||
Cmds map[string][]Cmd `json:"cmds"`
|
||||
Install Install `json:"install"`
|
||||
Start Install `json:"start"`
|
||||
}
|
||||
type Setting struct {
|
||||
BinPath string `json:"binPath"`
|
||||
ConfPath string `json:"confPath"`
|
||||
DataDir string `json:"dataDir"`
|
||||
LogDir string `json:"logDir"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Name string `json:"name"`
|
||||
Value any `json:"value"`
|
||||
}
|
||||
type Cmd struct {
|
||||
Name string `json:"name"`
|
||||
FilePath string `json:"filePath,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
BinPath string `json:"binPath,omitempty"`
|
||||
Cmds []string `json:"cmds,omitempty"`
|
||||
Waiting int `json:"waiting"`
|
||||
Kill bool `json:"kill"`
|
||||
Envs []Item `json:"envs"`
|
||||
}
|
||||
|
||||
type Install struct {
|
||||
Envs []Item `json:"envs"`
|
||||
Cmds []string `json:"cmds"`
|
||||
}
|
||||
|
||||
func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||
pluginName := r.URL.Query().Get("name")
|
||||
if pluginName == "" {
|
||||
libs.ErrorMsg(w, "the app name is empty!")
|
||||
return
|
||||
}
|
||||
exeDir := libs.GetRunDir()
|
||||
exePath := filepath.Join(exeDir, pluginName)
|
||||
log.Printf("the app path is %s", exePath)
|
||||
exePath := GetExePath(pluginName)
|
||||
//log.Printf("the app path is %s", exePath)
|
||||
if !libs.PathExists(exePath) {
|
||||
libs.ErrorMsg(w, "the app path is not exists!")
|
||||
return
|
||||
}
|
||||
installFile := filepath.Join(exePath, "install.json")
|
||||
if !libs.PathExists(installFile) {
|
||||
libs.ErrorMsg(w, "the install.json is not exists!")
|
||||
installInfo, err := GetInstallInfo(pluginName)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "the install.json is error:"+err.Error())
|
||||
return
|
||||
}
|
||||
if pluginName != installInfo.Name {
|
||||
libs.ErrorMsg(w, "the app name must equal the install.json!")
|
||||
return
|
||||
}
|
||||
if !installInfo.NeedInstall {
|
||||
libs.SuccessMsg(w, "success", "the app is installed!")
|
||||
return
|
||||
}
|
||||
storeFile := filepath.Join(exePath, "store.json")
|
||||
if !libs.PathExists(storeFile) {
|
||||
libs.ErrorMsg(w, "the store.json is not exists!")
|
||||
return
|
||||
}
|
||||
var storeInfo StoreInfo
|
||||
content, err := os.ReadFile(installFile)
|
||||
content, err := os.ReadFile(storeFile)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "cant read the install.json!")
|
||||
libs.ErrorMsg(w, "cant read the store.json!")
|
||||
return
|
||||
}
|
||||
//设置 info.json
|
||||
|
|
@ -85,17 +55,16 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
err = json.Unmarshal(contentBytes, &storeInfo)
|
||||
if err != nil {
|
||||
log.Printf("Error during unmarshal: %v", err)
|
||||
libs.ErrorMsg(w, "the install.json is error: "+err.Error())
|
||||
libs.ErrorMsg(w, "the store.json is error: "+err.Error())
|
||||
return
|
||||
}
|
||||
replacePlaceholdersInCmds(&storeInfo)
|
||||
infoFile := filepath.Join(exePath, "info.json")
|
||||
err = SaveStoreInfo(storeInfo, infoFile)
|
||||
storeInfo.Name = installInfo.Name
|
||||
err = SaveInfoFile(storeInfo)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "save the info.json is error!")
|
||||
libs.ErrorMsg(w, "the store info.json is error: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//设置config
|
||||
if libs.PathExists(storeInfo.Setting.ConfPath + ".tpl") {
|
||||
err = SaveStoreConfig(storeInfo, exePath)
|
||||
|
|
@ -109,20 +78,27 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
libs.ErrorMsg(w, "install the app is error!")
|
||||
return
|
||||
}
|
||||
var res string
|
||||
//复制static目录
|
||||
staticPath := filepath.Join(exePath, "static")
|
||||
if libs.PathExists(staticPath) {
|
||||
staticDir := libs.GetStaticDir()
|
||||
targetPath := filepath.Join(staticDir, pluginName)
|
||||
err = os.Rename(staticPath, targetPath)
|
||||
if err != nil {
|
||||
// libs.ErrorMsg(w, "copy the static is error!")
|
||||
// return
|
||||
log.Printf("copy the static is error! %v", err)
|
||||
if !libs.PathExists(targetPath) {
|
||||
err = os.Rename(staticPath, targetPath)
|
||||
if err != nil {
|
||||
log.Printf("copy the static is error! %v", err)
|
||||
}
|
||||
iconPath := filepath.Join(targetPath, storeInfo.Icon)
|
||||
if libs.PathExists(iconPath) {
|
||||
res = "http://localhost:56780/static/" + pluginName + "/" + storeInfo.Icon
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
libs.SuccessMsg(w, "success", "install the app success!")
|
||||
libs.SuccessMsg(w, res, "install the app success!")
|
||||
}
|
||||
|
||||
func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||
pluginName := r.URL.Query().Get("name")
|
||||
if pluginName == "" {
|
||||
|
|
@ -136,8 +112,16 @@ func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// return
|
||||
log.Printf("stop the app is error! %s", err)
|
||||
}
|
||||
exeDir := libs.GetRunDir()
|
||||
exePath := filepath.Join(exeDir, pluginName)
|
||||
installInfo, err := GetInstallInfo(pluginName)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "the install.json is error:"+err.Error())
|
||||
return
|
||||
}
|
||||
if installInfo.IsDev {
|
||||
libs.SuccessMsg(w, "success", "uninstall the app success!")
|
||||
return
|
||||
}
|
||||
exePath := GetExePath(pluginName)
|
||||
//log.Printf("the app path is %s", exePath)
|
||||
if libs.PathExists(exePath) {
|
||||
err := os.RemoveAll(exePath)
|
||||
|
|
@ -158,6 +142,28 @@ func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
libs.SuccessMsg(w, "success", "uninstall the app success!")
|
||||
|
||||
}
|
||||
func GetInstallInfo(pluginName string) (InstallInfo, error) {
|
||||
var installInfo InstallInfo
|
||||
exePath := GetExePath(pluginName)
|
||||
installFile := filepath.Join(exePath, "install.json")
|
||||
if !libs.PathExists(installFile) {
|
||||
return installInfo, fmt.Errorf("install.json is not exist:%s", installFile)
|
||||
}
|
||||
content, err := os.ReadFile(installFile)
|
||||
if err != nil {
|
||||
return installInfo, err
|
||||
}
|
||||
err = json.Unmarshal(content, &installInfo)
|
||||
if err != nil {
|
||||
return installInfo, err
|
||||
}
|
||||
return installInfo, nil
|
||||
}
|
||||
func GetExePath(pluginName string) string {
|
||||
exeDir := libs.GetRunDir()
|
||||
return filepath.Join(exeDir, pluginName)
|
||||
}
|
||||
|
||||
func convertValueToString(value any) string {
|
||||
var strValue string
|
||||
switch v := value.(type) {
|
||||
|
|
@ -187,157 +193,69 @@ func InstallStore(storeInfo StoreInfo) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
func RunCmds(storeInfo StoreInfo, cmdKey string) error {
|
||||
cmds := storeInfo.Cmds[cmdKey]
|
||||
for _, cmd := range cmds {
|
||||
|
||||
if cmd.Name == "stop" {
|
||||
runStop(storeInfo)
|
||||
}
|
||||
if cmd.Name == "start" {
|
||||
runStart(storeInfo)
|
||||
}
|
||||
if cmd.Name == "restart" {
|
||||
runRestart(storeInfo)
|
||||
}
|
||||
if cmd.Name == "exec" {
|
||||
runExec(storeInfo, cmd)
|
||||
}
|
||||
if cmd.Name == "writeFile" {
|
||||
err := WriteFile(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "deleteFile" {
|
||||
err := DeleteFile(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "unzip" {
|
||||
err := Unzip(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unzip file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "zip" {
|
||||
err := Zip(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unzip file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "mkdir" {
|
||||
err := MkDir(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
func RunCmd(storeInfo StoreInfo, cmd Cmd) error {
|
||||
if cmd.Name == "stop" {
|
||||
runStop(storeInfo)
|
||||
}
|
||||
if cmd.Name == "start" {
|
||||
runStart(storeInfo)
|
||||
}
|
||||
if cmd.Name == "restart" {
|
||||
runRestart(storeInfo)
|
||||
}
|
||||
if cmd.Name == "exec" {
|
||||
runExec(storeInfo, cmd)
|
||||
}
|
||||
if cmd.Name == "writeFile" {
|
||||
err := WriteFile(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func runStop(storeInfo StoreInfo) error {
|
||||
return StopCmd(storeInfo.Name)
|
||||
}
|
||||
func runStart(storeInfo StoreInfo) error {
|
||||
err := SetEnvs(storeInfo.Start.Envs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
if !libs.PathExists(storeInfo.Setting.BinPath) {
|
||||
return fmt.Errorf("script file %s does not exist", storeInfo.Setting.BinPath)
|
||||
}
|
||||
var cmd *exec.Cmd
|
||||
if len(storeInfo.Start.Cmds) > 0 {
|
||||
cmd = exec.Command(storeInfo.Setting.BinPath, storeInfo.Start.Cmds...)
|
||||
} else {
|
||||
cmd = exec.Command(storeInfo.Setting.BinPath)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = SetHideConsoleCursor(cmd)
|
||||
}
|
||||
// 启动脚本命令并返回可能的错误
|
||||
go func(cmd *exec.Cmd) {
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Println("Error starting script:", err)
|
||||
return
|
||||
}
|
||||
RegisterProcess(storeInfo.Name, cmd)
|
||||
}(cmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
func runRestart(storeInfo StoreInfo) error {
|
||||
err := runStop(storeInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
return runStart(storeInfo)
|
||||
}
|
||||
func runExec(storeInfo StoreInfo, cmdParam Cmd) error {
|
||||
err := SetEnvs(cmdParam.Envs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
log.Printf("bin path:%v", cmdParam.BinPath)
|
||||
log.Printf("cmds:%v", cmdParam.Cmds)
|
||||
cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...)
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = SetHideConsoleCursor(cmd)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
if cmdParam.Waiting > 0 {
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("failed to wait for exec process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
time.Sleep(time.Second * time.Duration(cmdParam.Waiting))
|
||||
}
|
||||
if cmdParam.Kill {
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
return fmt.Errorf("failed to kill process: %v", err)
|
||||
if cmd.Name == "changeFile" {
|
||||
err := ChangeFile(storeInfo, cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to change to file: %w", err)
|
||||
}
|
||||
}
|
||||
log.Printf("run exec process %s, name is %s", storeInfo.Name, cmdParam.Name)
|
||||
return nil
|
||||
}
|
||||
func WriteFile(cmd Cmd) error {
|
||||
if cmd.FilePath != "" {
|
||||
content := cmd.Content
|
||||
if content != "" {
|
||||
err := os.WriteFile(cmd.FilePath, []byte(content), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func DeleteFile(cmd Cmd) error {
|
||||
if cmd.FilePath != "" {
|
||||
err := os.Remove(cmd.FilePath)
|
||||
if cmd.Name == "deleteFile" {
|
||||
err := DeleteFile(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete file: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func MkDir(cmd Cmd) error {
|
||||
err := os.MkdirAll(cmd.FilePath, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make dir: %w", err)
|
||||
if cmd.Name == "unzip" {
|
||||
err := Unzip(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unzip file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "zip" {
|
||||
err := Zip(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unzip file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "mkdir" {
|
||||
err := MkDir(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func Unzip(cmd Cmd) error {
|
||||
return files.Decompress(cmd.FilePath, cmd.Content)
|
||||
}
|
||||
func Zip(cmd Cmd) error {
|
||||
return files.Encompress(cmd.FilePath, cmd.Content)
|
||||
func RunCmds(storeInfo StoreInfo, cmdKey string) error {
|
||||
cmds := storeInfo.Cmds[cmdKey]
|
||||
for _, cmd := range cmds {
|
||||
if _, ok := storeInfo.Cmds[cmd.Name]; ok {
|
||||
RunCmds(storeInfo, cmd.Name)
|
||||
} else {
|
||||
RunCmd(storeInfo, cmd)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetEnvs(envs []Item) error {
|
||||
if len(envs) > 0 {
|
||||
for _, item := range envs {
|
||||
|
|
@ -352,6 +270,11 @@ func SetEnvs(envs []Item) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
func SaveInfoFile(storeInfo StoreInfo) error {
|
||||
exePath := GetExePath(storeInfo.Name)
|
||||
infoFile := filepath.Join(exePath, "info.json")
|
||||
return SaveStoreInfo(storeInfo, infoFile)
|
||||
}
|
||||
func SaveStoreInfo(storeInfo StoreInfo, infoFile string) error {
|
||||
// 使用 json.MarshalIndent 直接获取内容的字节切片
|
||||
content, err := json.MarshalIndent(storeInfo, "", " ")
|
||||
|
|
|
|||
|
|
@ -10,12 +10,15 @@ import (
|
|||
)
|
||||
|
||||
type Process struct {
|
||||
Name string `json:"name"`
|
||||
Running bool `json:"running"`
|
||||
ExitCode int `json:"exitCode"`
|
||||
Cmd *exec.Cmd `json:"cmd"`
|
||||
Waiting bool `json:"waiting"`
|
||||
LastPing time.Time `json:"lastPing"`
|
||||
Name string `json:"name"`
|
||||
Running bool `json:"running"`
|
||||
ExitCode int `json:"exitCode"`
|
||||
Cmd *exec.Cmd `json:"cmd"`
|
||||
Pid int `json:"pid"`
|
||||
ProgressName string `json:"progressName"`
|
||||
Waiting bool `json:"waiting"`
|
||||
IsOn bool `json:"isOn"`
|
||||
LastPing time.Time `json:"lastPing"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -26,13 +29,16 @@ var (
|
|||
func respondWithError(w http.ResponseWriter, code int, message string) {
|
||||
http.Error(w, message, code)
|
||||
}
|
||||
func RegisterProcess(name string, cmdstr *exec.Cmd) {
|
||||
func RegisterProcess(name string, progressName string, isOn bool, cmdstr *exec.Cmd) {
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
processes[name] = &Process{
|
||||
Name: name,
|
||||
Running: true,
|
||||
Cmd: cmdstr,
|
||||
Name: name,
|
||||
Running: true,
|
||||
Cmd: cmdstr,
|
||||
Pid: cmdstr.Process.Pid,
|
||||
IsOn: isOn,
|
||||
ProgressName: progressName,
|
||||
}
|
||||
}
|
||||
func GetCmd(name string) *Process {
|
||||
|
|
@ -42,20 +48,9 @@ func GetCmd(name string) *Process {
|
|||
return processes[name]
|
||||
}
|
||||
func Status(w http.ResponseWriter, r *http.Request) {
|
||||
// processesMu.RLock()
|
||||
// defer processesMu.RUnlock()
|
||||
|
||||
var ps []Process
|
||||
for name, cmd := range processes {
|
||||
// if cmd.Cmd.ProcessState != nil && cmd.Cmd.ProcessState.Exited() {
|
||||
// cmd.Running = false
|
||||
// // 进程已经退出
|
||||
// ps = append(ps, Process{Name: name, Running: false, Waiting: cmd.Waiting, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
|
||||
// } else {
|
||||
// // 进程仍在运行
|
||||
// ps = append(ps, Process{Name: name, Running: true, Waiting: cmd.Waiting, LastPing: cmd.LastPing})
|
||||
// }
|
||||
ps = append(ps, Process{Name: name, Running: cmd.Running, Waiting: cmd.Waiting, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
|
||||
ps = append(ps, Process{Name: name, Running: cmd.Running, Waiting: cmd.Waiting, Pid: cmd.Pid, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(ps, "", " ")
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"godo/libs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
|
|
@ -49,27 +46,6 @@ func StartAll(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "All processes started.")
|
||||
}
|
||||
func GetStoreInfo(name string) (StoreInfo, error) {
|
||||
var storeInfo StoreInfo
|
||||
exeDir := libs.GetRunDir()
|
||||
infoPath := filepath.Join(exeDir, name, "info.json")
|
||||
if !libs.PathExists(infoPath) {
|
||||
return storeInfo, fmt.Errorf("process information for '%s' not found", name)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(infoPath)
|
||||
if err != nil {
|
||||
return storeInfo, fmt.Errorf("failed to read info.json: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(content, &storeInfo); err != nil {
|
||||
return storeInfo, fmt.Errorf("failed to unmarshal info.json: %v", err)
|
||||
}
|
||||
scriptPath := storeInfo.Setting.BinPath
|
||||
if !libs.PathExists(scriptPath) {
|
||||
return storeInfo, fmt.Errorf("script file '%s' not found", scriptPath)
|
||||
}
|
||||
return storeInfo, nil
|
||||
}
|
||||
|
||||
// ExecuteScript 执行指定名称的脚本。
|
||||
// 参数:
|
||||
|
|
@ -77,42 +53,14 @@ func GetStoreInfo(name string) (StoreInfo, error) {
|
|||
// 返回值:
|
||||
// 返回可能遇到的错误,如果执行成功,则返回nil。
|
||||
func ExecuteScript(name string) error {
|
||||
cmd, exists := processes[name]
|
||||
if !exists {
|
||||
log.Printf("process information for '%s' not found", name)
|
||||
//return fmt.Errorf("process information for '%s' not found", name)
|
||||
storeInfo, err := GetStoreInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = runStart(storeInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run script: %v", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
storeInfo, err := GetStoreInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.Running {
|
||||
return nil
|
||||
err = runStart(storeInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run script: %v", err)
|
||||
}
|
||||
|
||||
// if err := cmd.Cmd.Start(); err != nil {
|
||||
// log.Printf("failed to start process %s: %v", name, err)
|
||||
// return nil
|
||||
// }
|
||||
processes[name].Running = true
|
||||
go func() {
|
||||
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
log.Printf("failed to start process %s: %v", name, err)
|
||||
return
|
||||
}
|
||||
// processesMu.Lock()
|
||||
// defer processesMu.Unlock()
|
||||
|
||||
processes[name].Cmd = cmd.Cmd
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
func RunOutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
|
@ -11,6 +15,7 @@ import (
|
|||
)
|
||||
|
||||
func KillByPid(pid int) error {
|
||||
log.Println("Killing process with PID:", pid)
|
||||
p, err := process.NewProcess(int32(pid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create process object: %w", err)
|
||||
|
|
@ -23,15 +28,25 @@ func KillByPid(pid int) error {
|
|||
return nil
|
||||
}
|
||||
func StopCmd(name string) error {
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
cmd, ok := processes[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Process not found")
|
||||
}
|
||||
processes[name].Running = false
|
||||
err := KillByPid(cmd.Cmd.Process.Pid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to kill process: %w", err)
|
||||
//processes[name].Running = false
|
||||
if cmd.ProgressName != "" {
|
||||
err := KillProcessByName(cmd.ProgressName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to kill process: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := KillByPid(cmd.Pid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to kill process: %w", err)
|
||||
}
|
||||
}
|
||||
delete(processes, name)
|
||||
return nil
|
||||
}
|
||||
func StopProcess(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -54,7 +69,8 @@ func StopAllHandler() error {
|
|||
if err := KillByPid(cmd.Cmd.Process.Pid); err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %v", name, err)
|
||||
}
|
||||
processes[name].Running = false
|
||||
//processes[name].Running = false
|
||||
delete(processes, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -75,15 +91,15 @@ func ReStartProcess(w http.ResponseWriter, r *http.Request) {
|
|||
name := vars["name"]
|
||||
|
||||
// Stop the process first
|
||||
if cmd, ok := processes[name]; ok {
|
||||
if err := KillByPid(cmd.Cmd.Process.Pid); err != nil {
|
||||
if _, ok := processes[name]; ok {
|
||||
if err := StopCmd(name); err != nil {
|
||||
// respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s before restart: %v", name, err))
|
||||
// return
|
||||
log.Printf("Failed to stop process %s before restart: %v", name, err)
|
||||
}
|
||||
// processesMu.Lock()
|
||||
// defer processesMu.Unlock()
|
||||
processes[name].Running = false
|
||||
//processes[name].Running = false
|
||||
} else {
|
||||
respondWithError(w, http.StatusNotFound, fmt.Sprintf("Process %s not found to restart", name))
|
||||
return
|
||||
|
|
@ -98,3 +114,82 @@ func ReStartProcess(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Process %s restarted.", name)
|
||||
}
|
||||
func killByPids(pids []int) error {
|
||||
for _, pid := range pids {
|
||||
if err := KillByPid(pid); err != nil {
|
||||
return fmt.Errorf("failed to kill process with PID %d: %w", pid, err)
|
||||
}
|
||||
fmt.Printf("Killed process with PID %d\n", pid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KillProcessByName 终止所有与给定名称匹配的进程
|
||||
func KillProcessByName(processName string) error {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
// Windows 系统下的实现
|
||||
pids, err := findPidsWindows(processName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = killByPids(pids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// Unix/Linux 系统下的实现
|
||||
pids, err := findPidsUnix(processName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = killByPids(pids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findPidsUnix 在 Unix/Linux 系统下查找具有指定名称的进程的 PID
|
||||
func findPidsUnix(name string) ([]int, error) {
|
||||
cmd := exec.Command("pgrep", "-f", name)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute pgrep: %w", err)
|
||||
}
|
||||
pids := strings.Fields(string(output))
|
||||
result := make([]int, len(pids))
|
||||
for i, pidStr := range pids {
|
||||
pid, err := strconv.Atoi(pidStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert PID to integer: %w", err)
|
||||
}
|
||||
result[i] = pid
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// findPidsWindows 在 Windows 系统下查找具有指定名称的进程的 PID
|
||||
func findPidsWindows(name string) ([]int, error) {
|
||||
cmd := exec.Command("tasklist")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute tasklist: %w", err)
|
||||
}
|
||||
lines := strings.Split(string(output), "\r\n")
|
||||
var pids []int
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 3 {
|
||||
if strings.Contains(strings.ToLower(fields[0]), strings.ToLower(name)) {
|
||||
pid, err := strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert PID to integer: %w", err)
|
||||
}
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
return pids, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@ import (
|
|||
"godo/libs"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetStoreInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func GetStoreListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
cate := r.URL.Query().Get("cate")
|
||||
os := runtime.GOOS
|
||||
arch := runtime.GOARCH
|
||||
|
|
@ -39,3 +42,76 @@ func GetStoreInfoHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
}
|
||||
}
|
||||
func StoreSettingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var req map[string]any
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, err.Error())
|
||||
return
|
||||
}
|
||||
if req["name"] == nil {
|
||||
libs.ErrorMsg(w, "name is required")
|
||||
return
|
||||
}
|
||||
if req["cmdKey"] == nil {
|
||||
libs.ErrorMsg(w, "cmdKey is required")
|
||||
return
|
||||
}
|
||||
name := req["name"].(string)
|
||||
cmdKey := req["cmdKey"].(string)
|
||||
// 替换字符串类型的值中的反斜杠
|
||||
for key, value := range req {
|
||||
if strValue, ok := value.(string); ok {
|
||||
req[key] = strings.ReplaceAll(strValue, "\\", "/")
|
||||
}
|
||||
}
|
||||
storeInfo, err := GetStoreInfo(name)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, err.Error())
|
||||
return
|
||||
}
|
||||
// 更新storeInfo.Config
|
||||
for k, v := range req {
|
||||
if k != "name" && k != "cmdKey" {
|
||||
storeInfo.Config[k] = v // 如果k存在,则更新;如果不存在,则新增
|
||||
}
|
||||
}
|
||||
err = SaveInfoFile(storeInfo)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "the store info.json is error: "+err.Error())
|
||||
return
|
||||
}
|
||||
_, ok := storeInfo.Cmds[cmdKey]
|
||||
if !ok {
|
||||
libs.ErrorMsg(w, "cmdKey is not found")
|
||||
return
|
||||
}
|
||||
err = RunCmds(storeInfo, cmdKey)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, err.Error())
|
||||
return
|
||||
}
|
||||
libs.SuccessMsg(w, "success", "success")
|
||||
|
||||
}
|
||||
func GetStoreInfo(name string) (StoreInfo, error) {
|
||||
var storeInfo StoreInfo
|
||||
exeDir := libs.GetRunDir()
|
||||
infoPath := filepath.Join(exeDir, name, "info.json")
|
||||
if !libs.PathExists(infoPath) {
|
||||
return storeInfo, fmt.Errorf("process information for '%s' not found", name)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(infoPath)
|
||||
if err != nil {
|
||||
return storeInfo, fmt.Errorf("failed to read info.json: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(content, &storeInfo); err != nil {
|
||||
return storeInfo, fmt.Errorf("failed to unmarshal info.json: %v", err)
|
||||
}
|
||||
scriptPath := storeInfo.Setting.BinPath
|
||||
if !libs.PathExists(scriptPath) {
|
||||
return storeInfo, fmt.Errorf("script file '%s' not found", scriptPath)
|
||||
}
|
||||
return storeInfo, nil
|
||||
}
|
||||
|
|
|
|||
65
godo/store/types.go
Normal file
65
godo/store/types.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package store
|
||||
|
||||
// InstallInfo 描述了应用程序的安装信息。
|
||||
// 包含应用程序的名称、下载地址、版本号等关键安装信息。
|
||||
type InstallInfo struct {
|
||||
Name string `json:"name"` // 应用程序名称。
|
||||
URL string `json:"url"` // 应用程序下载地址。
|
||||
WebUrl string `json:"webUrl"` // 应用程序的网页地址。
|
||||
IsDev bool `json:"isDev"` // 标志位,表示是否为开发者版本。
|
||||
NeedDownload bool `json:"needDownload"` // 标志位,表示是否需要下载。
|
||||
NeedInstall bool `json:"needInstall"` // 标志位,表示是否需要安装。
|
||||
Version string `json:"version"` // 应用程序的版本号。
|
||||
Desc string `json:"desc"` // 应用程序的描述信息。
|
||||
Icon string `json:"icon"` // 应用程序的图标路径。
|
||||
CheckProgress bool `json:"checkProgress"` // 标志位,表示是否显示启动和停止。
|
||||
HasRestart bool `json:"hasRestart"` // 标志位,表示安装后是否需要重启。
|
||||
Setting bool `json:"setting"` // 标志位,表示是否需要配置。
|
||||
}
|
||||
|
||||
// StoreInfo 维护了应用程序商店的信息。
|
||||
// 包含应用程序的名称、图标、配置信息等。
|
||||
type StoreInfo struct {
|
||||
Name string `json:"name"` // 应用程序商店的名称。
|
||||
Icon string `json:"icon"` // 应用程序商店的图标路径。
|
||||
Setting Setting `json:"setting"` // 应用程序商店的配置信息。
|
||||
Config map[string]any `json:"config"` // 应用程序的配置信息映射。
|
||||
Cmds map[string][]Cmd `json:"cmds"` // 应用程序的命令集合。
|
||||
Install Install `json:"install"` // 安装应用程序的信息。
|
||||
Start Install `json:"start"` // 启动应用程序的信息。
|
||||
}
|
||||
|
||||
// Setting 描述了应用程序的设置信息。
|
||||
// 包含应用程序的二进制文件路径、配置文件路径等关键设置信息。
|
||||
type Setting struct {
|
||||
BinPath string `json:"binPath"` // 应用程序二进制文件的路径。
|
||||
ConfPath string `json:"confPath"` // 应用程序配置文件的路径。
|
||||
ProgressName string `json:"progressName"` // 进程的名称。
|
||||
IsOn bool `json:"isOn"` //是否守护进程运行。
|
||||
}
|
||||
|
||||
// Item 是一个通用的键值对结构体,用于表示配置项或环境变量等。
|
||||
type Item struct {
|
||||
Name string `json:"name"` // 配置项的名称。
|
||||
Value any `json:"value"` // 配置项的值。
|
||||
}
|
||||
|
||||
// Cmd 描述了一个命令的详细信息。
|
||||
// 包含命令的名称、执行路径、环境变量等。
|
||||
type Cmd struct {
|
||||
Name string `json:"name"` // 命令的名称。
|
||||
FilePath string `json:"filePath,omitempty"` // 命令文件的路径。
|
||||
Content string `json:"content,omitempty"` // 命令的内容。
|
||||
BinPath string `json:"binPath,omitempty"` // 执行命令的二进制文件路径。
|
||||
TplPath string `json:"tplPath,omitempty"` // 命令的模板路径。
|
||||
Cmds []string `json:"cmds,omitempty"` // 要执行的子命令列表。
|
||||
Waiting int `json:"waiting"` // 等待的时间。
|
||||
Kill bool `json:"kill"` // 标志位,表示是否需要终止之前的命令。
|
||||
Envs []Item `json:"envs"` // 命令执行时的环境变量。
|
||||
}
|
||||
|
||||
// Install 描述了安装过程中的环境变量和命令列表。
|
||||
type Install struct {
|
||||
Envs []Item `json:"envs"` // 安装过程中需要的环境变量。
|
||||
Cmds []string `json:"cmds"` // 安装过程中需要执行的命令列表。
|
||||
}
|
||||
Loading…
Reference in a new issue