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)
|
English | [简体中文](README.md)
|
||||||
|
|
||||||
[FAQ](./docs/Faq.md) | [API](./docs/Api.md)
|
[FAQ](./docs/Faq.md) | [API](./docs/Api.md) | [AppStore](./docs/Store.md)
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">GodoOS</h1>
|
<h1 align="center">GodoOS</h1>
|
||||||
一款高效的内网办公平台,内含word/excel/ppt/pdf/内网聊天/白板/思维导图等多个办公系统工具,支持原生文件存储。平台界面精仿windows风格,操作简便,同时保持低资源消耗和高性能运行。无需注册即可自动连接内网用户,实现即时通讯和文件共享。
|
一款高效的内网办公平台,内含word/excel/ppt/pdf/内网聊天/白板/思维导图等多个办公系统工具,支持原生文件存储。平台界面精仿windows风格,操作简便,同时保持低资源消耗和高性能运行。无需注册即可自动连接内网用户,实现即时通讯和文件共享。灵活高配置的应用商店,可无限扩展。
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
[English](README.en.md) | 简体中文
|
[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,确包含了所有的办公套件***
|
- ***精小,打包后仅65M,确包含了所有的办公套件***
|
||||||
- ***可无限扩展,支持自定义应用***
|
- ***可无限扩展,支持自定义应用***
|
||||||
- ***golang开发后端,低资源消耗和高性能***
|
- ***golang开发后端,低资源消耗和高性能***
|
||||||
|
- ***支持多平台,Windows、Linux、MacOS***
|
||||||
|
- ***只会html和拥有可执行的应用,简单学习一下应用商店配置即可开发出复杂的应用***
|
||||||
|
|
||||||
## 🚧 开发进程
|
## 🚧 开发进程
|
||||||
- 2024年7月,发布v1.0.0版本,发布后,项目进入第二阶段。
|
- 2024年7月,发布v1.0.0版本,发布后,项目进入第二阶段。
|
||||||
|
|
|
||||||
91
docs/Api.md
91
docs/Api.md
|
|
@ -16,6 +16,11 @@
|
||||||
- **Content-Type**: `application/json`
|
- **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`
|
- **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`
|
- **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`
|
- **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`
|
- **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`
|
- **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`
|
- **Content-Type**: `application/json`
|
||||||
- **响应体**: 操作结果
|
- **响应体**: 操作结果
|
||||||
|
#### 示例
|
||||||
|
```bash
|
||||||
|
# 清空文件系统
|
||||||
|
curl -X GET "http://localhost:56780/files/clear"
|
||||||
|
```
|
||||||
---
|
---
|
||||||
|
|
||||||
### 重命名文件或目录
|
### 重命名文件或目录
|
||||||
|
|
@ -132,7 +168,13 @@
|
||||||
#### 响应
|
#### 响应
|
||||||
- **Content-Type**: `application/json`
|
- **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`
|
- **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`
|
- **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`
|
- **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`
|
- **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`
|
- **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** (监听的文件或目录路径)
|
- **path** (监听的文件或目录路径)
|
||||||
- **callback** (事件回调函数)
|
- **callback** (事件回调函数)
|
||||||
- **errback** (错误回调函数)
|
- **errback** (错误回调函数)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
---
|
|
||||||
title: Markdown编辑器
|
|
||||||
icon: circle-info
|
|
||||||
---
|
|
||||||
|
|
||||||
## 概览
|
## 概览
|
||||||
Markdown 编辑器允许您使用丰富的文本编辑体验创建和编辑 Markdown 文件。它包括实时保存、文件导入/导出以及访问最近的 Markdown 文件等功能。
|
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']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||||
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
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",
|
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7/mysql5.7.zip",
|
||||||
"needDownload": true,
|
"needDownload": true,
|
||||||
"needInstall": true,
|
"needInstall": true,
|
||||||
|
"isDev":true,
|
||||||
"version": "5.7.37",
|
"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",
|
"icon": "https://img0.baidu.com/it/u=1087008535,3349487719&fm=253&fmt=auto&app=138&f=PNG?w=969&h=500",
|
||||||
"checkProgress": true,
|
"checkProgress": true,
|
||||||
"hasRestart": true,
|
"hasRestart": true,
|
||||||
"setting": [
|
"setting": true
|
||||||
{
|
|
||||||
"path": "setting.html",
|
|
||||||
"lang":{
|
|
||||||
"cn-zh": "设置",
|
|
||||||
"en": "Setting"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nginx",
|
"name": "nginx",
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { OpenDirDialog } from "@/util/goutil";
|
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({
|
const formData = ref({
|
||||||
importType: 'download',
|
importType: 'download',
|
||||||
url: '',
|
url: '',
|
||||||
devPath:''
|
devPath: ''
|
||||||
})
|
})
|
||||||
|
const progress = ref(0)
|
||||||
const addType = [
|
const addType = [
|
||||||
{
|
{
|
||||||
'name': '远程下载',
|
'name': '远程下载',
|
||||||
|
|
@ -21,12 +27,46 @@ const addType = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
function selectFile() {
|
function selectFile() {
|
||||||
OpenDirDialog().then((res: string) => {
|
OpenDirDialog().then((res: string) => {
|
||||||
formData.value.devPath = res;
|
formData.value.devPath = res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async function addAppByDownload() {
|
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() {
|
async function addAppByImport() {
|
||||||
|
|
||||||
|
|
@ -46,6 +86,9 @@ async function addAppByDev() {
|
||||||
<el-form-item label="下载地址">
|
<el-form-item label="下载地址">
|
||||||
<el-input v-model="formData.url" style="width: 280px;" />
|
<el-input v-model="formData.url" style="width: 280px;" />
|
||||||
</el-form-item>
|
</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>
|
<el-button type="primary" @click="addAppByDownload()">添加</el-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="formData.importType == 'local'">
|
<template v-if="formData.importType == 'local'">
|
||||||
|
|
@ -53,7 +96,8 @@ async function addAppByDev() {
|
||||||
</template>
|
</template>
|
||||||
<template v-if="formData.importType == 'dev'">
|
<template v-if="formData.importType == 'dev'">
|
||||||
<el-form-item label="本地路径">
|
<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-form-item>
|
||||||
<el-button type="primary" @click="addAppByDev()">添加</el-button>
|
<el-button type="primary" @click="addAppByDev()">添加</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -61,6 +105,6 @@ async function addAppByDev() {
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.el-button {
|
.el-button {
|
||||||
margin:20px 100px
|
margin: 20px 100px
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
<div class="app-content">
|
<div class="app-content">
|
||||||
<div class="app-title">{{ item?.name }}</div>
|
<div class="app-title">{{ item?.name }}</div>
|
||||||
<div class="app-desc">
|
<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"
|
<el-progress :text-inside="true" style="margin-top: 3px;" v-if="item?.progress" :stroke-width="20"
|
||||||
:percentage="item?.progress" />
|
:percentage="item?.progress" />
|
||||||
</div>
|
</div>
|
||||||
<div class="app-button">
|
<div class="app-button">
|
||||||
|
|
||||||
|
|
@ -17,17 +18,20 @@
|
||||||
<div v-if="item!.hasRestart && item?.isRuning">
|
<div v-if="item!.hasRestart && item?.isRuning">
|
||||||
<button @click="restart?.(item)">重启</button>
|
<button @click="restart?.(item)">重启</button>
|
||||||
</div>
|
</div>
|
||||||
|
<button @click="setting?.(item)" v-if="!item?.isRuning && item?.setting">配置</button>
|
||||||
<button @click="uninstall?.(item)" v-if="!item?.isRuning">卸载</button>
|
<button @click="uninstall?.(item)" v-if="!item?.isRuning">卸载</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<button v-if="item?.url" @click="install?.(item)" :disabled="item?.progress">安装</button>
|
<button @click="install?.(item)" :disabled="item?.progress">安装</button>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { BrowserWindow } from "@/system";
|
||||||
defineProps({
|
defineProps({
|
||||||
item: Object,
|
item: Object,
|
||||||
installedList: Array,
|
installedList: Array,
|
||||||
|
|
@ -37,11 +41,26 @@ defineProps({
|
||||||
start: Function,
|
start: Function,
|
||||||
restart: 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>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app-item {
|
.app-item {
|
||||||
width: 350px;
|
width: calc(100% - 20px);
|
||||||
height: 100px;
|
min-height: 100px;
|
||||||
|
min-width:200px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
@ -49,15 +68,23 @@ defineProps({
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #cccccc79;
|
border: 1px solid rgba(204, 204, 204, 0.79);
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.app-item {
|
||||||
|
flex-direction: column;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-img {
|
.app-img {
|
||||||
padding: 10px;
|
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #cccccc7b;
|
border: 1px solid rgba(204, 204, 204, 0.79);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,8 +94,7 @@ defineProps({
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
height: 80px;
|
height: 100%;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-title {
|
.app-title {
|
||||||
|
|
@ -76,31 +102,47 @@ defineProps({
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-desc {
|
.app-desc {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
user-select: none;
|
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 {
|
.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);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-item:active {
|
.app-item:active {
|
||||||
box-shadow: 0 0 0px #ccc;
|
box-shadow: none;
|
||||||
transform: translateY(0px);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-button {
|
.app-button {
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-button button {
|
.app-button button {
|
||||||
|
|
@ -111,15 +153,14 @@ defineProps({
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-button button:hover {
|
.app-button button:hover {
|
||||||
box-shadow: 0 0 10px #ccc;
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
/* transform: translateY(-2px); */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-button button:active {
|
.app-button button:active {
|
||||||
box-shadow: 0 0 0px #ccc;
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15);
|
||||||
transform: translateY(0px);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,17 @@ onMounted(async () => {
|
||||||
async function getList() {
|
async function getList() {
|
||||||
if (currentCate.value == 'add') return;
|
if (currentCate.value == 'add') return;
|
||||||
storeList.value = storeInitList
|
storeList.value = storeInitList
|
||||||
|
|
||||||
// const apiUrl = getSystemKey("apiUrl");
|
// const apiUrl = getSystemKey("apiUrl");
|
||||||
// const storeUrl = apiUrl + '/store/storelist?cate=' + currentCate.value
|
// const storeUrl = apiUrl + '/store/storelist?cate=' + currentCate.value
|
||||||
// fetch(storeUrl).then(res => {
|
// const res = await fetch(storeUrl)
|
||||||
// res.json().then(data => {
|
// if (!res.ok) {
|
||||||
// storeList.value = data
|
// notifyError(t('store.getStoreListError'))
|
||||||
// })
|
// return
|
||||||
// }).catch(() => {
|
// }
|
||||||
// notifyError(t("store.errorList"))
|
// const data = await res.json()
|
||||||
// })
|
// //console.log(data)
|
||||||
|
// storeList.value = data
|
||||||
await checkProgress()
|
await checkProgress()
|
||||||
isready.value = true;
|
isready.value = true;
|
||||||
}
|
}
|
||||||
|
|
@ -41,8 +43,8 @@ async function checkProgress() {
|
||||||
if (!completion.ok) {
|
if (!completion.ok) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let res:any = await completion.json()
|
let res: any = await completion.json()
|
||||||
if(!res || res.length < 1){
|
if (!res || res.length < 1) {
|
||||||
res = []
|
res = []
|
||||||
}
|
}
|
||||||
storeList.value.forEach((item: any, index: number) => {
|
storeList.value.forEach((item: any, index: number) => {
|
||||||
|
|
@ -50,7 +52,7 @@ async function checkProgress() {
|
||||||
console.log(pitem)
|
console.log(pitem)
|
||||||
if (pitem) {
|
if (pitem) {
|
||||||
storeList.value[index].isRuning = pitem.running
|
storeList.value[index].isRuning = pitem.running
|
||||||
}else{
|
} else {
|
||||||
storeList.value[index].isRuning = false
|
storeList.value[index].isRuning = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -69,7 +71,7 @@ function setCache() {
|
||||||
}
|
}
|
||||||
async function install(item: any) {
|
async function install(item: any) {
|
||||||
|
|
||||||
if (item.needDownload) {
|
if (item.needDownload && !item.isDev) {
|
||||||
await download(item)
|
await download(item)
|
||||||
}
|
}
|
||||||
if (item.needInstall) {
|
if (item.needInstall) {
|
||||||
|
|
@ -84,8 +86,11 @@ async function install(item: any) {
|
||||||
notifyError(res.message)
|
notifyError(res.message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if(res.data){
|
||||||
|
item.icon = res.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(item.isOut){
|
if (item.isOut) {
|
||||||
item.progress = 0
|
item.progress = 0
|
||||||
const completion = await fetch(apiUrl + '/store/installOut?url=' + item.url)
|
const completion = await fetch(apiUrl + '/store/installOut?url=' + item.url)
|
||||||
if (!completion.ok) {
|
if (!completion.ok) {
|
||||||
|
|
@ -98,10 +103,10 @@ async function install(item: any) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.isWeb) {
|
if (item.webUrl) {
|
||||||
sys.fs.writeFile(
|
sys.fs.writeFile(
|
||||||
`${sys._options.userLocation}Desktop/${item.name}.url`,
|
`${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) {
|
async function pauseApp(item: any) {
|
||||||
const res:any = await fetch(apiUrl + '/store/stop/' + item.name)
|
const res: any = await fetch(apiUrl + '/store/stop/' + item.name)
|
||||||
if(!res.ok){
|
if (!res.ok) {
|
||||||
const msg = await res.text()
|
const msg = await res.text()
|
||||||
notifyError(msg)
|
notifyError(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setTimeout(async() => {
|
setTimeout(async () => {
|
||||||
await checkProgress()
|
await checkProgress()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
}
|
}
|
||||||
async function restartApp(item: any) {
|
async function restartApp(item: any) {
|
||||||
await fetch(apiUrl + '/store/restart/' + item.name)
|
await fetch(apiUrl + '/store/restart/' + item.name)
|
||||||
setTimeout(async() => {
|
setTimeout(async () => {
|
||||||
await checkProgress()
|
await checkProgress()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
async function startApp(item: any) {
|
async function startApp(item: any) {
|
||||||
await fetch(apiUrl + '/store/start/' + item.name)
|
await fetch(apiUrl + '/store/start/' + item.name)
|
||||||
setTimeout(async() => {
|
setTimeout(async () => {
|
||||||
await checkProgress()
|
await checkProgress()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
@ -204,25 +209,10 @@ async function startApp(item: any) {
|
||||||
</div>
|
</div>
|
||||||
<div class="store">
|
<div class="store">
|
||||||
<div v-if="isready" class="store-top">
|
<div v-if="isready" class="store-top">
|
||||||
<!-- <div class="left-bar"></div> -->
|
|
||||||
<div class="right-main">
|
<div class="right-main">
|
||||||
<div class="main-title">
|
<div class="main-title">
|
||||||
<span class="sub-title">{{ currentTitle }} </span>
|
<span class="sub-title">{{ currentTitle }} </span>
|
||||||
</div>
|
</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 class="main-app">
|
||||||
<div v-for="item in storeList" v-if="currentCate != 'add'" class="store-item" :key="item.name">
|
<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"
|
<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("/listporgress", store.Status).Methods(http.MethodGet)
|
||||||
progressRouter.HandleFunc("/listport", store.ListPortsHandler).Methods(http.MethodGet)
|
progressRouter.HandleFunc("/listport", store.ListPortsHandler).Methods(http.MethodGet)
|
||||||
progressRouter.HandleFunc("/killport", store.KillPortHandler).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("/download", store.DownloadHandler).Methods(http.MethodGet)
|
||||||
progressRouter.HandleFunc("/install", store.InstallHandler).Methods(http.MethodGet)
|
progressRouter.HandleFunc("/install", store.InstallHandler).Methods(http.MethodGet)
|
||||||
progressRouter.HandleFunc("/installOut", store.RunOutHandler).Methods(http.MethodGet)
|
progressRouter.HandleFunc("/installOut", store.RunOutHandler).Methods(http.MethodGet)
|
||||||
progressRouter.HandleFunc("/uninstall", store.UnInstallHandler).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/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
|
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,11 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CheckActive(ctx context.Context) {
|
func CheckActive(ctx context.Context) {
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
ticker := time.NewTicker(10 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
activeCheckWg := sync.WaitGroup{}
|
activeCheckWg := sync.WaitGroup{}
|
||||||
|
|
@ -38,12 +37,15 @@ func checkInactiveProcesses() {
|
||||||
defer processesMu.RUnlock()
|
defer processesMu.RUnlock()
|
||||||
|
|
||||||
for name, p := range processes {
|
for name, p := range processes {
|
||||||
pid := p.Cmd.Process.Pid
|
pid := p.Pid
|
||||||
_, err := os.FindProcess(pid)
|
_, err := os.FindProcess(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
p.Running = false
|
p.Running = false
|
||||||
log.Printf("Process %s (PID: %d) not found.", name, pid)
|
log.Printf("Process %s (PID: %d) not found.", name, pid)
|
||||||
|
if p.IsOn {
|
||||||
|
go ExecuteScript(name)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Error finding process %s (PID: %d): %v", name, pid, err)
|
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 output []byte
|
||||||
var err error
|
var err error
|
||||||
if cmd.SysProcAttr == nil {
|
// if cmd.SysProcAttr == nil {
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
// cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
}
|
// }
|
||||||
//cmd.SysProcAttr.HideWindow = true // For Windows to hide the console window
|
//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 {
|
if output, err = cmd.CombinedOutput(); err != nil {
|
||||||
return false, fmt.Errorf("error checking process: %w", err)
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"godo/files"
|
|
||||||
"godo/libs"
|
"godo/libs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"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) {
|
func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
pluginName := r.URL.Query().Get("name")
|
pluginName := r.URL.Query().Get("name")
|
||||||
if pluginName == "" {
|
if pluginName == "" {
|
||||||
libs.ErrorMsg(w, "the app name is empty!")
|
libs.ErrorMsg(w, "the app name is empty!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
exeDir := libs.GetRunDir()
|
exePath := GetExePath(pluginName)
|
||||||
exePath := filepath.Join(exeDir, pluginName)
|
//log.Printf("the app path is %s", exePath)
|
||||||
log.Printf("the app path is %s", exePath)
|
|
||||||
if !libs.PathExists(exePath) {
|
if !libs.PathExists(exePath) {
|
||||||
libs.ErrorMsg(w, "the app path is not exists!")
|
libs.ErrorMsg(w, "the app path is not exists!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
installFile := filepath.Join(exePath, "install.json")
|
installInfo, err := GetInstallInfo(pluginName)
|
||||||
if !libs.PathExists(installFile) {
|
if err != nil {
|
||||||
libs.ErrorMsg(w, "the install.json is not exists!")
|
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
|
return
|
||||||
}
|
}
|
||||||
var storeInfo StoreInfo
|
var storeInfo StoreInfo
|
||||||
content, err := os.ReadFile(installFile)
|
content, err := os.ReadFile(storeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
libs.ErrorMsg(w, "cant read the install.json!")
|
libs.ErrorMsg(w, "cant read the store.json!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//设置 info.json
|
//设置 info.json
|
||||||
|
|
@ -85,17 +55,16 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
err = json.Unmarshal(contentBytes, &storeInfo)
|
err = json.Unmarshal(contentBytes, &storeInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error during unmarshal: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
replacePlaceholdersInCmds(&storeInfo)
|
replacePlaceholdersInCmds(&storeInfo)
|
||||||
infoFile := filepath.Join(exePath, "info.json")
|
storeInfo.Name = installInfo.Name
|
||||||
err = SaveStoreInfo(storeInfo, infoFile)
|
err = SaveInfoFile(storeInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
libs.ErrorMsg(w, "save the info.json is error!")
|
libs.ErrorMsg(w, "the store info.json is error: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//设置config
|
//设置config
|
||||||
if libs.PathExists(storeInfo.Setting.ConfPath + ".tpl") {
|
if libs.PathExists(storeInfo.Setting.ConfPath + ".tpl") {
|
||||||
err = SaveStoreConfig(storeInfo, exePath)
|
err = SaveStoreConfig(storeInfo, exePath)
|
||||||
|
|
@ -109,20 +78,27 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
libs.ErrorMsg(w, "install the app is error!")
|
libs.ErrorMsg(w, "install the app is error!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var res string
|
||||||
//复制static目录
|
//复制static目录
|
||||||
staticPath := filepath.Join(exePath, "static")
|
staticPath := filepath.Join(exePath, "static")
|
||||||
if libs.PathExists(staticPath) {
|
if libs.PathExists(staticPath) {
|
||||||
staticDir := libs.GetStaticDir()
|
staticDir := libs.GetStaticDir()
|
||||||
targetPath := filepath.Join(staticDir, pluginName)
|
targetPath := filepath.Join(staticDir, pluginName)
|
||||||
err = os.Rename(staticPath, targetPath)
|
if !libs.PathExists(targetPath) {
|
||||||
if err != nil {
|
err = os.Rename(staticPath, targetPath)
|
||||||
// libs.ErrorMsg(w, "copy the static is error!")
|
if err != nil {
|
||||||
// return
|
log.Printf("copy the static is error! %v", err)
|
||||||
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) {
|
func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
pluginName := r.URL.Query().Get("name")
|
pluginName := r.URL.Query().Get("name")
|
||||||
if pluginName == "" {
|
if pluginName == "" {
|
||||||
|
|
@ -136,8 +112,16 @@ func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// return
|
// return
|
||||||
log.Printf("stop the app is error! %s", err)
|
log.Printf("stop the app is error! %s", err)
|
||||||
}
|
}
|
||||||
exeDir := libs.GetRunDir()
|
installInfo, err := GetInstallInfo(pluginName)
|
||||||
exePath := filepath.Join(exeDir, 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)
|
//log.Printf("the app path is %s", exePath)
|
||||||
if libs.PathExists(exePath) {
|
if libs.PathExists(exePath) {
|
||||||
err := os.RemoveAll(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!")
|
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 {
|
func convertValueToString(value any) string {
|
||||||
var strValue string
|
var strValue string
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
|
|
@ -187,157 +193,69 @@ func InstallStore(storeInfo StoreInfo) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func RunCmds(storeInfo StoreInfo, cmdKey string) error {
|
func RunCmd(storeInfo StoreInfo, cmd Cmd) error {
|
||||||
cmds := storeInfo.Cmds[cmdKey]
|
if cmd.Name == "stop" {
|
||||||
for _, cmd := range cmds {
|
runStop(storeInfo)
|
||||||
|
}
|
||||||
if cmd.Name == "stop" {
|
if cmd.Name == "start" {
|
||||||
runStop(storeInfo)
|
runStart(storeInfo)
|
||||||
}
|
}
|
||||||
if cmd.Name == "start" {
|
if cmd.Name == "restart" {
|
||||||
runStart(storeInfo)
|
runRestart(storeInfo)
|
||||||
}
|
}
|
||||||
if cmd.Name == "restart" {
|
if cmd.Name == "exec" {
|
||||||
runRestart(storeInfo)
|
runExec(storeInfo, cmd)
|
||||||
}
|
}
|
||||||
if cmd.Name == "exec" {
|
if cmd.Name == "writeFile" {
|
||||||
runExec(storeInfo, cmd)
|
err := WriteFile(cmd)
|
||||||
}
|
if err != nil {
|
||||||
if cmd.Name == "writeFile" {
|
return fmt.Errorf("failed to write to file: %w", err)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
if cmd.Name == "changeFile" {
|
||||||
}
|
err := ChangeFile(storeInfo, cmd)
|
||||||
func runStop(storeInfo StoreInfo) error {
|
if err != nil {
|
||||||
return StopCmd(storeInfo.Name)
|
return fmt.Errorf("failed to change to file: %w", err)
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("run exec process %s, name is %s", storeInfo.Name, cmdParam.Name)
|
if cmd.Name == "deleteFile" {
|
||||||
return nil
|
err := DeleteFile(cmd)
|
||||||
}
|
|
||||||
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 err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete file: %w", err)
|
return fmt.Errorf("failed to delete file: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
if cmd.Name == "unzip" {
|
||||||
}
|
err := Unzip(cmd)
|
||||||
func MkDir(cmd Cmd) error {
|
if err != nil {
|
||||||
err := os.MkdirAll(cmd.FilePath, os.ModePerm)
|
return fmt.Errorf("failed to unzip file: %w", err)
|
||||||
if err != nil {
|
}
|
||||||
return fmt.Errorf("failed to make dir: %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
|
return nil
|
||||||
}
|
}
|
||||||
func Unzip(cmd Cmd) error {
|
func RunCmds(storeInfo StoreInfo, cmdKey string) error {
|
||||||
return files.Decompress(cmd.FilePath, cmd.Content)
|
cmds := storeInfo.Cmds[cmdKey]
|
||||||
}
|
for _, cmd := range cmds {
|
||||||
func Zip(cmd Cmd) error {
|
if _, ok := storeInfo.Cmds[cmd.Name]; ok {
|
||||||
return files.Encompress(cmd.FilePath, cmd.Content)
|
RunCmds(storeInfo, cmd.Name)
|
||||||
|
} else {
|
||||||
|
RunCmd(storeInfo, cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetEnvs(envs []Item) error {
|
func SetEnvs(envs []Item) error {
|
||||||
if len(envs) > 0 {
|
if len(envs) > 0 {
|
||||||
for _, item := range envs {
|
for _, item := range envs {
|
||||||
|
|
@ -352,6 +270,11 @@ func SetEnvs(envs []Item) error {
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
func SaveStoreInfo(storeInfo StoreInfo, infoFile string) error {
|
||||||
// 使用 json.MarshalIndent 直接获取内容的字节切片
|
// 使用 json.MarshalIndent 直接获取内容的字节切片
|
||||||
content, err := json.MarshalIndent(storeInfo, "", " ")
|
content, err := json.MarshalIndent(storeInfo, "", " ")
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Process struct {
|
type Process struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Running bool `json:"running"`
|
Running bool `json:"running"`
|
||||||
ExitCode int `json:"exitCode"`
|
ExitCode int `json:"exitCode"`
|
||||||
Cmd *exec.Cmd `json:"cmd"`
|
Cmd *exec.Cmd `json:"cmd"`
|
||||||
Waiting bool `json:"waiting"`
|
Pid int `json:"pid"`
|
||||||
LastPing time.Time `json:"lastPing"`
|
ProgressName string `json:"progressName"`
|
||||||
|
Waiting bool `json:"waiting"`
|
||||||
|
IsOn bool `json:"isOn"`
|
||||||
|
LastPing time.Time `json:"lastPing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -26,13 +29,16 @@ var (
|
||||||
func respondWithError(w http.ResponseWriter, code int, message string) {
|
func respondWithError(w http.ResponseWriter, code int, message string) {
|
||||||
http.Error(w, message, code)
|
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()
|
processesMu.Lock()
|
||||||
defer processesMu.Unlock()
|
defer processesMu.Unlock()
|
||||||
processes[name] = &Process{
|
processes[name] = &Process{
|
||||||
Name: name,
|
Name: name,
|
||||||
Running: true,
|
Running: true,
|
||||||
Cmd: cmdstr,
|
Cmd: cmdstr,
|
||||||
|
Pid: cmdstr.Process.Pid,
|
||||||
|
IsOn: isOn,
|
||||||
|
ProgressName: progressName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func GetCmd(name string) *Process {
|
func GetCmd(name string) *Process {
|
||||||
|
|
@ -42,20 +48,9 @@ func GetCmd(name string) *Process {
|
||||||
return processes[name]
|
return processes[name]
|
||||||
}
|
}
|
||||||
func Status(w http.ResponseWriter, r *http.Request) {
|
func Status(w http.ResponseWriter, r *http.Request) {
|
||||||
// processesMu.RLock()
|
|
||||||
// defer processesMu.RUnlock()
|
|
||||||
|
|
||||||
var ps []Process
|
var ps []Process
|
||||||
for name, cmd := range processes {
|
for name, cmd := range processes {
|
||||||
// if cmd.Cmd.ProcessState != nil && cmd.Cmd.ProcessState.Exited() {
|
ps = append(ps, Process{Name: name, Running: cmd.Running, Waiting: cmd.Waiting, Pid: cmd.Pid, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
|
||||||
// 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()})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonBytes, err := json.MarshalIndent(ps, "", " ")
|
jsonBytes, err := json.MarshalIndent(ps, "", " ")
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"godo/libs"
|
"godo/libs"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
|
@ -49,27 +46,6 @@ func StartAll(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(w, "All processes started.")
|
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 执行指定名称的脚本。
|
// ExecuteScript 执行指定名称的脚本。
|
||||||
// 参数:
|
// 参数:
|
||||||
|
|
@ -77,42 +53,14 @@ func GetStoreInfo(name string) (StoreInfo, error) {
|
||||||
// 返回值:
|
// 返回值:
|
||||||
// 返回可能遇到的错误,如果执行成功,则返回nil。
|
// 返回可能遇到的错误,如果执行成功,则返回nil。
|
||||||
func ExecuteScript(name string) error {
|
func ExecuteScript(name string) error {
|
||||||
cmd, exists := processes[name]
|
storeInfo, err := GetStoreInfo(name)
|
||||||
if !exists {
|
if err != nil {
|
||||||
log.Printf("process information for '%s' not found", name)
|
return err
|
||||||
//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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if cmd.Running {
|
err = runStart(storeInfo)
|
||||||
return nil
|
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
|
return nil
|
||||||
}
|
}
|
||||||
func RunOutHandler(w http.ResponseWriter, r *http.Request) {
|
func RunOutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
@ -11,6 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func KillByPid(pid int) error {
|
func KillByPid(pid int) error {
|
||||||
|
log.Println("Killing process with PID:", pid)
|
||||||
p, err := process.NewProcess(int32(pid))
|
p, err := process.NewProcess(int32(pid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create process object: %w", err)
|
return fmt.Errorf("failed to create process object: %w", err)
|
||||||
|
|
@ -23,15 +28,25 @@ func KillByPid(pid int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func StopCmd(name string) error {
|
func StopCmd(name string) error {
|
||||||
|
processesMu.Lock()
|
||||||
|
defer processesMu.Unlock()
|
||||||
cmd, ok := processes[name]
|
cmd, ok := processes[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Process not found")
|
return fmt.Errorf("Process not found")
|
||||||
}
|
}
|
||||||
processes[name].Running = false
|
//processes[name].Running = false
|
||||||
err := KillByPid(cmd.Cmd.Process.Pid)
|
if cmd.ProgressName != "" {
|
||||||
if err != nil {
|
err := KillProcessByName(cmd.ProgressName)
|
||||||
return fmt.Errorf("failed to kill process: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
func StopProcess(w http.ResponseWriter, r *http.Request) {
|
func StopProcess(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -54,7 +69,8 @@ func StopAllHandler() error {
|
||||||
if err := KillByPid(cmd.Cmd.Process.Pid); err != nil {
|
if err := KillByPid(cmd.Cmd.Process.Pid); err != nil {
|
||||||
return fmt.Errorf("failed to stop process %s: %v", name, err)
|
return fmt.Errorf("failed to stop process %s: %v", name, err)
|
||||||
}
|
}
|
||||||
processes[name].Running = false
|
//processes[name].Running = false
|
||||||
|
delete(processes, name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -75,15 +91,15 @@ func ReStartProcess(w http.ResponseWriter, r *http.Request) {
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
|
|
||||||
// Stop the process first
|
// Stop the process first
|
||||||
if cmd, ok := processes[name]; ok {
|
if _, ok := processes[name]; ok {
|
||||||
if err := KillByPid(cmd.Cmd.Process.Pid); err != nil {
|
if err := StopCmd(name); err != nil {
|
||||||
// respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s before restart: %v", name, err))
|
// respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s before restart: %v", name, err))
|
||||||
// return
|
// return
|
||||||
log.Printf("Failed to stop process %s before restart: %v", name, err)
|
log.Printf("Failed to stop process %s before restart: %v", name, err)
|
||||||
}
|
}
|
||||||
// processesMu.Lock()
|
// processesMu.Lock()
|
||||||
// defer processesMu.Unlock()
|
// defer processesMu.Unlock()
|
||||||
processes[name].Running = false
|
//processes[name].Running = false
|
||||||
} else {
|
} else {
|
||||||
respondWithError(w, http.StatusNotFound, fmt.Sprintf("Process %s not found to restart", name))
|
respondWithError(w, http.StatusNotFound, fmt.Sprintf("Process %s not found to restart", name))
|
||||||
return
|
return
|
||||||
|
|
@ -98,3 +114,82 @@ func ReStartProcess(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(w, "Process %s restarted.", name)
|
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"
|
"godo/libs"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetStoreInfoHandler(w http.ResponseWriter, r *http.Request) {
|
func GetStoreListHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
cate := r.URL.Query().Get("cate")
|
cate := r.URL.Query().Get("cate")
|
||||||
os := runtime.GOOS
|
os := runtime.GOOS
|
||||||
arch := runtime.GOARCH
|
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