2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 07:57:21 +08:00

feat: docker 管理器

This commit is contained in:
耗子
2024-10-31 01:23:46 +08:00
parent e90c6f4f7b
commit 52e8830605
7 changed files with 247 additions and 0 deletions

View 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)
},
})
}

View File

@@ -0,0 +1,5 @@
package podman
type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
}

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

View File

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

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

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

View 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