mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
534bd98230 | ||
|
|
1045f2d475 | ||
|
|
c5d007e4dd | ||
|
|
da249a2cef | ||
|
|
f738cd42be | ||
|
|
d7d15479f2 | ||
|
|
d9de82dc04 | ||
|
|
2d0d3704ae | ||
|
|
a3bad3ccf9 | ||
|
|
41471f93f0 | ||
|
|
a817beb029 | ||
|
|
e6322ad76b | ||
|
|
df003c5fb7 | ||
|
|
10f8e8f3c4 | ||
|
|
a750e87940 | ||
|
|
c12f690753 | ||
|
|
0dd51d0c35 | ||
|
|
7f627c041c | ||
|
|
472ca884fd |
@@ -200,6 +200,7 @@ type ComposeInfo struct {
|
||||
Workdir string `json:"workdir"`
|
||||
Path string `json:"path"`
|
||||
Containers []ComposeContainer `json:"containers"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
type ComposeContainer struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
@@ -218,7 +219,7 @@ type ComposeCreate struct {
|
||||
type ComposeOperation struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start stop down"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
|
||||
WithFile bool `json:"withFile"`
|
||||
}
|
||||
type ComposeUpdate struct {
|
||||
|
||||
@@ -175,21 +175,22 @@ type WebsiteUpdateDirPermission struct {
|
||||
}
|
||||
|
||||
type WebsiteProxyConfig struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
Enable bool `json:"enable" `
|
||||
Cache bool `json:"cache" `
|
||||
CacheTime int `json:"cacheTime" `
|
||||
CacheUnit string `json:"cacheUnit"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Modifier string `json:"modifier"`
|
||||
Match string `json:"match" validate:"required"`
|
||||
ProxyPass string `json:"proxyPass" validate:"required"`
|
||||
ProxyHost string `json:"proxyHost" validate:"required"`
|
||||
Content string `json:"content"`
|
||||
FilePath string `json:"filePath"`
|
||||
Replaces map[string]string `json:"replaces"`
|
||||
SNI bool `json:"sni"`
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
Enable bool `json:"enable" `
|
||||
Cache bool `json:"cache" `
|
||||
CacheTime int `json:"cacheTime" `
|
||||
CacheUnit string `json:"cacheUnit"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Modifier string `json:"modifier"`
|
||||
Match string `json:"match" validate:"required"`
|
||||
ProxyPass string `json:"proxyPass" validate:"required"`
|
||||
ProxyHost string `json:"proxyHost" validate:"required"`
|
||||
Content string `json:"content"`
|
||||
FilePath string `json:"filePath"`
|
||||
Replaces map[string]string `json:"replaces"`
|
||||
SNI bool `json:"sni"`
|
||||
ProxySSLName string `json:"proxySSLName"`
|
||||
}
|
||||
|
||||
type WebsiteProxyReq struct {
|
||||
|
||||
@@ -4,12 +4,11 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -161,7 +160,26 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
BackDatas = records[start:end]
|
||||
}
|
||||
return int64(total), BackDatas, nil
|
||||
listItem := loadEnv(BackDatas)
|
||||
return int64(total), listItem, nil
|
||||
}
|
||||
|
||||
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
|
||||
for i := 0; i < len(list); i++ {
|
||||
envFilePath := filepath.Join(path.Dir(list[i].Path), "1panel.env")
|
||||
file, err := os.ReadFile(envFilePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines := strings.Split(string(file), "\n")
|
||||
for _, line := range lines {
|
||||
lineItem := strings.TrimSpace(line)
|
||||
if len(lineItem) != 0 && !strings.HasPrefix(lineItem, "#") {
|
||||
list[i].Env = append(list[i].Env, lineItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
@@ -175,6 +193,9 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
if err := u.loadPath(&req); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := newComposeEnv(req.Path, req.Env); err != nil {
|
||||
return false, err
|
||||
}
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, "config")
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -183,59 +204,6 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func formatYAML(data []byte) []byte {
|
||||
return []byte(strings.ReplaceAll(string(data), "\t", " "))
|
||||
}
|
||||
|
||||
func updateDockerComposeWithEnv(req dto.ComposeCreate) error {
|
||||
data, err := ioutil.ReadFile(req.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read docker-compose.yml: %v", err)
|
||||
}
|
||||
var composeItem DockerCompose
|
||||
if err := yaml.Unmarshal(data, &composeItem); err != nil {
|
||||
return fmt.Errorf("failed to parse docker-compose.yml: %v", err)
|
||||
}
|
||||
for serviceName, service := range composeItem.Services {
|
||||
envMap := make(map[string]string)
|
||||
if existingEnv, exists := service["environment"].([]interface{}); exists {
|
||||
for _, env := range existingEnv {
|
||||
envStr := env.(string)
|
||||
parts := strings.SplitN(envStr, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
envMap[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, env := range req.Env {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
envMap[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
envVars := []string{}
|
||||
for key, value := range envMap {
|
||||
envVars = append(envVars, key+"="+value)
|
||||
}
|
||||
service["environment"] = envVars
|
||||
composeItem.Services[serviceName] = service
|
||||
}
|
||||
if composeItem.Networks != nil {
|
||||
for key := range composeItem.Networks {
|
||||
composeItem.Networks[key] = map[string]interface{}{}
|
||||
}
|
||||
}
|
||||
newData, err := yaml.Marshal(&composeItem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal docker-compose.yml: %v", err)
|
||||
}
|
||||
formattedData := formatYAML(newData)
|
||||
if err := ioutil.WriteFile(req.Path, formattedData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write docker-compose.yml: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
|
||||
if cmd.CheckIllegal(req.Name, req.Path) {
|
||||
return "", buserr.New(constant.ErrCmdIllegal)
|
||||
@@ -260,11 +228,8 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(req.Env) > 0 {
|
||||
if err := updateDockerComposeWithEnv(req); err != nil {
|
||||
fmt.Printf("failed to update docker-compose.yml with env: %v\n", err)
|
||||
return "", err
|
||||
}
|
||||
if err := newComposeEnv(req.Path, req.Env); err != nil {
|
||||
return "", err
|
||||
}
|
||||
go func() {
|
||||
defer file.Close()
|
||||
@@ -293,6 +258,16 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
if req.Operation == "delete" {
|
||||
if stdout, err := compose.Operate(req.Path, "down"); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
if req.WithFile {
|
||||
_ = os.RemoveAll(path.Dir(req.Path))
|
||||
}
|
||||
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
|
||||
return nil
|
||||
}
|
||||
if req.Operation == "up" {
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
@@ -303,74 +278,6 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
|
||||
if req.Operation == "down" {
|
||||
if req.WithFile {
|
||||
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
|
||||
_ = os.RemoveAll(path.Dir(req.Path))
|
||||
} else {
|
||||
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
|
||||
if composeItem.Path == "" {
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["path"] = req.Path
|
||||
_ = composeRepo.UpdateRecord(req.Name, upMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateComposeWithEnv(req dto.ComposeUpdate) error {
|
||||
var composeItem DockerCompose
|
||||
if err := yaml.Unmarshal([]byte(req.Content), &composeItem); err != nil {
|
||||
return fmt.Errorf("failed to parse docker-compose content: %v", err)
|
||||
}
|
||||
for serviceName, service := range composeItem.Services {
|
||||
envMap := make(map[string]string)
|
||||
for _, env := range req.Env {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
envMap[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
newEnvVars := []string{}
|
||||
if existingEnv, exists := service["environment"].([]interface{}); exists {
|
||||
for _, env := range existingEnv {
|
||||
envStr := env.(string)
|
||||
parts := strings.SplitN(envStr, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
key := parts[0]
|
||||
if value, found := envMap[key]; found {
|
||||
newEnvVars = append(newEnvVars, key+"="+value)
|
||||
delete(envMap, key)
|
||||
} else {
|
||||
newEnvVars = append(newEnvVars, envStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for key, value := range envMap {
|
||||
newEnvVars = append(newEnvVars, key+"="+value)
|
||||
}
|
||||
if len(newEnvVars) > 0 {
|
||||
service["environment"] = newEnvVars
|
||||
} else {
|
||||
delete(service, "environment")
|
||||
}
|
||||
composeItem.Services[serviceName] = service
|
||||
}
|
||||
if composeItem.Networks != nil {
|
||||
for key := range composeItem.Networks {
|
||||
composeItem.Networks[key] = map[string]interface{}{}
|
||||
}
|
||||
}
|
||||
newData, err := yaml.Marshal(&composeItem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal docker-compose.yml: %v", err)
|
||||
}
|
||||
if err := ioutil.WriteFile(req.Path, newData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write docker-compose.yml to path: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -382,21 +289,20 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
if len(req.Env) > 0 {
|
||||
if err := updateComposeWithEnv(req); err != nil {
|
||||
return fmt.Errorf("failed to update docker-compose with env: %v", err)
|
||||
}
|
||||
} else {
|
||||
file, err := os.OpenFile(req.Path, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(req.Content)
|
||||
write.Flush()
|
||||
global.LOG.Infof("docker-compose.yml %s has been replaced", req.Path)
|
||||
file, err := os.OpenFile(req.Path, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(req.Content)
|
||||
write.Flush()
|
||||
|
||||
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.Path)
|
||||
if err := newComposeEnv(req.Path, req.Env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
if err := recreateCompose(string(oldFile), req.Path); err != nil {
|
||||
return fmt.Errorf("update failed when handle compose up, err: %s, recreate failed: %v", string(stdout), err)
|
||||
@@ -445,3 +351,26 @@ func recreateCompose(content, path string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newComposeEnv(pathItem string, env []string) error {
|
||||
if len(env) == 0 {
|
||||
return nil
|
||||
}
|
||||
envFilePath := path.Join(path.Dir(pathItem), "1panel.env")
|
||||
|
||||
file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to create env file: %v", err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
for _, env := range env {
|
||||
envItem := strings.TrimSpace(env)
|
||||
if _, err := file.WriteString(fmt.Sprintf("%s\n", envItem)); err != nil {
|
||||
global.LOG.Errorf("failed to write env to file: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("1panel.env file successfully created or updated with env variables in %s", envFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,10 +3,6 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
network "net"
|
||||
"os"
|
||||
"sort"
|
||||
@@ -14,6 +10,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -268,13 +269,13 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(fields[1]) == "tmpfs" {
|
||||
if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[2], "K") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[6], "docker") {
|
||||
if strings.Contains(fields[6], "docker") || strings.Contains(fields[6], "podman") || strings.Contains(fields[6], "containerd") || strings.HasPrefix(fields[6], "/var/lib/containers") {
|
||||
continue
|
||||
}
|
||||
isExclude := false
|
||||
|
||||
@@ -1667,6 +1667,9 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
}
|
||||
if req.SNI {
|
||||
location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
|
||||
if req.ProxySSLName != "" {
|
||||
location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
|
||||
}
|
||||
} else {
|
||||
location.UpdateDirective("proxy_ssl_server_name", []string{"off"})
|
||||
}
|
||||
@@ -1749,6 +1752,9 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
|
||||
if directive.GetName() == "proxy_ssl_server_name" {
|
||||
proxyConfig.SNI = directive.GetParameters()[0] == "on"
|
||||
}
|
||||
if directive.GetName() == "proxy_ssl_name" {
|
||||
proxyConfig.ProxySSLName = directive.GetParameters()[0]
|
||||
}
|
||||
}
|
||||
res = append(res, proxyConfig)
|
||||
}
|
||||
|
||||
@@ -217,15 +217,14 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
|
||||
if req.Domains != "" {
|
||||
domainArray := strings.Split(req.Domains, "\n")
|
||||
for _, domain := range domainArray {
|
||||
if !common.IsValidDomain(domain) {
|
||||
err = buserr.WithName("ErrDomainFormat", domain)
|
||||
return nil, err
|
||||
} else {
|
||||
if ipAddress := net.ParseIP(domain); ipAddress == nil {
|
||||
domains = append(domains, domain)
|
||||
} else {
|
||||
ips = append(ips, ipAddress)
|
||||
if ipAddress := net.ParseIP(domain); ipAddress == nil {
|
||||
if !common.IsValidDomain(domain) {
|
||||
err = buserr.WithName("ErrDomainFormat", domain)
|
||||
return nil, err
|
||||
}
|
||||
domains = append(domains, domain)
|
||||
} else {
|
||||
ips = append(ips, ipAddress)
|
||||
}
|
||||
}
|
||||
if len(domains) > 0 {
|
||||
|
||||
@@ -29,10 +29,17 @@ type FirewallClient interface {
|
||||
}
|
||||
|
||||
func NewFirewallClient() (FirewallClient, error) {
|
||||
if _, err := os.Stat("/usr/sbin/firewalld"); err == nil {
|
||||
_, firewalldErr := os.Stat("/usr/sbin/firewalld")
|
||||
_, ufwErr := os.Stat("/usr/sbin/ufw")
|
||||
|
||||
if firewalldErr == nil && ufwErr == nil {
|
||||
return nil, buserr.New("firewalld and ufw both found, only one firewall should be active")
|
||||
}
|
||||
|
||||
if firewalldErr == nil {
|
||||
return client.NewFirewalld()
|
||||
}
|
||||
if _, err := os.Stat("/usr/sbin/ufw"); err == nil {
|
||||
if ufwErr == nil {
|
||||
return client.NewUfw()
|
||||
}
|
||||
return nil, buserr.New(constant.ErrFirewall)
|
||||
|
||||
@@ -234,11 +234,13 @@ func (l *Location) AddSubFilter(subFilters map[string]string) {
|
||||
}
|
||||
l.UpdateDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
|
||||
l.UpdateDirective("sub_filter_once", []string{"off"})
|
||||
l.UpdateDirective("sub_filter_types", []string{"*"})
|
||||
}
|
||||
|
||||
func (l *Location) RemoveSubFilter() {
|
||||
l.RemoveDirective("sub_filter", []string{})
|
||||
l.RemoveDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
|
||||
l.RemoveDirective("sub_filter_once", []string{"off"})
|
||||
l.RemoveDirective("sub_filter_types", []string{"*"})
|
||||
l.Replaces = nil
|
||||
}
|
||||
|
||||
@@ -11,4 +11,5 @@ location ^~ /test {
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
add_header Cache-Control no-cache;
|
||||
proxy_ssl_server_name off;
|
||||
proxy_ssl_name $proxy_host;
|
||||
}
|
||||
|
||||
@@ -241,6 +241,8 @@ export namespace Container {
|
||||
path: string;
|
||||
containers: Array<ComposeContainer>;
|
||||
expand: boolean;
|
||||
envStr: string;
|
||||
env: Array<string>;
|
||||
}
|
||||
export interface ComposeContainer {
|
||||
name: string;
|
||||
@@ -268,6 +270,7 @@ export namespace Container {
|
||||
path: string;
|
||||
content: string;
|
||||
env: Array<string>;
|
||||
createdBy: string;
|
||||
}
|
||||
|
||||
export interface TemplateCreate {
|
||||
|
||||
@@ -174,6 +174,7 @@ export namespace Setting {
|
||||
}
|
||||
export interface LicenseStatus {
|
||||
productPro: string;
|
||||
trial: boolean;
|
||||
status: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,6 +373,8 @@ export namespace Website {
|
||||
content?: string;
|
||||
proxyAddress?: string;
|
||||
proxyProtocol?: string;
|
||||
sni: boolean;
|
||||
proxySSLName: string;
|
||||
}
|
||||
|
||||
export interface ProxReplace {
|
||||
|
||||
@@ -108,6 +108,9 @@ const showBack = computed(() => {
|
||||
.el-button + .el-button {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.el-button-group .el-button {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.content-container_form {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-loading="isLoading">
|
||||
<div v-loading="firstLoading">
|
||||
<div v-if="defaultButton">
|
||||
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
|
||||
{{ $t('commons.button.watch') }}
|
||||
@@ -78,6 +78,7 @@ const minPage = ref(1);
|
||||
const maxPage = ref(1);
|
||||
const logs = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const firstLoading = ref(true);
|
||||
|
||||
const readReq = reactive({
|
||||
id: 0,
|
||||
@@ -129,6 +130,7 @@ const getContent = (pre: boolean) => {
|
||||
}
|
||||
isLoading.value = true;
|
||||
ReadByLine(readReq).then((res) => {
|
||||
firstLoading.value = false;
|
||||
if (!end.value && res.data.end) {
|
||||
lastLogs.value = [...logs.value];
|
||||
}
|
||||
@@ -193,7 +195,7 @@ const getContent = (pre: boolean) => {
|
||||
maxPage.value = res.data.total;
|
||||
minPage.value = res.data.total;
|
||||
}
|
||||
if (logs.value && logs.value.length > 3000) {
|
||||
if (logs.value && logs.value.length > 1000) {
|
||||
logs.value.splice(0, readReq.pageSize);
|
||||
if (minPage.value > 1) {
|
||||
minPage.value--;
|
||||
@@ -278,8 +280,6 @@ const init = () => {
|
||||
}
|
||||
readReq.latest = true;
|
||||
search();
|
||||
|
||||
nextTick(() => {});
|
||||
};
|
||||
|
||||
const clearLog = (): void => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-card class="router_card">
|
||||
<div class="flex w-full justify-between items-center md:justify-between md:flex-row">
|
||||
<div class="flex w-full flex-col md:justify-between md:flex-row">
|
||||
<el-radio-group v-model="activeName" @change="handleChange">
|
||||
<el-radio-button
|
||||
class="router_card_button"
|
||||
@@ -76,7 +76,6 @@ onMounted(() => {
|
||||
<style lang="scss">
|
||||
.router_card {
|
||||
--el-card-padding: 0;
|
||||
width: 100%;
|
||||
.el-card__body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -211,8 +211,8 @@ const onRecover = async (row: File.File) => {
|
||||
currentRow.value = row;
|
||||
if (type.value !== 'app' && type.value !== 'website') {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('commons.msg.backupHelper', [name.value + '( ' + detailName.value + ' )']),
|
||||
i18n.global.t('commons.button.backup'),
|
||||
i18n.global.t('commons.msg.recoverHelper', [row.name]),
|
||||
i18n.global.t('commons.button.recover'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
|
||||
@@ -655,7 +655,7 @@ const message = {
|
||||
privilegedHelper:
|
||||
'Allows the container to perform certain privileged operations on the host, which may increase container risks. Use with caution!',
|
||||
editComposeHelper:
|
||||
'The environment variables manually entered in the menu will override existing variables with the same name. If they do not exist, they will be added.',
|
||||
'Note: The environment variables set will be written to the 1panel.env file by default.\nIf you want to use these parameters in the container, you also need to manually add an env_file reference in the compose file.',
|
||||
|
||||
upgradeHelper: 'Repository Name/Image Name: Image Version',
|
||||
upgradeWarning2:
|
||||
@@ -772,7 +772,7 @@ const message = {
|
||||
deleteFile: 'Delete file',
|
||||
allDelete: 'Permanently Delete',
|
||||
deleteComposeHelper:
|
||||
'1. Delete container orchestration records \n2. Delete all container orchestration files, including configuration and persistent files',
|
||||
'Delete all files related to container compose, including configuration files and persistent files. Please proceed with caution!',
|
||||
deleteCompose: '" Delete this composition.',
|
||||
apps: 'Apps',
|
||||
local: 'Local',
|
||||
@@ -792,6 +792,8 @@ const message = {
|
||||
composeDetailHelper:
|
||||
'The compose is created external to 1Panel. The start and stop operations are not supported.',
|
||||
composeOperatorHelper: '{1} operation will be performed on {0}. Do you want to continue?',
|
||||
composeDownHelper:
|
||||
'This will stop and remove all containers and networks under the {0} compose. Do you want to continue?',
|
||||
|
||||
setting: 'Setting',
|
||||
operatorStatusHelper: 'This action will {0} Docker service, do you want to continue?',
|
||||
|
||||
@@ -629,7 +629,8 @@ const message = {
|
||||
emptyUser: '為空時,將使用容器默認的用戶登錄',
|
||||
privileged: '特權模式',
|
||||
privilegedHelper: '允許容器在主機上執行某些特權操作,可能會增加容器風險,請謹慎開啟!',
|
||||
editComposeHelper: '在菜單中手動輸入的環境變量會覆蓋原有的同名變量,若不存在則新增。',
|
||||
editComposeHelper:
|
||||
'注意:設置的環境變數會默認寫入 1panel.env 文件。\n若需在容器中使用這些參數,還需在 compose 文件中手動添加 env_file 引用。',
|
||||
|
||||
upgradeHelper: '倉庫名稱/鏡像名稱:鏡像版本',
|
||||
upgradeWarning2: '升級操作需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
|
||||
@@ -741,7 +742,7 @@ const message = {
|
||||
composeHelper: '通過 1Panel 編輯或者模版創建的編排,將保存在 {0}/docker/compose 路徑下',
|
||||
deleteFile: '刪除文件',
|
||||
allDelete: '徹底刪除',
|
||||
deleteComposeHelper: '1. 刪除容器編排記錄 \n2. 刪除容器編排的所有文件,包括配置文件和持久化文件',
|
||||
deleteComposeHelper: '刪除容器編排的所有文件,包括配置文件和持久化文件,請謹慎操作!',
|
||||
deleteCompose: '" 刪除此編排',
|
||||
apps: '應用商店',
|
||||
local: '本地',
|
||||
@@ -760,6 +761,7 @@ const message = {
|
||||
up: '啟動',
|
||||
composeDetailHelper: '該 compose 為 1Panel 編排外部創建。暫不支持啟停操作。',
|
||||
composeOperatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?',
|
||||
composeDownHelper: '將停止並刪除 {0} 編排下所有容器及網絡,是否繼續?',
|
||||
|
||||
setting: '配置',
|
||||
operatorStatusHelper: '此操作將{0}Docker 服務,是否繼續?',
|
||||
|
||||
@@ -632,8 +632,8 @@ const message = {
|
||||
emptyUser: '为空时,将使用容器默认的用户登录',
|
||||
privileged: '特权模式',
|
||||
privilegedHelper: '允许容器在主机上执行某些特权操作,可能会增加容器风险,谨慎开启!',
|
||||
editComposeHelper: '菜单中手动输入的环境变量会覆盖原有的同名变量,如果不存在则新增',
|
||||
|
||||
editComposeHelper:
|
||||
'注意:设置的环境变量会默认写入 1panel.env 文件。\n如需在容器中使用这些参数,还需在 compose 文件中手动添加 env_file 引用。',
|
||||
upgradeHelper: '仓库名称/镜像名称:镜像版本',
|
||||
upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?',
|
||||
oldImage: '当前镜像',
|
||||
@@ -743,7 +743,7 @@ const message = {
|
||||
composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下',
|
||||
deleteFile: '删除文件',
|
||||
allDelete: '彻底删除',
|
||||
deleteComposeHelper: '1. 删除容器编排记录 \n2. 删除容器编排的所有文件,包括配置文件和持久化文件',
|
||||
deleteComposeHelper: '删除容器编排的所有文件,包括配置文件和持久化文件,请谨慎操作!',
|
||||
deleteCompose: '" 删除此编排',
|
||||
apps: '应用商店',
|
||||
local: '本地',
|
||||
@@ -762,6 +762,7 @@ const message = {
|
||||
up: '启动',
|
||||
composeDetailHelper: '该 compose 为 1Panel 编排外部创建。暂不支持启停操作。',
|
||||
composeOperatorHelper: '将对 {0} 进行 {1} 操作,是否继续?',
|
||||
composeDownHelper: '将停止并删除 {0} 编排下所有容器及网络,是否继续?',
|
||||
|
||||
setting: '配置',
|
||||
operatorStatusHelper: '此操作将{0}Docker 服务,是否继续?',
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface GlobalState {
|
||||
defaultNetwork: string;
|
||||
|
||||
isProductPro: boolean;
|
||||
isTrial: boolean;
|
||||
productProExpires: number;
|
||||
|
||||
errStatus: string;
|
||||
|
||||
@@ -38,6 +38,7 @@ const GlobalStore = defineStore({
|
||||
defaultNetwork: 'all',
|
||||
|
||||
isProductPro: false,
|
||||
isTrial: false,
|
||||
productProExpires: 0,
|
||||
|
||||
errStatus: '',
|
||||
|
||||
@@ -61,6 +61,7 @@ export async function loadProductProFromDB() {
|
||||
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
|
||||
if (globalStore.isProductPro) {
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
globalStore.isTrial = res.data.trial;
|
||||
}
|
||||
}
|
||||
switchTheme();
|
||||
@@ -81,6 +82,7 @@ export async function getXpackSettingForTheme() {
|
||||
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
|
||||
if (globalStore.isProductPro) {
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
globalStore.isTrial = res.data.trial;
|
||||
}
|
||||
if (!globalStore.isProductPro) {
|
||||
globalStore.isProductPro = false;
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
placeholder="#Define or paste the content of your docker-compose file here"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="width: 100%; height: calc(100vh - 376px)"
|
||||
style="width: 100%; height: calc(100vh - 400px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
@@ -94,6 +94,18 @@
|
||||
v-model="form.envStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
<span class="input-help whitespace-break-spaces">{{ $t('container.editComposeHelper') }}</span>
|
||||
<codemirror
|
||||
v-model="form.envFileContent"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
></codemirror>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -153,6 +165,7 @@ const form = reactive({
|
||||
template: null as number,
|
||||
env: [],
|
||||
envStr: '',
|
||||
envFileContent: `env_file:\n - 1panel.env`,
|
||||
});
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.imageName],
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<el-form ref="deleteForm" v-loading="loading">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteFile" :label="$t('container.allDelete')" />
|
||||
<el-checkbox v-model="deleteFile" :label="$t('container.deleteFile')" />
|
||||
<span class="input-help whitespace-break-spaces">
|
||||
{{ $t('container.deleteComposeHelper') }}
|
||||
</span>
|
||||
@@ -69,7 +69,7 @@ const submit = async () => {
|
||||
let params = {
|
||||
name: composeName.value,
|
||||
path: composePath.value,
|
||||
operation: 'down',
|
||||
operation: 'delete',
|
||||
withFile: deleteFile.value,
|
||||
};
|
||||
await composeOperator(params)
|
||||
|
||||
@@ -143,7 +143,6 @@ const dialogContainerLogRef = ref();
|
||||
|
||||
const opRef = ref();
|
||||
|
||||
const emit = defineEmits<{ (e: 'back'): void }>();
|
||||
interface DialogProps {
|
||||
createdBy: string;
|
||||
name: string;
|
||||
@@ -260,15 +259,18 @@ const onOperate = async (op: string) => {
|
||||
};
|
||||
|
||||
const onComposeOperate = async (operation: string) => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('container.composeOperatorHelper', [composeName.value, i18n.global.t('container.' + operation)]),
|
||||
i18n.global.t('container.' + operation),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
},
|
||||
).then(async () => {
|
||||
let mes =
|
||||
operation === 'down'
|
||||
? i18n.global.t('container.composeDownHelper', [composeName.value])
|
||||
: i18n.global.t('container.composeOperatorHelper', [
|
||||
composeName.value,
|
||||
i18n.global.t('container.' + operation),
|
||||
]);
|
||||
ElMessageBox.confirm(mes, i18n.global.t('container.' + operation), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
let params = {
|
||||
name: composeName.value,
|
||||
path: composePath.value,
|
||||
@@ -280,11 +282,7 @@ const onComposeOperate = async (operation: string) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
if (operation === 'down') {
|
||||
emit('back');
|
||||
} else {
|
||||
search();
|
||||
}
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
placeholder="#Define or paste the content of your docker-compose file here"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="width: 100%; height: calc(100vh - 175px)"
|
||||
:style="{ width: '100%', height: `calc(100vh - ${loadHeight()})` }"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
@@ -28,15 +28,34 @@
|
||||
v-model="content"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.env')" prop="environmentStr">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
:rows="3"
|
||||
v-model="environmentStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
<span class="input-help">{{ $t('container.editComposeHelper') }}</span>
|
||||
<div v-if="createdBy === '1Panel'">
|
||||
<el-form-item
|
||||
:label="$t('container.env')"
|
||||
prop="environmentStr"
|
||||
v-if="createdBy === '1Panel'"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
:rows="3"
|
||||
v-model="environmentStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
<span class="input-help whitespace-break-spaces">
|
||||
{{ $t('container.editComposeHelper') }}
|
||||
</span>
|
||||
<codemirror
|
||||
v-model="envFileContent"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
></codemirror>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -64,6 +83,7 @@ import { MsgSuccess } from '@/utils/message';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { ElForm } from 'element-plus';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
const loading = ref(false);
|
||||
const composeVisible = ref(false);
|
||||
const extensions = [javascript(), oneDark];
|
||||
@@ -71,6 +91,9 @@ const path = ref();
|
||||
const content = ref();
|
||||
const name = ref();
|
||||
const environmentStr = ref();
|
||||
const environmentEnv = ref();
|
||||
const createdBy = ref();
|
||||
const envFileContent = `env_file:\n - 1panel.env`;
|
||||
|
||||
const onSubmitEdit = async () => {
|
||||
const param = {
|
||||
@@ -78,6 +101,7 @@ const onSubmitEdit = async () => {
|
||||
path: path.value,
|
||||
content: content.value,
|
||||
env: environmentStr.value,
|
||||
createdBy: createdBy.value,
|
||||
};
|
||||
if (environmentStr.value != undefined) {
|
||||
param.env = environmentStr.value.split('\n');
|
||||
@@ -88,6 +112,7 @@ const onSubmitEdit = async () => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
composeVisible.value = false;
|
||||
emit('search');
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
@@ -98,14 +123,22 @@ interface DialogProps {
|
||||
name: string;
|
||||
path: string;
|
||||
content: string;
|
||||
env: Array<string>;
|
||||
envStr: string;
|
||||
createdBy: string;
|
||||
}
|
||||
|
||||
const loadHeight = () => {
|
||||
return createdBy.value === '1Panel' ? '300px' : '200px';
|
||||
};
|
||||
const acceptParams = (props: DialogProps): void => {
|
||||
composeVisible.value = true;
|
||||
path.value = props.path;
|
||||
name.value = props.name;
|
||||
content.value = props.content;
|
||||
environmentStr.value = '';
|
||||
createdBy.value = props.createdBy;
|
||||
environmentEnv.value = props.env || [];
|
||||
environmentStr.value = environmentEnv.value.join('\n');
|
||||
};
|
||||
const handleClose = () => {
|
||||
composeVisible.value = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div v-show="isOnDetail">
|
||||
<ComposeDetail @back="backList" ref="composeDetailRef" />
|
||||
<ComposeDetail ref="composeDetailRef" />
|
||||
</div>
|
||||
<el-card v-if="dockerStatus != 'Running'" class="mask-prompt">
|
||||
<span>{{ $t('container.serviceUnavailable') }}</span>
|
||||
@@ -83,7 +83,7 @@
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<EditDialog ref="dialogEditRef" />
|
||||
<EditDialog @search="search" ref="dialogEditRef" />
|
||||
<CreateDialog @search="search" ref="dialogRef" />
|
||||
<DeleteDialog @search="search" ref="dialogDelRef" />
|
||||
</div>
|
||||
@@ -185,10 +185,6 @@ const getContainerStatus = (containers) => {
|
||||
return i18n.global.t('container.running') + ` (${runningCount}/${totalCount})`;
|
||||
}
|
||||
};
|
||||
const backList = async () => {
|
||||
isOnDetail.value = false;
|
||||
search();
|
||||
};
|
||||
|
||||
const dialogRef = ref();
|
||||
const onOpenDialog = async () => {
|
||||
@@ -211,6 +207,8 @@ const onEdit = async (row: Container.ComposeInfo) => {
|
||||
name: row.name,
|
||||
path: row.path,
|
||||
content: res.data,
|
||||
env: row.env,
|
||||
createdBy: row.createdBy,
|
||||
};
|
||||
dialogEditRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
@@ -369,7 +369,7 @@ const dialogUpgradeRef = ref();
|
||||
const dialogCommitRef = ref();
|
||||
const dialogPortJumpRef = ref();
|
||||
const opRef = ref();
|
||||
const includeAppStore = ref(true);
|
||||
const includeAppStore = ref();
|
||||
const columns = ref([]);
|
||||
|
||||
const dockerStatus = ref('Running');
|
||||
@@ -423,6 +423,7 @@ const dialogRenameRef = ref();
|
||||
const dialogPruneRef = ref();
|
||||
|
||||
const search = async (column?: any) => {
|
||||
localStorage.setItem('includeAppStore', includeAppStore.value ? 'true' : 'false');
|
||||
let filterItem = props.filters ? props.filters : '';
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
||||
@@ -767,6 +768,8 @@ const buttons = [
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
let includeItem = localStorage.getItem('includeAppStore');
|
||||
includeAppStore.value = !includeItem || includeItem === 'true';
|
||||
loadStatus();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -42,37 +42,39 @@
|
||||
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
|
||||
<CardWithHeader :header="$t('home.overview')">
|
||||
<CardWithHeader :header="$t('home.overview')" height="166px">
|
||||
<template #body>
|
||||
<div class="h-overview">
|
||||
<div>
|
||||
<span>{{ $t('menu.website') }}</span>
|
||||
<div class="count">
|
||||
<span @click="goRouter('/websites')">{{ baseInfo?.websiteNumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ $t('menu.database') }} - {{ $t('database.all') }}</span>
|
||||
<div class="count">
|
||||
<span @click="goRouter('/databases')">{{ baseInfo?.databaseNumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ $t('menu.cronjob') }}</span>
|
||||
<div class="count">
|
||||
<span @click="goRouter('/cronjobs')">
|
||||
{{ baseInfo?.cronjobNumber }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ $t('home.appInstalled') }}</span>
|
||||
<div class="count">
|
||||
<span @click="goRouter('/apps/installed')">
|
||||
{{ baseInfo?.appInstalledNumber }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<span>{{ $t('menu.website') }}</span>
|
||||
<div class="count">
|
||||
<span @click="goRouter('/websites')">{{ baseInfo?.websiteNumber }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ $t('menu.database') }} - {{ $t('database.all') }}</span>
|
||||
<div class="count">
|
||||
<span @click="goRouter('/databases')">{{ baseInfo?.databaseNumber }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ $t('menu.cronjob') }}</span>
|
||||
<div class="count">
|
||||
<span @click="goRouter('/cronjobs')">
|
||||
{{ baseInfo?.cronjobNumber }}
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span>{{ $t('home.appInstalled') }}</span>
|
||||
<div class="count">
|
||||
<span @click="goRouter('/apps/installed')">
|
||||
{{ baseInfo?.appInstalledNumber }}
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
</CardWithHeader>
|
||||
@@ -83,44 +85,42 @@
|
||||
</CardWithHeader>
|
||||
<CardWithHeader :header="$t('menu.monitor')" style="margin-top: 20px; margin-bottom: 20px">
|
||||
<template #header-r>
|
||||
<div
|
||||
class="float-right flex flex-col items-end flex-wrap sm:items-center gap-2 sm:flex-row sm:gap-4"
|
||||
<el-radio-group
|
||||
style="float: right; margin-left: 5px"
|
||||
v-model="chartOption"
|
||||
@change="changeOption"
|
||||
>
|
||||
<el-radio-group v-model="chartOption" @change="changeOption">
|
||||
<el-radio-button value="network">{{ $t('home.network') }}</el-radio-button>
|
||||
<el-radio-button value="io">{{ $t('home.io') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div>
|
||||
<el-select
|
||||
v-if="chartOption === 'network'"
|
||||
@change="onLoadBaseInfo(false, 'network')"
|
||||
v-model="searchInfo.netOption"
|
||||
class="p-w-200 float-right"
|
||||
>
|
||||
<template #prefix>{{ $t('home.networkCard') }}</template>
|
||||
<el-option
|
||||
v-for="item in netOptions"
|
||||
:key="item"
|
||||
:label="item == 'all' ? $t('commons.table.all') : item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-if="chartOption === 'io'"
|
||||
v-model="searchInfo.ioOption"
|
||||
@change="onLoadBaseInfo(false, 'io')"
|
||||
class="p-w-200 float-right"
|
||||
>
|
||||
<template #prefix>{{ $t('home.disk') }}</template>
|
||||
<el-option
|
||||
v-for="item in ioOptions"
|
||||
:key="item"
|
||||
:label="item == 'all' ? $t('commons.table.all') : item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<el-radio-button value="network">{{ $t('home.network') }}</el-radio-button>
|
||||
<el-radio-button value="io">{{ $t('home.io') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-select
|
||||
v-if="chartOption === 'network'"
|
||||
@change="onLoadBaseInfo(false, 'network')"
|
||||
v-model="searchInfo.netOption"
|
||||
class="p-w-200 float-right"
|
||||
>
|
||||
<template #prefix>{{ $t('home.networkCard') }}</template>
|
||||
<el-option
|
||||
v-for="item in netOptions"
|
||||
:key="item"
|
||||
:label="item == 'all' ? $t('commons.table.all') : item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-if="chartOption === 'io'"
|
||||
v-model="searchInfo.ioOption"
|
||||
@change="onLoadBaseInfo(false, 'io')"
|
||||
class="p-w-200 float-right"
|
||||
>
|
||||
<template #prefix>{{ $t('home.disk') }}</template>
|
||||
<el-option
|
||||
v-for="item in ioOptions"
|
||||
:key="item"
|
||||
:label="item == 'all' ? $t('commons.table.all') : item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<template #body>
|
||||
<div style="position: relative; margin-top: 20px">
|
||||
@@ -685,20 +685,14 @@ onBeforeUnmount(() => {
|
||||
<style lang="scss">
|
||||
.h-overview {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex-direction: row;
|
||||
|
||||
span:first-child {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
@media only screen and (max-width: 1300px) {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
span:first-child {
|
||||
font-size: 16px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,8 @@ const acceptParams = (props: BatchRoleProps) => {
|
||||
addForm.paths.push(file.path);
|
||||
});
|
||||
addForm.mode = Number.parseInt(String(props.files[0].mode), 8);
|
||||
addForm.group = props.files[0].group;
|
||||
addForm.user = props.files[0].user;
|
||||
addForm.group = props.files[0].group || props.files[0].gid;
|
||||
addForm.user = props.files[0].user || props.files[0].uid;
|
||||
addForm.sub = true;
|
||||
|
||||
mode.value = String(props.files[0].mode);
|
||||
|
||||
@@ -201,6 +201,7 @@ const search = async () => {
|
||||
hasLicense.value = true;
|
||||
if (globalStore.isProductPro) {
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
globalStore.isTrial = res.data.trial;
|
||||
}
|
||||
license.licenseName = res.data.licenseName;
|
||||
license.message = res.data.message;
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
<el-switch v-model="proxy.sni"></el-switch>
|
||||
<span class="input-help">{{ $t('website.sniHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="proxy_ssl_name" prop="proxySSLName" v-if="proxy.sni">
|
||||
<el-input v-model.trim="proxy.proxySSLName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.cacheTime')" prop="cacheTime" v-if="proxy.cache">
|
||||
<el-input v-model.number="proxy.cacheTime" maxlength="15">
|
||||
<template #append>
|
||||
@@ -47,7 +50,7 @@
|
||||
</el-form-item>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t('website.proxyPass')" prop="proxyPass">
|
||||
<el-form-item :label="$t('website.proxyPass')" prop="proxyAddress">
|
||||
<el-input
|
||||
v-model.trim="proxy.proxyAddress"
|
||||
:placeholder="$t('website.proxyHelper')"
|
||||
@@ -139,6 +142,7 @@ const rules = ref({
|
||||
cacheTime: [Rules.requiredInput, checkNumberRange(1, 65535)],
|
||||
proxyPass: [Rules.requiredInput],
|
||||
proxyHost: [Rules.requiredInput],
|
||||
proxyAddress: [Rules.requiredInput],
|
||||
});
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
@@ -159,6 +163,8 @@ const initData = (): Website.ProxyConfig => ({
|
||||
replaces: {},
|
||||
proxyAddress: '',
|
||||
proxyProtocol: 'http://',
|
||||
sni: false,
|
||||
proxySSLName: '$proxy_host',
|
||||
});
|
||||
let proxy = ref(initData());
|
||||
const replaces = ref<any>([]);
|
||||
|
||||
Reference in New Issue
Block a user