mirror of
https://github.com/GlueOps/autoglue.git
synced 2026-02-13 04:40:05 +01:00
feat: cluster page ui
Signed-off-by: allanice001 <allanice001@gmail.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -46,6 +46,11 @@ components:
|
||||
load_balancer_id:
|
||||
type: string
|
||||
type: object
|
||||
dto.AttachNodePoolRequest:
|
||||
properties:
|
||||
node_pool_id:
|
||||
type: string
|
||||
type: object
|
||||
dto.AttachRecordSetRequest:
|
||||
properties:
|
||||
record_set_id:
|
||||
@@ -1154,7 +1159,7 @@ info:
|
||||
name: GlueOps
|
||||
description: API for managing K3s clusters across cloud providers
|
||||
title: AutoGlue API
|
||||
version: "1.0"
|
||||
version: ""
|
||||
openapi: 3.1.0
|
||||
paths:
|
||||
/.well-known/jwks.json:
|
||||
@@ -2853,6 +2858,139 @@ paths:
|
||||
summary: Set (or replace) the kubeconfig for a cluster
|
||||
tags:
|
||||
- Clusters
|
||||
/clusters/{clusterID}/node-pools:
|
||||
post:
|
||||
description: Adds an entry in the cluster_node_pools join table.
|
||||
operationId: AttachNodePool
|
||||
parameters:
|
||||
- description: Organization UUID
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
schema:
|
||||
type: string
|
||||
- description: Cluster ID
|
||||
in: path
|
||||
name: clusterID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/dto.AttachNodePoolRequest'
|
||||
description: payload
|
||||
required: true
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/dto.ClusterResponse'
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: bad request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: organization required
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: cluster or node pool not found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: db error
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: Attach a node pool to a cluster
|
||||
tags:
|
||||
- Clusters
|
||||
/clusters/{clusterID}/node-pools/{nodePoolID}:
|
||||
delete:
|
||||
description: Removes an entry from the cluster_node_pools join table.
|
||||
operationId: DetachNodePool
|
||||
parameters:
|
||||
- description: Organization UUID
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
schema:
|
||||
type: string
|
||||
- description: Cluster ID
|
||||
in: path
|
||||
name: clusterID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- description: Node Pool ID
|
||||
in: path
|
||||
name: nodePoolID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/dto.ClusterResponse'
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: bad request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: organization required
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: cluster or node pool not found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: db error
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: Detach a node pool from a cluster
|
||||
tags:
|
||||
- Clusters
|
||||
/credentials:
|
||||
get:
|
||||
description: Returns credential metadata for the current org. Secrets are never
|
||||
|
||||
@@ -35,5 +35,7 @@ func mountClusterRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) ht
|
||||
c.Post("/{clusterID}/kubeconfig", handlers.SetClusterKubeconfig(db))
|
||||
c.Delete("/{clusterID}/kubeconfig", handlers.ClearClusterKubeconfig(db))
|
||||
|
||||
c.Post("/{clusterID}/node-pools", handlers.AttachNodePool(db))
|
||||
c.Delete("/{clusterID}/node-pools/{nodePoolID}", handlers.DeleteNodePool(db))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1294,6 +1294,199 @@ func ClearClusterKubeconfig(db *gorm.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// AttachNodePool godoc
|
||||
//
|
||||
// @ID AttachNodePool
|
||||
// @Summary Attach a node pool to a cluster
|
||||
// @Description Adds an entry in the cluster_node_pools join table.
|
||||
// @Tags Clusters
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization UUID"
|
||||
// @Param clusterID path string true "Cluster ID"
|
||||
// @Param body body dto.AttachNodePoolRequest true "payload"
|
||||
// @Success 200 {object} dto.ClusterResponse
|
||||
// @Failure 400 {string} string "bad request"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "organization required"
|
||||
// @Failure 404 {string} string "cluster or node pool not found"
|
||||
// @Failure 500 {string} string "db error"
|
||||
// @Router /clusters/{clusterID}/node-pools [post]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func AttachNodePool(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
||||
if !ok {
|
||||
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
||||
return
|
||||
}
|
||||
|
||||
clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id")
|
||||
return
|
||||
}
|
||||
|
||||
var in dto.AttachNodePoolRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Load cluster (org scoped)
|
||||
var cluster models.Cluster
|
||||
if err := db.
|
||||
Where("id = ? AND organization_id = ?", clusterID, orgID).
|
||||
First(&cluster).Error; err != nil {
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found")
|
||||
return
|
||||
}
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
||||
return
|
||||
}
|
||||
|
||||
// Load node pool (org scoped)
|
||||
var np models.NodePool
|
||||
if err := db.
|
||||
Where("id = ? AND organization_id = ?", in.NodePoolID, orgID).
|
||||
First(&np).Error; err != nil {
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
utils.WriteError(w, http.StatusNotFound, "nodepool_not_found", "node pool not found for organization")
|
||||
return
|
||||
}
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
||||
return
|
||||
}
|
||||
|
||||
// Create association in join table
|
||||
if err := db.Model(&cluster).Association("NodePools").Append(&np); err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "failed to attach node pool")
|
||||
return
|
||||
}
|
||||
|
||||
_ = markClusterNeedsValidation(db, cluster.ID)
|
||||
|
||||
// Reload for rich response
|
||||
if err := db.
|
||||
Preload("CaptainDomain").
|
||||
Preload("ControlPlaneRecordSet").
|
||||
Preload("AppsLoadBalancer").
|
||||
Preload("GlueOpsLoadBalancer").
|
||||
Preload("BastionServer").
|
||||
Preload("NodePools").
|
||||
Preload("NodePools.Labels").
|
||||
Preload("NodePools.Annotations").
|
||||
Preload("NodePools.Taints").
|
||||
Preload("NodePools.Servers").
|
||||
First(&cluster, "id = ?", cluster.ID).Error; err != nil {
|
||||
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster))
|
||||
}
|
||||
}
|
||||
|
||||
// DetachNodePool godoc
|
||||
//
|
||||
// @ID DetachNodePool
|
||||
// @Summary Detach a node pool from a cluster
|
||||
// @Description Removes an entry from the cluster_node_pools join table.
|
||||
// @Tags Clusters
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization UUID"
|
||||
// @Param clusterID path string true "Cluster ID"
|
||||
// @Param nodePoolID path string true "Node Pool ID"
|
||||
// @Success 200 {object} dto.ClusterResponse
|
||||
// @Failure 400 {string} string "bad request"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "organization required"
|
||||
// @Failure 404 {string} string "cluster or node pool not found"
|
||||
// @Failure 500 {string} string "db error"
|
||||
// @Router /clusters/{clusterID}/node-pools/{nodePoolID} [delete]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func DetachNodePool(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, ok := httpmiddleware.OrgIDFrom(r.Context())
|
||||
if !ok {
|
||||
utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID")
|
||||
return
|
||||
}
|
||||
|
||||
clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id")
|
||||
return
|
||||
}
|
||||
|
||||
nodePoolID, err := uuid.Parse(chi.URLParam(r, "nodePoolID"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_nodepool_id", "invalid node pool id")
|
||||
return
|
||||
}
|
||||
|
||||
var cluster models.Cluster
|
||||
if err := db.
|
||||
Where("id = ? AND organization_id = ?", clusterID, orgID).
|
||||
First(&cluster).Error; err != nil {
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found")
|
||||
return
|
||||
}
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
||||
return
|
||||
}
|
||||
|
||||
var np models.NodePool
|
||||
if err := db.
|
||||
Where("id = ? AND organization_id = ?", nodePoolID, orgID).
|
||||
First(&np).Error; err != nil {
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
utils.WriteError(w, http.StatusNotFound, "nodepool_not_found", "node pool not found for organization")
|
||||
return
|
||||
}
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.Model(&cluster).Association("NodePools").Delete(&np); err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "failed to detach node pool")
|
||||
return
|
||||
}
|
||||
|
||||
_ = markClusterNeedsValidation(db, cluster.ID)
|
||||
|
||||
if err := db.
|
||||
Preload("CaptainDomain").
|
||||
Preload("ControlPlaneRecordSet").
|
||||
Preload("AppsLoadBalancer").
|
||||
Preload("GlueOpsLoadBalancer").
|
||||
Preload("BastionServer").
|
||||
Preload("NodePools").
|
||||
Preload("NodePools.Labels").
|
||||
Preload("NodePools.Annotations").
|
||||
Preload("NodePools.Taints").
|
||||
Preload("NodePools.Servers").
|
||||
First(&cluster, "id = ?", cluster.ID).Error; err != nil {
|
||||
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster))
|
||||
}
|
||||
}
|
||||
|
||||
// -- Helpers
|
||||
|
||||
func clusterToDTO(c models.Cluster) dto.ClusterResponse {
|
||||
|
||||
@@ -56,3 +56,7 @@ type AttachBastionRequest struct {
|
||||
type SetKubeconfigRequest struct {
|
||||
Kubeconfig string `json:"kubeconfig"`
|
||||
}
|
||||
|
||||
type AttachNodePoolRequest struct {
|
||||
NodePoolID uuid.UUID `json:"node_pool_id"`
|
||||
}
|
||||
|
||||
@@ -19,14 +19,11 @@ type Cluster struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
|
||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id"`
|
||||
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
|
||||
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Region string `json:"region"`
|
||||
|
||||
Status string `gorm:"type:varchar(20);not null;default:'pre_pending'" json:"status"`
|
||||
LastError string `gorm:"type:text;not null;default:''" json:"last_error"`
|
||||
|
||||
CaptainDomainID *uuid.UUID `gorm:"type:uuid" json:"captain_domain_id"`
|
||||
CaptainDomain Domain `gorm:"foreignKey:CaptainDomainID" json:"captain_domain"`
|
||||
ControlPlaneRecordSetID *uuid.UUID `gorm:"type:uuid" json:"control_plane_record_set_id,omitempty"`
|
||||
@@ -37,16 +34,12 @@ type Cluster struct {
|
||||
GlueOpsLoadBalancer *LoadBalancer `gorm:"foreignKey:GlueOpsLoadBalancerID" json:"glueops_load_balancer,omitempty"`
|
||||
BastionServerID *uuid.UUID `gorm:"type:uuid" json:"bastion_server_id,omitempty"`
|
||||
BastionServer *Server `gorm:"foreignKey:BastionServerID" json:"bastion_server,omitempty"`
|
||||
|
||||
NodePools []NodePool `gorm:"many2many:cluster_node_pools;constraint:OnDelete:CASCADE" json:"node_pools,omitempty"`
|
||||
|
||||
RandomToken string `json:"random_token"`
|
||||
CertificateKey string `json:"certificate_key"`
|
||||
|
||||
EncryptedKubeconfig string `gorm:"type:text" json:"-"`
|
||||
KubeIV string `json:"-"`
|
||||
KubeTag string `json:"-"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at,omitempty" gorm:"type:timestamptz;column:created_at;not null;default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"type:timestamptz;autoUpdateTime;column:updated_at;not null;default:now()"`
|
||||
}
|
||||
|
||||
5
main.go
5
main.go
@@ -2,10 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/glueops/autoglue/cmd"
|
||||
"github.com/glueops/autoglue/docs"
|
||||
"github.com/glueops/autoglue/internal/version"
|
||||
)
|
||||
|
||||
// @title AutoGlue API
|
||||
// @version 1.0
|
||||
// @version dev
|
||||
// @description API for managing K3s clusters across cloud providers
|
||||
// @contact.name GlueOps
|
||||
|
||||
@@ -37,5 +39,6 @@ import (
|
||||
// @description Org-level secret
|
||||
|
||||
func main() {
|
||||
docs.SwaggerInfo.Version = version.Version
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
@@ -2,22 +2,23 @@ import { AppShell } from "@/layouts/app-shell.tsx"
|
||||
import { Route, Routes } from "react-router-dom"
|
||||
|
||||
import { ProtectedRoute } from "@/components/protected-route.tsx"
|
||||
import { AnnotationPage } from "@/pages/annotations/annotation-page.tsx"
|
||||
import { Login } from "@/pages/auth/login.tsx"
|
||||
import { CredentialPage } from "@/pages/credentials/credential-page.tsx"
|
||||
import { DnsPage } from "@/pages/dns/dns-page.tsx"
|
||||
import { DocsPage } from "@/pages/docs/docs-page.tsx"
|
||||
import { JobsPage } from "@/pages/jobs/jobs-page.tsx"
|
||||
import { LabelsPage } from "@/pages/labels/labels-page.tsx"
|
||||
import { LoadBalancersPage } from "@/pages/loadbalancers/load-balancers-page"
|
||||
import { MePage } from "@/pages/me/me-page.tsx"
|
||||
import { NodePoolsPage } from "@/pages/nodepools/node-pools-page.tsx"
|
||||
import { AnnotationPage } from "@/pages/annotation-page.tsx"
|
||||
import { ClustersPage } from "@/pages/cluster-page"
|
||||
import { CredentialPage } from "@/pages/credential-page.tsx"
|
||||
import { DnsPage } from "@/pages/dns-page.tsx"
|
||||
import { DocsPage } from "@/pages/docs-page.tsx"
|
||||
import { JobsPage } from "@/pages/jobs-page.tsx"
|
||||
import { LabelsPage } from "@/pages/labels-page.tsx"
|
||||
import { LoadBalancersPage } from "@/pages/load-balancers-page"
|
||||
import { Login } from "@/pages/login.tsx"
|
||||
import { MePage } from "@/pages/me-page.tsx"
|
||||
import { NodePoolsPage } from "@/pages/node-pools-page.tsx"
|
||||
import { OrgApiKeys } from "@/pages/org/api-keys.tsx"
|
||||
import { OrgMembers } from "@/pages/org/members.tsx"
|
||||
import { OrgSettings } from "@/pages/org/settings.tsx"
|
||||
import { ServerPage } from "@/pages/servers/server-page.tsx"
|
||||
import { SshPage } from "@/pages/ssh/ssh-page.tsx"
|
||||
import { TaintsPage } from "@/pages/taints/taints-page.tsx"
|
||||
import { ServerPage } from "@/pages/server-page.tsx"
|
||||
import { SshPage } from "@/pages/ssh-page.tsx"
|
||||
import { TaintsPage } from "@/pages/taints-page.tsx"
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
@@ -42,6 +43,7 @@ export default function App() {
|
||||
<Route path="/credentials" element={<CredentialPage />} />
|
||||
<Route path="/dns" element={<DnsPage />} />
|
||||
<Route path="/load-balancers" element={<LoadBalancersPage />} />
|
||||
<Route path="/clusters" element={<ClustersPage />} />
|
||||
|
||||
<Route path="/admin/jobs" element={<JobsPage />} />
|
||||
</Route>
|
||||
|
||||
150
ui/src/api/clusters.ts
Normal file
150
ui/src/api/clusters.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { withRefresh } from "@/api/with-refresh"
|
||||
import type {
|
||||
DtoAttachBastionRequest,
|
||||
DtoAttachCaptainDomainRequest,
|
||||
DtoAttachLoadBalancerRequest,
|
||||
DtoAttachRecordSetRequest,
|
||||
DtoCreateClusterRequest,
|
||||
DtoSetKubeconfigRequest,
|
||||
DtoUpdateClusterRequest,
|
||||
} from "@/sdk"
|
||||
import { makeClusterApi } from "@/sdkClient"
|
||||
|
||||
const clusters = makeClusterApi()
|
||||
|
||||
export const clustersApi = {
|
||||
// --- basic CRUD ---
|
||||
|
||||
listClusters: (q?: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.listClusters(q ? { q } : {})
|
||||
}),
|
||||
|
||||
getCluster: (id: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.getCluster({ clusterID: id })
|
||||
}),
|
||||
|
||||
createCluster: (body: DtoCreateClusterRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.createCluster({
|
||||
dtoCreateClusterRequest: body,
|
||||
})
|
||||
}),
|
||||
|
||||
updateCluster: (id: string, body: DtoUpdateClusterRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.updateCluster({
|
||||
clusterID: id,
|
||||
dtoUpdateClusterRequest: body,
|
||||
})
|
||||
}),
|
||||
|
||||
deleteCluster: (id: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.deleteCluster({ clusterID: id })
|
||||
}),
|
||||
|
||||
// --- kubeconfig ---
|
||||
|
||||
setKubeconfig: (clusterID: string, body: DtoSetKubeconfigRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.setClusterKubeconfig({
|
||||
clusterID,
|
||||
dtoSetKubeconfigRequest: body,
|
||||
})
|
||||
}),
|
||||
|
||||
clearKubeconfig: (clusterID: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.clearClusterKubeconfig({ clusterID })
|
||||
}),
|
||||
|
||||
// --- captain domain ---
|
||||
|
||||
attachCaptainDomain: (clusterID: string, body: DtoAttachCaptainDomainRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.attachCaptainDomain({
|
||||
clusterID,
|
||||
dtoAttachCaptainDomainRequest: body,
|
||||
})
|
||||
}),
|
||||
|
||||
detachCaptainDomain: (clusterID: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.detachCaptainDomain({ clusterID })
|
||||
}),
|
||||
|
||||
// --- control plane record set ---
|
||||
|
||||
attachControlPlaneRecordSet: (clusterID: string, body: DtoAttachRecordSetRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.attachControlPlaneRecordSet({
|
||||
clusterID,
|
||||
dtoAttachRecordSetRequest: body,
|
||||
})
|
||||
}),
|
||||
|
||||
detachControlPlaneRecordSet: (clusterID: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.detachControlPlaneRecordSet({ clusterID })
|
||||
}),
|
||||
|
||||
// --- load balancers ---
|
||||
|
||||
attachAppsLoadBalancer: (clusterID: string, body: DtoAttachLoadBalancerRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.attachAppsLoadBalancer({
|
||||
clusterID,
|
||||
dtoAttachLoadBalancerRequest: body,
|
||||
})
|
||||
}),
|
||||
|
||||
detachAppsLoadBalancer: (clusterID: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.detachAppsLoadBalancer({ clusterID })
|
||||
}),
|
||||
|
||||
attachGlueOpsLoadBalancer: (clusterID: string, body: DtoAttachLoadBalancerRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.attachGlueOpsLoadBalancer({
|
||||
clusterID,
|
||||
dtoAttachLoadBalancerRequest: body,
|
||||
})
|
||||
}),
|
||||
|
||||
detachGlueOpsLoadBalancer: (clusterID: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.detachGlueOpsLoadBalancer({ clusterID })
|
||||
}),
|
||||
|
||||
// --- bastion ---
|
||||
|
||||
attachBastion: (clusterID: string, body: DtoAttachBastionRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.attachBastionServer({
|
||||
clusterID,
|
||||
dtoAttachBastionRequest: body,
|
||||
})
|
||||
}),
|
||||
|
||||
detachBastion: (clusterID: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.detachBastionServer({ clusterID })
|
||||
}),
|
||||
|
||||
// -- node-pools
|
||||
|
||||
attachNodePool: (clusterID: string, nodePoolID: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.attachNodePool({
|
||||
clusterID,
|
||||
dtoAttachNodePoolRequest: { node_pool_id: nodePoolID },
|
||||
})
|
||||
}),
|
||||
|
||||
detachNodePool: (clusterID: string, nodePoolID: string) =>
|
||||
withRefresh(async () => {
|
||||
return await clusters.detachNodePool({ clusterID, nodePoolID })
|
||||
}),
|
||||
}
|
||||
1273
ui/src/pages/cluster-page.tsx
Normal file
1273
ui/src/pages/cluster-page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,66 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { credentialsApi } from "@/api/credentials";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { AlertTriangle, Eye, Loader2, MoreHorizontal, Pencil, Plus, Search, Trash2 } from "lucide-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
|
||||
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
|
||||
|
||||
import { useMemo, useState } from "react"
|
||||
import { credentialsApi } from "@/api/credentials"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import {
|
||||
AlertTriangle,
|
||||
Eye,
|
||||
Loader2,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
Plus,
|
||||
Search,
|
||||
Trash2,
|
||||
} from "lucide-react"
|
||||
import { Controller, useForm } from "react-hook-form"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
|
||||
// -------------------- Constants --------------------
|
||||
|
||||
Reference in New Issue
Block a user