add update
This commit is contained in:
parent
6f7313b770
commit
601ae5e340
33 changed files with 1041 additions and 291 deletions
1
CHANGELOG.md
Normal file
1
CHANGELOG.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
## 变更记录
|
||||
|
|
@ -133,6 +133,9 @@ English | [简体中文](README.md)
|
|||
- Support standard kanban, a must-have tool for project management, which can quickly create kanban and place it in different folders
|
||||
<img src="./docs/img/kanban.png" width="600" />
|
||||
|
||||
### 22、 Process management
|
||||
- Support process management, can view process list, kill processes
|
||||
|
||||
## Development
|
||||
### Build
|
||||
- Front end construction
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@
|
|||
<img src="./docs/img/setting-store.png" width="600" />
|
||||
|
||||
### 十五、应用商店
|
||||
- 应用商店管理,可添加外部应用。
|
||||
- 应用商店管理,丰富的外部接口,可导入/添加/下载外部应用。
|
||||
|
||||
### 十六、截图
|
||||
- 一个简单的截图工具。截图后文件存到本地。
|
||||
|
|
@ -148,6 +148,9 @@
|
|||
- 支持标准看板,项目管理必备工具,可快速创建看板并放置到不同的文件夹
|
||||
<img src="./docs/img/kanban.png" width="600" />
|
||||
|
||||
### 二十二、进程管理
|
||||
- 支持进程管理,可以查看进程列表,杀死进程
|
||||
|
||||
## 开发
|
||||
### 构建
|
||||
- 前端构建
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type InstallInfo struct {
|
|||
CheckProgress bool `json:"checkProgress"` // 标志位,表示是否显示启动和停止。
|
||||
HasRestart bool `json:"hasRestart"` // 标志位,表示安装后是否需要重启。
|
||||
Setting bool `json:"setting"` // 标志位,表示是否需要配置。
|
||||
Dependencies []Item `json:"dependencies"` // 依赖项。
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
9
frontend/components.d.ts
vendored
9
frontend/components.d.ts
vendored
|
|
@ -7,6 +7,7 @@ export {}
|
|||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Ad: typeof import('./src/components/desktop/Ad.vue')['default']
|
||||
AddApp: typeof import('./src/components/store/AddApp.vue')['default']
|
||||
AppIcon: typeof import('./src/components/taskbar/AppIcon.vue')['default']
|
||||
AppIconGroup: typeof import('./src/components/taskbar/AppIconGroup.vue')['default']
|
||||
|
|
@ -42,6 +43,8 @@ declare module 'vue' {
|
|||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
|
|
@ -51,6 +54,7 @@ declare module 'vue' {
|
|||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
|
|
@ -58,6 +62,8 @@ declare module 'vue' {
|
|||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSpace: typeof import('element-plus/es')['ElSpace']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElText: typeof import('element-plus/es')['ElText']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
Error: typeof import('./src/components/taskbar/Error.vue')['default']
|
||||
|
|
@ -92,6 +98,7 @@ declare module 'vue' {
|
|||
OsImage: typeof import('./src/components/builtin/OsImage.vue')['default']
|
||||
PdfViewer: typeof import('./src/components/builtin/PdfViewer.vue')['default']
|
||||
PictureStore: typeof import('./src/components/builtin/PictureStore.vue')['default']
|
||||
ProcessManager: typeof import('./src/components/setting/ProcessManager.vue')['default']
|
||||
QuickLink: typeof import('./src/components/computer/QuickLink.vue')['default']
|
||||
RectChosen: typeof import('./src/components/builtin/RectChosen.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
|
@ -106,11 +113,13 @@ declare module 'vue' {
|
|||
SetSystem: typeof import('./src/components/setting/SetSystem.vue')['default']
|
||||
Setting: typeof import('./src/components/setting/Setting.vue')['default']
|
||||
SetUpdate: typeof import('./src/components/setting/SetUpdate.vue')['default']
|
||||
Sponsors: typeof import('./src/components/desktop/Sponsors.vue')['default']
|
||||
StartMenu: typeof import('./src/components/taskbar/StartMenu.vue')['default']
|
||||
StartOption: typeof import('./src/components/taskbar/StartOption.vue')['default']
|
||||
StateIcon: typeof import('./src/components/taskbar/StateIcon.vue')['default']
|
||||
Store: typeof import('./src/components/store/Store.vue')['default']
|
||||
Taskbar: typeof import('./src/components/taskbar/Taskbar.vue')['default']
|
||||
Upgrade: typeof import('./src/components/desktop/Upgrade.vue')['default']
|
||||
UpPopover: typeof import('./src/components/computer/UpPopover.vue')['default']
|
||||
UrlBrowser: typeof import('./src/components/builtin/UrlBrowser.vue')['default']
|
||||
Version: typeof import('./src/components/builtin/Version.vue')['default']
|
||||
|
|
|
|||
|
|
@ -54,6 +54,12 @@
|
|||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">progress</div>
|
||||
<div class="code-name">&#xe625;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">zip</div>
|
||||
|
|
@ -696,9 +702,9 @@
|
|||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1722214929182') format('woff2'),
|
||||
url('iconfont.woff?t=1722214929182') format('woff'),
|
||||
url('iconfont.ttf?t=1722214929182') format('truetype');
|
||||
src: url('iconfont.woff2?t=1722391811007') format('woff2'),
|
||||
url('iconfont.woff?t=1722391811007') format('woff'),
|
||||
url('iconfont.ttf?t=1722391811007') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
|
|
@ -724,6 +730,15 @@
|
|||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-progress"></span>
|
||||
<div class="name">
|
||||
progress
|
||||
</div>
|
||||
<div class="code-name">.icon-progress
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-zip"></span>
|
||||
<div class="name">
|
||||
|
|
@ -1687,6 +1702,14 @@
|
|||
<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-progress"></use>
|
||||
</svg>
|
||||
<div class="name">progress</div>
|
||||
<div class="code-name">#icon-progress</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-zip"></use>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4412427 */
|
||||
src: url('iconfont.woff2?t=1722214929182') format('woff2'),
|
||||
url('iconfont.woff?t=1722214929182') format('woff'),
|
||||
url('iconfont.ttf?t=1722214929182') format('truetype');
|
||||
src: url('iconfont.woff2?t=1722391811007') format('woff2'),
|
||||
url('iconfont.woff?t=1722391811007') format('woff'),
|
||||
url('iconfont.ttf?t=1722391811007') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
|
@ -13,6 +13,10 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-progress:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.icon-zip:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -5,6 +5,13 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "39799840",
|
||||
"name": "progress",
|
||||
"font_class": "progress",
|
||||
"unicode": "e625",
|
||||
"unicode_decimal": 58917
|
||||
},
|
||||
{
|
||||
"icon_id": "30862364",
|
||||
"name": "zip",
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
108
frontend/src/components/desktop/Ad.vue
Normal file
108
frontend/src/components/desktop/Ad.vue
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div class="ad-container" v-show="state.ad.isShow" @click="onSponsorsClick">
|
||||
<el-carousel height="240px" indicator-position="none" :arrow="setCarouselShow" @change="onCarouselChange">
|
||||
<el-carousel-item v-for="(v, k) in upgradeStore.adList" :key="k">
|
||||
<img :src="v.url" v-if="v.url" class="ad-img" />
|
||||
<div class="ad-text" v-html="v.text"></div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div class="ad-close">
|
||||
<el-icon :size="20" @click.stop="onCloseSponsors">
|
||||
<CircleCloseFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutSponsors">
|
||||
import { reactive, computed, onMounted } from 'vue';
|
||||
import { useUpgradeStore } from '@/stores/upgrade';
|
||||
const upgradeStore = useUpgradeStore();
|
||||
|
||||
// 定义变量内容
|
||||
const state = reactive({
|
||||
ad: {
|
||||
isShow: false,
|
||||
index: 0,
|
||||
},
|
||||
});
|
||||
|
||||
// 设置轮播图箭头显示
|
||||
const setCarouselShow = computed(() => {
|
||||
return upgradeStore.adList.length <= 1 ? 'never' : 'hover';
|
||||
});
|
||||
// 关闭赞助商
|
||||
const onCloseSponsors = () => {
|
||||
state.ad.isShow = false;
|
||||
};
|
||||
// 轮播图改变时
|
||||
const onCarouselChange = (e: number) => {
|
||||
state.ad.index = e;
|
||||
};
|
||||
// 当前项内容点击
|
||||
const onSponsorsClick = () => {
|
||||
const link = upgradeStore.adList[state.ad.index]?.link
|
||||
if(link){
|
||||
window.open(link)
|
||||
}
|
||||
};
|
||||
// 延迟显示,防止影响其它界面加载
|
||||
const delayShow = () => {
|
||||
setTimeout(() => {
|
||||
state.ad.isShow = true;
|
||||
}, 3000);
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
delayShow();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ad-container {
|
||||
position: fixed;
|
||||
top: calc(50vh - 175px);
|
||||
left: calc(50vw - 175px);
|
||||
z-index: 3;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
.ad-img {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
}
|
||||
.ad-text {
|
||||
padding: 10px;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
.ad-close {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
right: -30px;
|
||||
top: -30px;
|
||||
:deep(i) {
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
bottom: 9px;
|
||||
color: #afafaf;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
&:hover {
|
||||
transition: all 0.3s ease;
|
||||
:deep(i) {
|
||||
color: #409EFF;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -17,6 +17,9 @@
|
|||
<NotifyGroup></NotifyGroup>
|
||||
<MessageCenterPop></MessageCenterPop>
|
||||
<Chosen></Chosen>
|
||||
<Sponsors v-if="upgradeStore.hasNotice"></Sponsors>
|
||||
<Upgrade v-if="upgradeStore.hasUpgrade"></Upgrade>
|
||||
<Ad v-if="upgradeStore.hasAd"></Ad>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<Taskbar></Taskbar>
|
||||
|
|
@ -31,10 +34,12 @@ import { useFileDrag } from "@/hook/useFileDrag";
|
|||
import { Rect, useRectChosen } from "@/hook/useRectChosen";
|
||||
import { useSystem } from "@/system";
|
||||
import { onErrorCaptured } from "vue";
|
||||
import { useUpgradeStore } from '@/stores/upgrade';
|
||||
|
||||
const { createDesktopContextMenu } = useContextMenu();
|
||||
const { choseStart, chosing, choseEnd, getRect, Chosen } = useRectChosen();
|
||||
const system = useSystem();
|
||||
const upgradeStore = useUpgradeStore();
|
||||
const { dragFileToDrop } = useFileDrag(system);
|
||||
|
||||
let chosenCallback: (rect: Rect) => void = () => {
|
||||
|
|
@ -87,6 +92,7 @@ function handleRightClick(e: MouseEvent) {
|
|||
onErrorCaptured((err) => {
|
||||
system.emitError(err.message.toString());
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.desktop {
|
||||
|
|
|
|||
114
frontend/src/components/desktop/Sponsors.vue
Normal file
114
frontend/src/components/desktop/Sponsors.vue
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div class="sponsors-container" v-show="state.sponsors.isShow" @click="onSponsorsClick">
|
||||
<el-carousel height="240px" indicator-position="none" :arrow="setCarouselShow" @change="onCarouselChange">
|
||||
<el-carousel-item v-for="(v, k) in upgradeStore.noticeList" :key="k">
|
||||
<img :src="v.url" v-if="v.url" class="sponsors-img" />
|
||||
<div class="sponsors-text" v-html="v.text"></div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div class="sponsors-close">
|
||||
<el-icon :size="20" @click.stop="onCloseSponsors">
|
||||
<CircleCloseFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutSponsors">
|
||||
import { reactive, computed, onMounted } from 'vue';
|
||||
import { useUpgradeStore } from '@/stores/upgrade';
|
||||
const upgradeStore = useUpgradeStore();
|
||||
|
||||
// 定义变量内容
|
||||
const state = reactive({
|
||||
sponsors: {
|
||||
// list: [
|
||||
// {
|
||||
// url: "",
|
||||
// text: "",
|
||||
// link: '',
|
||||
// },
|
||||
// ],
|
||||
isShow: false,
|
||||
index: 0,
|
||||
},
|
||||
});
|
||||
|
||||
// 设置轮播图箭头显示
|
||||
const setCarouselShow = computed(() => {
|
||||
return upgradeStore.noticeList.length <= 1 ? 'never' : 'hover';
|
||||
});
|
||||
// 关闭赞助商
|
||||
const onCloseSponsors = () => {
|
||||
state.sponsors.isShow = false;
|
||||
};
|
||||
// 轮播图改变时
|
||||
const onCarouselChange = (e: number) => {
|
||||
state.sponsors.index = e;
|
||||
};
|
||||
// 当前项内容点击
|
||||
const onSponsorsClick = () => {
|
||||
const link = upgradeStore.noticeList[state.sponsors.index]?.link
|
||||
if(link){
|
||||
window.open(link)
|
||||
}
|
||||
};
|
||||
// 延迟显示,防止影响其它界面加载
|
||||
const delayShow = () => {
|
||||
setTimeout(() => {
|
||||
state.sponsors.isShow = true;
|
||||
}, 3000);
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
delayShow();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sponsors-container {
|
||||
position: fixed;
|
||||
right: 6px;
|
||||
bottom: 48px;
|
||||
z-index: 3;
|
||||
width: 200px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
.sponsors-img {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
}
|
||||
.sponsors-text {
|
||||
padding: 10px;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
.sponsors-close {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
right: -30px;
|
||||
bottom: -30px;
|
||||
:deep(i) {
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
top: 9px;
|
||||
color: #afafaf;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
&:hover {
|
||||
transition: all 0.3s ease;
|
||||
:deep(i) {
|
||||
color: #409EFF;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
143
frontend/src/components/desktop/Upgrade.vue
Normal file
143
frontend/src/components/desktop/Upgrade.vue
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
<template>
|
||||
<div class="upgrade-dialog">
|
||||
<el-dialog
|
||||
v-model="state.isUpgrade"
|
||||
width="300px"
|
||||
destroy-on-close
|
||||
:show-close="false"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div class="upgrade-title">
|
||||
<div class="upgrade-title-warp">
|
||||
<span class="upgrade-title-warp-txt">{{ $t('upgrade.title') }}</span>
|
||||
<span class="upgrade-title-warp-version">v{{ upgradeStore.versionTag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upgrade-content">
|
||||
GodoOS {{ $t('upgrade.msg') }}
|
||||
<div class="mt5">
|
||||
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md" target="_black">
|
||||
CHANGELOG.md
|
||||
</el-link>
|
||||
</div>
|
||||
<div class="upgrade-content-desc mt5">{{ $t('upgrade.desc') }}</div>
|
||||
<div class="upgrade-content-desc" v-if="upgradeStore.progress > 0">
|
||||
<el-progress :text-inside="true" :stroke-width="20" :percentage="upgradeStore.progress" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="upgrade-btn">
|
||||
<el-button round size="default" type="info" text @click="onCancel">{{ $t('upgrade.btnOne') }}</el-button>
|
||||
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading">{{ state.btnTxt }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutUpgrade">
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { t } from '@/i18n';
|
||||
import { useUpgradeStore } from '@/stores/upgrade';
|
||||
const upgradeStore = useUpgradeStore();
|
||||
const state = reactive({
|
||||
isUpgrade: false,
|
||||
isLoading: false,
|
||||
btnTxt: '',
|
||||
});
|
||||
|
||||
|
||||
// 残忍拒绝
|
||||
const onCancel = () => {
|
||||
state.isUpgrade = false;
|
||||
};
|
||||
// 马上更新
|
||||
const onUpgrade = () => {
|
||||
state.isLoading = true;
|
||||
state.btnTxt = t('upgrade.btnTwoLoading');
|
||||
setTimeout(async () => {
|
||||
await upgradeStore.update();
|
||||
state.isLoading = false;
|
||||
}, 2000);
|
||||
};
|
||||
// 延迟显示,防止刷新时界面显示太快
|
||||
const delayShow = () => {
|
||||
setTimeout(() => {
|
||||
state.isUpgrade = true;
|
||||
}, 2000);
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
delayShow();
|
||||
setTimeout(() => {
|
||||
state.btnTxt = t('upgrade.btnTwo');
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.upgrade-dialog {
|
||||
:deep(.el-dialog) {
|
||||
padding: 0 !important;
|
||||
.el-dialog__body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
.el-dialog__header {
|
||||
display: none !important;
|
||||
}
|
||||
.upgrade-title {
|
||||
text-align: center;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: #2882dc;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-bottom-left-radius: 100%;
|
||||
border-bottom-right-radius: 100%;
|
||||
}
|
||||
.upgrade-title-warp {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
.upgrade-title-warp-txt {
|
||||
color: #ffffff;
|
||||
font-size: 22px;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
.upgrade-title-warp-version {
|
||||
color: #ffffff;
|
||||
background-color: #2882dc;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: -2px;
|
||||
right: -50px;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.upgrade-content {
|
||||
padding: 20px;
|
||||
line-height: 22px;
|
||||
.upgrade-content-desc {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.upgrade-btn {
|
||||
border-top: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 15px 20px;
|
||||
.el-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
58
frontend/src/components/setting/ProcessManager.vue
Normal file
58
frontend/src/components/setting/ProcessManager.vue
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, inject } from 'vue';
|
||||
import { getSystemKey } from "@/system/config";
|
||||
import {t} from "@/i18n";
|
||||
import { BrowserWindow } from '@/system/window/BrowserWindow';
|
||||
const win = inject<BrowserWindow>('browserWindow');
|
||||
const processList = ref([]);
|
||||
const apiUrl = getSystemKey("apiUrl");
|
||||
let timerId: ReturnType<typeof setTimeout>; // 用于保存定时器ID
|
||||
|
||||
onMounted(() => {
|
||||
fetchProcesses();
|
||||
});
|
||||
win?.on("close", () => {
|
||||
//console.log(timerId)
|
||||
clearTimeout(timerId);
|
||||
})
|
||||
const fetchProcesses = async () => {
|
||||
try {
|
||||
const response = await fetch(apiUrl + '/store/listport');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
processList.value = data.processes;
|
||||
timerId = setTimeout(() => {
|
||||
fetchProcesses()
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
console.error('Error fetching processes:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const killProcess = async (name: string) => {
|
||||
try {
|
||||
const response = await fetch(apiUrl + '/store/killport?name=' + name);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
fetchProcesses(); // 刷新列表
|
||||
} catch (error) {
|
||||
console.error('Error killing process:', error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<el-table :data="processList" stripe style="width: 96%;margin:auto;border:none" max-height="550">
|
||||
<el-table-column prop="port" :label="t('process.port')" sortable />
|
||||
<el-table-column prop="pid" :label="t('process.pid')" sortable />
|
||||
<el-table-column prop="name" :label="t('process.name')" />
|
||||
<el-table-column prop="proto" :label="t('process.proto')" />
|
||||
<el-table-column :label="t('process.action')">
|
||||
<template #default="{ row }">
|
||||
<el-button circle icon="Remove" @click="killProcess(row.name)" style="width: 35px;height: 35px;" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref, defineProps } from "vue";
|
||||
import { OpenDirDialog } from "@/util/goutil";
|
||||
import { getSystemKey, parseJson } from "@/system/config";
|
||||
const apiUrl = getSystemKey("apiUrl");
|
||||
const installed = getSystemKey("intstalledPlugins");
|
||||
import { notifySuccess, notifyError } from "@/util/msg";
|
||||
import { t } from "@/i18n";
|
||||
const props = defineProps({
|
||||
install: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const formData = ref({
|
||||
importType: 'download',
|
||||
url: '',
|
||||
|
|
@ -17,10 +22,10 @@ const addType = [
|
|||
'name': '远程下载',
|
||||
'type': 'download'
|
||||
},
|
||||
{
|
||||
'name': '本地导入',
|
||||
'type': 'local'
|
||||
},
|
||||
// {
|
||||
// 'name': '本地导入',
|
||||
// 'type': 'local'
|
||||
// },
|
||||
{
|
||||
'name': '开发模式',
|
||||
'type': 'dev'
|
||||
|
|
@ -63,6 +68,8 @@ async function addAppByDownload() {
|
|||
if (res.done) {
|
||||
notifySuccess(t("store.downloadSuccess"))
|
||||
progress.value = 0
|
||||
const pluginName = res.path.split("/").pop().split(".")[0]
|
||||
await installPlugin(pluginName)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +79,20 @@ async function addAppByImport() {
|
|||
|
||||
}
|
||||
async function addAppByDev() {
|
||||
|
||||
if(formData.value.devPath && formData.value.devPath != ""){
|
||||
await installPlugin(formData.value.devPath)
|
||||
}
|
||||
}
|
||||
async function installPlugin(pluginName: string) {
|
||||
const completion = await fetch(apiUrl + '/store/installInfo?name=' + pluginName)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.installError"))
|
||||
return
|
||||
}
|
||||
const item = await completion.json()
|
||||
//console.log(item)
|
||||
item.isOut = true
|
||||
await props.install(item.data)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,82 +1,38 @@
|
|||
<script setup lang="ts">
|
||||
import { inject, onMounted, ref } from "vue";
|
||||
import { inject, onMounted } from "vue";
|
||||
import { System } from "@/system";
|
||||
import { notifySuccess, notifyError } from "@/util/msg";
|
||||
import { useStoreStore } from "@/stores/store";
|
||||
import { setSystemKey,parseJson } from "@/system/config";
|
||||
import { t } from "@/i18n";
|
||||
import storeInitList from "@/assets/store.json";
|
||||
import { getSystemKey, setSystemKey, parseJson } from "@/system/config";
|
||||
const sys: any = inject<System>("system");
|
||||
const currentCateId = ref(0)
|
||||
const currentTitle = ref(t("store.hots"))
|
||||
const currentCate = ref('hots')
|
||||
const categoryList = ['hots', 'work', 'development', 'games', 'education', 'news', 'shopping', 'social', 'utilities', 'others', 'add']
|
||||
const categoryIcon = ['HomeFilled', 'Odometer', 'Postcard', 'TrendCharts', 'School', 'HelpFilled', 'ShoppingCart', 'ChatLineRound', 'MessageBox', 'Ticket', 'CirclePlusFilled']
|
||||
const isready = ref(false);
|
||||
const installed = getSystemKey("intstalledPlugins");
|
||||
const apiUrl = getSystemKey("apiUrl");
|
||||
const store = useStoreStore()
|
||||
|
||||
const installedList: any = ref(installed);
|
||||
const storeList: any = ref([])
|
||||
onMounted(async () => {
|
||||
await getList();
|
||||
|
||||
await store.getList();
|
||||
});
|
||||
async function getList() {
|
||||
if (currentCate.value == 'add') return;
|
||||
storeList.value = storeInitList
|
||||
|
||||
// const apiUrl = getSystemKey("apiUrl");
|
||||
// const storeUrl = apiUrl + '/store/storelist?cate=' + currentCate.value
|
||||
// const res = await fetch(storeUrl)
|
||||
// if (!res.ok) {
|
||||
// notifyError(t('store.getStoreListError'))
|
||||
// return
|
||||
// }
|
||||
// const data = await res.json()
|
||||
// //console.log(data)
|
||||
// storeList.value = data
|
||||
await checkProgress()
|
||||
isready.value = true;
|
||||
}
|
||||
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
|
||||
currentTitle.value = t("store." + item)
|
||||
await getList()
|
||||
}
|
||||
function setCache() {
|
||||
setSystemKey("intstalledPlugins", installedList.value);
|
||||
setSystemKey("intstalledPlugins", store.installedList);
|
||||
setTimeout(() => {
|
||||
sys.refershAppList();
|
||||
}, 1000);
|
||||
}
|
||||
async function install(item: any) {
|
||||
|
||||
if (item.isOut) {
|
||||
item.progress = 0
|
||||
const flag = store.addOutList(item)
|
||||
if(!flag){
|
||||
notifyError(t("store.installError"))
|
||||
}
|
||||
}
|
||||
if (item.needDownload && !item.isDev) {
|
||||
await download(item)
|
||||
}
|
||||
|
||||
if (item.needInstall) {
|
||||
item.progress = 0
|
||||
const completion = await fetch(apiUrl + '/store/install?name=' + item.name)
|
||||
const completion = await fetch(store.apiUrl + '/store/install?name=' + item.name)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.installError"))
|
||||
return
|
||||
|
|
@ -87,22 +43,10 @@ async function install(item: any) {
|
|||
return
|
||||
}
|
||||
if (res.data) {
|
||||
item.icon = res.data
|
||||
}
|
||||
}
|
||||
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
|
||||
item.icon = res.data.icon
|
||||
}
|
||||
}
|
||||
|
||||
if (item.webUrl) {
|
||||
sys.fs.writeFile(
|
||||
`${sys._options.userLocation}Desktop/${item.name}.url`,
|
||||
|
|
@ -110,18 +54,18 @@ async function install(item: any) {
|
|||
);
|
||||
}
|
||||
|
||||
notifySuccess(t("install.success"))
|
||||
installedList.value.push(item.name);
|
||||
await checkProgress()
|
||||
notifySuccess(t("store.installSuccess"))
|
||||
store.installedList.push(item.name);
|
||||
await store.checkProgress()
|
||||
setCache();
|
||||
}
|
||||
|
||||
async function uninstall(item: any) {
|
||||
if (item.needInstall) {
|
||||
item.progress = 0
|
||||
const completion = await fetch(apiUrl + '/store/uninstall?name=' + item.name)
|
||||
const completion = await fetch(store.apiUrl + '/store/uninstall?name=' + item.name)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.installError"))
|
||||
notifyError(t("store.hasSameName"))
|
||||
return
|
||||
}
|
||||
const res = await completion.json()
|
||||
|
|
@ -131,14 +75,14 @@ async function uninstall(item: any) {
|
|||
}
|
||||
}
|
||||
sys.fs.unlink(`${sys._options.userLocation}Desktop/${item.name}.url`);
|
||||
notifySuccess(t("uninstall.success"))
|
||||
delete installedList.value[installedList.value.indexOf(item.name)];
|
||||
notifySuccess(t("store.uninstallSuccess"))
|
||||
delete store.installedList[store.installedList.indexOf(item.name)];
|
||||
setCache();
|
||||
}
|
||||
async function download(item: any) {
|
||||
//console.log(item)
|
||||
if (item.progress) return;
|
||||
const completion = await fetch(apiUrl + '/store/download?url=' + item.url)
|
||||
const completion = await fetch(store.apiUrl + '/store/download?url=' + item.url)
|
||||
if (!completion.ok) {
|
||||
notifyError(t("store.downloadError"))
|
||||
}
|
||||
|
|
@ -156,7 +100,7 @@ async function download(item: any) {
|
|||
const json = await new TextDecoder().decode(value);
|
||||
//console.log(json)
|
||||
const res = parseJson(json)
|
||||
console.log(res)
|
||||
//console.log(res)
|
||||
if (res) {
|
||||
if (res.progress) {
|
||||
item.progress = res.progress
|
||||
|
|
@ -170,27 +114,27 @@ async function download(item: any) {
|
|||
}
|
||||
}
|
||||
async function pauseApp(item: any) {
|
||||
const res: any = await fetch(apiUrl + '/store/stop/' + item.name)
|
||||
const res: any = await fetch(store.apiUrl + '/store/stop/' + item.name)
|
||||
if (!res.ok) {
|
||||
const msg = await res.text()
|
||||
notifyError(msg)
|
||||
return
|
||||
}
|
||||
setTimeout(async () => {
|
||||
await checkProgress()
|
||||
await store.checkProgress()
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
async function restartApp(item: any) {
|
||||
await fetch(apiUrl + '/store/restart/' + item.name)
|
||||
await fetch(store.apiUrl + '/store/restart/' + item.name)
|
||||
setTimeout(async () => {
|
||||
await checkProgress()
|
||||
await store.checkProgress()
|
||||
}, 1000)
|
||||
}
|
||||
async function startApp(item: any) {
|
||||
await fetch(apiUrl + '/store/start/' + item.name)
|
||||
await fetch(store.apiUrl + '/store/start/' + item.name)
|
||||
setTimeout(async () => {
|
||||
await checkProgress()
|
||||
await store.checkProgress()
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
|
@ -198,27 +142,27 @@ async function startApp(item: any) {
|
|||
<div class="outer">
|
||||
<div class="main">
|
||||
<div class="left">
|
||||
<div class="left-icon" v-for="(item, key) in categoryList" @click="changeCate(key, item)">
|
||||
<div class="icon-derc" v-if="key == currentCateId"></div>
|
||||
<div class="left-icon" v-for="(item, key) in store.categoryList" @click="store.changeCate(key, item)">
|
||||
<div class="icon-derc" v-if="key == store.currentCateId"></div>
|
||||
<el-tooltip class="box-item" effect="dark" :content="t('store.' + item)" placement="right">
|
||||
<el-icon size="22">
|
||||
<component :is="categoryIcon[key]" />
|
||||
<component :is="store.categoryIcon[key]" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="store">
|
||||
<div v-if="isready" class="store-top">
|
||||
<div v-if="store.isready" class="store-top">
|
||||
<div class="right-main">
|
||||
<div class="main-title">
|
||||
<span class="sub-title">{{ currentTitle }} </span>
|
||||
<span class="sub-title">{{ store.currentTitle }} </span>
|
||||
</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"
|
||||
<div v-for="item in store.storeList" v-if="store.currentCate != 'add'" class="store-item" :key="item.name">
|
||||
<AppItem :item="item" :installed-list="store.installedList" :install="install" :uninstall="uninstall"
|
||||
:pause="pauseApp" :start="startApp" :restart="restartApp" />
|
||||
</div>
|
||||
<AddApp v-else />
|
||||
<AddApp v-else :install="install" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"hello": "hello",
|
||||
"startMenu.power": "Power",
|
||||
"setting": "Setting",
|
||||
"progressmanger": "Progress",
|
||||
"startMenu.shutdown": "Shutdown",
|
||||
"startMenu.recover": "Recover",
|
||||
"windows.setting": "Windows Setting",
|
||||
|
|
@ -146,5 +147,21 @@
|
|||
"others": "Others",
|
||||
"add": "Add",
|
||||
"errorList": "Cannot get the list of applications"
|
||||
},
|
||||
"process":{
|
||||
"title":"Process Manager",
|
||||
"port":"Port",
|
||||
"pid":"PID",
|
||||
"name":"Name",
|
||||
"proto":"Protocol",
|
||||
"action":"Actions"
|
||||
},
|
||||
"upgrade": {
|
||||
"title": "New version",
|
||||
"msg": "The new version is available, please update it now! Dont worry, the update is fast!",
|
||||
"desc": "Prompt: Update will restore the default configuration",
|
||||
"btnOne": "Cruel refusal",
|
||||
"btnTwo": "Update now",
|
||||
"btnTwoLoading": "Updating"
|
||||
}
|
||||
}
|
||||
|
|
@ -147,6 +147,29 @@
|
|||
"utilities": "实用工具",
|
||||
"others": "其他",
|
||||
"add":"添加应用",
|
||||
"errorList":"获取列表失败!"
|
||||
"errorList":"获取列表失败!",
|
||||
"urlEmpty":"网址为空",
|
||||
"hasSameName":"存在相同名称的应用!",
|
||||
"installError":"安装失败!",
|
||||
"installSuccess":"安装成功!",
|
||||
"uninstallSuccess":"卸载成功",
|
||||
"downloadError":"下载失败!",
|
||||
"cantStream":"暂不支持流下载!"
|
||||
},
|
||||
"process":{
|
||||
"title":"进程管理",
|
||||
"port":"端口",
|
||||
"pid":"PID",
|
||||
"name":"名称",
|
||||
"proto":"类型",
|
||||
"action":"操作"
|
||||
},
|
||||
"upgrade": {
|
||||
"title": "新版本升级",
|
||||
"msg": "新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!",
|
||||
"desc": "提示:更新会还原默认配置",
|
||||
"btnOne": "残忍拒绝",
|
||||
"btnTwo": "马上更新",
|
||||
"btnTwoLoading": "更新中"
|
||||
}
|
||||
}
|
||||
108
frontend/src/stores/store.ts
Normal file
108
frontend/src/stores/store.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from "vue";
|
||||
import { t } from "@/i18n";
|
||||
// import storeInitList from "@/assets/store.json";
|
||||
import { getSystemKey } from "@/system/config";
|
||||
export const useStoreStore = defineStore('storeStore', () => {
|
||||
const currentCateId = ref(0)
|
||||
const currentTitle = ref(t("store.hots"))
|
||||
const currentCate = ref('hots')
|
||||
const categoryList = ['hots', 'work', 'development', 'games', 'education', 'news', 'shopping', 'social', 'utilities', 'others', 'add']
|
||||
const categoryIcon = ['HomeFilled', 'Odometer', 'Postcard', 'TrendCharts', 'School', 'HelpFilled', 'ShoppingCart', 'ChatLineRound', 'MessageBox', 'Ticket', 'CirclePlusFilled']
|
||||
const isready = ref(false);
|
||||
const installed = getSystemKey("intstalledPlugins");
|
||||
const apiUrl = getSystemKey("apiUrl");
|
||||
const installedList: any = ref(installed);
|
||||
const storeList: any = ref([])
|
||||
const outList: any = ref([])
|
||||
async function getList() {
|
||||
if (currentCate.value == 'add') return;
|
||||
//storeList.value = storeInitList
|
||||
const storeUrl = apiUrl + '/store/storelist?cate=' + currentCate.value
|
||||
const res = await fetch(storeUrl)
|
||||
if (!res.ok) {
|
||||
return []
|
||||
}
|
||||
let list:any = await res.json()
|
||||
//console.log(data)
|
||||
if (outList.value.length > 0 && currentCate.value == 'hots') {
|
||||
const names = list.value.map((item : any) => item.name)
|
||||
const adds:any = []
|
||||
outList.value.forEach((item : any) => {
|
||||
if (!names.includes(item.name)) {
|
||||
adds.push(item)
|
||||
}
|
||||
})
|
||||
list = adds.concat(list)
|
||||
}
|
||||
storeList.value = list
|
||||
await checkProgress()
|
||||
isready.value = true;
|
||||
}
|
||||
async function addOutList(item:any) {
|
||||
const has = outList.value.find((i:any) => i.name === item.name)
|
||||
if(!has) {
|
||||
item.isOut = true
|
||||
outList.value.push(item)
|
||||
await getList()
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
async function changeCate(index: number, item: string) {
|
||||
currentCateId.value = index
|
||||
currentCate.value = item
|
||||
currentTitle.value = t("store." + item)
|
||||
await getList()
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
currentCateId,
|
||||
categoryIcon,
|
||||
currentTitle,
|
||||
currentCate,
|
||||
categoryList,
|
||||
isready,
|
||||
installedList,
|
||||
storeList,
|
||||
outList,
|
||||
apiUrl,
|
||||
changeCate,
|
||||
getList,
|
||||
addOutList,
|
||||
checkProgress
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
storage: localStorage,
|
||||
paths: [
|
||||
"outList"
|
||||
]
|
||||
}, // name 字段用localstorage存储
|
||||
],
|
||||
}
|
||||
})
|
||||
88
frontend/src/stores/upgrade.ts
Normal file
88
frontend/src/stores/upgrade.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { getSystemKey, setSystemKey, parseJson, getSystemConfig } from '@/system/config'
|
||||
import { RestartApp } from '@/util/goutil';
|
||||
import { ElMessage } from 'element-plus'
|
||||
export const useUpgradeStore = defineStore('upgradeStore', () => {
|
||||
const hasUpgrade = ref(false);
|
||||
const hasNotice = ref(false);
|
||||
const hasAd = ref(false);
|
||||
const updateUrl = ref('');
|
||||
const versionTag = ref(0)
|
||||
const currentVersion = ref(0)
|
||||
const progress = ref(0)
|
||||
const noticeList:any = ref([])
|
||||
const adList:any = ref([])
|
||||
async function checkUpdate() {
|
||||
const config = getSystemConfig();
|
||||
currentVersion.value = config.version;
|
||||
const releaseRes = await fetch(`${config.apiUrl}/system/updateInfo`)
|
||||
if (!releaseRes.ok) return;
|
||||
const releaseData = await releaseRes.json()
|
||||
versionTag.value = releaseData.version
|
||||
if(versionTag.value > config.version){
|
||||
hasUpgrade.value = true
|
||||
updateUrl.value = releaseData.url
|
||||
}
|
||||
if (releaseData.noticeList && releaseData.noticeList.length > 0) {
|
||||
hasNotice.value = true
|
||||
noticeList.value = releaseData.noticeList
|
||||
}
|
||||
if (!hasUpgrade.value && releaseData.adList && releaseData.adList.length > 0) {
|
||||
hasAd.value = true
|
||||
adList.value = releaseData.adList
|
||||
}
|
||||
}
|
||||
async function update() {
|
||||
const apiUrl = getSystemKey('apiUrl')
|
||||
const upUrl = `${apiUrl}/system/update?url=${updateUrl.value}`
|
||||
const upRes = await fetch(upUrl)
|
||||
if (!upRes.ok) return;
|
||||
const reader: any = upRes.body?.getReader();
|
||||
if (!reader) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: "the system has not stream!"
|
||||
})
|
||||
}
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
reader.releaseLock();
|
||||
break;
|
||||
}
|
||||
const rawjson = new TextDecoder().decode(value);
|
||||
const json = parseJson(rawjson);
|
||||
//console.log(json)
|
||||
if (json) {
|
||||
if (json.progress) {
|
||||
progress.value = json.progress
|
||||
}
|
||||
if (json.updateCompleted) {
|
||||
hasUpgrade.value = false
|
||||
progress.value = 0
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: "update completed!"
|
||||
})
|
||||
setSystemKey('version', versionTag.value)
|
||||
currentVersion.value = versionTag.value
|
||||
RestartApp()
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasUpgrade,
|
||||
hasNotice,
|
||||
hasAd,
|
||||
versionTag,
|
||||
updateUrl,
|
||||
noticeList,
|
||||
adList,
|
||||
progress,
|
||||
checkUpdate,
|
||||
update
|
||||
}
|
||||
})
|
||||
|
|
@ -57,6 +57,19 @@ export const appList = [
|
|||
isMagnet: true,
|
||||
isMenuList: true,
|
||||
},
|
||||
{
|
||||
name: 'process.title',
|
||||
appIcon: "progress",
|
||||
content: "ProcessManager",
|
||||
width: 800,
|
||||
height: 600,
|
||||
frame: true,
|
||||
center: true,
|
||||
resizable: false,
|
||||
isDeskTop: false,
|
||||
isMagnet: true,
|
||||
isMenuList: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "create.shortcut",
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { Tray, TrayOptions } from './menu/Tary';
|
|||
import { InitSystemFile, InitUserFile } from './core/SystemFileConfig';
|
||||
import { createInitFile } from './core/createInitFile';
|
||||
import { getSystemConfig, getSystemKey, setSystemKey, setSystemConfig, clearSystemConfig } from './config'
|
||||
import { checkUpdate } from '@/util/update';
|
||||
import { useUpgradeStore } from '@/stores/upgrade';
|
||||
import { RestartApp } from '@/util/goutil';
|
||||
|
||||
export type OsPlugin = (system: System) => void;
|
||||
|
|
@ -115,7 +115,8 @@ export class System {
|
|||
if (this._rootState.magnet?.length < 1) {
|
||||
this.refershAppList()
|
||||
}
|
||||
checkUpdate();
|
||||
const upgradeStore = useUpgradeStore();
|
||||
upgradeStore.checkUpdate()
|
||||
setTimeout(() => {
|
||||
if (this._rootState.magnet?.length < 1) {
|
||||
this.recover()
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import { getSystemConfig,setSystemKey,parseJson } from '@/system/config'
|
||||
import { RestartApp } from './goutil';
|
||||
import { Dialog } from '@/system';
|
||||
import { ElMessage } from 'element-plus'
|
||||
export async function checkUpdate() {
|
||||
const config = getSystemConfig();
|
||||
const updateGiteeUrl = `${config.apiUrl}/system/updateInfo`
|
||||
const releaseRes = await fetch(updateGiteeUrl)
|
||||
if (!releaseRes.ok) return;
|
||||
const releaseData = await releaseRes.json()
|
||||
const versionTag = releaseData.version;
|
||||
if (!versionTag) return;
|
||||
if (versionTag <= config.version) return;
|
||||
const updateUrl = releaseData.url
|
||||
if (!updateUrl || updateUrl == '') return;
|
||||
const dialogRes: any = await Dialog.showMessageBox({
|
||||
title: '更新提示',
|
||||
message: `发现新版本:${versionTag},是否更新?`
|
||||
})
|
||||
//console.log(dialogRes)
|
||||
if (dialogRes.response !== -1) {
|
||||
return;
|
||||
}
|
||||
const { setProgress,dialogwin } = Dialog.showProcessDialog({
|
||||
message: '正在更新',
|
||||
});
|
||||
const upUrl = `${config.apiUrl}/system/update?url=${updateUrl}`
|
||||
const upRes = await fetch(upUrl)
|
||||
if (!upRes.ok) return;
|
||||
const reader: any = upRes.body?.getReader();
|
||||
if (!reader) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: "the system has not stream!"
|
||||
})
|
||||
}
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
reader.releaseLock();
|
||||
break;
|
||||
}
|
||||
const rawjson = new TextDecoder().decode(value);
|
||||
const json = parseJson(rawjson);
|
||||
console.log(json)
|
||||
if(json){
|
||||
if(json.progress){
|
||||
setProgress(json.progress)
|
||||
}
|
||||
if(json.updateCompleted){
|
||||
dialogwin.close()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: "update completed!"
|
||||
})
|
||||
setSystemKey('version',versionTag)
|
||||
RestartApp()
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,14 +39,16 @@ func OsStart() {
|
|||
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("/listport", store.ListAllProcessesHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/killport", store.KillProcessByNameHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/storelist", store.GetStoreListHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/download", store.DownloadHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/install", store.InstallHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/installInfo", store.GetInstallInfoHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/installOut", store.RunOutHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/uninstall", store.UnInstallHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/setting", store.StoreSettingHandler).Methods(http.MethodPost)
|
||||
progressRouter.HandleFunc("/upload", store.UploadHandler).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ type APIResponse struct {
|
|||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func writeJSONResponse(w http.ResponseWriter, res APIResponse, status int) {
|
||||
func WriteJSONResponse(w http.ResponseWriter, res APIResponse, status int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(res)
|
||||
|
|
@ -20,11 +20,11 @@ func writeJSONResponse(w http.ResponseWriter, res APIResponse, status int) {
|
|||
|
||||
// HTTPError 返回带有JSON错误消息的HTTP错误
|
||||
func HTTPError(w http.ResponseWriter, status int, message string) {
|
||||
writeJSONResponse(w, APIResponse{Message: message, Code: -1}, status)
|
||||
WriteJSONResponse(w, APIResponse{Message: message, Code: -1}, status)
|
||||
}
|
||||
func ErrorMsg(w http.ResponseWriter, message string) {
|
||||
writeJSONResponse(w, APIResponse{Message: message, Code: -1}, 200)
|
||||
WriteJSONResponse(w, APIResponse{Message: message, Code: -1}, 200)
|
||||
}
|
||||
func SuccessMsg(w http.ResponseWriter, data any, message string) {
|
||||
writeJSONResponse(w, APIResponse{Message: message, Data: data, Code: 0}, 200)
|
||||
WriteJSONResponse(w, APIResponse{Message: message, Data: data, Code: 0}, 200)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,21 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
libs.ErrorMsg(w, "the install.json is error:"+err.Error())
|
||||
return
|
||||
}
|
||||
if len(installInfo.Dependencies) > 0 {
|
||||
var needInstalls []Item
|
||||
for _, item := range installInfo.Dependencies {
|
||||
info, err := GetInstallInfo(item.Value.(string))
|
||||
if err != nil {
|
||||
needInstalls = append(needInstalls, item)
|
||||
continue
|
||||
}
|
||||
if info.Version != installInfo.Version {
|
||||
needInstalls = append(needInstalls, item)
|
||||
}
|
||||
}
|
||||
libs.WriteJSONResponse(w, libs.APIResponse{Message: "you need install apps!", Data: needInstalls, Code: -1}, 200)
|
||||
return
|
||||
}
|
||||
if pluginName != installInfo.Name {
|
||||
libs.ErrorMsg(w, "the app name must equal the install.json!")
|
||||
return
|
||||
|
|
@ -78,7 +93,6 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
libs.ErrorMsg(w, "install the app is error!")
|
||||
return
|
||||
}
|
||||
var res string
|
||||
//复制static目录
|
||||
staticPath := filepath.Join(exePath, "static")
|
||||
if libs.PathExists(staticPath) {
|
||||
|
|
@ -91,12 +105,12 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
iconPath := filepath.Join(targetPath, storeInfo.Icon)
|
||||
if libs.PathExists(iconPath) {
|
||||
res = "http://localhost:56780/static/" + pluginName + "/" + storeInfo.Icon
|
||||
installInfo.Icon = "http://localhost:56780/static/" + pluginName + "/" + storeInfo.Icon
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
libs.SuccessMsg(w, res, "install the app success!")
|
||||
libs.SuccessMsg(w, installInfo, "install the app success!")
|
||||
}
|
||||
|
||||
func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,34 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PortRangeResponse struct {
|
||||
Start int `json:"start"`
|
||||
End int `json:"end"`
|
||||
EnabledPorts []int `json:"enabled_ports"`
|
||||
type ProcessSystemInfo struct {
|
||||
PID int `json:"pid"`
|
||||
Port int `json:"port"`
|
||||
Proto string `json:"proto"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func getProcessIdsOnPort(port int) ([]string, error) {
|
||||
type AllProcessesResponse struct {
|
||||
Processes []ProcessSystemInfo `json:"processes"`
|
||||
}
|
||||
|
||||
var processInfoRegex = regexp.MustCompile(`(\d+)\s+.*:\s*(\d+)\s+.*LISTEN\s+.*:(\d+)`)
|
||||
|
||||
func listAllProcesses() ([]ProcessSystemInfo, error) {
|
||||
osType := runtime.GOOS
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
|
@ -27,55 +37,115 @@ func getProcessIdsOnPort(port int) ([]string, error) {
|
|||
|
||||
switch osType {
|
||||
case "darwin", "linux":
|
||||
cmd = exec.Command("lsof", "-ti", fmt.Sprintf("tcp:%d", port))
|
||||
cmd = exec.Command("lsof", "-i", "-n", "-P")
|
||||
case "windows":
|
||||
cmd = exec.Command("powershell", "-Command", "Get-Process | Where-Object {$_.Id -eq "+strconv.Itoa(port)+"} | Select-Object -ExpandProperty Id")
|
||||
cmd = exec.Command("netstat", "-ano")
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported operating system")
|
||||
}
|
||||
|
||||
output, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
// 如果lsof或powershell命令找不到任何进程,它会返回非零退出代码,这是正常情况
|
||||
if exitError.ExitCode() != 1 {
|
||||
return nil, fmt.Errorf("failed to list processes on port %d: %v", port, err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to list processes on port %d: %v", port, err)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to list all processes: %v", err)
|
||||
}
|
||||
|
||||
pids := strings.Fields(strings.TrimSpace(string(output)))
|
||||
return pids, nil
|
||||
}
|
||||
func listEnabledPorts(portRangeStart, portRangeEnd int) ([]int, error) {
|
||||
var usedPorts []int
|
||||
var wg sync.WaitGroup
|
||||
processes := make([]ProcessSystemInfo, 0)
|
||||
|
||||
for i := portRangeStart; i <= portRangeEnd; i++ {
|
||||
currentPort := i // 创建一个新的变量来绑定当前的i值
|
||||
wg.Add(1)
|
||||
go func() { // 注意这里不再直接传入port,而是使用currentPort
|
||||
defer wg.Done()
|
||||
// 解析输出
|
||||
switch osType {
|
||||
case "darwin", "linux":
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(output)) // 使用bufio.Scanner
|
||||
|
||||
pids, err := getProcessIdsOnPort(currentPort)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
matches := processInfoRegex.FindStringSubmatch(line)
|
||||
if matches != nil {
|
||||
pid, _ := strconv.Atoi(matches[1])
|
||||
port, _ := strconv.Atoi(matches[3])
|
||||
processName, err := getProcessName(osType, pid)
|
||||
if err != nil {
|
||||
log.Printf("Error checking port %d: %v", currentPort, err)
|
||||
log.Printf("Failed to get process name for PID %d: %v", pid, err)
|
||||
continue
|
||||
}
|
||||
processes = append(processes, ProcessSystemInfo{
|
||||
PID: pid,
|
||||
Port: port,
|
||||
Proto: matches[2],
|
||||
Name: processName,
|
||||
})
|
||||
}
|
||||
}
|
||||
case "windows":
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(output))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// 需要针对Windows的netstat输出格式进行解析
|
||||
// 示例:TCP 0.0.0.0:80 0.0.0.0:* LISTENING 1234
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 4 && fields[3] == "LISTENING" {
|
||||
_, port, err := net.SplitHostPort(fields[1])
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse port: %v", err)
|
||||
continue
|
||||
}
|
||||
pid, _ := strconv.Atoi(fields[4])
|
||||
processName, err := getProcessName(osType, pid)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get process name for PID %d: %v", pid, err)
|
||||
continue
|
||||
}
|
||||
portInt, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
log.Printf("Failed to convert port to integer: %v", err)
|
||||
continue
|
||||
}
|
||||
processes = append(processes, ProcessSystemInfo{
|
||||
PID: pid,
|
||||
Port: portInt,
|
||||
Proto: fields[0],
|
||||
Name: processName,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(pids) > 0 {
|
||||
usedPorts = append(usedPorts, currentPort)
|
||||
}
|
||||
}()
|
||||
return processes, nil
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
func getProcessName(osType string, pid int) (string, error) {
|
||||
var cmd *exec.Cmd
|
||||
var output []byte
|
||||
var err error
|
||||
|
||||
return usedPorts, nil
|
||||
switch osType {
|
||||
case "darwin", "linux":
|
||||
cmd = exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "comm=")
|
||||
case "windows":
|
||||
cmd = exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid), "/NH")
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported operating system")
|
||||
}
|
||||
|
||||
func killProcess(pid int) error {
|
||||
output, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get process name: %v", err)
|
||||
}
|
||||
// log.Printf("output: %s", output)
|
||||
switch osType {
|
||||
case "darwin", "linux":
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
case "windows":
|
||||
parts := strings.Fields(string(output))
|
||||
if len(parts) >= 1 {
|
||||
return parts[0], nil
|
||||
}
|
||||
return "", fmt.Errorf("no process name found in output")
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unknown error getting process name")
|
||||
}
|
||||
|
||||
func killProcessByName(name string) error {
|
||||
osType := runtime.GOOS
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
|
@ -83,90 +153,42 @@ func killProcess(pid int) error {
|
|||
|
||||
switch osType {
|
||||
case "darwin", "linux":
|
||||
cmd = exec.Command("kill", "-9", strconv.Itoa(pid))
|
||||
cmd = exec.Command("pkill", name)
|
||||
case "windows":
|
||||
cmd = exec.Command("taskkill", "/F", "/PID", strconv.Itoa(pid)) // /F 表示强制结束
|
||||
cmd = exec.Command("taskkill", "/IM", name, "/F") // /F 表示强制结束
|
||||
default:
|
||||
return fmt.Errorf("unsupported operating system")
|
||||
}
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Printf("Failed to kill process with PID %d: %v", pid, err)
|
||||
log.Printf("Failed to kill process with name %s: %v", name, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func killAllProcessesOnPort(port int, w http.ResponseWriter) {
|
||||
pids, err := getProcessIdsOnPort(port)
|
||||
func KillProcessByNameHandler(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.URL.Query().Get("name")
|
||||
if err := killProcessByName(name); err != nil {
|
||||
http.Error(w, fmt.Sprintf("Failed to kill process: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Process '%s' has been killed", name)
|
||||
}
|
||||
|
||||
func ListAllProcessesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
processes, err := listAllProcesses()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to list processes", http.StatusInternalServerError)
|
||||
http.Error(w, "Failed to list all processes", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for _, pidStr := range pids {
|
||||
if pidStr == "" {
|
||||
continue
|
||||
response := AllProcessesResponse{
|
||||
Processes: processes,
|
||||
}
|
||||
|
||||
pidInt, err := strconv.Atoi(pidStr)
|
||||
if err != nil {
|
||||
log.Printf("Failed to convert PID to integer: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := killProcess(pidInt); err != nil {
|
||||
log.Printf("Failed to kill process with PID %d: %v", pidInt, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "All processes on port %d have been killed", port)
|
||||
}
|
||||
func KillPortHandler(w http.ResponseWriter, r *http.Request) {
|
||||
portStr := r.URL.Query().Get("port")
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid port number", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
killAllProcessesOnPort(port, w)
|
||||
}
|
||||
func ListPortsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
startStr := r.URL.Query().Get("start")
|
||||
endStr := r.URL.Query().Get("end")
|
||||
|
||||
// 设置默认值
|
||||
start := 56711
|
||||
end := 56730
|
||||
|
||||
// 如果参数存在,则尝试转换为整数,否则使用默认值
|
||||
if startStr != "" {
|
||||
start, _ = strconv.Atoi(startStr)
|
||||
}
|
||||
|
||||
if endStr != "" {
|
||||
end, _ = strconv.Atoi(endStr)
|
||||
}
|
||||
|
||||
ports, err := listEnabledPorts(start, end)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to list ports", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 构造JSON响应结构体
|
||||
response := PortRangeResponse{
|
||||
Start: start,
|
||||
End: end,
|
||||
EnabledPorts: ports,
|
||||
}
|
||||
|
||||
// 设置响应内容类型为JSON
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// 编码并写入响应体
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
http.Error(w, "Failed to encode response as JSON", http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -42,6 +42,19 @@ func GetStoreListHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
}
|
||||
}
|
||||
func GetInstallInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.URL.Query().Get("name")
|
||||
if name == "" {
|
||||
libs.ErrorMsg(w, "name is required")
|
||||
return
|
||||
}
|
||||
info, err := GetInstallInfo(name)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, err.Error())
|
||||
return
|
||||
}
|
||||
libs.SuccessMsg(w, info, "")
|
||||
}
|
||||
func StoreSettingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var req map[string]any
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ type InstallInfo struct {
|
|||
CheckProgress bool `json:"checkProgress"` // 标志位,表示是否显示启动和停止。
|
||||
HasRestart bool `json:"hasRestart"` // 标志位,表示安装后是否需要重启。
|
||||
Setting bool `json:"setting"` // 标志位,表示是否需要配置。
|
||||
Dependencies []Item `json:"dependencies"` // 依赖项。
|
||||
}
|
||||
|
||||
// StoreInfo 维护了应用程序商店的信息。
|
||||
|
|
|
|||
66
godo/store/upload.go
Normal file
66
godo/store/upload.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"godo/files"
|
||||
"godo/libs"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UploadHandler 处理上传的HTTP请求
|
||||
func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 解析上传的文件
|
||||
err := r.ParseMultipartForm(10000 << 20) // 限制最大上传大小为1000MB
|
||||
if err != nil {
|
||||
http.Error(w, "上传文件过大"+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := r.FormFile("files") // 表单字段名为"files"
|
||||
if err != nil {
|
||||
http.Error(w, "没有找到文件", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 读取文件内容
|
||||
fileBytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
log.Printf("读取文件内容出错: %v", err)
|
||||
http.Error(w, "读取文件内容出错", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cachePath := libs.GetCacheDir()
|
||||
baseName := filepath.Base(header.Filename)
|
||||
filenameNoExt := strings.TrimSuffix(baseName, filepath.Ext(baseName))
|
||||
|
||||
savePath := filepath.Join(cachePath, baseName)
|
||||
|
||||
out, err := os.Create(savePath)
|
||||
if err != nil {
|
||||
log.Printf("创建文件出错: %v", err)
|
||||
http.Error(w, "保存文件出错", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// 将文件内容写入到服务器上的文件
|
||||
_, err = out.Write(fileBytes)
|
||||
if err != nil {
|
||||
log.Printf("写入文件出错: %v", err)
|
||||
http.Error(w, "写入文件出错", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
runDir := libs.GetRunDir()
|
||||
err = files.HandlerFile(savePath, runDir)
|
||||
if err != nil {
|
||||
log.Printf("Error moving file: %v", err)
|
||||
}
|
||||
|
||||
libs.SuccessMsg(w, filenameNoExt, "File already exists and is of correct size")
|
||||
}
|
||||
Loading…
Reference in a new issue