mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 07:57:21 +08:00
feat: docker 管理器
This commit is contained in:
19
internal/apps/docker/init.go
Normal file
19
internal/apps/docker/init.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package podman
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/apploader"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
apploader.Register(&types.App{
|
||||
Slug: "docker",
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService()
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.UpdateConfig)
|
||||
},
|
||||
})
|
||||
}
|
||||
5
internal/apps/docker/request.go
Normal file
5
internal/apps/docker/request.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package podman
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
45
internal/apps/docker/service.go
Normal file
45
internal/apps/docker/service.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package podman
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type Service struct{}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read("/etc/docker/daemon.json")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/docker/daemon.json", req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("docker"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
_ "github.com/TheTNB/panel/internal/apps/benchmark"
|
||||
_ "github.com/TheTNB/panel/internal/apps/docker"
|
||||
_ "github.com/TheTNB/panel/internal/apps/fail2ban"
|
||||
_ "github.com/TheTNB/panel/internal/apps/frp"
|
||||
_ "github.com/TheTNB/panel/internal/apps/gitea"
|
||||
|
||||
4
web/src/api/apps/docker/index.ts
Normal file
4
web/src/api/apps/docker/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { http } from '@/utils'
|
||||
|
||||
export const getConfig = () => http.Get('/apps/docker/config')
|
||||
export const updateConfig = (config: string) => http.Post('/apps/docker/config', { config })
|
||||
150
web/src/views/apps/docker/IndexView.vue
Normal file
150
web/src/views/apps/docker/IndexView.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'apps-docker-index'
|
||||
})
|
||||
|
||||
import Editor from '@guolao/vue-monaco-editor'
|
||||
import { NButton, NPopconfirm } from 'naive-ui'
|
||||
|
||||
import { getConfig, updateConfig } from '@/api/apps/docker'
|
||||
import systemctl from '@/api/panel/systemctl'
|
||||
|
||||
const currentTab = ref('status')
|
||||
const status = ref(false)
|
||||
const isEnabled = ref(false)
|
||||
|
||||
const { data: config }: { data: any } = useRequest(getConfig, {
|
||||
initialData: {
|
||||
config: ''
|
||||
}
|
||||
})
|
||||
|
||||
const statusStr = computed(() => {
|
||||
return status.value ? '正常运行中' : '已停止运行'
|
||||
})
|
||||
|
||||
const getStatus = async () => {
|
||||
await systemctl.status('docker').then((res: any) => {
|
||||
status.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const getIsEnabled = async () => {
|
||||
await systemctl.isEnabled('docker').then((res: any) => {
|
||||
isEnabled.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const handleSaveConfig = async () => {
|
||||
useRequest(() => updateConfig(config.value)).onSuccess(() => {
|
||||
window.$message.success('保存成功')
|
||||
})
|
||||
}
|
||||
|
||||
const handleStart = async () => {
|
||||
await systemctl.start('docker')
|
||||
window.$message.success('启动成功')
|
||||
await getStatus()
|
||||
}
|
||||
|
||||
const handleStop = async () => {
|
||||
await systemctl.stop('docker')
|
||||
window.$message.success('停止成功')
|
||||
await getStatus()
|
||||
}
|
||||
|
||||
const handleRestart = async () => {
|
||||
await systemctl.restart('docker')
|
||||
window.$message.success('重启成功')
|
||||
await getStatus()
|
||||
}
|
||||
|
||||
const handleIsEnabled = async () => {
|
||||
if (isEnabled.value) {
|
||||
await systemctl.enable('docker')
|
||||
window.$message.success('开启自启动成功')
|
||||
} else {
|
||||
await systemctl.disable('docker')
|
||||
window.$message.success('禁用自启动成功')
|
||||
}
|
||||
await getIsEnabled()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getStatus()
|
||||
getIsEnabled()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<common-page show-footer>
|
||||
<template #action>
|
||||
<n-button
|
||||
v-if="currentTab == 'config'"
|
||||
class="ml-16"
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
</template>
|
||||
<n-tabs v-model:value="currentTab" type="line" animated>
|
||||
<n-tab-pane name="status" tab="运行状态">
|
||||
<n-flex vertical>
|
||||
<n-card title="运行状态" rounded-10>
|
||||
<template #header-extra>
|
||||
<n-switch v-model:value="isEnabled" @update:value="handleIsEnabled">
|
||||
<template #checked> 自启动开 </template>
|
||||
<template #unchecked> 自启动关 </template>
|
||||
</n-switch>
|
||||
</template>
|
||||
<n-space vertical>
|
||||
<n-alert :type="status ? 'success' : 'error'">
|
||||
{{ statusStr }}
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
确定要停止 Docker 吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-flex>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="config" tab="配置">
|
||||
<n-space vertical>
|
||||
<n-alert type="warning">
|
||||
此处修改的是 Docker 配置文件(/etc/docker/daemon.json)
|
||||
</n-alert>
|
||||
<Editor
|
||||
v-model:value="config"
|
||||
language="ini"
|
||||
theme="vs-dark"
|
||||
height="60vh"
|
||||
mt-8
|
||||
:options="{
|
||||
automaticLayout: true,
|
||||
formatOnType: true,
|
||||
formatOnPaste: true
|
||||
}"
|
||||
/>
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
</template>
|
||||
23
web/src/views/apps/docker/route.ts
Normal file
23
web/src/views/apps/docker/route.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { RouteType } from '~/types/router'
|
||||
|
||||
const Layout = () => import('@/layout/IndexView.vue')
|
||||
|
||||
export default {
|
||||
name: 'docker',
|
||||
path: '/apps/docker',
|
||||
component: Layout,
|
||||
isHidden: true,
|
||||
children: [
|
||||
{
|
||||
name: 'apps-docker-index',
|
||||
path: '',
|
||||
component: () => import('./IndexView.vue'),
|
||||
meta: {
|
||||
title: 'Docker',
|
||||
icon: 'logos:docker-icon',
|
||||
role: ['admin'],
|
||||
requireAuth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
} as RouteType
|
||||
Reference in New Issue
Block a user