Browse Source

add update

master
godo 10 months ago
parent
commit
601ae5e340
  1. 1
      CHANGELOG.md
  2. 3
      README.en.md
  3. 5
      README.md
  4. 1
      docs/Store.md
  5. 9
      frontend/components.d.ts
  6. 29
      frontend/public/font/demo_index.html
  7. 10
      frontend/public/font/iconfont.css
  8. 2
      frontend/public/font/iconfont.js
  9. 7
      frontend/public/font/iconfont.json
  10. BIN
      frontend/public/font/iconfont.ttf
  11. BIN
      frontend/public/font/iconfont.woff
  12. BIN
      frontend/public/font/iconfont.woff2
  13. 108
      frontend/src/components/desktop/Ad.vue
  14. 6
      frontend/src/components/desktop/Desktop.vue
  15. 114
      frontend/src/components/desktop/Sponsors.vue
  16. 143
      frontend/src/components/desktop/Upgrade.vue
  17. 58
      frontend/src/components/setting/ProcessManager.vue
  18. 34
      frontend/src/components/store/AddApp.vue
  19. 138
      frontend/src/components/store/Store.vue
  20. 29
      frontend/src/i18n/lang/en.json
  21. 27
      frontend/src/i18n/lang/zh.json
  22. 108
      frontend/src/stores/store.ts
  23. 88
      frontend/src/stores/upgrade.ts
  24. 13
      frontend/src/system/applist.ts
  25. 5
      frontend/src/system/index.ts
  26. 62
      frontend/src/util/update.ts
  27. 6
      godo/cmd/main.go
  28. 8
      godo/libs/msg.go
  29. 20
      godo/store/install.go
  30. 218
      godo/store/port.go
  31. 13
      godo/store/store.go
  32. 1
      godo/store/types.go
  33. 66
      godo/store/upload.go

1
CHANGELOG.md

@ -0,0 +1 @@
## 变更记录

3
README.en.md

@ -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

5
README.md

@ -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" />
### 二十二、进程管理
- 支持进程管理,可以查看进程列表,杀死进程
## 开发
### 构建
- 前端构建

1
docs/Store.md

@ -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

@ -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']

29
frontend/public/font/demo_index.html

@ -54,6 +54,12 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe625;</span>
<div class="name">progress</div>
<div class="code-name">&amp;#xe625;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe623;</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>

10
frontend/public/font/iconfont.css

@ -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";
}

2
frontend/public/font/iconfont.js

File diff suppressed because one or more lines are too long

7
frontend/public/font/iconfont.json

@ -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",

BIN
frontend/public/font/iconfont.ttf

Binary file not shown.

BIN
frontend/public/font/iconfont.woff

Binary file not shown.

BIN
frontend/public/font/iconfont.woff2

Binary file not shown.

108
frontend/src/components/desktop/Ad.vue

@ -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>

6
frontend/src/components/desktop/Desktop.vue

@ -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

@ -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

@ -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

@ -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>

34
frontend/src/components/store/AddApp.vue

@ -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>

138
frontend/src/components/store/Store.vue

@ -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
@ -86,23 +42,11 @@ async function install(item: any) {
notifyError(res.message)
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
if (res.data) {
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>

29
frontend/src/i18n/lang/en.json

@ -2,6 +2,7 @@
"hello": "hello",
"startMenu.power": "Power",
"setting": "Setting",
"progressmanger": "Progress",
"startMenu.shutdown": "Shutdown",
"startMenu.recover": "Recover",
"windows.setting": "Windows Setting",
@ -133,18 +134,34 @@
"cannot.create.shortcut": "Cannot Create Shortcut",
"shortcut.has.been.created": "Shortcut Has Been Created",
"system.message": "System Message",
"store" : {
"hots":"Popular",
"store": {
"hots": "Popular",
"work": "Work",
"development": "Development",
"games": "Games",
"education": "Education",
"news": "News",
"shopping":"Shopping",
"shopping": "Shopping",
"social": "Social Networking",
"utilities": "Utilities",
"others": "Others",
"add":"Add",
"errorList":"Cannot get the list of applications"
}
"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"
}
}

27
frontend/src/i18n/lang/zh.json

@ -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

@ -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

@ -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
}
})

13
frontend/src/system/applist.ts

@ -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",

5
frontend/src/system/index.ts

@ -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()

62
frontend/src/util/update.ts

@ -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;
}
}
}
}

6
godo/cmd/main.go

@ -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)

8
godo/libs/msg.go

@ -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)
}

20
godo/store/install.go

@ -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) {

218
godo/store/port.go

@ -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)
return nil, fmt.Errorf("failed to list all processes: %v", err)
}
processes := make([]ProcessSystemInfo, 0)
// 解析输出
switch osType {
case "darwin", "linux":
scanner := bufio.NewScanner(bytes.NewBuffer(output)) // 使用bufio.Scanner
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("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,
})
}
} else {
return nil, fmt.Errorf("failed to list processes on port %d: %v", port, err)
}
}
pids := strings.Fields(strings.TrimSpace(string(output)))
return pids, nil
return processes, nil
}
func listEnabledPorts(portRangeStart, portRangeEnd int) ([]int, error) {
var usedPorts []int
var wg sync.WaitGroup
for i := portRangeStart; i <= portRangeEnd; i++ {
currentPort := i // 创建一个新的变量来绑定当前的i值
wg.Add(1)
go func() { // 注意这里不再直接传入port,而是使用currentPort
defer wg.Done()
pids, err := getProcessIdsOnPort(currentPort)
if err != nil {
log.Printf("Error checking port %d: %v", currentPort, err)
}
if len(pids) > 0 {
usedPorts = append(usedPorts, currentPort)
}
}()
func getProcessName(osType string, pid int) (string, error) {
var cmd *exec.Cmd
var output []byte
var err error
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")
}
wg.Wait()
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 usedPorts, nil
return "", fmt.Errorf("unknown error getting process name")
}
func killProcess(pid int) error {
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)
if err != nil {
http.Error(w, "Failed to list processes", http.StatusInternalServerError)
return
}
for _, pidStr := range pids {
if pidStr == "" {
continue
}
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)
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
}
killAllProcessesOnPort(port, w)
fmt.Fprintf(w, "Process '%s' has been killed", name)
}
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)
func ListAllProcessesHandler(w http.ResponseWriter, r *http.Request) {
processes, err := listAllProcesses()
if err != nil {
http.Error(w, "Failed to list ports", http.StatusInternalServerError)
http.Error(w, "Failed to list all processes", http.StatusInternalServerError)
return
}
// 构造JSON响应结构体
response := PortRangeResponse{
Start: start,
End: end,
EnabledPorts: ports,
response := AllProcessesResponse{
Processes: processes,
}
// 设置响应内容类型为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

13
godo/store/store.go

@ -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)

1
godo/store/types.go

@ -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

@ -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…
Cancel
Save