Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/app/api/v2/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (b *BaseApi) LoadAppLauncherOption(c *gin.Context) {
}

func (b *BaseApi) SyncAppLauncher(c *gin.Context) {
var req dto.SyncFromMaster
var req []dto.AppLauncherSync
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
Expand Down
4 changes: 4 additions & 0 deletions agent/app/dto/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ type DashboardCurrent struct {
ShotTime time.Time `json:"shotTime"`
}

type AppLauncherSync struct {
Key string `json:"key"`
}

type DiskInfo struct {
Path string `json:"path"`
Type string `json:"type"`
Expand Down
10 changes: 10 additions & 0 deletions agent/app/repo/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package repo

import (
"context"

"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"

Expand All @@ -19,6 +20,7 @@ type ITaskRepo interface {
Update(ctx context.Context, task *model.Task) error
UpdateRunningTaskToFailed() error
CountExecutingTask() (int64, error)
Delete(opts ...DBOption) error

WithByID(id string) DBOption
WithResourceID(id uint) DBOption
Expand Down Expand Up @@ -108,3 +110,11 @@ func (t TaskRepo) CountExecutingTask() (int64, error) {
err := getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Count(&count).Error
return count, err
}

func (u TaskRepo) Delete(opts ...DBOption) error {
db := global.TaskDB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.Task{}).Error
}
7 changes: 2 additions & 5 deletions agent/app/service/backup_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,13 @@ func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.Recor
var datas []dto.RecordFileSize
var wg sync.WaitGroup
for i := 0; i < len(list); i++ {
item := dto.RecordFileSize{ID: list[i].ID}
datas = append(datas, dto.RecordFileSize{ID: list[i].ID})
if val, ok := clientMap[fmt.Sprintf("%v", list[i].DownloadID)]; ok {
wg.Add(1)
go func(index int) {
item.Size, _ = val.client.Size(path.Join(val.backupPath, list[i].FilePath))
datas = append(datas, item)
datas[index].Size, _ = val.client.Size(path.Join(val.backupPath, list[i].FilePath))
wg.Done()
}(i)
} else {
datas = append(datas, item)
}
}
wg.Wait()
Expand Down
37 changes: 6 additions & 31 deletions agent/app/service/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ package service

import (
"encoding/json"
"fmt"
network "net"
"os"
"sort"
"strings"
"sync"
"time"

"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"

"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
Expand All @@ -33,7 +29,7 @@ import (
type DashboardService struct{}

type IDashboardService interface {
Sync(req dto.SyncFromMaster) error
Sync(req []dto.AppLauncherSync) error

LoadOsInfo() (*dto.OsInfo, error)
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
Expand All @@ -48,33 +44,12 @@ func NewIDashboardService() IDashboardService {
return &DashboardService{}
}

func (u *DashboardService) Sync(req dto.SyncFromMaster) error {
var launcherItem model.AppLauncher
if err := json.Unmarshal([]byte(req.Data), &launcherItem); err != nil {
return err
}
launcher, _ := launcherRepo.Get(settingRepo.WithByKey(req.Name))
switch req.Operation {
case "create":
if launcher.ID != 0 {
launcherItem.ID = launcher.ID
return launcherRepo.Save(&launcherItem)
}
return launcherRepo.Create(&launcherItem)
case "delete":
if launcher.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
return launcherRepo.Delete(repo.WithByID(launcher.ID))
case "update":
if launcher.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
launcherItem.ID = launcher.ID
return launcherRepo.Save(&launcherItem)
default:
return fmt.Errorf("not support such operation %s", req.Operation)
func (u *DashboardService) Sync(req []dto.AppLauncherSync) error {
var launchers []model.AppLauncher
for _, item := range req {
launchers = append(launchers, model.AppLauncher{Key: item.Key})
}
return launcherRepo.SyncAll(launchers)
}

func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code snippet is from a C# library for managing panels on an open-source software platform called 1Panel. There are a few points to note:

  1. The sync functions do not take arguments, which is not consistent with their current implementation.
  2. In the sync function implementations, there's too much duplicated code for creating models and saving them in different cases of operations like creation, update, delete. It would be beneficial to refactor this logic into smaller, more concise functions.

Here are some suggestions:
a. Refactor:

  • Simplify the loadOsInfo() function by extracting common functionality (e.g., loading settings) instead of duplicating code every time it needs to load a particular section or option in the OS information payload.

b. Functionality Improvements:

  • Clean up repetitive calls within each individual function.
  • Add comments where necessary to clarify what each function does without using a lot of text.
  • Ensure that all functions return appropriate errors if anything unexpected occurs.

c. Testing and Quality Assurance:

  • Implement unit tests for critical sections of your code to ensure correct behavior under various scenarios.
  • Conduct comprehensive integration testing across multiple platforms, environments, and applications before deploying.

d. Documentation:

  • Create detailed docstrings for both public API methods (like the Sync() method) and private helper functions. This will help other developers understand expected behavior and requirements, particularly when they implement these methods outside the original base package.

It is good practice to maintain consistency and efficiency throughout the development process but also adapt and grow over time. Good luck!

Expand Down
23 changes: 9 additions & 14 deletions agent/app/service/device_clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/docker/docker/api/types/filters"

"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
Expand All @@ -37,7 +38,6 @@ const (
uploadPath = "1panel/uploads"
downloadPath = "1panel/download"
logPath = "1panel/log"
taskPath = "1panel/task"
)

func (u *DeviceService) Scan() dto.CleanData {
Expand Down Expand Up @@ -254,18 +254,10 @@ func (u *DeviceService) Clean(req []dto.Clean) {
dropFileOrDir(path.Join(global.Dir.BaseDir, logPath, item.Name))
}
case "task_log":
pathItem := path.Join(global.Dir.BaseDir, taskPath, item.Name)
dropFileOrDir(path.Join(global.Dir.BaseDir, taskPath, item.Name))
pathItem := path.Join(global.Dir.BaseDir, logPath, item.Name)
dropFileOrDir(pathItem)
if len(item.Name) == 0 {
files, _ := os.ReadDir(pathItem)
if len(files) == 0 {
continue
}
for _, file := range files {
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByRecordFile(path.Join(pathItem, file.Name())))
}
} else {
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByRecordFile(pathItem))
_ = taskRepo.Delete(repo.WithByType(item.Name))
}
case "images":
dropImages()
Expand Down Expand Up @@ -506,8 +498,8 @@ func loadLogTree(fileOp fileUtils.FileOp) []dto.CleanTree {
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "system_log", Size: uint64(size), Children: list1, Type: "system_log", IsRecommend: true})

path2 := path.Join(global.Dir.BaseDir, taskPath)
list2 := loadTreeWithAllFile(false, path2, "task_log", path2, fileOp)
path2 := path.Join(global.Dir.BaseDir, logPath)
list2 := loadTreeWithDir(false, "task_log", path2, fileOp)
size2, _ := fileOp.GetDirSize(path2)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "task_log", Size: uint64(size2), Children: list2, Type: "task_log"})
return treeData
Expand Down Expand Up @@ -570,6 +562,9 @@ func loadTreeWithDir(isCheck bool, treeType, pathItem string, fileOp fileUtils.F
if (treeType == "old_upgrade" || treeType == "upgrade") && !strings.HasPrefix(file.Name(), "upgrade_2023") {
continue
}
if treeType == "task_log" && file.Name() == "ssl" {
continue
}
if file.IsDir() {
size, err := fileOp.GetDirSize(path.Join(pathItem, file.Name()))
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code appears to be well written but not extensive enough for detailed analysis of regularity, potential issues, or optimization. The main difference seems to relate to whether the directory "uploadPath" should include an extra slash after "/uploads". It might be helpful if more context is provided for proper review.

No significant problems were detected according to the checks performed.

Expand Down
34 changes: 13 additions & 21 deletions core/app/service/app_launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/utils/req_helper"
"github.com/1Panel-dev/1Panel/core/utils/xpack"
)

Expand Down Expand Up @@ -37,32 +38,23 @@ func (u *LauncherService) Search() ([]string, error) {

func (u *LauncherService) ChangeShow(req dto.SettingUpdate) error {
launcher, _ := launcherRepo.Get(repo.WithByKey(req.Key))
if req.Value == constant.StatusEnable {
if launcher.ID != 0 {
go syncLauncherToAgent(launcher, "create")
return nil
}
launcher.Key = req.Key
if err := launcherRepo.Create(&launcher); err != nil {
if req.Value == constant.StatusEnable && launcher.ID == 0 {
if err := launcherRepo.Create(&model.AppLauncher{Key: req.Key}); err != nil {
return err
}
go syncLauncherToAgent(launcher, "create")
return nil
}
if launcher.ID == 0 {
go syncLauncherToAgent(launcher, "delete")
return nil
}
if err := launcherRepo.Delete(repo.WithByKey(req.Key)); err != nil {
return err
if req.Value == constant.StatusDisable && launcher.ID != 0 {
if err := launcherRepo.Delete(repo.WithByKey(req.Key)); err != nil {
return err
}
}
go syncLauncherToAgent(launcher, "delete")
go syncLauncherToAgent()
return nil
}

func syncLauncherToAgent(launcher model.AppLauncher, operation string) {
itemData, _ := json.Marshal(launcher)
itemJson := dto.SyncToAgent{Name: launcher.Key, Operation: operation, Data: string(itemData)}
bodyItem, _ := json.Marshal(itemJson)
_ = xpack.RequestToAllAgent("/api/v2/backups/sync", http.MethodPost, bytes.NewReader((bodyItem)))
func syncLauncherToAgent() {
launchers, _ := launcherRepo.List()
itemData, _ := json.Marshal(launchers)
_, _ = req_helper.NewLocalClient("/api/v2/dashboard/app/launcher/sync", http.MethodPost, bytes.NewReader((itemData)))
_ = xpack.RequestToAllAgent("/api/v2/dashboard/app/launcher/sync", http.MethodPost, bytes.NewReader((itemData)))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key difference between the two codes is that one uses req.Value which should be an enum of status, while the other does not use it at all. This causes some unexpected behavior if we try to set launch state using the wrong value (constant.StatusDisable instead of constant.StatusEnable). There's also a missing line return fmt.Errorf("Unsupported operation %s for agent '%v'.", operation, req) to handle unsupported operation.

This can cause issues with agents responding to requests incorrectly depending on what they actually support since different enums may correspond to differing statuses like 'create' vs 'delete'.

Optimization suggestion:

  • Instead of using req.Value, define an enum for easier management of launcher states (e.g., const.Enable etc.). Also remove unused lines related to this field.

For example:

if req.Value == constant.StatusEnable && launcher.ID != 0 {
	if err := launcherRepo.Create(&model.AppLauncher{Key: req.Key});
	else { // Add more logic here based on desired outcome.
		go syncLauncherToAgent(launcher, "update"); // or any other appropriate function for update operations
		return nil
	}
} else if req.Value == constant.StatusDisable && launcher.ID != 0 {
	// Code for disabling launcher goes here
} else { // Handle cases where value is invalid
	return fmt.Errorf("Unsupported operation %q for agent '%s'.", req.Value.String(), launcherKey)
}

1 change: 1 addition & 0 deletions frontend/src/components/status/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const getType = (status: string) => {
case 'disable':
case 'unhealthy':
case 'failed':
case 'lost':
return 'danger';
case 'paused':
case 'exited':
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3185,9 +3185,8 @@ const message = {
'Detected that there is already 1panel service on this node. Adding this node will use the original service port and installation directory of 1panel. Do you want to continue?',
coreExist:
'Detected that there is already 1panel-core service on this node. Unable to add this node, please check and try again!',
agentExist: 'Detected that there is already 1panel-agent service on this node',
forceAdd: 'Force Add',
forceAddHelper: 'Force add will forcibly replace the 1panel-agent service on this node',
agentExist:
'Detected that the 1panel-agent service already exists on this node. Continuing to add will retain the node data and only replace the 1panel-agent service. Do you want to continue?',
reinstallHelper: 'Reinstall node {0}, do you want to continue?',
unhealthyCheck: 'Abnormal Check',
fixOperation: 'Fix Operation',
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2997,9 +2997,8 @@ const message = {
'このノードに既に1panelサービスが存在します。このノードを追加すると、1panelの元のサービスポートとインストールディレクトリが使用されます。続行しますか?',
coreExist:
'このノードに既に1panel-coreサービスが存在します。このノードを追加できません。確認して再試行してください!',
agentExist: 'このノードに既に1panel-agentサービスが存在します',
forceAdd: '強制追加',
forceAddHelper: '強制追加は、このノードの1panel-agentサービスを強制的に置き換えます',
agentExist:
'このノードに1panel-agentサービスが既に存在することが検出されました。追加を続行すると、ノードデータは保持され、1panel-agentサービスのみが置き換えられます。続行しますか?',
reinstallHelper: 'ノード{0}を再インストールします。続行しますか?',
unhealthyCheck: '異常チェック',
fixOperation: '修正操作',
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2946,9 +2946,8 @@ const message = {
'이 노드에 이미 1panel 서비스가 존재합니다. 이 노드를 추가하면 1panel의 원래 서비스 포트와 설치 디렉토리를 사용합니다. 계속하시겠습니까?',
coreExist:
'이 노드에 이미 1panel-core 서비스가 존재합니다. 이 노드를 추가할 수 없습니다. 확인 후 다시 시도하십시오!',
agentExist: '이 노드에 이미 1panel-agent 서비스가 존재합니다',
forceAdd: '강제 추가',
forceAddHelper: '강제 추가는 이 노드의 1panel-agent 서비스를 강제로 교체합니다',
agentExist:
'이 노드에 1panel-agent 서비스가 이미 존재하는 것으로 감지되었습니다. 추가를 계속하면 노드 데이터는 유지되고 1panel-agent 서비스만 교체됩니다. 계속하시겠습니까?',
reinstallHelper: '노드 {0}를 재설치합니다. 계속하시겠습니까?',
unhealthyCheck: '비정상 체크',
fixOperation: '수정 작업',
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/lang/modules/ms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3065,9 +3065,8 @@ const message = {
'Dikesan bahawa terdapat perkhidmatan 1panel yang sudah ada pada nod ini. Menambah nod ini akan menggunakan port dan direktori pemasangan perkhidmatan asal 1panel. Adakah anda ingin meneruskan?',
coreExist:
'Dikesan bahawa terdapat perkhidmatan 1panel-core yang sudah ada pada nod ini. Tidak dapat menambah nod ini, sila semak dan cuba lagi!',
agentExist: 'Dikesan bahawa terdapat perkhidmatan 1panel-agent yang sudah ada pada nod ini',
forceAdd: 'Tambah Secara Paksa',
forceAddHelper: 'Tambah secara paksa akan menggantikan perkhidmatan 1panel-agent pada nod ini',
agentExist:
'Terbukti bahawa perkhidmatan 1panel-agent sudah wujud pada nod ini. Melanjutkan penambahan akan mengekalkan data nod dan hanya menggantikan perkhidmatan 1panel-agent. Adakah anda ingin meneruskan?',
reinstallHelper: 'Pasang semula nod {0}, adakah anda ingin meneruskan?',
unhealthyCheck: 'Pemeriksaan Tidak Normal',
fixOperation: 'Operasi Pembetulan',
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/lang/modules/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3069,9 +3069,8 @@ const message = {
'Detectado que já existe um serviço 1panel neste nó. Adicionar este nó usará a porta e o diretório de instalação do serviço original do 1panel. Deseja continuar?',
coreExist:
'Detectado que já existe um serviço 1panel-core neste nó. Não é possível adicionar este nó, por favor verifique e tente novamente!',
agentExist: 'Detectado que já existe um serviço 1panel-agent neste nó',
forceAdd: 'Adicionar Forçadamente',
forceAddHelper: 'Adicionar forçadamente substituirá o serviço 1panel-agent neste nó',
agentExist:
'Detectado que o serviço 1panel-agent já existe neste nó. Continuar a adicionar irá manter os dados do nó e apenas substituir o serviço 1panel-agent. Você deseja continuar?',
reinstallHelper: 'Reinstalar o nó {0}, deseja continuar?',
unhealthyCheck: 'Verificação Anormal',
fixOperation: 'Operação de Correção',
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/lang/modules/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3058,9 +3058,8 @@ const message = {
'Обнаружено, что на этом узле уже существует служба 1panel. Добавление этого узла будет использовать оригинальный порт и каталог установки службы 1panel. Вы хотите продолжить?',
coreExist:
'Обнаружено, что на этом узле уже существует служба 1panel-core. Невозможно добавить этот узел, пожалуйста, проверьте и попробуйте снова!',
agentExist: 'Обнаружено, что на этом узле уже существует служба 1panel-agent',
forceAdd: 'Принудительное добавление',
forceAddHelper: 'Принудительное добавление заменит службу 1panel-agent на этом узле',
agentExist:
'Обнаружено, что служба 1panel-agent уже существует на этом узле. Продолжение добавления сохранит данные узла и только заменит службу 1panel-agent. Вы хотите продолжить?',
reinstallHelper: 'Переустановить узел {0}, вы хотите продолжить?',
unhealthyCheck: 'Проверка на неисправности',
fixOperation: 'Решение проблемы',
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/lang/modules/zh-Hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2948,9 +2948,8 @@ const message = {
panelExist:
'已檢測到該節點上已存在 1panel 服務,新增該節點將沿用 1panel 原服務的埠號及安裝目錄,是否繼續?',
coreExist: '已檢測到該節點上已存在 1panel-core 服務,無法新增該節點,請檢查後再試!',
agentExist: '已檢測到該節點上已存在 1panel-agent 服務',
forceAdd: '強制新增',
forceAddHelper: '強制新增將強制替換該節點上的 1panel-agent 服務',
agentExist:
'檢測到該節點上已存在 1panel-agent 服務,繼續添加將保留該節點數據,僅替換 1panel-agent 服務,是否繼續?',
reinstallHelper: '重新安裝節點 {0},是否繼續?',
unhealthyCheck: '異常檢查',
fixOperation: '修復方案',
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2935,9 +2935,8 @@ const message = {
panelExist:
'检测到该节点上已存在 1panel 服务,添加该节点将沿用 1panel 原服务的端口以及安装目录,是否继续?',
coreExist: '检测到该节点上已存在 1panel-core 服务,无法添加该节点,请检查后重试!',
agentExist: '检测到该节点上已存在 1panel-agent 服务',
forceAdd: '强制添加',
forceAddHelper: '强制添加,将强制替换该节点上的 1panel-agent 服务',
agentExist:
'检测到该节点上已存在 1panel-agent 服务,继续添加将保留该节点数据,仅替换 1panel-agent 服务,是否继续?',
reinstallHelper: '重新安装节点 {0}, 是否继续?',
unhealthyCheck: '异常检查',
fixOperation: '修复方案',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/container/container/operate/index.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<LayoutContent
back-name="Container"
back-name="ContainerItem"
:title="isCreate ? $t('container.create') : $t('commons.button.edit') + ' - ' + form.name"
>
<template #prompt>
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/views/cronjob/operate/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ const acceptParams = (params: DialogProps): void => {
list = dialogData.value.rowData.sourceAccountIDs.split(',');
for (const item of list) {
if (item) {
dialogData.value.rowData.sourceAccountItems.push(item);
dialogData.value.rowData.sourceAccountItems.push(Number(item));
}
}
}
Expand Down Expand Up @@ -678,6 +678,10 @@ const verifyScript = (rule: any, value: any, callback: any) => {

const verifySpec = (rule: any, value: any, callback: any) => {
if (dialogData.value.rowData!.specCustom) {
if (dialogData.value.rowData!.specs.length === 0) {
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
return;
}
for (let i = 0; i < dialogData.value.rowData!.specs.length; i++) {
if (dialogData.value.rowData!.specs[i]) {
continue;
Expand Down Expand Up @@ -803,7 +807,6 @@ const rules = reactive({
{ validator: verifySpec, trigger: 'blur', required: true },
{ validator: verifySpec, trigger: 'change', required: true },
],
specCustom: [Rules.requiredSelect],

script: [{ validator: verifyScript, trigger: 'blur', required: true }],
containerName: [Rules.requiredSelect],
Expand Down
Loading
Loading