1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-28 19:14:13 +08:00

feat: 镜像仓库增加状态列,连接判断

This commit is contained in:
ssongliu 2022-12-18 23:00:24 +08:00 committed by ssongliu
parent 5945098844
commit 1ed0bb691a
14 changed files with 155 additions and 52 deletions
backend
app
init
migration/migrations
router
utils/common
frontend/src
api/interface
views
container
cronjob

@ -87,13 +87,7 @@ func (b *BaseApi) UpdateRepo(c *gin.Context) {
return return
} }
upMap := make(map[string]interface{}) if err := imageRepoService.Update(req); err != nil {
upMap["download_url"] = req.DownloadUrl
upMap["protocol"] = req.Protocol
upMap["username"] = req.Username
upMap["password"] = req.Password
upMap["auth"] = req.Auth
if err := imageRepoService.Update(req.ID, upMap); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }

@ -9,7 +9,6 @@ type DaemonJsonConf struct {
Status string `json:"status"` Status string `json:"status"`
Mirrors []string `json:"registryMirrors"` Mirrors []string `json:"registryMirrors"`
Registries []string `json:"insecureRegistries"` Registries []string `json:"insecureRegistries"`
Bip string `json:"bip"`
LiveRestore bool `json:"liveRestore"` LiveRestore bool `json:"liveRestore"`
CgroupDriver string `json:"cgroupDriver"` CgroupDriver string `json:"cgroupDriver"`
} }

@ -28,6 +28,9 @@ type ImageRepoInfo struct {
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Username string `json:"username"` Username string `json:"username"`
Auth bool `json:"auth"` Auth bool `json:"auth"`
Status string `json:"status"`
Message string `json:"message"`
} }
type ImageRepoOption struct { type ImageRepoOption struct {

@ -9,4 +9,7 @@ type ImageRepo struct {
Username string `gorm:"type:varchar(256)" json:"username"` Username string `gorm:"type:varchar(256)" json:"username"`
Password string `gorm:"type:varchar(256)" json:"password"` Password string `gorm:"type:varchar(256)" json:"password"`
Auth bool `gorm:"type:varchar(256)" json:"auth"` Auth bool `gorm:"type:varchar(256)" json:"auth"`
Status string `gorm:"type:varchar(64)" json:"status"`
Message string `gorm:"type:varchar(256)" json:"message"`
} }

@ -31,18 +31,17 @@ type daemonJsonItem struct {
Status string `json:"status"` Status string `json:"status"`
Mirrors []string `json:"registry-mirrors"` Mirrors []string `json:"registry-mirrors"`
Registries []string `json:"insecure-registries"` Registries []string `json:"insecure-registries"`
Bip string `json:"bip"`
LiveRestore bool `json:"live-restore"` LiveRestore bool `json:"live-restore"`
ExecOpts []string `json:"exec-opts"` ExecOpts []string `json:"exec-opts"`
} }
func (u *DockerService) LoadDockerStatus() string { func (u *DockerService) LoadDockerStatus() string {
status := constant.StatusRunning status := constant.StatusRunning
cmd := exec.Command("systemctl", "is-active", "docker") // cmd := exec.Command("systemctl", "is-active", "docker")
stdout, err := cmd.CombinedOutput() // stdout, err := cmd.CombinedOutput()
if string(stdout) != "active\n" || err != nil { // if string(stdout) != "active\n" || err != nil {
status = constant.Stopped // status = constant.Stopped
} // }
return status return status
} }
@ -91,7 +90,6 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
Status: status, Status: status,
Mirrors: conf.Mirrors, Mirrors: conf.Mirrors,
Registries: conf.Registries, Registries: conf.Registries,
Bip: conf.Bip,
LiveRestore: conf.LiveRestore, LiveRestore: conf.LiveRestore,
CgroupDriver: driver, CgroupDriver: driver,
} }
@ -132,11 +130,6 @@ func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
} else { } else {
deamonMap["registry-mirrors"] = req.Mirrors deamonMap["registry-mirrors"] = req.Mirrors
} }
if len(req.Bip) == 0 {
delete(deamonMap, "bip")
} else {
deamonMap["bip"] = req.Bip
}
if !req.LiveRestore { if !req.LiveRestore {
delete(deamonMap, "live-restore") delete(deamonMap, "live-restore")
} else { } else {

@ -2,12 +2,15 @@ package service
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -17,8 +20,8 @@ type ImageRepoService struct{}
type IImageRepoService interface { type IImageRepoService interface {
Page(search dto.PageInfo) (int64, interface{}, error) Page(search dto.PageInfo) (int64, interface{}, error)
List() ([]dto.ImageRepoOption, error) List() ([]dto.ImageRepoOption, error)
Create(imageRepoDto dto.ImageRepoCreate) error Create(req dto.ImageRepoCreate) error
Update(id uint, upMap map[string]interface{}) error Update(req dto.ImageRepoUpdate) error
BatchDelete(ids []uint) error BatchDelete(ids []uint) error
} }
@ -43,17 +46,19 @@ func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) {
ops, err := imageRepoRepo.List(commonRepo.WithOrderBy("created_at desc")) ops, err := imageRepoRepo.List(commonRepo.WithOrderBy("created_at desc"))
var dtoOps []dto.ImageRepoOption var dtoOps []dto.ImageRepoOption
for _, op := range ops { for _, op := range ops {
if op.Status == constant.StatusSuccess {
var item dto.ImageRepoOption var item dto.ImageRepoOption
if err := copier.Copy(&item, &op); err != nil { if err := copier.Copy(&item, &op); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
} }
dtoOps = append(dtoOps, item) dtoOps = append(dtoOps, item)
} }
}
return dtoOps, err return dtoOps, err
} }
func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error { func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(imageRepoDto.Name)) imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(req.Name))
if imageRepo.ID != 0 { if imageRepo.ID != 0 {
return constant.ErrRecordExist return constant.ErrRecordExist
} }
@ -63,7 +68,7 @@ func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error {
return err return err
} }
if len(fileSetting.Value) == 0 { if len(fileSetting.Value) == 0 {
return errors.New("error daemon.json path in request") return errors.New("error daemon.json")
} }
if _, err := os.Stat(fileSetting.Value); err != nil && os.IsNotExist(err) { if _, err := os.Stat(fileSetting.Value); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(fileSetting.Value, os.ModePerm); err != nil { if err = os.MkdirAll(fileSetting.Value, os.ModePerm); err != nil {
@ -72,8 +77,7 @@ func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error {
} }
} }
} }
if req.Protocol == "http" {
if imageRepoDto.Protocol == "http" {
file, err := ioutil.ReadFile(fileSetting.Value) file, err := ioutil.ReadFile(fileSetting.Value)
if err != nil { if err != nil {
return err return err
@ -85,11 +89,10 @@ func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error {
} }
if _, ok := deamonMap["insecure-registries"]; ok { if _, ok := deamonMap["insecure-registries"]; ok {
if k, v := deamonMap["insecure-registries"].([]interface{}); v { if k, v := deamonMap["insecure-registries"].([]interface{}); v {
k = append(k, imageRepoDto.DownloadUrl) deamonMap["insecure-registries"] = common.RemoveRepeatElement(append(k, req.DownloadUrl))
deamonMap["insecure-registries"] = k
} }
} else { } else {
deamonMap["insecure-registries"] = []string{imageRepoDto.DownloadUrl} deamonMap["insecure-registries"] = []string{req.DownloadUrl}
} }
newJson, err := json.MarshalIndent(deamonMap, "", "\t") newJson, err := json.MarshalIndent(deamonMap, "", "\t")
if err != nil { if err != nil {
@ -99,12 +102,19 @@ func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error {
return err return err
} }
} }
if err := copier.Copy(&imageRepo, &imageRepoDto); err != nil {
if err := copier.Copy(&imageRepo, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error()) return errors.WithMessage(constant.ErrStructTransform, err.Error())
} }
imageRepo.Status = constant.StatusSuccess
if err := u.checkConn(req.DownloadUrl, req.Username, req.Password); err != nil {
imageRepo.Status = constant.StatusFailed
imageRepo.Message = err.Error()
}
if err := imageRepoRepo.Create(&imageRepo); err != nil { if err := imageRepoRepo.Create(&imageRepo); err != nil {
return err return err
} }
cmd := exec.Command("systemctl", "restart", "docker") cmd := exec.Command("systemctl", "restart", "docker")
stdout, err := cmd.CombinedOutput() stdout, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@ -123,12 +133,97 @@ func (u *ImageRepoService) BatchDelete(ids []uint) error {
return errors.New("The default value cannot be edit !") return errors.New("The default value cannot be edit !")
} }
} }
return imageRepoRepo.Delete(commonRepo.WithIdsIn(ids)) repos, err := imageRepoRepo.List(commonRepo.WithIdsIn(ids))
if err != nil {
return err
}
fileSetting, err := settingRepo.Get(settingRepo.WithByKey("DaemonJsonPath"))
if err != nil {
return err
}
if len(fileSetting.Value) == 0 {
return errors.New("error daemon.json")
} }
func (u *ImageRepoService) Update(id uint, upMap map[string]interface{}) error { deamonMap := make(map[string]interface{})
if id == 1 { file, err := ioutil.ReadFile(fileSetting.Value)
if err != nil {
return err
}
if err := json.Unmarshal(file, &deamonMap); err != nil {
return err
}
iRegistries := deamonMap["insecure-registries"]
registries, _ := iRegistries.([]string)
if len(registries) != 0 {
for _, repo := range repos {
if repo.Protocol == "http" {
for i, regi := range registries {
if regi == repo.DownloadUrl {
registries = append(registries[:i], registries[i+1:]...)
}
}
}
}
}
if len(registries) == 0 {
delete(deamonMap, "insecure-registries")
}
newJson, err := json.MarshalIndent(deamonMap, "", "\t")
if err != nil {
return err
}
if err := ioutil.WriteFile(fileSetting.Value, newJson, 0640); err != nil {
return err
}
for _, repo := range repos {
if repo.Auth {
cmd := exec.Command("docker", "logout", fmt.Sprintf("%s://%s", repo.Protocol, repo.DownloadUrl))
_, _ = cmd.CombinedOutput()
}
}
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(ids)); err != nil {
return err
}
cmd := exec.Command("systemctl", "restart", "docker")
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
return nil
}
func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if req.ID == 1 {
return errors.New("The default value cannot be deleted !") return errors.New("The default value cannot be deleted !")
} }
return imageRepoRepo.Update(id, upMap)
upMap := make(map[string]interface{})
upMap["download_url"] = req.DownloadUrl
upMap["protocol"] = req.Protocol
upMap["username"] = req.Username
upMap["password"] = req.Password
upMap["auth"] = req.Auth
upMap["status"] = constant.StatusSuccess
upMap["message"] = ""
if err := u.checkConn(req.DownloadUrl, req.Username, req.Password); err != nil {
upMap["status"] = constant.StatusFailed
upMap["message"] = err.Error()
}
return imageRepoRepo.Update(req.ID, upMap)
}
func (u *ImageRepoService) checkConn(host, user, password string) error {
cmd := exec.Command("docker", "login", "-u", user, "-p", password, host)
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
if strings.Contains(string(stdout), "Login Succeeded") {
return nil
}
return errors.New(string(stdout))
} }

@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/go-gormigrate/gormigrate/v2" "github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm" "gorm.io/gorm"
@ -167,6 +168,7 @@ var AddTableImageRepo = &gormigrate.Migration{
item := &model.ImageRepo{ item := &model.ImageRepo{
Name: "Docker Hub", Name: "Docker Hub",
DownloadUrl: "docker.io", DownloadUrl: "docker.io",
Status: constant.StatusSuccess,
} }
if err := tx.Create(item).Error; err != nil { if err := tx.Create(item).Error; err != nil {
return err return err

@ -43,8 +43,8 @@ func Routers() *gin.Engine {
Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
Router.Use(middleware.OperationLog()) Router.Use(middleware.OperationLog())
Router.Use(middleware.CSRF()) // Router.Use(middleware.CSRF())
Router.Use(middleware.LoadCsrfToken()) // Router.Use(middleware.LoadCsrfToken())
setWebStatic(Router) setWebStatic(Router)

@ -6,6 +6,7 @@ import (
"io" "io"
mathRand "math/rand" mathRand "math/rand"
"net" "net"
"reflect"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
@ -104,3 +105,14 @@ func IsNum(s string) bool {
_, err := strconv.ParseFloat(s, 64) _, err := strconv.ParseFloat(s, 64)
return err == nil return err == nil
} }
func RemoveRepeatElement(a interface{}) (ret []interface{}) {
va := reflect.ValueOf(a)
for i := 0; i < va.Len(); i++ {
if i > 0 && reflect.DeepEqual(va.Index(i-1).Interface(), va.Index(i).Interface()) {
continue
}
ret = append(ret, va.Index(i).Interface())
}
return ret
}

@ -240,7 +240,6 @@ export namespace Container {
} }
export interface DaemonJsonConf { export interface DaemonJsonConf {
status: string; status: string;
bip: string;
registryMirrors: Array<string>; registryMirrors: Array<string>;
insecureRegistries: Array<string>; insecureRegistries: Array<string>;
liveRestore: boolean; liveRestore: boolean;

@ -28,6 +28,16 @@
fix fix
/> />
<el-table-column :label="$t('container.protocol')" prop="protocol" min-width="60" fix /> <el-table-column :label="$t('container.protocol')" prop="protocol" min-width="60" fix />
<el-table-column :label="$t('commons.table.status')" prop="status" min-width="60" fix>
<template #default="{ row }">
<el-tag v-if="row.status === 'Success'" type="success">
{{ $t('commons.status.success') }}
</el-tag>
<el-tooltip v-else effect="dark" :content="row.message" placement="bottom">
<el-tag type="danger">{{ $t('commons.status.failed') }}</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix> <el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
<template #default="{ row }"> <template #default="{ row }">
{{ dateFromat(0, 0, row.createdAt) }} {{ dateFromat(0, 0, row.createdAt) }}

@ -65,9 +65,6 @@
v-model="form.registries" v-model="form.registries"
/> />
</el-form-item> </el-form-item>
<el-form-item label="bip">
<el-input clearable v-model="form.bip" />
</el-form-item>
<el-form-item label="live-restore" prop="liveRestore"> <el-form-item label="live-restore" prop="liveRestore">
<el-switch v-model="form.liveRestore"></el-switch> <el-switch v-model="form.liveRestore"></el-switch>
<span class="input-help">{{ $t('container.liveHelper') }}</span> <span class="input-help">{{ $t('container.liveHelper') }}</span>
@ -138,7 +135,6 @@ const loadLoadDir = async (path: string) => {
const form = reactive({ const form = reactive({
status: '', status: '',
bip: '',
mirrors: '', mirrors: '',
registries: '', registries: '',
liveRestore: false, liveRestore: false,
@ -212,7 +208,6 @@ const onSubmitSave = async () => {
let itemRegistries = form.registries.split('\n'); let itemRegistries = form.registries.split('\n');
let param = { let param = {
status: form.status, status: form.status,
bip: form.bip,
registryMirrors: itemMirrors.filter(function (el) { registryMirrors: itemMirrors.filter(function (el) {
return el !== null && el !== '' && el !== undefined; return el !== null && el !== '' && el !== undefined;
}), }),
@ -254,7 +249,6 @@ const changeMode = async () => {
const search = async () => { const search = async () => {
const res = await loadDaemonJson(); const res = await loadDaemonJson();
form.bip = res.data.bip;
form.status = res.data.status; form.status = res.data.status;
form.cgroupDriver = res.data.cgroupDriver; form.cgroupDriver = res.data.cgroupDriver;
form.liveRestore = res.data.liveRestore; form.liveRestore = res.data.liveRestore;

@ -24,11 +24,10 @@
@click="onChangeStatus(row.id, 'disable')" @click="onChangeStatus(row.id, 'disable')"
link link
type="success" type="success"
icon="VideoPlay"
> >
{{ $t('commons.status.enabled') }} {{ $t('commons.status.enabled') }}
</el-button> </el-button>
<el-button v-else link type="danger" @click="onChangeStatus(row.id, 'enable')" icon="VideoPause"> <el-button v-else link type="danger" @click="onChangeStatus(row.id, 'enable')">
{{ $t('commons.status.disabled') }} {{ $t('commons.status.disabled') }}
</el-button> </el-button>
</template> </template>

@ -259,10 +259,10 @@ const varifySpec = (rule: any, value: any, callback: any) => {
const specOptions = [ const specOptions = [
{ label: i18n.global.t('cronjob.perMonth'), value: 'perMonth' }, { label: i18n.global.t('cronjob.perMonth'), value: 'perMonth' },
{ label: i18n.global.t('cronjob.perWeek'), value: 'perWeek' }, { label: i18n.global.t('cronjob.perWeek'), value: 'perWeek' },
{ label: i18n.global.t('cronjob.perNDay'), value: 'perNDay' },
{ label: i18n.global.t('cronjob.perDay'), value: 'perDay' }, { label: i18n.global.t('cronjob.perDay'), value: 'perDay' },
{ label: i18n.global.t('cronjob.perNHour'), value: 'perNHour' },
{ label: i18n.global.t('cronjob.perHour'), value: 'perHour' }, { label: i18n.global.t('cronjob.perHour'), value: 'perHour' },
{ label: i18n.global.t('cronjob.perNDay'), value: 'perNDay' },
{ label: i18n.global.t('cronjob.perNHour'), value: 'perNHour' },
{ label: i18n.global.t('cronjob.perNMinute'), value: 'perNMinute' }, { label: i18n.global.t('cronjob.perNMinute'), value: 'perNMinute' },
]; ];
const weekOptions = [ const weekOptions = [