add progress
This commit is contained in:
parent
389cffec76
commit
d8839f9138
41 changed files with 905 additions and 444 deletions
|
|
@ -71,6 +71,7 @@
|
|||
- 文件拖拽上传
|
||||
- 文件搜索
|
||||
- 原生文件存储
|
||||
- 直接压缩/解压文件夹(本地存储支持zip/tar/gz/bz2)
|
||||
<img src="./docs/img/file.png" width="600" />
|
||||
|
||||
### 三、内网聊天
|
||||
|
|
|
|||
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
|
|
@ -52,6 +52,7 @@ declare module 'vue' {
|
|||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
|
|
|
|||
|
|
@ -54,6 +54,30 @@
|
|||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">zip</div>
|
||||
<div class="code-name">&#xe623;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">gz</div>
|
||||
<div class="code-name">&#xe8b0;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">tar</div>
|
||||
<div class="code-name">&#xe620;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">bz2</div>
|
||||
<div class="code-name">&#xe65e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">chat</div>
|
||||
|
|
@ -672,9 +696,9 @@
|
|||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1718176711902') format('woff2'),
|
||||
url('iconfont.woff?t=1718176711902') format('woff'),
|
||||
url('iconfont.ttf?t=1718176711902') format('truetype');
|
||||
src: url('iconfont.woff2?t=1722214929182') format('woff2'),
|
||||
url('iconfont.woff?t=1722214929182') format('woff'),
|
||||
url('iconfont.ttf?t=1722214929182') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
|
|
@ -700,6 +724,42 @@
|
|||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-zip"></span>
|
||||
<div class="name">
|
||||
zip
|
||||
</div>
|
||||
<div class="code-name">.icon-zip
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-gz"></span>
|
||||
<div class="name">
|
||||
gz
|
||||
</div>
|
||||
<div class="code-name">.icon-gz
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-tar"></span>
|
||||
<div class="name">
|
||||
tar
|
||||
</div>
|
||||
<div class="code-name">.icon-tar
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-bz2"></span>
|
||||
<div class="name">
|
||||
bz2
|
||||
</div>
|
||||
<div class="code-name">.icon-bz2
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-chat"></span>
|
||||
<div class="name">
|
||||
|
|
@ -1627,6 +1687,38 @@
|
|||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-zip"></use>
|
||||
</svg>
|
||||
<div class="name">zip</div>
|
||||
<div class="code-name">#icon-zip</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-gz"></use>
|
||||
</svg>
|
||||
<div class="name">gz</div>
|
||||
<div class="code-name">#icon-gz</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-tar"></use>
|
||||
</svg>
|
||||
<div class="name">tar</div>
|
||||
<div class="code-name">#icon-tar</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-bz2"></use>
|
||||
</svg>
|
||||
<div class="name">bz2</div>
|
||||
<div class="code-name">#icon-bz2</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-chat"></use>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4412427 */
|
||||
src: url('iconfont.woff2?t=1718176711902') format('woff2'),
|
||||
url('iconfont.woff?t=1718176711902') format('woff'),
|
||||
url('iconfont.ttf?t=1718176711902') format('truetype');
|
||||
src: url('iconfont.woff2?t=1722214929182') format('woff2'),
|
||||
url('iconfont.woff?t=1722214929182') format('woff'),
|
||||
url('iconfont.ttf?t=1722214929182') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
|
@ -13,6 +13,22 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-zip:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.icon-gz:before {
|
||||
content: "\e8b0";
|
||||
}
|
||||
|
||||
.icon-tar:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.icon-bz2:before {
|
||||
content: "\e65e";
|
||||
}
|
||||
|
||||
.icon-chat:before {
|
||||
content: "\e65f";
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -5,6 +5,34 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "30862364",
|
||||
"name": "zip",
|
||||
"font_class": "zip",
|
||||
"unicode": "e623",
|
||||
"unicode_decimal": 58915
|
||||
},
|
||||
{
|
||||
"icon_id": "10135229",
|
||||
"name": "gz",
|
||||
"font_class": "gz",
|
||||
"unicode": "e8b0",
|
||||
"unicode_decimal": 59568
|
||||
},
|
||||
{
|
||||
"icon_id": "21942534",
|
||||
"name": "tar",
|
||||
"font_class": "tar",
|
||||
"unicode": "e620",
|
||||
"unicode_decimal": 58912
|
||||
},
|
||||
{
|
||||
"icon_id": "40271559",
|
||||
"name": "bz2",
|
||||
"font_class": "bz2",
|
||||
"unicode": "e65e",
|
||||
"unicode_decimal": 58974
|
||||
},
|
||||
{
|
||||
"icon_id": "9442749",
|
||||
"name": "chat",
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
[
|
||||
{
|
||||
"name": "mysql5.7",
|
||||
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7/mysql-windows-5.7.7z",
|
||||
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7/mysql5.7.zip",
|
||||
"needDownload": true,
|
||||
"needInstall": true,
|
||||
"version": "5.7.37",
|
||||
|
|
@ -17,5 +17,35 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nginx",
|
||||
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7andredis5.0/nginx.zip",
|
||||
"needDownload": true,
|
||||
"needInstall": true,
|
||||
"version": "1.25.3",
|
||||
"icon": "https://p9.itc.cn/q_70/images03/20211130/a01ca555bc16455cbb2b50aed44fb64f.jpeg",
|
||||
"checkProgress": true,
|
||||
"hasRestart": true
|
||||
},
|
||||
{
|
||||
"name": "redis5.0",
|
||||
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7andredis5.0/redis5.0.zip",
|
||||
"needDownload": true,
|
||||
"needInstall": true,
|
||||
"version": "5.0.0",
|
||||
"icon": "https://img1.baidu.com/it/u=4217735613,2025794445&fm=253&fmt=auto&app=138&f=JPEG?w=293&h=247",
|
||||
"checkProgress": true,
|
||||
"hasRestart": true
|
||||
},
|
||||
{
|
||||
"name": "Notepad3",
|
||||
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7andredis5.0/Notepad3.zip",
|
||||
"needDownload": true,
|
||||
"needInstall": true,
|
||||
"version": "3.0.0",
|
||||
"icon": "https://www.waerfa.com/images/qfinx.png",
|
||||
"checkProgress": true,
|
||||
"hasRestart": true
|
||||
}
|
||||
]
|
||||
|
|
@ -64,7 +64,8 @@
|
|||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useSystem, basename, dirname, join, OsFileWithoutContent } from '@/system/index.ts';
|
||||
import { useSystem, basename, dirname, join, OsFileWithoutContent, Notify } from '@/system/index.ts';
|
||||
import { getSystemKey } from '@/system/config'
|
||||
import { emitEvent, mountEvent } from '@/system/event';
|
||||
import { useContextMenu } from '@/hook/useContextMenu.ts';
|
||||
import { t, dealSystemName } from '@/i18n';
|
||||
|
|
@ -228,9 +229,26 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
|
|||
}
|
||||
const ext = item.name.split(".").pop();
|
||||
|
||||
const zipSucess = (res: any) => {
|
||||
if (!res || res.code < 0) {
|
||||
new Notify({
|
||||
title: t('tips'),
|
||||
content: t('error'),
|
||||
});
|
||||
} else {
|
||||
props.onRefresh();
|
||||
new Notify({
|
||||
title: t('tips'),
|
||||
content: t('file.zip.success'),
|
||||
});
|
||||
if (item.parentPath == '/C/Users/Desktop') {
|
||||
sys.refershAppList()
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
// eslint-disable-next-line prefer-const
|
||||
let menuArr = [
|
||||
let menuArr: any = [
|
||||
{
|
||||
label: t('open'),
|
||||
click: () => {
|
||||
|
|
@ -247,6 +265,49 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
|
|||
// },
|
||||
|
||||
];
|
||||
if (item.isDirectory) {
|
||||
if (getSystemKey('storeType') == 'local') {
|
||||
menuArr.push({
|
||||
label: t('zip'),
|
||||
submenu: [
|
||||
{
|
||||
label: 'zip',
|
||||
click: () => {
|
||||
sys.fs.zip(item.path, 'zip').then((res: any) => {
|
||||
zipSucess(res)
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'tar',
|
||||
click: () => {
|
||||
sys.fs.zip(item.path, 'tar').then((res: any) => {
|
||||
zipSucess(res)
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'gz',
|
||||
click: () => {
|
||||
sys.fs.zip(item.path, 'gz').then((res: any) => {
|
||||
zipSucess(res)
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
} else {
|
||||
menuArr.push({
|
||||
label: t('zip'),
|
||||
click: () => {
|
||||
sys.fs.zip(item.path, 'zip').then((res: any) => {
|
||||
zipSucess(res)
|
||||
});
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
if (choose.ifShow) {
|
||||
menuArr.push({
|
||||
label: t('selected'),
|
||||
|
|
@ -269,7 +330,7 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
|
|||
})
|
||||
}
|
||||
// eslint-disable-next-line prefer-const
|
||||
let extMenus = useAppMenu(item, index);
|
||||
let extMenus = useAppMenu(item, sys, props);
|
||||
if (extMenus && extMenus.length > 0) {
|
||||
// eslint-disable-next-line prefer-spread
|
||||
menuArr.push.apply(menuArr, extMenus)
|
||||
|
|
@ -295,22 +356,17 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
|
|||
{
|
||||
label: t('delete'),
|
||||
click: async () => {
|
||||
// await Promise.all(
|
||||
// chosenIndexs.value.map((index) => {
|
||||
// const item = props.fileList[index];
|
||||
// deleteFile(item);
|
||||
// props.onRefresh();
|
||||
// return true;
|
||||
// })
|
||||
// );
|
||||
for (let i = 0; i < chosenIndexs.value.length; i++) {
|
||||
await deleteFile(props.fileList[chosenIndexs.value[i]]);
|
||||
}
|
||||
chosenIndexs.value = [];
|
||||
props.onRefresh();
|
||||
if (item.parentPath == '/C/Users/Desktop') {
|
||||
sys.refershAppList()
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
];
|
||||
// eslint-disable-next-line prefer-spread
|
||||
menuArr.push.apply(menuArr, fileMenus)
|
||||
|
|
|
|||
|
|
@ -57,15 +57,11 @@
|
|||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// import WinButton from '@/components/win/WinButton.vue';
|
||||
import { ref } from "vue";
|
||||
// import { useSystem } from '@/system';
|
||||
// import { Dialog } from '@/dialog/Dialog';
|
||||
import { t } from "@/i18n";
|
||||
import { getSystemKey } from "@/system/config";
|
||||
// const system = useSystem();
|
||||
const items = [
|
||||
// t('backup'), // '备份',
|
||||
t("version"), // '版本',
|
||||
];
|
||||
const version = getSystemKey("version");
|
||||
|
|
@ -75,58 +71,6 @@ const selectItem = (index: number) => {
|
|||
activeIndex.value = index;
|
||||
};
|
||||
|
||||
// async function handleClick(num: number) {
|
||||
// if (num === 0) {
|
||||
// //导出配置
|
||||
// const cfg = await system?.serializeState();
|
||||
// try {
|
||||
// await navigator.clipboard.writeText(cfg!);
|
||||
// Dialog.showMessageBox({
|
||||
// title: t('export.config'),
|
||||
// message: t('export.success.saved.to.clipboard'),
|
||||
// type: 'info',
|
||||
// buttons: [t('confirm')],
|
||||
// });
|
||||
// } catch (err) {
|
||||
// Dialog.showMessageBox({
|
||||
// title: t('export.config'),
|
||||
// message: t('export.failed'),
|
||||
// type: 'error',
|
||||
// buttons: [t('confirm')],
|
||||
// });
|
||||
// }
|
||||
// } else if (num === 1) {
|
||||
// // 导入配置
|
||||
// try {
|
||||
// const req = await Dialog.showMessageBox({
|
||||
// title: t('import.config'),
|
||||
// // message: '导入会覆盖现有的文件,是否继续?',
|
||||
// message: t('import.config.will.cover.existing.files.continue'),
|
||||
// type: 'warning',
|
||||
// buttons: [t('confirm'), t('cancel')],
|
||||
// });
|
||||
// if (req.response === 1) return;
|
||||
// await system?.deserializeState(inputConfig.value);
|
||||
// setTimeout(() => {
|
||||
// system?.reboot();
|
||||
// }, 10000);
|
||||
// await Dialog.showMessageBox({
|
||||
// title: t('import.success'),
|
||||
// message: t('import.success.reboot.soon'),
|
||||
// type: 'warning',
|
||||
// buttons: [t('confirm')],
|
||||
// });
|
||||
// system?.reboot();
|
||||
// } catch (err) {
|
||||
// Dialog.showMessageBox({
|
||||
// title: t('import.config'),
|
||||
// message: t('import.failed'),
|
||||
// type: 'error',
|
||||
// buttons: [t('confirm')],
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,28 @@
|
|||
<template>
|
||||
<div class="app-item">
|
||||
<img
|
||||
v-if="item?.icon"
|
||||
draggable="false"
|
||||
class="app-img"
|
||||
:src="item?.icon"
|
||||
alt=""
|
||||
/>
|
||||
<img v-if="item?.icon" draggable="false" class="app-img" :src="item?.icon" alt="" />
|
||||
<div class="app-content">
|
||||
<div class="app-title">{{ item?.name }}</div>
|
||||
<div class="app-desc">{{ item?.desc ?? item?.name }}</div>
|
||||
<div class="app-desc">
|
||||
<el-progress :text-inside="true" style="margin-top: 3px;" v-if="item?.progress" :stroke-width="20"
|
||||
:percentage="item?.progress" />
|
||||
</div>
|
||||
<div class="app-button">
|
||||
<div
|
||||
v-if="installedList!.includes(item?.name)"
|
||||
>
|
||||
<button @click="uninstall?.(item)">卸载</button>
|
||||
|
||||
<template v-if="installedList!.includes(item?.name)">
|
||||
<div v-if="item!.checkProgress">
|
||||
<button @click="pause?.(item)" v-if="item?.isRuning">暂停</button>
|
||||
<button @click="start?.(item)" v-else>启动</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button v-if="item?.url" @click="install?.(item)">安装</button>
|
||||
<div v-if="item!.hasRestart && item?.isRuning">
|
||||
<button @click="restart?.(item)">重启</button>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -29,6 +33,9 @@ defineProps({
|
|||
installedList: Array,
|
||||
install: Function,
|
||||
uninstall: Function,
|
||||
pause: Function,
|
||||
start: Function,
|
||||
restart: Function
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
|
|
@ -53,6 +60,7 @@ defineProps({
|
|||
border: 1px solid #cccccc7b;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
|
|
@ -62,32 +70,39 @@ defineProps({
|
|||
height: 80px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 6px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.app-desc {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.app-item:hover {
|
||||
box-shadow: 0 0 20px 2px #dddddd5b;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.app-item:active {
|
||||
box-shadow: 0 0 0px #ccc;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
.app-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.app-button button {
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
|
|
@ -97,10 +112,12 @@ defineProps({
|
|||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.app-button button:hover {
|
||||
box-shadow: 0 0 10px #ccc;
|
||||
/* transform: translateY(-2px); */
|
||||
}
|
||||
|
||||
.app-button button:active {
|
||||
box-shadow: 0 0 0px #ccc;
|
||||
transform: translateY(0px);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ async function getList(){
|
|||
if (currentCate.value == 'add') return;
|
||||
storeList.value = storeInitList
|
||||
// const apiUrl = getSystemKey("apiUrl");
|
||||
// const storeUrl = apiUrl + '/system/storeList?cate=' + currentCate.value
|
||||
// const storeUrl = apiUrl + '/store/storelist?cate=' + currentCate.value
|
||||
// fetch(storeUrl).then(res => {
|
||||
// res.json().then(data => {
|
||||
// storeList.value = data
|
||||
|
|
@ -33,8 +33,28 @@ async function getList(){
|
|||
// }).catch(() => {
|
||||
// notifyError(t("store.errorList"))
|
||||
// })
|
||||
await checkProgress()
|
||||
isready.value = true;
|
||||
}
|
||||
async function checkProgress() {
|
||||
const completion: any = await fetch(apiUrl + '/store/listporgress')
|
||||
if (!completion.ok) {
|
||||
return
|
||||
}
|
||||
let res:any = await completion.json()
|
||||
if(!res || res.length < 1){
|
||||
res = []
|
||||
}
|
||||
storeList.value.forEach((item: any, index: number) => {
|
||||
const pitem: any = res.find((i: any) => i.name == item.name)
|
||||
console.log(pitem)
|
||||
if (pitem) {
|
||||
storeList.value[index].isRuning = pitem.running
|
||||
}else{
|
||||
storeList.value[index].isRuning = false
|
||||
}
|
||||
})
|
||||
}
|
||||
async function changeCate(index: number, item: string) {
|
||||
currentCateId.value = index
|
||||
currentCate.value = item
|
||||
|
|
@ -53,9 +73,32 @@ async function install(item: any) {
|
|||
await download(item)
|
||||
}
|
||||
if (item.needInstall) {
|
||||
|
||||
item.progress = 0
|
||||
const completion = await fetch(apiUrl + '/store/install?name=' + item.name)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.installError"))
|
||||
return
|
||||
}
|
||||
if(item.url){
|
||||
const res = await completion.json()
|
||||
if (res.code && res.code < 0) {
|
||||
notifyError(res.message)
|
||||
return
|
||||
}
|
||||
}
|
||||
if(item.isOut){
|
||||
item.progress = 0
|
||||
const completion = await fetch(apiUrl + '/store/installOut?url=' + item.url)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.installError"))
|
||||
return
|
||||
}
|
||||
const res = await completion.json()
|
||||
if (res.code && res.code < 0) {
|
||||
notifyError(res.message)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (item.isWeb) {
|
||||
sys.fs.writeFile(
|
||||
`${sys._options.userLocation}Desktop/${item.name}.url`,
|
||||
`link::url::${item.url}::${item.icon}`
|
||||
|
|
@ -64,10 +107,24 @@ async function install(item: any) {
|
|||
|
||||
notifySuccess(t("install.success"))
|
||||
installedList.value.push(item.name);
|
||||
await checkProgress()
|
||||
setCache();
|
||||
}
|
||||
|
||||
function uninstall(item: any) {
|
||||
async function uninstall(item: any) {
|
||||
if (item.needInstall) {
|
||||
item.progress = 0
|
||||
const completion = await fetch(apiUrl + '/store/uninstall?name=' + item.name)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.installError"))
|
||||
return
|
||||
}
|
||||
const res = await completion.json()
|
||||
if (res.code && res.code < 0) {
|
||||
notifyError(res.message)
|
||||
return
|
||||
}
|
||||
}
|
||||
sys.fs.unlink(`${sys._options.userLocation}Desktop/${item.name}.url`);
|
||||
notifySuccess(t("uninstall.success"))
|
||||
delete installedList.value[installedList.value.indexOf(item.name)];
|
||||
|
|
@ -75,6 +132,7 @@ function uninstall(item: any) {
|
|||
}
|
||||
async function download(item: any) {
|
||||
//console.log(item)
|
||||
if (item.progress) return;
|
||||
const completion = await fetch(apiUrl + '/store/download?url=' + item.url)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.downloadError"))
|
||||
|
|
@ -95,13 +153,41 @@ async function download(item: any) {
|
|||
const res = parseJson(json)
|
||||
console.log(res)
|
||||
if (res) {
|
||||
if (res.progress) {
|
||||
item.progress = res.progress
|
||||
}
|
||||
if (res.done) {
|
||||
notifySuccess(t("store.downloadSuccess"))
|
||||
item.progress = 0
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async function pauseApp(item: any) {
|
||||
const res:any = await fetch(apiUrl + '/store/stop/' + item.name)
|
||||
if(!res.ok){
|
||||
const msg = await res.text()
|
||||
notifyError(msg)
|
||||
return
|
||||
}
|
||||
setTimeout(async() => {
|
||||
await checkProgress()
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
async function restartApp(item: any) {
|
||||
await fetch(apiUrl + '/store/restart/' + item.name)
|
||||
setTimeout(async() => {
|
||||
await checkProgress()
|
||||
}, 1000)
|
||||
}
|
||||
async function startApp(item: any) {
|
||||
await fetch(apiUrl + '/store/start/' + item.name)
|
||||
setTimeout(async() => {
|
||||
await checkProgress()
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="outer">
|
||||
|
|
@ -139,7 +225,8 @@ async function download(item: any) {
|
|||
</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" />
|
||||
<AppItem :item="item" :installed-list="installedList" :install="install" :uninstall="uninstall"
|
||||
:pause="pauseApp" :start="startApp" :restart="restartApp" />
|
||||
</div>
|
||||
<AddApp v-else />
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const router = useRouter();
|
|||
const props = defineProps<{
|
||||
win: any;
|
||||
}>();
|
||||
|
||||
const win = ref(props.win)
|
||||
if (props.win.path) {
|
||||
router.push(props.win.path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { OsFileWithoutContent } from '@/system/core/FileSystem';
|
||||
import { BrowserWindow } from '@/system/window/BrowserWindow';
|
||||
export function useAppMenu(item: OsFileWithoutContent, _: number) {
|
||||
import { OsFileWithoutContent, BrowserWindow, Notify } from '@/system';
|
||||
import { t } from "@/i18n"
|
||||
export function useAppMenu(item: OsFileWithoutContent, sys: any, props: any) {
|
||||
let menuArr: any = [];
|
||||
const ext: any = item.name.split(".").pop();
|
||||
const picExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff'];
|
||||
if (picExt.includes(ext)) {
|
||||
menuArr.push({
|
||||
label: '编辑',
|
||||
label: t('piceditor'),
|
||||
click: () => {
|
||||
const photoWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
icon: "picedit",
|
||||
center: true,
|
||||
title: '图像绘图',
|
||||
title: t('piceditor'),
|
||||
url: "/picedit/index.html",
|
||||
config: item
|
||||
});
|
||||
|
|
@ -21,25 +21,34 @@ export function useAppMenu(item: OsFileWithoutContent, _: number) {
|
|||
},
|
||||
},)
|
||||
}
|
||||
// const fileExt = ['txt', 'html', 'json', 'xml', 'css', 'js','vue','go','php','java','py']
|
||||
// if (fileExt.includes(ext)) {
|
||||
// menuArr.push({
|
||||
// label: '文本编辑',
|
||||
// click: () => {
|
||||
// const textWindow = new BrowserWindow({
|
||||
// width: 800,
|
||||
// height: 600,
|
||||
// icon: "editorbt",
|
||||
// center: true,
|
||||
// title: '文件编辑',
|
||||
// url: "/text/index.html",
|
||||
// config: item
|
||||
// });
|
||||
// textWindow.show()
|
||||
// },
|
||||
// },)
|
||||
// }
|
||||
const zipExt = ['zip', 'tar', 'gz', 'bz2']
|
||||
const unzipSucess = (res: any) => {
|
||||
//console.log(res)
|
||||
if (!res || res.code < 0) {
|
||||
new Notify({
|
||||
title: t('tips'),
|
||||
content: t('error'),
|
||||
});
|
||||
} else {
|
||||
props.onRefresh();
|
||||
new Notify({
|
||||
title: t('tips'),
|
||||
content: t('file.unzip.success'),
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
if (zipExt.includes(ext)) {
|
||||
menuArr.push({
|
||||
label: t('unzip'),
|
||||
click: () => {
|
||||
//console.log(item.path)
|
||||
sys.fs.unzip(item.path).then((res: any) => {
|
||||
unzipSucess(res)
|
||||
})
|
||||
},
|
||||
},)
|
||||
}
|
||||
return menuArr;
|
||||
}
|
||||
//export { useAppMenu };
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ function useContextMenu() {
|
|||
callback?.();
|
||||
});
|
||||
}
|
||||
|
||||
//console.log(path)
|
||||
let menuArr = [
|
||||
{
|
||||
|
|
@ -72,10 +73,6 @@ function useContextMenu() {
|
|||
label: '数据表格(Excel)',
|
||||
click: () => clickFunc('xlsx', '未命名数据表格'),
|
||||
},
|
||||
// {
|
||||
// label: '笔记(Note)',
|
||||
// click: () => clickFunc('note', '未命名笔记'),
|
||||
// },
|
||||
{
|
||||
label: '文本文件(txt)',
|
||||
click: () => clickFunc('txt', '未命名文本文件'),
|
||||
|
|
@ -97,36 +94,13 @@ function useContextMenu() {
|
|||
label: '白板(Baiban)',
|
||||
click: () => clickFunc('bb', '未命名白板'),
|
||||
},
|
||||
// {
|
||||
// label: '工作绘图(Flow)',
|
||||
// click: () => clickFunc('flow', '未命名工作绘图'),
|
||||
// },
|
||||
{
|
||||
label: '甘特图(Gant)',
|
||||
click: () => clickFunc('gant', '未命名项目'),
|
||||
},
|
||||
// {
|
||||
// label: '原型图(Proto)',
|
||||
// click: () => clickFunc('proto', '原型图'),
|
||||
// },
|
||||
// {
|
||||
// label: '流程图(Flow)',
|
||||
// click: () => clickFunc('mind', '流程图'),
|
||||
// },
|
||||
// {
|
||||
// label: '泳道图(Uml)',
|
||||
// click: () => clickFunc('uml', '泳道图'),
|
||||
// },
|
||||
// {
|
||||
// label: '用例图(Case)',
|
||||
// click: () => clickFunc('case', '泳道图'),
|
||||
// },
|
||||
// {
|
||||
// label: '顺序图(Seq)',
|
||||
// click: () => clickFunc('seq', '顺序图'),
|
||||
// },
|
||||
],
|
||||
},
|
||||
|
||||
// {
|
||||
// label: '新建报告',
|
||||
// submenu: [
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@
|
|||
"tips": "Tips",
|
||||
"file.not.exist": "File Not Exist",
|
||||
"file.save.success": "File Save Success",
|
||||
"file.zip.success": "File Zip Success",
|
||||
"file.unzip.success": "File Unzip Success",
|
||||
"new": "New",
|
||||
"back": "Back",
|
||||
"path.not.exist": "Path Not Exist",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
"backup": "备份",
|
||||
"import.success.reboot.soon": "导入成功,即将重启",
|
||||
"import.failed": "导入失败",
|
||||
"zip":"压缩",
|
||||
"unzip":"解压",
|
||||
"import": "导入",
|
||||
"export": "导出",
|
||||
"refresh": "刷新",
|
||||
|
|
@ -58,6 +60,8 @@
|
|||
"tips": "提示",
|
||||
"file.not.exist": "文件不存在",
|
||||
"file.save.success": "文件保存成功",
|
||||
"file.zip.success": "文件压缩成功",
|
||||
"file.unzip.success": "文件解压成功",
|
||||
"new": "新建",
|
||||
"back": "返回",
|
||||
"path.not.exist": "路径不存在",
|
||||
|
|
|
|||
|
|
@ -187,19 +187,33 @@ export function handleWatch(path: string, callback: any, errback: any) {
|
|||
errback("Your browser does not support server-sent events.")
|
||||
}
|
||||
}
|
||||
export async function handleZip(path: string, ext: string): Promise<any> {
|
||||
const res = await fetch(`${API_BASE_URL}/zip?path=${encodeURIComponent(path)}&ext=${ext}`);
|
||||
if (!res.ok) {
|
||||
return false;
|
||||
}
|
||||
return await res.json();
|
||||
}
|
||||
export async function handleUnZip(path: string): Promise<any> {
|
||||
const res = await fetch(`${API_BASE_URL}/unzip?path=${encodeURIComponent(path)}`);
|
||||
if (!res.ok) {
|
||||
return false;
|
||||
}
|
||||
return await res.json();
|
||||
}
|
||||
export const useOsFile = () => {
|
||||
return {
|
||||
async readdir(path: string) {
|
||||
const response = await handleReadDir(path);
|
||||
if (response && response.data) {
|
||||
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response.data;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
async stat(path: string) {
|
||||
const response = await handleStat(path);
|
||||
if (response && response.data) {
|
||||
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response.data;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
|
@ -207,81 +221,71 @@ export const useOsFile = () => {
|
|||
const modes = osFileModeToOctal(mode)
|
||||
const response = await handleChmod(path, modes);
|
||||
if (response) {
|
||||
return response; // 假设返回的JSON包含操作状态或其他相关信息
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async exists(path: string) {
|
||||
//return handleExists(path);
|
||||
const response = await handleExists(path);
|
||||
if (response && response.data) {
|
||||
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response.data;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async readFile(path: string) {
|
||||
// 注意:handleReadFile返回的是JSON,但通常readFile期望返回Buffer或字符串
|
||||
// 根据你的后端API实际返回的数据类型调整此方法
|
||||
const response = await handleReadFile(path);
|
||||
if (response && response.data) {
|
||||
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response.data;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async unlink(path: string) {
|
||||
//return handleUnlink(path);
|
||||
const response = await handleUnlink(path);
|
||||
if (response) {
|
||||
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async rename(oldPath: string, newPath: string) {
|
||||
//return handleRename(oldPath, newPath);
|
||||
|
||||
const response = await handleRename(oldPath, newPath);
|
||||
if (response) {
|
||||
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async rmdir(path: string) {
|
||||
//return handleRmdir(path);
|
||||
const response = await handleRmdir(path);
|
||||
if (response) {
|
||||
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async mkdir(path: string) {
|
||||
//return handleMkdir(path);
|
||||
const response = await handleMkdir(path);
|
||||
if (response) {
|
||||
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async copyFile(srcPath: string, dstPath: string) {
|
||||
//return handleCopyFile(srcPath, dstPath);
|
||||
const response = await handleCopyFile(srcPath, dstPath);
|
||||
if (response) {
|
||||
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async writeFile(path: string, content: string | Blob) {
|
||||
//return handleWriteFile(path, content);
|
||||
const response = await handleWriteFile(path, content);
|
||||
if (response) {
|
||||
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async appendFile(path: string, content: string | Blob) {
|
||||
//return handleAppendFile(path, content);
|
||||
const response = await handleAppendFile(path, content);
|
||||
if (response) {
|
||||
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
|
@ -289,6 +293,20 @@ export const useOsFile = () => {
|
|||
console.log(path, options)
|
||||
return true;
|
||||
},
|
||||
async zip(path: string, ext: string) {
|
||||
const response = await handleZip(path, ext);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async unzip(path: string) {
|
||||
const response = await handleUnZip(path);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
serializeFileSystem() {
|
||||
return Promise.reject('fs 不支持序列化');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { SystemOptions } from '../type/type';
|
|||
import { InitSystemFile, InitUserFile } from './SystemFileConfig';
|
||||
import { createInitFile } from './createInitFile';
|
||||
import { OsFileMode } from './FileMode';
|
||||
import JSZip from "jszip";
|
||||
type DateLike = Date | string | number;
|
||||
// Os文件模式枚举
|
||||
class OsFileInfo {
|
||||
|
|
@ -405,7 +406,7 @@ class OsFileSystem implements OsFileInterface {
|
|||
* @param path 文件路径
|
||||
* @returns 文件内容
|
||||
*/
|
||||
async readFile(path: string): Promise<string | null> {
|
||||
async readFile(path: string): Promise<string | any> {
|
||||
try {
|
||||
const volume = this.checkVolumePath(path);
|
||||
if (volume) {
|
||||
|
|
@ -891,12 +892,13 @@ class OsFileSystem implements OsFileInterface {
|
|||
const transedPath = fspath.transformPath(path);
|
||||
// 获取父路径
|
||||
let parentPath = fspath.dirname(transedPath);
|
||||
|
||||
// 如果父路径为空,设置为根路径
|
||||
if (parentPath === '') parentPath = '/';
|
||||
// 判断文件是否存在
|
||||
const exists = await this.exists(parentPath);
|
||||
// 如果文件不存在,则抛出错误
|
||||
if (!exists) {
|
||||
if (!exists && path != parentPath) {
|
||||
this.onerror('Cannot create directory to a non-exist path:' + parentPath);
|
||||
return Promise.reject('Cannot create directory to a non-exist path:' + parentPath);
|
||||
}
|
||||
|
|
@ -1072,6 +1074,75 @@ class OsFileSystem implements OsFileInterface {
|
|||
};
|
||||
});
|
||||
}
|
||||
async zip(path: string, ext?: string) {
|
||||
ext = 'zip';
|
||||
const ziplibs = new JSZip();
|
||||
const that = this;
|
||||
async function dfsPackage(path: string, ziplibs: JSZip) {
|
||||
const dir = await that.readdir(path);
|
||||
|
||||
for (let i = 0; i < dir.length; i++) {
|
||||
const item = dir[i];
|
||||
const stat = await that.stat(item.path);
|
||||
if (stat) {
|
||||
if (stat.isDirectory) {
|
||||
await dfsPackage(item.path, ziplibs);
|
||||
} else {
|
||||
const content = await that.readFile(item.path);
|
||||
try {
|
||||
atob(content || "");
|
||||
ziplibs.file(item.path, content || "");
|
||||
} catch (error) {
|
||||
ziplibs.file(item.path, content || "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await dfsPackage(path, ziplibs);
|
||||
const content = await ziplibs.generateAsync({ type: "blob" })
|
||||
const targetPath = path + "." + ext;
|
||||
await that.writeFile(targetPath, content);
|
||||
return { code: 0 }
|
||||
}
|
||||
async unzip(path: string) {
|
||||
const stat = await this.stat(path);
|
||||
if (stat?.isDirectory) {
|
||||
return { code: -1 }
|
||||
}
|
||||
const that = this;
|
||||
try {
|
||||
const content = await this.readFile(path);
|
||||
const ziplibs = new JSZip();
|
||||
const unziped = await ziplibs.loadAsync(content);
|
||||
const targetPath = path.substring(0, path.lastIndexOf("."))
|
||||
|
||||
if (!await that.exists(targetPath)) {
|
||||
await that.mkdir(targetPath)
|
||||
}
|
||||
const unzipArray: Array<JSZip.JSZipObject> = [];
|
||||
unziped.forEach((_, zipEntry) => {
|
||||
unzipArray.push(zipEntry);
|
||||
});
|
||||
for (let i = 0; i < unzipArray.length; i++) {
|
||||
const zipEntry = unzipArray[i];
|
||||
if (zipEntry.dir) {
|
||||
await this.mkdir(zipEntry.name);
|
||||
} else {
|
||||
let fileC: any = await zipEntry.async("string");
|
||||
if (!fileC.startsWith("link::")) {
|
||||
fileC = await zipEntry.async("arraybuffer");
|
||||
}
|
||||
this.writeFile(zipEntry.name, fileC);
|
||||
}
|
||||
}
|
||||
return { code: 0 }
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return { code: -1 }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export { OsFile, OsFileInfo, OsFileSystem };
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ export function initBuiltinFileOpener(system: System) {
|
|||
height: 600,
|
||||
center: true,
|
||||
title: t("computer"),
|
||||
content: "MyComputer",
|
||||
content: "Computer",
|
||||
//icon: myComputerLogoIcon,
|
||||
icon: "computer",
|
||||
config: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { OsFileSystem, OsFileWithoutContent } from '@/system/core/FileSystem';
|
||||
|
||||
import { System } from '@/system';
|
||||
const unknownicon = 'unknow'
|
||||
import unknownicon from "@/assets/unknown.png"
|
||||
export function dealExeIcon(content: string | null | undefined) {
|
||||
|
||||
if (!content) return unknownicon;
|
||||
|
|
@ -42,19 +42,6 @@ export async function dealIcon(
|
|||
const content = await system?.fs.readFile(file.path);
|
||||
return dealExeIcon(content);
|
||||
}
|
||||
// if (ext === '.png') {
|
||||
// if (system.fs instanceof OsFileSystem) {
|
||||
// if (system.fs.checkVolumePath(file.path)) {
|
||||
// return imageicon;
|
||||
// } else {
|
||||
// // 只有当非自定义文件系统和非自定义卷才直接展示icon
|
||||
// const content = await system.fs.readFile(file.path);
|
||||
// return 'data:image/png;base64,' + content || unknownicon;
|
||||
// }
|
||||
// } else {
|
||||
// return imageicon;
|
||||
// }
|
||||
// }
|
||||
if (ext === 'ln') {
|
||||
if (stopCircle) {
|
||||
return unknownicon;
|
||||
|
|
@ -69,27 +56,4 @@ export async function dealIcon(
|
|||
}
|
||||
}
|
||||
return file.ext;
|
||||
// const exts = ['mp3','mp4','txt','js','json','xml','svg','html','css','bmp','gif','jpg','png','tiff','webp']
|
||||
// if(exts.includes(file.ext)){
|
||||
// return '/image/ext/' + file.ext + '.svg'
|
||||
// }
|
||||
// if (ext === '.mp3') return audioicon;
|
||||
// if (ext === '.mp4') return videoicon;
|
||||
|
||||
// if (ext === '.txt') return txtIcon;
|
||||
// if (ext === '.js') return jsIcon;
|
||||
// if (ext === '.json') return jsonIcon;
|
||||
// if (ext === '.xml') return xmlIcon;
|
||||
// if (ext === '.svg') return svgIcon;
|
||||
// if (ext === '.html') return htmlIcon;
|
||||
// if (ext === '.css') return cssIcon;
|
||||
|
||||
// if (ext === '.bmp') return bmpIcon;
|
||||
// if (ext === '.gif') return gifIcon;
|
||||
// if (ext === '.jpg') return jpgIcon;
|
||||
// if (ext === '.png') return pngIcon;
|
||||
// if (ext === '.tiff') return tiffIcon;
|
||||
// if (ext === '.webp') return webpIcon;
|
||||
|
||||
//return system.getOpener(ext)?.icon || unknownicon;
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -42,7 +42,7 @@ require (
|
|||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
)
|
||||
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -105,8 +105,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"godo/files"
|
||||
"godo/libs"
|
||||
"godo/localchat"
|
||||
"godo/progress"
|
||||
"godo/store"
|
||||
"godo/sys"
|
||||
"log"
|
||||
|
|
@ -27,32 +26,32 @@ func OsStart() {
|
|||
router.Use(loggingMiddleware{}.Middleware)
|
||||
staticDir := libs.GetStaticDir()
|
||||
router.PathPrefix("/static").Handler(http.StripPrefix("/static", http.FileServer(http.Dir(staticDir))))
|
||||
router.HandleFunc("/ping", progress.Ping).Methods(http.MethodGet)
|
||||
router.HandleFunc("/ping", store.Ping).Methods(http.MethodGet)
|
||||
if libs.PathExists("./dist") {
|
||||
router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("./dist"))))
|
||||
} else {
|
||||
router.HandleFunc("/", progress.Ping).Methods(http.MethodGet)
|
||||
router.HandleFunc("/", store.Ping).Methods(http.MethodGet)
|
||||
}
|
||||
progressRouter := router.PathPrefix("/progress").Subrouter()
|
||||
progressRouter.HandleFunc("/start/{name}", progress.StartProcess).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/stop/{name}", progress.StopProcess).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/startall", progress.StartAll).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/stopall", progress.StopAll).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/restart/{name}", progress.ReStartProcess).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/list", progress.Status).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/listport", progress.ListPortsHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/killport", progress.KillPortHandler).Methods(http.MethodGet)
|
||||
progressRouter := router.PathPrefix("/store").Subrouter()
|
||||
progressRouter.HandleFunc("/start/{name}", store.StartProcess).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/stop/{name}", store.StopProcess).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/startall", store.StartAll).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/stopall", store.StopAll).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/restart/{name}", store.ReStartProcess).Methods(http.MethodGet)
|
||||
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("/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)
|
||||
|
||||
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/system/setting", sys.HandleSetConfig).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/store/list", store.GetStoreInfoHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/store/download", store.DownloadHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/store/install", store.InstallHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/store/uninstall", store.UnInstallHandler).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/files/info", files.HandleSystemInfo).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/info", files.HandleSystemInfo).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/read", files.HandleReadDir).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/stat", files.HandleStat).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/chmod", files.HandleChmod).Methods(http.MethodPost)
|
||||
|
|
@ -66,12 +65,14 @@ func OsStart() {
|
|||
router.HandleFunc("/file/copyfile", files.HandleCopyFile).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/writefile", files.HandleWriteFile).Methods(http.MethodPost)
|
||||
router.HandleFunc("/file/appendfile", files.HandleAppendFile).Methods(http.MethodPost)
|
||||
router.HandleFunc("/file/zip", files.HandleZip).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/unzip", files.HandleUnZip).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/watch", files.WatchHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/localchat/sse", localchat.SseHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/localchat/message", localchat.HandleMessage).Methods(http.MethodPost)
|
||||
router.HandleFunc("/localchat/upload", localchat.MultiUploadHandler).Methods(http.MethodPost)
|
||||
|
||||
go progress.CheckActive(context.Background())
|
||||
go store.CheckActive(context.Background())
|
||||
log.Printf("Listening on port: %v", serverAddress)
|
||||
srv = &http.Server{Addr: serverAddress, Handler: router}
|
||||
Serve(srv)
|
||||
|
|
@ -79,7 +80,7 @@ func OsStart() {
|
|||
func OsStop() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
err := progress.StopAllHandler()
|
||||
err := store.StopAllHandler()
|
||||
if err != nil {
|
||||
log.Fatalf("Servers forced to shutdown error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -398,6 +398,58 @@ func HandleChmod(w http.ResponseWriter, r *http.Request) {
|
|||
res := libs.APIResponse{Message: fmt.Sprintf("Permissions of file '%s' successfully changed to %o.", path, mode)}
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
func HandleZip(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Query().Get("path")
|
||||
ext := r.URL.Query().Get("ext")
|
||||
if ext == "" {
|
||||
ext = "zip"
|
||||
}
|
||||
if err := validateFilePath(path); err != nil {
|
||||
libs.HTTPError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
basePath, err := libs.GetOsDir()
|
||||
if err != nil {
|
||||
libs.HTTPError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
sourcePath := filepath.Join(basePath, path)
|
||||
compressedFilePath := filepath.Join(basePath, path+"."+ext)
|
||||
err = CompressFileOrFolder(sourcePath, compressedFilePath)
|
||||
if err != nil {
|
||||
libs.HTTPError(w, http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
res := libs.APIResponse{Message: fmt.Sprintf("success zip %s.%s.", path, ext)}
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func HandleUnZip(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Query().Get("path")
|
||||
|
||||
if err := validateFilePath(path); err != nil {
|
||||
libs.ErrorMsg(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
basePath, err := libs.GetOsDir()
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
zipFilePath := filepath.Join(basePath, path)
|
||||
destPath := filepath.Dir(zipFilePath)
|
||||
err = Decompress(zipFilePath, destPath)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, err.Error())
|
||||
return
|
||||
}
|
||||
res := libs.APIResponse{Message: fmt.Sprintf("success unzip %s.", path)}
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
// parseMode converts a string representation of a mode to an os.FileMode
|
||||
func parseMode(modeStr string) (os.FileMode, error) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"godo/libs"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -48,13 +49,26 @@ func Untar(reader io.Reader, destPath string) error {
|
|||
}
|
||||
|
||||
target := filepath.Join(destPath, header.Name)
|
||||
|
||||
// Ensure that all directories in the path are created.
|
||||
if header.Typeflag == tar.TypeDir {
|
||||
if !libs.PathExists(target) {
|
||||
if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// If it's a regular file, ensure its parent directories exist.
|
||||
if !libs.PathExists(target) {
|
||||
if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create the file and write the contents from the tar archive.
|
||||
outFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -114,21 +128,33 @@ func Unzip(zipFilePath, destPath string) error {
|
|||
|
||||
path := filepath.Join(destPath, f.Name)
|
||||
|
||||
// Ensure parent directory exists before creating the file or directory.
|
||||
if !libs.PathExists(filepath.Dir(path)) {
|
||||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(path, f.Mode())
|
||||
// Create the directory itself if it doesn't exist yet.
|
||||
if !libs.PathExists(path) {
|
||||
if err := os.Mkdir(path, f.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Dir(path), f.Mode())
|
||||
|
||||
// Open the file for writing, truncating if it already exists.
|
||||
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
_, err = io.Copy(outFile, rc)
|
||||
if err != nil {
|
||||
// Write the contents of the zip file entry to the output file.
|
||||
if _, err := io.Copy(outFile, rc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -155,11 +181,15 @@ func IsSupportedCompressionFormat(filename string) bool {
|
|||
|
||||
// HandlerFile 根据文件后缀判断是否支持解压,支持则解压,否则移动文件
|
||||
func HandlerFile(filePath string, destDir string) error {
|
||||
if !IsSupportedCompressionFormat(filePath) {
|
||||
// log.Printf("HandlerFile: %s", filePath)
|
||||
// log.Printf("destDir: %s", destDir)
|
||||
if IsSupportedCompressionFormat(filePath) {
|
||||
// 移动文件
|
||||
newPath := filepath.Join(destDir, filepath.Base(filePath))
|
||||
return os.Rename(filePath, newPath)
|
||||
}
|
||||
// newPath := filepath.Join(destDir, filepath.Base(filePath))
|
||||
// return os.Rename(filePath, newPath)
|
||||
// 解压文件
|
||||
return Decompress(filePath, destDir)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,8 +87,6 @@ func IsSupportedFormat(filename string) bool {
|
|||
".zip": true,
|
||||
".tar": true,
|
||||
".gz": true,
|
||||
".bz2": true,
|
||||
// 添加其他支持的复合格式
|
||||
}
|
||||
|
||||
for format := range supportedFormats {
|
||||
|
|
|
|||
|
|
@ -17,5 +17,5 @@ require (
|
|||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
package progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func StartProcess(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
err := ExecuteScript(name)
|
||||
|
||||
if err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
fmt.Fprintf(w, "Process %s started.", name)
|
||||
}
|
||||
func ExecuteStartAll() error {
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
|
||||
for name, cmd := range processes {
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func StartAll(w http.ResponseWriter, r *http.Request) {
|
||||
if err := ExecuteStartAll(); err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "All processes started.")
|
||||
}
|
||||
|
||||
// ExecuteScript 执行指定名称的脚本。
|
||||
// 参数:
|
||||
// name - 脚本的名称。
|
||||
// 返回值:
|
||||
// 返回可能遇到的错误,如果执行成功,则返回nil。
|
||||
func ExecuteScript(name string) error {
|
||||
cmd, exists := processes[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("process information for '%s' not found", name)
|
||||
}
|
||||
if cmd.Running {
|
||||
return fmt.Errorf("process %s is already running", name)
|
||||
}
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start process %s: %v", name, err)
|
||||
}
|
||||
cmd.Running = true
|
||||
return nil
|
||||
// 确保name存在于ProcessInfoMap中
|
||||
// _, ok := ProcessInfoMap[name]
|
||||
// if !ok {
|
||||
// return fmt.Errorf("process information for '%s' not found", name)
|
||||
// }
|
||||
// // 根据操作系统添加.exe后缀
|
||||
// binaryExt := ""
|
||||
// if runtime.GOOS == "windows" {
|
||||
// binaryExt = ".exe"
|
||||
// }
|
||||
// scriptName := name + binaryExt
|
||||
// exeDir := libs.GetRunDir() // 获取执行程序的目录
|
||||
// scriptPath := filepath.Join(exeDir, name, scriptName) // 拼接脚本的完整路径
|
||||
// // 检查脚本文件的存在性
|
||||
// if !libs.PathExists(scriptPath) {
|
||||
// return fmt.Errorf("script file %s does not exist", scriptPath)
|
||||
// }
|
||||
|
||||
// // 设置并启动脚本执行命令
|
||||
// var cmd *exec.Cmd
|
||||
// cmd = exec.Command(scriptPath)
|
||||
// if runtime.GOOS == "windows" {
|
||||
// // 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
// cmd = SetHideConsoleCursor(cmd)
|
||||
// }
|
||||
// go func() {
|
||||
// // 启动脚本命令并返回可能的错误
|
||||
// if err := cmd.Start(); err != nil {
|
||||
// log.Printf("failed to start process %s: %v", name, err)
|
||||
// return
|
||||
// }
|
||||
// RegisterProcess(name, cmd)
|
||||
// }()
|
||||
// return nil
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package progress
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -73,7 +73,7 @@ func IsProcessRunning(appName string) (bool, error) {
|
|||
if cmd.SysProcAttr == nil {
|
||||
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 output, err = cmd.CombinedOutput(); err != nil {
|
||||
return false, fmt.Errorf("error checking process: %w", err)
|
||||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"godo/files"
|
||||
"godo/libs"
|
||||
"godo/progress"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -14,6 +13,7 @@ import (
|
|||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StoreInfo struct {
|
||||
|
|
@ -44,7 +44,8 @@ type Cmd struct {
|
|||
Content string `json:"content,omitempty"`
|
||||
BinPath string `json:"binPath,omitempty"`
|
||||
Cmds []string `json:"cmds,omitempty"`
|
||||
Waiting bool `json:"waiting"`
|
||||
Waiting int `json:"waiting"`
|
||||
Kill bool `json:"kill"`
|
||||
Envs []Item `json:"envs"`
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +62,7 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
exeDir := libs.GetRunDir()
|
||||
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) {
|
||||
libs.ErrorMsg(w, "the app path is not exists!")
|
||||
return
|
||||
|
|
@ -115,8 +116,9 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
targetPath := filepath.Join(staticDir, pluginName)
|
||||
err = os.Rename(staticPath, targetPath)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "copy the static is error!")
|
||||
return
|
||||
// libs.ErrorMsg(w, "copy the static is error!")
|
||||
// return
|
||||
log.Printf("copy the static is error! %v", err)
|
||||
}
|
||||
}
|
||||
libs.SuccessMsg(w, "success", "install the app success!")
|
||||
|
|
@ -127,10 +129,12 @@ func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
libs.ErrorMsg(w, "the app name is empty!")
|
||||
return
|
||||
}
|
||||
err := progress.StopCmd(pluginName)
|
||||
log.Printf("uninstall the app %s", pluginName)
|
||||
err := StopCmd(pluginName)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "stop the app is error!")
|
||||
return
|
||||
// libs.ErrorMsg(w, "stop the app is error!")
|
||||
// return
|
||||
log.Printf("stop the app is error! %s", err)
|
||||
}
|
||||
exeDir := libs.GetRunDir()
|
||||
exePath := filepath.Join(exeDir, pluginName)
|
||||
|
|
@ -233,29 +237,35 @@ func RunCmds(storeInfo StoreInfo, cmdKey string) error {
|
|||
return nil
|
||||
}
|
||||
func runStop(storeInfo StoreInfo) error {
|
||||
return progress.StopCmd(storeInfo.Name)
|
||||
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 len(storeInfo.Start.Cmds) > 0 {
|
||||
if !libs.PathExists(storeInfo.Setting.BinPath) {
|
||||
return fmt.Errorf("script file %s does not exist", storeInfo.Setting.BinPath)
|
||||
}
|
||||
cmd := exec.Command(storeInfo.Setting.BinPath, storeInfo.Start.Cmds...)
|
||||
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 = progress.SetHideConsoleCursor(cmd)
|
||||
cmd = SetHideConsoleCursor(cmd)
|
||||
}
|
||||
// 启动脚本命令并返回可能的错误
|
||||
go func(cmd *exec.Cmd) {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start process %s: %w", storeInfo.Name, err)
|
||||
log.Println("Error starting script:", err)
|
||||
return
|
||||
}
|
||||
RegisterProcess(storeInfo.Name, cmd)
|
||||
}(cmd)
|
||||
|
||||
progress.RegisterProcess(storeInfo.Name, cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func runRestart(storeInfo StoreInfo) error {
|
||||
|
|
@ -275,15 +285,21 @@ func runExec(storeInfo StoreInfo, cmdParam Cmd) error {
|
|||
cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...)
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = progress.SetHideConsoleCursor(cmd)
|
||||
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 {
|
||||
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)
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
//go:build !windows
|
||||
|
||||
package progress
|
||||
package store
|
||||
|
||||
import (
|
||||
// 导入包
|
||||
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package progress
|
||||
package store
|
||||
|
||||
type ProcessInfo struct {
|
||||
Port string
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package progress
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package progress
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
|
@ -10,12 +10,12 @@ import (
|
|||
)
|
||||
|
||||
type Process struct {
|
||||
Name string
|
||||
Running bool
|
||||
ExitCode int
|
||||
Cmd *exec.Cmd
|
||||
Waiting bool
|
||||
LastPing time.Time
|
||||
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"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -29,7 +29,6 @@ func respondWithError(w http.ResponseWriter, code int, message string) {
|
|||
func RegisterProcess(name string, cmdstr *exec.Cmd) {
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
|
||||
processes[name] = &Process{
|
||||
Name: name,
|
||||
Running: true,
|
||||
|
|
@ -43,19 +42,20 @@ func GetCmd(name string) *Process {
|
|||
return processes[name]
|
||||
}
|
||||
func Status(w http.ResponseWriter, r *http.Request) {
|
||||
processesMu.RLock()
|
||||
defer processesMu.RUnlock()
|
||||
// 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})
|
||||
}
|
||||
// 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()})
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(ps, "", " ")
|
||||
136
godo/store/start.go
Normal file
136
godo/store/start.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"godo/libs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func StartProcess(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
err := ExecuteScript(name)
|
||||
|
||||
if err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
fmt.Fprintf(w, "Process %s started.", name)
|
||||
}
|
||||
func ExecuteStartAll() error {
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
|
||||
for name, cmd := range processes {
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func StartAll(w http.ResponseWriter, r *http.Request) {
|
||||
if err := ExecuteStartAll(); err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
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 执行指定名称的脚本。
|
||||
// 参数:
|
||||
// name - 脚本的名称。
|
||||
// 返回值:
|
||||
// 返回可能遇到的错误,如果执行成功,则返回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
|
||||
|
||||
}
|
||||
if cmd.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
url := r.URL.Query().Get("url")
|
||||
fileName := filepath.Base(url)
|
||||
cacheDir := libs.GetCacheDir()
|
||||
filePath := filepath.Join(cacheDir, fileName)
|
||||
if !libs.PathExists(filePath) {
|
||||
libs.ErrorMsg(w, "file not found")
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(filePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
libs.ErrorMsg(w, fmt.Sprintf("start error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 如果 cmd.Start() 成功,返回成功消息
|
||||
libs.SuccessMsg(w, "", "start success")
|
||||
|
||||
}
|
||||
|
|
@ -1,23 +1,37 @@
|
|||
package progress
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/shirou/gopsutil/process"
|
||||
)
|
||||
|
||||
func KillByPid(pid int) error {
|
||||
p, err := process.NewProcess(int32(pid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create process object: %w", err)
|
||||
}
|
||||
|
||||
// 杀死进程及其子进程
|
||||
if err := p.Kill(); err != nil {
|
||||
return fmt.Errorf("failed to kill process: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func StopCmd(name string) error {
|
||||
cmd, ok := processes[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Process not found")
|
||||
}
|
||||
if err := cmd.Cmd.Process.Kill(); err != nil {
|
||||
return fmt.Errorf("failed to kill process: %v", err)
|
||||
processes[name].Running = false
|
||||
err := KillByPid(cmd.Cmd.Process.Pid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to kill process: %w", err)
|
||||
}
|
||||
//delete(processes, name) // 更新status,表示进程已停止
|
||||
cmd.Running = false
|
||||
return nil
|
||||
}
|
||||
func StopProcess(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -37,9 +51,10 @@ func StopAllHandler() error {
|
|||
defer processesMu.Unlock()
|
||||
|
||||
for name, cmd := range processes {
|
||||
if err := cmd.Cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
if err := KillByPid(cmd.Cmd.Process.Pid); err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %v", name, err)
|
||||
}
|
||||
processes[name].Running = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -49,15 +64,6 @@ func StopAll(w http.ResponseWriter, r *http.Request) {
|
|||
respondWithError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// processesMu.Lock()
|
||||
// defer processesMu.Unlock()
|
||||
|
||||
// for name, cmd := range processes {
|
||||
// if err := cmd.Cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
// respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s: %v", name, err))
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "All processes stopped.")
|
||||
|
|
@ -70,16 +76,19 @@ func ReStartProcess(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Stop the process first
|
||||
if cmd, ok := processes[name]; ok {
|
||||
if err := cmd.Cmd.Process.Kill(); err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s before restart: %v", name, err))
|
||||
return
|
||||
if err := KillByPid(cmd.Cmd.Process.Pid); 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)
|
||||
}
|
||||
//delete(processes, name)
|
||||
cmd.Running = false
|
||||
// processesMu.Lock()
|
||||
// defer processesMu.Unlock()
|
||||
processes[name].Running = false
|
||||
} else {
|
||||
respondWithError(w, http.StatusNotFound, fmt.Sprintf("Process %s not found to restart", name))
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
// Start the process again
|
||||
err := ExecuteScript(name)
|
||||
if err != nil {
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package progress
|
||||
package store
|
||||
|
||||
import (
|
||||
// 导入包
|
||||
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
Loading…
Reference in a new issue