mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-06 05:52:19 +08:00
支持限制用户可选择的系统
This commit is contained in:
@@ -240,6 +240,12 @@ func createContainer(w http.ResponseWriter, r *http.Request) {
|
||||
jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Template is not enabled or downloaded"})
|
||||
return
|
||||
}
|
||||
if ids, err := normalizeAllowedImageIDs(cfg.AllowedImageIDs); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: err.Error()})
|
||||
return
|
||||
} else {
|
||||
cfg.AllowedImageIDs = ids
|
||||
}
|
||||
if cfg.VCPU <= 0 {
|
||||
cfg.VCPU = 1
|
||||
}
|
||||
|
||||
@@ -22,12 +22,13 @@ import (
|
||||
)
|
||||
|
||||
type HostInfo struct {
|
||||
CPU CpuInfo `json:"cpu"`
|
||||
RAM MemoryInfo `json:"ram"`
|
||||
Disk DiskInfo `json:"disk"`
|
||||
Network NetworkInfo `json:"network"`
|
||||
DiskIO DiskIOInfo `json:"disk_io"`
|
||||
Load LoadInfo `json:"load"`
|
||||
CPU CpuInfo `json:"cpu"`
|
||||
RAM MemoryInfo `json:"ram"`
|
||||
Disk DiskInfo `json:"disk"`
|
||||
Network NetworkInfo `json:"network"`
|
||||
DiskIO DiskIOInfo `json:"disk_io"`
|
||||
Load LoadInfo `json:"load"`
|
||||
Runtime HostRuntimeProbe `json:"runtime"`
|
||||
}
|
||||
|
||||
type HostProbeReport struct {
|
||||
@@ -247,9 +248,32 @@ func getHostInfo() HostInfo {
|
||||
info.CPU.Usage = getCPUUsage()
|
||||
info.Network, info.DiskIO = getHostRates()
|
||||
info.Load = getLoadInfo()
|
||||
info.Runtime = detectRuntimeProbeQuick()
|
||||
return info
|
||||
}
|
||||
|
||||
func detectRuntimeProbeQuick() HostRuntimeProbe {
|
||||
devKVM := fileExists("/dev/kvm")
|
||||
nested, detail := detectNestedVirtualization()
|
||||
lxcOK := commandExists("lxc-create")
|
||||
kvmSupportedArch := runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64"
|
||||
kvmOK := kvmSupportedArch && devKVM && commandExists("virsh") && commandExists(kvmQEMUCheckKey())
|
||||
probe := HostRuntimeProbe{
|
||||
LXCAvailable: lxcOK,
|
||||
KVMAvailable: kvmOK,
|
||||
DevKVM: devKVM,
|
||||
NestedVirtualization: nested,
|
||||
NestedDetail: detail,
|
||||
SupportMode: "unsupported",
|
||||
}
|
||||
if probe.KVMAvailable {
|
||||
probe.SupportMode = "kvm_lxc"
|
||||
} else if probe.LXCAvailable {
|
||||
probe.SupportMode = "lxc_only"
|
||||
}
|
||||
return probe
|
||||
}
|
||||
|
||||
func getMemoryInfo() MemoryInfo {
|
||||
f, err := os.Open("/proc/meminfo")
|
||||
if err != nil {
|
||||
|
||||
@@ -541,6 +541,24 @@ func HandleEnabledImages(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
runtime := runtimeFromRequest(r.URL.Query().Get("type"))
|
||||
enabledSet := getEnabledImageSet()
|
||||
var subUser *config.SubUser
|
||||
var targetContainer *config.Container
|
||||
currentImageIDs := map[string]bool{}
|
||||
if isSubUserRequest(r) {
|
||||
subUser = subUserFromRequest(r)
|
||||
if identifier := r.URL.Query().Get("container"); identifier != "" {
|
||||
targetContainer = containerByIdentifier(identifier)
|
||||
if targetContainer == nil || !isContainerAllowedForRequest(r, identifier) {
|
||||
jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Access denied to this container"})
|
||||
return
|
||||
}
|
||||
currentImageIDs[targetContainer.Template] = true
|
||||
} else {
|
||||
for _, id := range subUserCurrentImageIDs(subUser) {
|
||||
currentImageIDs[id] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]map[string]string, 0)
|
||||
if runtime == config.VirtualizationKVM {
|
||||
@@ -549,7 +567,10 @@ func HandleEnabledImages(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
for _, t := range kvm.GetImages() {
|
||||
if downloaded, _ := kvm.ImageDownloadedInfo(t.ID); enabledSet[t.ID] && downloaded {
|
||||
if subUser != nil && !isImageAllowedForSubUser(subUser, targetContainer, t.ID) {
|
||||
continue
|
||||
}
|
||||
if downloaded, _ := kvm.ImageDownloadedInfo(t.ID); downloaded && (enabledSet[t.ID] || currentImageIDs[t.ID]) {
|
||||
result = append(result, map[string]string{
|
||||
"id": t.ID, "name": t.Name, "distro": t.Distro, "release": t.Release, "arch": t.Arch,
|
||||
"description": t.Description, "type": config.VirtualizationKVM, "desktop": t.Desktop,
|
||||
@@ -558,7 +579,10 @@ func HandleEnabledImages(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
} else {
|
||||
for _, t := range lxc.GetTemplates() {
|
||||
if enabledSet[t.ID] && isImageDownloaded(t.Distro, t.Release, t.Arch) {
|
||||
if subUser != nil && !isImageAllowedForSubUser(subUser, targetContainer, t.ID) {
|
||||
continue
|
||||
}
|
||||
if downloaded := isImageDownloaded(t.Distro, t.Release, t.Arch); downloaded && (enabledSet[t.ID] || currentImageIDs[t.ID]) {
|
||||
result = append(result, map[string]string{
|
||||
"id": t.ID, "name": t.Name, "distro": t.Distro, "release": t.Release, "arch": t.Arch,
|
||||
"variant": t.Variant, "description": t.Description, "type": config.VirtualizationLXC,
|
||||
@@ -574,6 +598,42 @@ func isTemplateEnabledAndDownloaded(templateID string) bool {
|
||||
return isImageEnabledAndDownloaded(templateID, runtimeFromTemplateID(templateID))
|
||||
}
|
||||
|
||||
func imageTemplateExists(templateID string) bool {
|
||||
return lxc.FindTemplate(templateID) != nil || kvm.FindImage(templateID) != nil
|
||||
}
|
||||
|
||||
func isImageDownloadedForRuntime(templateID string, runtime string) bool {
|
||||
runtime = runtimeFromRequest(runtime)
|
||||
if runtime == config.VirtualizationKVM {
|
||||
if !hostKVMAvailable() {
|
||||
return false
|
||||
}
|
||||
image := kvm.FindImage(templateID)
|
||||
if image == nil {
|
||||
return false
|
||||
}
|
||||
downloaded, _ := kvm.ImageDownloadedInfo(image.ID)
|
||||
return downloaded
|
||||
}
|
||||
tmpl := lxc.FindTemplate(templateID)
|
||||
if tmpl == nil {
|
||||
return false
|
||||
}
|
||||
return isImageDownloaded(tmpl.Distro, tmpl.Release, tmpl.Arch)
|
||||
}
|
||||
|
||||
func isTemplateAvailableForRequest(r *http.Request, c *config.Container, templateID string, runtime string) bool {
|
||||
if isSubUserRequest(r) {
|
||||
if !isTemplateAllowedForRequest(r, c, templateID) {
|
||||
return false
|
||||
}
|
||||
if c != nil && c.Template == templateID {
|
||||
return isImageDownloadedForRuntime(templateID, runtime)
|
||||
}
|
||||
}
|
||||
return isImageEnabledAndDownloaded(templateID, runtime)
|
||||
}
|
||||
|
||||
func isImageEnabledAndDownloaded(templateID string, runtime string) bool {
|
||||
runtime = runtimeFromRequest(runtime)
|
||||
if runtime == config.VirtualizationKVM {
|
||||
|
||||
+230
-41
@@ -22,24 +22,30 @@ func generateRandomStr(length int) string {
|
||||
}
|
||||
|
||||
type subUserResponse struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password,omitempty"`
|
||||
ContainerNames []string `json:"container_names"`
|
||||
ContainerUUIDs []string `json:"container_uuids,omitempty"`
|
||||
AccessCode string `json:"access_code"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password,omitempty"`
|
||||
ContainerNames []string `json:"container_names"`
|
||||
ContainerUUIDs []string `json:"container_uuids,omitempty"`
|
||||
AllowedImageIDs []string `json:"allowed_image_ids,omitempty"`
|
||||
ImageLimitConfigured bool `json:"image_limit_configured,omitempty"`
|
||||
CurrentImageIDs []string `json:"current_image_ids,omitempty"`
|
||||
AccessCode string `json:"access_code"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
func newSubUserResponse(su config.SubUser, password string) subUserResponse {
|
||||
return subUserResponse{
|
||||
ID: su.ID,
|
||||
Username: su.Username,
|
||||
Password: password,
|
||||
ContainerNames: su.ContainerNames,
|
||||
ContainerUUIDs: su.ContainerUUIDs,
|
||||
AccessCode: su.AccessCode,
|
||||
CreatedAt: su.CreatedAt,
|
||||
ID: su.ID,
|
||||
Username: su.Username,
|
||||
Password: password,
|
||||
ContainerNames: su.ContainerNames,
|
||||
ContainerUUIDs: su.ContainerUUIDs,
|
||||
AllowedImageIDs: effectiveSubUserAllowedImageIDs(&su),
|
||||
ImageLimitConfigured: su.ImageLimitConfigured,
|
||||
CurrentImageIDs: subUserCurrentImageIDs(&su),
|
||||
AccessCode: su.AccessCode,
|
||||
CreatedAt: su.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +100,10 @@ func HandleSubUserCreate(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
su.ContainerNames = appendUniqueString(su.ContainerNames, containerName)
|
||||
su.ContainerUUIDs = appendUniqueString(su.ContainerUUIDs, c.UUID)
|
||||
if !su.ImageLimitConfigured && len(su.AllowedImageIDs) == 0 {
|
||||
su.AllowedImageIDs = effectiveContainerAllowedImageIDs(c)
|
||||
su.ImageLimitConfigured = true
|
||||
}
|
||||
config.SaveConfig()
|
||||
jsonResponse(w, http.StatusOK, APIResponse{
|
||||
Success: true,
|
||||
@@ -114,14 +124,16 @@ func HandleSubUserCreate(w http.ResponseWriter, r *http.Request) {
|
||||
accessCode := generateRandomStr(8)
|
||||
|
||||
subUser := config.SubUser{
|
||||
ID: "sub-" + generateRandomStr(8),
|
||||
Username: username,
|
||||
Password: password,
|
||||
PassHash: string(hash),
|
||||
ContainerNames: []string{containerName},
|
||||
ContainerUUIDs: []string{c.UUID},
|
||||
AccessCode: accessCode,
|
||||
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||
ID: "sub-" + generateRandomStr(8),
|
||||
Username: username,
|
||||
Password: password,
|
||||
PassHash: string(hash),
|
||||
ContainerNames: []string{containerName},
|
||||
ContainerUUIDs: []string{c.UUID},
|
||||
AllowedImageIDs: effectiveContainerAllowedImageIDs(c),
|
||||
ImageLimitConfigured: true,
|
||||
AccessCode: accessCode,
|
||||
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
config.AppConfig.SubUsers = append(config.AppConfig.SubUsers, subUser)
|
||||
@@ -306,6 +318,155 @@ func requestAllowedContainers(r *http.Request) (subUserAccess, bool) {
|
||||
return subUserAllowedContainers(r)
|
||||
}
|
||||
|
||||
func subUserFromRequest(r *http.Request) *config.SubUser {
|
||||
username := ""
|
||||
if ctx, ok := authContextFromRequest(r); ok && ctx.Type == authTypeSubUser {
|
||||
username = ctx.Username
|
||||
}
|
||||
if username == "" {
|
||||
if claims, ok := claimsFromRequest(r); ok {
|
||||
username, _ = claims["sub_user"].(string)
|
||||
}
|
||||
}
|
||||
if username == "" {
|
||||
return nil
|
||||
}
|
||||
for i := range config.AppConfig.SubUsers {
|
||||
if config.AppConfig.SubUsers[i].Username == username {
|
||||
return &config.AppConfig.SubUsers[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeAllowedImageIDs(ids []string) ([]string, error) {
|
||||
seen := map[string]bool{}
|
||||
result := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" || seen[id] {
|
||||
continue
|
||||
}
|
||||
if !imageTemplateExists(id) {
|
||||
return nil, fmt.Errorf("unknown image template: %s", id)
|
||||
}
|
||||
seen[id] = true
|
||||
result = append(result, id)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func isTemplateAllowedForRequest(r *http.Request, c *config.Container, templateID string) bool {
|
||||
if !isSubUserRequest(r) {
|
||||
return true
|
||||
}
|
||||
return isImageAllowedForSubUser(subUserFromRequest(r), c, templateID)
|
||||
}
|
||||
|
||||
func isImageAllowedForSubUser(su *config.SubUser, c *config.Container, templateID string) bool {
|
||||
if su == nil || strings.TrimSpace(templateID) == "" {
|
||||
return false
|
||||
}
|
||||
for _, id := range effectiveSubUserAllowedImageIDs(su) {
|
||||
if id == templateID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func effectiveContainerAllowedImageIDs(c *config.Container) []string {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
if c.ImageLimitConfigured || len(c.AllowedImageIDs) > 0 {
|
||||
return cleanImageIDList(c.AllowedImageIDs)
|
||||
}
|
||||
if c.Template != "" {
|
||||
return []string{c.Template}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func effectiveSubUserAllowedImageIDs(su *config.SubUser) []string {
|
||||
if su == nil {
|
||||
return nil
|
||||
}
|
||||
if su.ImageLimitConfigured || len(su.AllowedImageIDs) > 0 {
|
||||
return cleanImageIDList(su.AllowedImageIDs)
|
||||
}
|
||||
result := []string{}
|
||||
seen := map[string]bool{}
|
||||
for _, c := range subUserAssignedContainers(su) {
|
||||
for _, id := range effectiveContainerAllowedImageIDs(c) {
|
||||
if id != "" && !seen[id] {
|
||||
seen[id] = true
|
||||
result = append(result, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cleanImageIDList(ids []string) []string {
|
||||
result := make([]string, 0, len(ids))
|
||||
seen := map[string]bool{}
|
||||
for _, id := range ids {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" || seen[id] {
|
||||
continue
|
||||
}
|
||||
seen[id] = true
|
||||
result = append(result, id)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func subUserCurrentImageIDs(su *config.SubUser) []string {
|
||||
seen := map[string]bool{}
|
||||
result := []string{}
|
||||
for _, c := range subUserAssignedContainers(su) {
|
||||
if c.Template != "" && !seen[c.Template] {
|
||||
seen[c.Template] = true
|
||||
result = append(result, c.Template)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func subUserAssignedContainers(su *config.SubUser) []*config.Container {
|
||||
if su == nil {
|
||||
return nil
|
||||
}
|
||||
result := make([]*config.Container, 0, len(su.ContainerUUIDs)+len(su.ContainerNames))
|
||||
seen := map[string]bool{}
|
||||
for _, uuid := range su.ContainerUUIDs {
|
||||
if c := config.FindContainerByUUID(uuid); c != nil {
|
||||
key := c.UUID
|
||||
if key == "" {
|
||||
key = c.Name
|
||||
}
|
||||
if !seen[key] {
|
||||
seen[key] = true
|
||||
result = append(result, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range su.ContainerNames {
|
||||
if c := config.FindContainerByName(name); c != nil {
|
||||
key := c.UUID
|
||||
if key == "" {
|
||||
key = c.Name
|
||||
}
|
||||
if !seen[key] {
|
||||
seen[key] = true
|
||||
result = append(result, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func isAccessRestrictedRequest(r *http.Request) bool {
|
||||
_, restricted := requestAllowedContainers(r)
|
||||
return restricted
|
||||
@@ -580,18 +741,21 @@ func splitBy(s, sep string) []string {
|
||||
|
||||
// SubUserListItem is the enriched sub-user info returned by the list API
|
||||
type SubUserListItem struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
ContainerNames []string `json:"container_names"`
|
||||
ContainerUUIDs []string `json:"container_uuids"`
|
||||
ContainerName string `json:"container_name"`
|
||||
ContainerUUID string `json:"container_uuid"`
|
||||
AccessCode string `json:"access_code"`
|
||||
Password string `json:"password,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastLogin string `json:"last_login"`
|
||||
LastLoginIP string `json:"last_login_ip"`
|
||||
LastLoginUA string `json:"last_login_ua"`
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
ContainerNames []string `json:"container_names"`
|
||||
ContainerUUIDs []string `json:"container_uuids"`
|
||||
AllowedImageIDs []string `json:"allowed_image_ids"`
|
||||
ImageLimitConfigured bool `json:"image_limit_configured"`
|
||||
CurrentImageIDs []string `json:"current_image_ids"`
|
||||
ContainerName string `json:"container_name"`
|
||||
ContainerUUID string `json:"container_uuid"`
|
||||
AccessCode string `json:"access_code"`
|
||||
Password string `json:"password,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastLogin string `json:"last_login"`
|
||||
LastLoginIP string `json:"last_login_ip"`
|
||||
LastLoginUA string `json:"last_login_ua"`
|
||||
}
|
||||
|
||||
// HandleSubUserList returns the list of all sub-users with container info
|
||||
@@ -607,13 +771,16 @@ func HandleSubUserList(w http.ResponseWriter, r *http.Request) {
|
||||
result := make([]SubUserListItem, 0, len(config.AppConfig.SubUsers))
|
||||
for _, su := range config.AppConfig.SubUsers {
|
||||
item := SubUserListItem{
|
||||
ID: su.ID,
|
||||
Username: su.Username,
|
||||
ContainerNames: su.ContainerNames,
|
||||
ContainerUUIDs: su.ContainerUUIDs,
|
||||
AccessCode: su.AccessCode,
|
||||
Password: su.Password,
|
||||
CreatedAt: su.CreatedAt,
|
||||
ID: su.ID,
|
||||
Username: su.Username,
|
||||
ContainerNames: su.ContainerNames,
|
||||
ContainerUUIDs: su.ContainerUUIDs,
|
||||
AllowedImageIDs: effectiveSubUserAllowedImageIDs(&su),
|
||||
ImageLimitConfigured: su.ImageLimitConfigured,
|
||||
CurrentImageIDs: subUserCurrentImageIDs(&su),
|
||||
AccessCode: su.AccessCode,
|
||||
Password: su.Password,
|
||||
CreatedAt: su.CreatedAt,
|
||||
}
|
||||
|
||||
// Resolve container name from first active UUID
|
||||
@@ -711,6 +878,28 @@ func HandleSubUserAction(w http.ResponseWriter, r *http.Request) {
|
||||
logs := filterSubUserLoginLogs(target.Username)
|
||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: logs})
|
||||
|
||||
case action == "images" && r.Method == http.MethodPut:
|
||||
if !requireScope(w, r, "subuser:update") {
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
AllowedImageIDs []string `json:"allowed_image_ids"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
|
||||
return
|
||||
}
|
||||
ids, err := normalizeAllowedImageIDs(req.AllowedImageIDs)
|
||||
if err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
target.AllowedImageIDs = ids
|
||||
target.ImageLimitConfigured = true
|
||||
target.TokenVersion++
|
||||
config.SaveConfig()
|
||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: newSubUserResponse(*target, target.Password)})
|
||||
|
||||
default:
|
||||
jsonResponse(w, http.StatusNotFound, APIResponse{Success: false, Message: "Action not found"})
|
||||
}
|
||||
|
||||
@@ -560,7 +560,11 @@ func HandleSingleTaskAction(w http.ResponseWriter, r *http.Request, id int, acti
|
||||
if c := config.FindContainer(id); c != nil {
|
||||
runtime = c.Runtime()
|
||||
}
|
||||
if !isImageEnabledAndDownloaded(templateID, runtime) {
|
||||
if !isTemplateAllowedForRequest(r, c, templateID) {
|
||||
jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Template is not allowed for this user"})
|
||||
return
|
||||
}
|
||||
if !isTemplateAvailableForRequest(r, c, templateID, runtime) {
|
||||
jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Template is not enabled or downloaded"})
|
||||
return
|
||||
}
|
||||
@@ -656,6 +660,12 @@ func HandleBatchCreate(w http.ResponseWriter, r *http.Request) {
|
||||
jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: name + ": template is not enabled or downloaded"})
|
||||
return
|
||||
}
|
||||
if ids, err := normalizeAllowedImageIDs(req.Containers[i].AllowedImageIDs); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: name + ": " + err.Error()})
|
||||
return
|
||||
} else {
|
||||
req.Containers[i].AllowedImageIDs = ids
|
||||
}
|
||||
if req.Containers[i].PortMappingCount < 0 {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: name + ": port mapping count cannot be negative"})
|
||||
return
|
||||
@@ -777,6 +787,10 @@ func HandleBatchAction(w http.ResponseWriter, r *http.Request) {
|
||||
jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Access denied to one or more containers"})
|
||||
return
|
||||
}
|
||||
if taskType == TaskReinstall && !isTemplateAllowedForRequest(r, c, req.TemplateID) {
|
||||
jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: c.Name + ": template is not allowed for this user"})
|
||||
return
|
||||
}
|
||||
if taskConfig != nil {
|
||||
if err := validateReinstallSSHAuth(c, req.TemplateID, *taskConfig); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: c.Name + ": " + err.Error()})
|
||||
|
||||
Reference in New Issue
Block a user