mirror of
https://github.com/GlueOps/autoglue.git
synced 2026-02-13 21:00:06 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1feb3e29e1 | ||
|
|
0e9ce98624 | ||
|
|
96aef81959 | ||
|
|
62232e18f3 | ||
|
|
515327153c | ||
|
|
5f2e885a8e | ||
|
|
01b48efba0 | ||
|
|
1c87566c5b | ||
|
|
ad00a3c45d | ||
|
|
158fdce780 | ||
|
|
c4fd344364 | ||
|
|
953e724ba0 | ||
|
|
256acfd686 | ||
|
|
1dd0a39aad | ||
|
|
7ef0605c2b | ||
|
|
8a92727b88 | ||
|
|
1f9920a04c | ||
|
|
5fd96ec40f | ||
|
|
bc72df3c9a | ||
|
|
56ea963b47 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -137,4 +137,6 @@ notes.txt
|
||||
|
||||
.terraform
|
||||
.terraform.lock*
|
||||
terraform.tfstate*
|
||||
terraform.tfstate*
|
||||
|
||||
ui/src/sdk
|
||||
5
Makefile
5
Makefile
@@ -23,7 +23,7 @@ MODULE_PATH ?= $(GIT_HOST)/$(GIT_USER)/$(BIN)
|
||||
|
||||
# SDK / module settings (Go)
|
||||
SDK_REPO ?= $(BIN)-sdk-go # repo name used for module path
|
||||
SDK_OUTDIR ?= sdk/go # output directory (inside repo)
|
||||
SDK_OUTDIR ?= ../autoglue-sdk-go # output directory (inside repo)
|
||||
SDK_PKG ?= ${BIN} # package name inside the SDK
|
||||
|
||||
UI_SSG_ROUTES ?= /,/login,/docs,/pricing
|
||||
@@ -70,7 +70,7 @@ export GO_POST_PROCESS_FILE := gofmt -w
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# --- version metadata (ldflags) ---
|
||||
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
VERSION := $(shell git describe --tags --always 2>/dev/null || echo "dev")
|
||||
COMMIT := $(shell git rev-parse HEAD 2>/dev/null || echo "none")
|
||||
DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
BUILT_BY := $(shell whoami)
|
||||
@@ -247,7 +247,6 @@ TS_PROPS := -p npmName=$(SDK_TS_NPM_NAME) -p npmVersion=$(SDK_TS_NPM_VER) $
|
||||
# --- sdk generation (Go) ---
|
||||
sdk-go: $(DOCS_JSON) validate-spec check-tags ## Generate Go SDK + tidy module
|
||||
@echo ">> Generating Go SDK (module $(GIT_HOST_CLEAN)/$(GIT_USER_CLEAN)/$(SDK_REPO_CLEAN), Go $(GO_VERSION))..."
|
||||
@rm -rf "$(SDK_OUTDIR_CLEAN)"; mkdir -p "$(SDK_OUTDIR_CLEAN)"
|
||||
@$(call OGC_GENERATE,go,$(SDK_OUTDIR_CLEAN),--additional-properties=packageName=$(SDK_PKG_CLEAN) $(OAG_GIT_PROPS))
|
||||
@cd "$(SDK_OUTDIR_CLEAN)"; \
|
||||
$(GOCMD) mod edit -go=$(GO_VERSION); \
|
||||
|
||||
@@ -42,7 +42,7 @@ services:
|
||||
- postgres
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit@sha256:6abc8e633df15eaf785cfcf38bae48e66f64beecdc03121e249d0f9ec15f0707
|
||||
image: axllent/mailpit@sha256:e22dce5b36f93c77082e204a3942fb6b283b7896e057458400a4c88344c3df68
|
||||
restart: always
|
||||
ports:
|
||||
- "1025:1025"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -49,6 +49,39 @@ definitions:
|
||||
example: https://accounts.google.com/o/oauth2/v2/auth?client_id=...
|
||||
type: string
|
||||
type: object
|
||||
dto.ClusterResponse:
|
||||
properties:
|
||||
bastion_server:
|
||||
$ref: '#/definitions/dto.ServerResponse'
|
||||
captain_domain:
|
||||
type: string
|
||||
certificate_key:
|
||||
type: string
|
||||
cluster_load_balancer:
|
||||
type: string
|
||||
control_load_balancer:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
node_pools:
|
||||
items:
|
||||
$ref: '#/definitions/dto.NodePoolResponse'
|
||||
type: array
|
||||
provider:
|
||||
type: string
|
||||
random_token:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
dto.CreateAnnotationRequest:
|
||||
properties:
|
||||
key:
|
||||
@@ -56,6 +89,75 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
dto.CreateClusterRequest:
|
||||
properties:
|
||||
captain_domain:
|
||||
type: string
|
||||
cluster_load_balancer:
|
||||
type: string
|
||||
control_load_balancer:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
dto.CreateCredentialRequest:
|
||||
properties:
|
||||
account_id:
|
||||
maxLength: 32
|
||||
type: string
|
||||
kind:
|
||||
description: aws_access_key, api_token, basic_auth, oauth2
|
||||
type: string
|
||||
name:
|
||||
description: human label
|
||||
maxLength: 100
|
||||
type: string
|
||||
provider:
|
||||
enum:
|
||||
- aws
|
||||
- cloudflare
|
||||
- hetzner
|
||||
- digitalocean
|
||||
- generic
|
||||
type: string
|
||||
region:
|
||||
maxLength: 32
|
||||
type: string
|
||||
schema_version:
|
||||
description: secret schema version
|
||||
minimum: 1
|
||||
type: integer
|
||||
scope:
|
||||
description: '{"service":"route53"} or {"arn":"..."}'
|
||||
type: object
|
||||
scope_kind:
|
||||
enum:
|
||||
- provider
|
||||
- service
|
||||
- resource
|
||||
type: string
|
||||
scope_version:
|
||||
description: scope schema version
|
||||
minimum: 1
|
||||
type: integer
|
||||
secret:
|
||||
description: encrypted later
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- provider
|
||||
- schema_version
|
||||
- scope
|
||||
- scope_kind
|
||||
- scope_version
|
||||
- secret
|
||||
type: object
|
||||
dto.CreateLabelRequest:
|
||||
properties:
|
||||
key:
|
||||
@@ -124,7 +226,46 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
dto.CredentialOut:
|
||||
properties:
|
||||
account_id:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
schema_version:
|
||||
type: integer
|
||||
scope:
|
||||
type: object
|
||||
scope_kind:
|
||||
type: string
|
||||
scope_version:
|
||||
type: integer
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
dto.EnqueueRequest:
|
||||
properties:
|
||||
payload:
|
||||
type: object
|
||||
queue:
|
||||
example: default
|
||||
type: string
|
||||
run_at:
|
||||
example: "2025-11-05T08:00:00Z"
|
||||
type: string
|
||||
type:
|
||||
example: email.send
|
||||
type: string
|
||||
type: object
|
||||
dto.JWK:
|
||||
properties:
|
||||
@@ -416,6 +557,24 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
dto.UpdateCredentialRequest:
|
||||
properties:
|
||||
account_id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
scope:
|
||||
type: object
|
||||
scope_kind:
|
||||
type: string
|
||||
scope_version:
|
||||
type: integer
|
||||
secret:
|
||||
description: set if rotating
|
||||
type: object
|
||||
type: object
|
||||
dto.UpdateLabelRequest:
|
||||
properties:
|
||||
key:
|
||||
@@ -476,6 +635,42 @@ definitions:
|
||||
example: ok
|
||||
type: string
|
||||
type: object
|
||||
handlers.VersionResponse:
|
||||
properties:
|
||||
built:
|
||||
example: "2025-11-08T12:34:56Z"
|
||||
type: string
|
||||
builtBy:
|
||||
example: ci
|
||||
type: string
|
||||
commit:
|
||||
example: a1b2c3d
|
||||
type: string
|
||||
commitTime:
|
||||
example: "2025-11-08T12:31:00Z"
|
||||
type: string
|
||||
go:
|
||||
example: go1.23.3
|
||||
type: string
|
||||
goArch:
|
||||
example: amd64
|
||||
type: string
|
||||
goOS:
|
||||
example: linux
|
||||
type: string
|
||||
modified:
|
||||
example: false
|
||||
type: boolean
|
||||
revision:
|
||||
example: a1b2c3d4e5f6abcdef
|
||||
type: string
|
||||
vcs:
|
||||
example: git
|
||||
type: string
|
||||
version:
|
||||
example: 1.4.2
|
||||
type: string
|
||||
type: object
|
||||
handlers.createUserKeyRequest:
|
||||
properties:
|
||||
expires_in_hours:
|
||||
@@ -1284,6 +1479,339 @@ paths:
|
||||
summary: Rotate refresh token
|
||||
tags:
|
||||
- Auth
|
||||
/clusters:
|
||||
get:
|
||||
description: Returns clusters for the organization in X-Org-ID. Filter by `q`
|
||||
(name contains).
|
||||
operationId: ListClusters
|
||||
parameters:
|
||||
- description: Organization UUID
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
type: string
|
||||
- description: Name contains (case-insensitive)
|
||||
in: query
|
||||
name: q
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/dto.ClusterResponse'
|
||||
type: array
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
type: string
|
||||
"403":
|
||||
description: organization required
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: failed to list clusters
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: List clusters (org scoped)
|
||||
tags:
|
||||
- Clusters
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Creates a cluster. If `kubeconfig` is provided, it will be encrypted
|
||||
per-organization and stored securely (never returned).
|
||||
operationId: CreateCluster
|
||||
parameters:
|
||||
- description: Organization UUID
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
type: string
|
||||
- description: payload
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CreateClusterRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/dto.ClusterResponse'
|
||||
"400":
|
||||
description: invalid json
|
||||
schema:
|
||||
type: string
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
type: string
|
||||
"403":
|
||||
description: organization required
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: create failed
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: Create cluster (org scoped)
|
||||
tags:
|
||||
- Clusters
|
||||
/credentials:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns credential metadata for the current org. Secrets are never
|
||||
returned.
|
||||
operationId: ListCredentials
|
||||
parameters:
|
||||
- description: Organization ID (UUID)
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
type: string
|
||||
- description: Filter by provider (e.g., aws)
|
||||
in: query
|
||||
name: provider
|
||||
type: string
|
||||
- description: Filter by kind (e.g., aws_access_key)
|
||||
in: query
|
||||
name: kind
|
||||
type: string
|
||||
- description: Filter by scope kind (provider/service/resource)
|
||||
in: query
|
||||
name: scope_kind
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/dto.CredentialOut'
|
||||
type: array
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
type: string
|
||||
"403":
|
||||
description: organization required
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: List credentials (metadata only)
|
||||
tags:
|
||||
- Credentials
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
operationId: CreateCredential
|
||||
parameters:
|
||||
- description: Organization ID (UUID)
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
type: string
|
||||
- description: Credential payload
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CreateCredentialRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CredentialOut'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
type: string
|
||||
"403":
|
||||
description: organization required
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: Create a credential (encrypts secret)
|
||||
tags:
|
||||
- Credentials
|
||||
/credentials/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
operationId: DeleteCredential
|
||||
parameters:
|
||||
- description: Organization ID (UUID)
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
type: string
|
||||
- description: Credential ID (UUID)
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"404":
|
||||
description: not found
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: Delete credential
|
||||
tags:
|
||||
- Credentials
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
operationId: GetCredential
|
||||
parameters:
|
||||
- description: Organization ID (UUID)
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
type: string
|
||||
- description: Credential ID (UUID)
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CredentialOut'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
type: string
|
||||
"403":
|
||||
description: organization required
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: Get credential by ID (metadata only)
|
||||
tags:
|
||||
- Credentials
|
||||
patch:
|
||||
consumes:
|
||||
- application/json
|
||||
operationId: UpdateCredential
|
||||
parameters:
|
||||
- description: Organization ID (UUID)
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
type: string
|
||||
- description: Credential ID (UUID)
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Fields to update
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.UpdateCredentialRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.CredentialOut'
|
||||
"403":
|
||||
description: X-Org-ID required
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
description: not found
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: Update credential metadata and/or rotate secret
|
||||
tags:
|
||||
- Credentials
|
||||
/credentials/{id}/reveal:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
operationId: RevealCredential
|
||||
parameters:
|
||||
- description: Organization ID (UUID)
|
||||
in: header
|
||||
name: X-Org-ID
|
||||
type: string
|
||||
- description: Credential ID (UUID)
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"403":
|
||||
description: organization required
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
description: not found
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- OrgKeyAuth: []
|
||||
- OrgSecretAuth: []
|
||||
summary: Reveal decrypted secret (one-time read)
|
||||
tags:
|
||||
- Credentials
|
||||
/healthz:
|
||||
get:
|
||||
consumes:
|
||||
@@ -3587,6 +4115,22 @@ paths:
|
||||
summary: Update node taint (org scoped)
|
||||
tags:
|
||||
- Taints
|
||||
/version:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns build/runtime metadata for the running service.
|
||||
operationId: Version // operationId
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.VersionResponse'
|
||||
summary: Service version information
|
||||
tags:
|
||||
- Meta
|
||||
schemes:
|
||||
- http
|
||||
- https
|
||||
|
||||
7
go.mod
7
go.mod
@@ -9,6 +9,7 @@ require (
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-chi/httprate v0.15.0
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
@@ -18,7 +19,7 @@ require (
|
||||
github.com/swaggo/http-swagger/v2 v2.0.2
|
||||
github.com/swaggo/swag/v2 v2.0.0-rc4
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/oauth2 v0.32.0
|
||||
golang.org/x/oauth2 v0.33.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/datatypes v1.2.7
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
@@ -29,11 +30,14 @@ require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/spec v0.20.9 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
@@ -46,6 +50,7 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
|
||||
16
go.sum
16
go.sum
@@ -22,6 +22,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
@@ -43,6 +45,14 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
@@ -89,6 +99,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@@ -181,8 +193,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
||||
@@ -71,6 +71,7 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs) http.Handler {
|
||||
v1.Get("/.well-known/jwks.json", handlers.JWKSHandler)
|
||||
|
||||
v1.Get("/healthz", handlers.HealthCheck)
|
||||
v1.Get("/version", handlers.Version)
|
||||
|
||||
v1.Route("/auth", func(a chi.Router) {
|
||||
a.Post("/{provider}/start", handlers.AuthStart(db))
|
||||
@@ -126,6 +127,16 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs) http.Handler {
|
||||
})
|
||||
})
|
||||
|
||||
v1.Route("/credentials", func(c chi.Router) {
|
||||
c.Use(authOrg)
|
||||
c.Get("/", handlers.ListCredentials(db))
|
||||
c.Post("/", handlers.CreateCredential(db))
|
||||
c.Get("/{id}", handlers.GetCredential(db))
|
||||
c.Patch("/{id}", handlers.UpdateCredential(db))
|
||||
c.Delete("/{id}", handlers.DeleteCredential(db))
|
||||
c.Post("/{id}/reveal", handlers.RevealCredential(db))
|
||||
})
|
||||
|
||||
v1.Route("/ssh", func(s chi.Router) {
|
||||
s.Use(authOrg)
|
||||
s.Get("/", handlers.ListPublicSshKeys(db))
|
||||
|
||||
@@ -40,6 +40,7 @@ func NewRuntime() *Runtime {
|
||||
&models.Annotation{},
|
||||
&models.NodePool{},
|
||||
&models.Cluster{},
|
||||
&models.Credential{},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
186
internal/handlers/clusters.go
Normal file
186
internal/handlers/clusters.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/glueops/autoglue/internal/api/httpmiddleware"
|
||||
"github.com/glueops/autoglue/internal/common"
|
||||
"github.com/glueops/autoglue/internal/handlers/dto"
|
||||
"github.com/glueops/autoglue/internal/models"
|
||||
"github.com/glueops/autoglue/internal/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ListClusters godoc
|
||||
//
|
||||
// @ID ListClusters
|
||||
// @Summary List clusters (org scoped)
|
||||
// @Description Returns clusters for the organization in X-Org-ID. Filter by `q` (name contains).
|
||||
// @Tags Clusters
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization UUID"
|
||||
// @Param q query string false "Name contains (case-insensitive)"
|
||||
// @Success 200 {array} dto.ClusterResponse
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "organization required"
|
||||
// @Failure 500 {string} string "failed to list clusters"
|
||||
// @Router /clusters [get]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func ListClusters(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
|
||||
}
|
||||
|
||||
q := db.Where("organization_id = ?", orgID)
|
||||
if needle := strings.TrimSpace(r.URL.Query().Get("q")); needle != "" {
|
||||
q = q.Where(`name ILIKE ?`, "%"+needle+"%")
|
||||
}
|
||||
|
||||
var rows []models.Cluster
|
||||
if err := q.
|
||||
Preload("NodePools").
|
||||
Preload("NodePools.Labels").
|
||||
Preload("NodePools.Annotations").
|
||||
Preload("NodePools.Labels").
|
||||
Preload("NodePools.Taints").
|
||||
Preload("NodePools.Servers").
|
||||
Preload("BastionServer").
|
||||
Find(&rows).Error; err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
|
||||
return
|
||||
}
|
||||
|
||||
out := make([]dto.ClusterResponse, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, clusterToDTO(row))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// CreateCluster godoc
|
||||
//
|
||||
// @ID CreateCluster
|
||||
// @Summary Create cluster (org scoped)
|
||||
// @Description Creates a cluster. If `kubeconfig` is provided, it will be encrypted per-organization and stored securely (never returned).
|
||||
// @Tags Clusters
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization UUID"
|
||||
// @Param body body dto.CreateClusterRequest true "payload"
|
||||
// @Success 201 {object} dto.ClusterResponse
|
||||
// @Failure 400 {string} string "invalid json"
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "organization required"
|
||||
// @Failure 500 {string} string "create failed"
|
||||
// @Router /clusters [post]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func CreateCluster(db *gorm.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// -- Helpers
|
||||
|
||||
func clusterToDTO(c models.Cluster) dto.ClusterResponse {
|
||||
var bastion *dto.ServerResponse
|
||||
if c.BastionServer != nil {
|
||||
b := serverToDTO(*c.BastionServer)
|
||||
bastion = &b
|
||||
}
|
||||
|
||||
nps := make([]dto.NodePoolResponse, 0, len(c.NodePools))
|
||||
for _, np := range c.NodePools {
|
||||
nps = append(nps, nodePoolToDTO(np))
|
||||
}
|
||||
|
||||
return dto.ClusterResponse{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Provider: c.Provider,
|
||||
Region: c.Region,
|
||||
Status: c.Status,
|
||||
CaptainDomain: c.CaptainDomain,
|
||||
ClusterLoadBalancer: c.ClusterLoadBalancer,
|
||||
RandomToken: c.RandomToken,
|
||||
CertificateKey: c.CertificateKey,
|
||||
ControlLoadBalancer: c.ControlLoadBalancer,
|
||||
NodePools: nps,
|
||||
BastionServer: bastion,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func nodePoolToDTO(np models.NodePool) dto.NodePoolResponse {
|
||||
labels := make([]dto.LabelResponse, 0, len(np.Labels))
|
||||
for _, l := range np.Labels {
|
||||
labels = append(labels, dto.LabelResponse{
|
||||
Key: l.Key,
|
||||
Value: l.Value,
|
||||
})
|
||||
}
|
||||
|
||||
annotations := make([]dto.AnnotationResponse, 0, len(np.Annotations))
|
||||
for _, a := range np.Annotations {
|
||||
annotations = append(annotations, dto.AnnotationResponse{
|
||||
Key: a.Key,
|
||||
Value: a.Value,
|
||||
})
|
||||
}
|
||||
|
||||
taints := make([]dto.TaintResponse, 0, len(np.Taints))
|
||||
for _, t := range np.Taints {
|
||||
taints = append(taints, dto.TaintResponse{
|
||||
Key: t.Key,
|
||||
Value: t.Value,
|
||||
Effect: t.Effect,
|
||||
})
|
||||
}
|
||||
|
||||
servers := make([]dto.ServerResponse, 0, len(np.Servers))
|
||||
for _, s := range np.Servers {
|
||||
servers = append(servers, serverToDTO(s))
|
||||
}
|
||||
|
||||
return dto.NodePoolResponse{
|
||||
AuditFields: common.AuditFields{
|
||||
ID: np.ID,
|
||||
OrganizationID: np.OrganizationID,
|
||||
CreatedAt: np.CreatedAt,
|
||||
UpdatedAt: np.UpdatedAt,
|
||||
},
|
||||
Name: np.Name,
|
||||
Role: dto.NodeRole(np.Role),
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
Taints: taints,
|
||||
Servers: servers,
|
||||
}
|
||||
}
|
||||
|
||||
func serverToDTO(s models.Server) dto.ServerResponse {
|
||||
return dto.ServerResponse{
|
||||
ID: s.ID,
|
||||
Hostname: s.Hostname,
|
||||
PrivateIPAddress: s.PrivateIPAddress,
|
||||
PublicIPAddress: s.PublicIPAddress,
|
||||
Role: s.Role,
|
||||
Status: s.Status,
|
||||
SSHUser: s.SSHUser,
|
||||
SshKeyID: s.SshKeyID,
|
||||
CreatedAt: s.CreatedAt.UTC().Format(time.RFC3339),
|
||||
UpdatedAt: s.UpdatedAt.UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
561
internal/handlers/credentials.go
Normal file
561
internal/handlers/credentials.go
Normal file
@@ -0,0 +1,561 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/glueops/autoglue/internal/api/httpmiddleware"
|
||||
"github.com/glueops/autoglue/internal/handlers/dto"
|
||||
"github.com/glueops/autoglue/internal/models"
|
||||
"github.com/glueops/autoglue/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ListCredentials godoc
|
||||
// @ID ListCredentials
|
||||
// @Summary List credentials (metadata only)
|
||||
// @Description Returns credential metadata for the current org. Secrets are never returned.
|
||||
// @Tags Credentials
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization ID (UUID)"
|
||||
// @Param provider query string false "Filter by provider (e.g., aws)"
|
||||
// @Param kind query string false "Filter by kind (e.g., aws_access_key)"
|
||||
// @Param scope_kind query string false "Filter by scope kind (provider/service/resource)"
|
||||
// @Success 200 {array} dto.CredentialOut
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "organization required"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /credentials [get]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func ListCredentials(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
|
||||
}
|
||||
q := db.Where("organization_id = ?", orgID)
|
||||
if v := r.URL.Query().Get("provider"); v != "" {
|
||||
q = q.Where("provider = ?", v)
|
||||
}
|
||||
if v := r.URL.Query().Get("kind"); v != "" {
|
||||
q = q.Where("kind = ?", v)
|
||||
}
|
||||
if v := r.URL.Query().Get("scope_kind"); v != "" {
|
||||
q = q.Where("scope_kind = ?", v)
|
||||
}
|
||||
|
||||
var rows []models.Credential
|
||||
if err := q.Order("updated_at DESC").Find(&rows).Error; err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
|
||||
return
|
||||
}
|
||||
out := make([]dto.CredentialOut, 0, len(rows))
|
||||
for i := range rows {
|
||||
out = append(out, credOut(&rows[i]))
|
||||
}
|
||||
utils.WriteJSON(w, http.StatusOK, out)
|
||||
}
|
||||
}
|
||||
|
||||
// GetCredential godoc
|
||||
// @ID GetCredential
|
||||
// @Summary Get credential by ID (metadata only)
|
||||
// @Tags Credentials
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization ID (UUID)"
|
||||
// @Param id path string true "Credential ID (UUID)"
|
||||
// @Success 200 {object} dto.CredentialOut
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "organization required"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /credentials/{id} [get]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func GetCredential(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
|
||||
}
|
||||
|
||||
idStr := chi.URLParam(r, "id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid UUID")
|
||||
return
|
||||
}
|
||||
|
||||
var row models.Credential
|
||||
if err := db.Where("organization_id = ? AND id = ?", orgID, id).First(&row).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
utils.WriteError(w, http.StatusNotFound, "not_found", "credential not found")
|
||||
return
|
||||
}
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
|
||||
return
|
||||
}
|
||||
utils.WriteJSON(w, http.StatusOK, credOut(&row))
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCredential godoc
|
||||
// @ID CreateCredential
|
||||
// @Summary Create a credential (encrypts secret)
|
||||
// @Tags Credentials
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization ID (UUID)"
|
||||
// @Param body body dto.CreateCredentialRequest true "Credential payload"
|
||||
// @Success 201 {object} dto.CredentialOut
|
||||
// @Failure 401 {string} string "Unauthorized"
|
||||
// @Failure 403 {string} string "organization required"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /credentials [post]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func CreateCredential(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
|
||||
}
|
||||
|
||||
var in dto.CreateCredentialRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := dto.Validate.Struct(in); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "validation_error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cred, err := SaveCredentialWithScope(
|
||||
r.Context(), db, orgID,
|
||||
in.Provider, in.Kind, in.SchemaVersion,
|
||||
in.ScopeKind, in.ScopeVersion, json.RawMessage(in.Scope), json.RawMessage(in.Secret),
|
||||
in.Name, in.AccountID, in.Region,
|
||||
)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "save_failed", err.Error())
|
||||
return
|
||||
}
|
||||
utils.WriteJSON(w, http.StatusCreated, credOut(cred))
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCredential godoc
|
||||
// @ID UpdateCredential
|
||||
// @Summary Update credential metadata and/or rotate secret
|
||||
// @Tags Credentials
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization ID (UUID)"
|
||||
// @Param id path string true "Credential ID (UUID)"
|
||||
// @Param body body dto.UpdateCredentialRequest true "Fields to update"
|
||||
// @Success 200 {object} dto.CredentialOut
|
||||
// @Failure 403 {string} string "X-Org-ID required"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Router /credentials/{id} [patch]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func UpdateCredential(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
|
||||
}
|
||||
|
||||
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid UUID")
|
||||
return
|
||||
}
|
||||
|
||||
var row models.Credential
|
||||
if err := db.Where("organization_id = ? AND id = ?", orgID, id).First(&row).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
utils.WriteError(w, http.StatusNotFound, "not_found", "credential not found")
|
||||
return
|
||||
}
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var in dto.UpdateCredentialRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Update metadata
|
||||
if in.Name != nil {
|
||||
row.Name = *in.Name
|
||||
}
|
||||
if in.AccountID != nil {
|
||||
row.AccountID = *in.AccountID
|
||||
}
|
||||
if in.Region != nil {
|
||||
row.Region = *in.Region
|
||||
}
|
||||
|
||||
// Update scope (re-validate + fingerprint)
|
||||
if in.ScopeKind != nil || in.Scope != nil || in.ScopeVersion != nil {
|
||||
newKind := row.ScopeKind
|
||||
if in.ScopeKind != nil {
|
||||
newKind = *in.ScopeKind
|
||||
}
|
||||
newVersion := row.ScopeVersion
|
||||
if in.ScopeVersion != nil {
|
||||
newVersion = *in.ScopeVersion
|
||||
}
|
||||
if in.Scope == nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "validation_error", "scope must be provided when changing scope kind/version")
|
||||
return
|
||||
}
|
||||
prScopes := dto.ScopeRegistry[row.Provider]
|
||||
kScopes := prScopes[newKind]
|
||||
sdef := kScopes[newVersion]
|
||||
dst := sdef.New()
|
||||
if err := json.Unmarshal(*in.Scope, dst); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "invalid_scope_json", err.Error())
|
||||
return
|
||||
}
|
||||
if err := sdef.Validate(dst); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "invalid_scope", err.Error())
|
||||
return
|
||||
}
|
||||
canonScope, err := canonicalJSON(dst)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "canon_error", err.Error())
|
||||
return
|
||||
}
|
||||
row.Scope = canonScope
|
||||
row.ScopeKind = newKind
|
||||
row.ScopeVersion = newVersion
|
||||
row.ScopeFingerprint = sha256Hex(canonScope)
|
||||
}
|
||||
|
||||
// Rotate secret
|
||||
if in.Secret != nil {
|
||||
// validate against current Provider/Kind/SchemaVersion
|
||||
def := dto.CredentialRegistry[row.Provider][row.Kind][row.SchemaVersion]
|
||||
dst := def.New()
|
||||
if err := json.Unmarshal(*in.Secret, dst); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "invalid_secret_json", err.Error())
|
||||
return
|
||||
}
|
||||
if err := def.Validate(dst); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "invalid_secret", err.Error())
|
||||
return
|
||||
}
|
||||
canonSecret, err := canonicalJSON(dst)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "canon_error", err.Error())
|
||||
return
|
||||
}
|
||||
cipher, iv, tag, err := utils.EncryptForOrg(orgID, canonSecret, db)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "encrypt_error", err.Error())
|
||||
return
|
||||
}
|
||||
row.EncryptedData = cipher
|
||||
row.IV = iv
|
||||
row.Tag = tag
|
||||
}
|
||||
|
||||
if err := db.Save(&row).Error; err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
|
||||
return
|
||||
}
|
||||
utils.WriteJSON(w, http.StatusOK, credOut(&row))
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCredential godoc
|
||||
// @ID DeleteCredential
|
||||
// @Summary Delete credential
|
||||
// @Tags Credentials
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization ID (UUID)"
|
||||
// @Param id path string true "Credential ID (UUID)"
|
||||
// @Success 204
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Router /credentials/{id} [delete]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func DeleteCredential(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
|
||||
}
|
||||
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid UUID")
|
||||
return
|
||||
}
|
||||
res := db.Where("organization_id = ? AND id = ?", orgID, id).Delete(&models.Credential{})
|
||||
if res.Error != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", res.Error.Error())
|
||||
return
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
utils.WriteError(w, http.StatusNotFound, "not_found", "credential not found")
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// RevealCredential godoc
|
||||
// @ID RevealCredential
|
||||
// @Summary Reveal decrypted secret (one-time read)
|
||||
// @Tags Credentials
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-Org-ID header string false "Organization ID (UUID)"
|
||||
// @Param id path string true "Credential ID (UUID)"
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Failure 403 {string} string "organization required"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Router /credentials/{id}/reveal [post]
|
||||
// @Security BearerAuth
|
||||
// @Security OrgKeyAuth
|
||||
// @Security OrgSecretAuth
|
||||
func RevealCredential(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
|
||||
}
|
||||
|
||||
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid UUID")
|
||||
return
|
||||
}
|
||||
|
||||
var row models.Credential
|
||||
if err := db.Where("organization_id = ? AND id = ?", orgID, id).First(&row).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
utils.WriteError(w, http.StatusNotFound, "not_found", "credential not found")
|
||||
return
|
||||
}
|
||||
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plain, err := utils.DecryptForOrg(orgID, row.EncryptedData, row.IV, row.Tag, db)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, "decrypt_error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, plain)
|
||||
}
|
||||
}
|
||||
|
||||
// -- Helpers
|
||||
|
||||
func canonicalJSON(v any) ([]byte, error) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var m any
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return marshalSorted(m)
|
||||
}
|
||||
|
||||
func marshalSorted(v any) ([]byte, error) {
|
||||
switch vv := v.(type) {
|
||||
case map[string]any:
|
||||
keys := make([]string, 0, len(vv))
|
||||
for k := range vv {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
buf := bytes.NewBufferString("{")
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
kb, _ := json.Marshal(k)
|
||||
buf.Write(kb)
|
||||
buf.WriteByte(':')
|
||||
b, err := marshalSorted(vv[k])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.Bytes(), nil
|
||||
case []any:
|
||||
buf := bytes.NewBufferString("[")
|
||||
for i, e := range vv {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
b, err := marshalSorted(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
return buf.Bytes(), nil
|
||||
default:
|
||||
return json.Marshal(v)
|
||||
}
|
||||
}
|
||||
|
||||
func sha256Hex(b []byte) string {
|
||||
sum := sha256.Sum256(b)
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// SaveCredentialWithScope validates secret+scope, encrypts, fingerprints, and stores.
|
||||
func SaveCredentialWithScope(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
orgID uuid.UUID,
|
||||
provider, kind string,
|
||||
schemaVersion int,
|
||||
scopeKind string,
|
||||
scopeVersion int,
|
||||
rawScope json.RawMessage,
|
||||
rawSecret json.RawMessage,
|
||||
name, accountID, region string,
|
||||
) (*models.Credential, error) {
|
||||
// 1) secret shape
|
||||
pv, ok := dto.CredentialRegistry[provider]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown provider %q", provider)
|
||||
}
|
||||
kv, ok := pv[kind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown kind %q for provider %q", kind, provider)
|
||||
}
|
||||
def, ok := kv[schemaVersion]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported schema version %d for %s/%s", schemaVersion, provider, kind)
|
||||
}
|
||||
|
||||
secretDst := def.New()
|
||||
if err := json.Unmarshal(rawSecret, secretDst); err != nil {
|
||||
return nil, fmt.Errorf("payload is not valid JSON for %s/%s: %w", provider, kind, err)
|
||||
}
|
||||
if err := def.Validate(secretDst); err != nil {
|
||||
return nil, fmt.Errorf("invalid %s/%s: %w", provider, kind, err)
|
||||
}
|
||||
|
||||
// 2) scope shape
|
||||
prScopes, ok := dto.ScopeRegistry[provider]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no scopes registered for provider %q", provider)
|
||||
}
|
||||
kScopes, ok := prScopes[scopeKind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid scope_kind %q for provider %q", scopeKind, provider)
|
||||
}
|
||||
sdef, ok := kScopes[scopeVersion]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported scope version %d for %s/%s", scopeVersion, provider, scopeKind)
|
||||
}
|
||||
|
||||
scopeDst := sdef.New()
|
||||
if err := json.Unmarshal(rawScope, scopeDst); err != nil {
|
||||
return nil, fmt.Errorf("invalid scope JSON: %w", err)
|
||||
}
|
||||
if err := sdef.Validate(scopeDst); err != nil {
|
||||
return nil, fmt.Errorf("invalid scope: %w", err)
|
||||
}
|
||||
|
||||
// 3) canonicalize scope (also what we persist in plaintext)
|
||||
canonScope, err := canonicalJSON(scopeDst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fp := sha256Hex(canonScope) // or HMAC if you have a server-side key
|
||||
|
||||
// 4) canonicalize + encrypt secret
|
||||
canonSecret, err := canonicalJSON(secretDst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cipher, iv, tag, err := utils.EncryptForOrg(orgID, canonSecret, db)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
|
||||
cred := &models.Credential{
|
||||
OrganizationID: orgID,
|
||||
Provider: provider,
|
||||
Kind: kind,
|
||||
SchemaVersion: schemaVersion,
|
||||
Name: name,
|
||||
ScopeKind: scopeKind,
|
||||
Scope: datatypes.JSON(canonScope),
|
||||
ScopeVersion: scopeVersion,
|
||||
AccountID: accountID,
|
||||
Region: region,
|
||||
ScopeFingerprint: fp,
|
||||
EncryptedData: cipher,
|
||||
IV: iv,
|
||||
Tag: tag,
|
||||
}
|
||||
|
||||
if err := db.WithContext(ctx).Create(cred).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
// credOut converts model → response DTO
|
||||
func credOut(c *models.Credential) dto.CredentialOut {
|
||||
return dto.CredentialOut{
|
||||
ID: c.ID.String(),
|
||||
Provider: c.Provider,
|
||||
Kind: c.Kind,
|
||||
SchemaVersion: c.SchemaVersion,
|
||||
Name: c.Name,
|
||||
ScopeKind: c.ScopeKind,
|
||||
ScopeVersion: c.ScopeVersion,
|
||||
Scope: dto.RawJSON(c.Scope),
|
||||
AccountID: c.AccountID,
|
||||
Region: c.Region,
|
||||
CreatedAt: c.CreatedAt.UTC().Format(time.RFC3339),
|
||||
UpdatedAt: c.UpdatedAt.UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
34
internal/handlers/dto/clusters.go
Normal file
34
internal/handlers/dto/clusters.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ClusterResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Region string `json:"region"`
|
||||
Status string `json:"status"`
|
||||
CaptainDomain string `json:"captain_domain"`
|
||||
ClusterLoadBalancer string `json:"cluster_load_balancer"`
|
||||
RandomToken string `json:"random_token"`
|
||||
CertificateKey string `json:"certificate_key"`
|
||||
ControlLoadBalancer string `json:"control_load_balancer"`
|
||||
NodePools []NodePoolResponse `json:"node_pools,omitempty"`
|
||||
BastionServer *ServerResponse `json:"bastion_server,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CreateClusterRequest struct {
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Region string `json:"region"`
|
||||
Status string `json:"status"`
|
||||
CaptainDomain string `json:"captain_domain"`
|
||||
ClusterLoadBalancer *string `json:"cluster_load_balancer"`
|
||||
ControlLoadBalancer *string `json:"control_load_balancer"`
|
||||
}
|
||||
138
internal/handlers/dto/credentials.go
Normal file
138
internal/handlers/dto/credentials.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// RawJSON is a swagger-friendly wrapper for json.RawMessage.
|
||||
type RawJSON = json.RawMessage
|
||||
|
||||
var Validate = validator.New()
|
||||
|
||||
func init() {
|
||||
_ = Validate.RegisterValidation("awsarn", func(fl validator.FieldLevel) bool {
|
||||
v := fl.Field().String()
|
||||
return len(v) > 10 && len(v) < 2048 && len(v) >= 4 && v[:4] == "arn:"
|
||||
})
|
||||
}
|
||||
|
||||
/*** Shapes for secrets ***/
|
||||
|
||||
type AWSCredential struct {
|
||||
AccessKeyID string `json:"access_key_id" validate:"required,alphanum,len=20"`
|
||||
SecretAccessKey string `json:"secret_access_key" validate:"required"`
|
||||
Region string `json:"region" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type BasicAuth struct {
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
type APIToken struct {
|
||||
Token string `json:"token" validate:"required"`
|
||||
}
|
||||
|
||||
type OAuth2Credential struct {
|
||||
ClientID string `json:"client_id" validate:"required"`
|
||||
ClientSecret string `json:"client_secret" validate:"required"`
|
||||
RefreshToken string `json:"refresh_token" validate:"required"`
|
||||
}
|
||||
|
||||
/*** Shapes for scopes ***/
|
||||
|
||||
type AWSProviderScope struct{}
|
||||
|
||||
type AWSServiceScope struct {
|
||||
Service string `json:"service" validate:"required,oneof=route53 s3 ec2 iam rds dynamodb"`
|
||||
}
|
||||
|
||||
type AWSResourceScope struct {
|
||||
ARN string `json:"arn" validate:"required,awsarn"`
|
||||
}
|
||||
|
||||
/*** Registries ***/
|
||||
|
||||
type ProviderDef struct {
|
||||
New func() any
|
||||
Validate func(any) error
|
||||
}
|
||||
|
||||
type ScopeDef struct {
|
||||
New func() any
|
||||
Validate func(any) error
|
||||
Specificity int // 0=provider, 1=service, 2=resource
|
||||
}
|
||||
|
||||
// Secret shapes per provider/kind/version
|
||||
|
||||
var CredentialRegistry = map[string]map[string]map[int]ProviderDef{
|
||||
"aws": {
|
||||
"aws_access_key": {
|
||||
1: {New: func() any { return &AWSCredential{} }, Validate: func(x any) error { return Validate.Struct(x) }},
|
||||
},
|
||||
},
|
||||
"cloudflare": {"api_token": {1: {New: func() any { return &APIToken{} }, Validate: func(x any) error { return Validate.Struct(x) }}}},
|
||||
"hetzner": {"api_token": {1: {New: func() any { return &APIToken{} }, Validate: func(x any) error { return Validate.Struct(x) }}}},
|
||||
"digitalocean": {"api_token": {1: {New: func() any { return &APIToken{} }, Validate: func(x any) error { return Validate.Struct(x) }}}},
|
||||
"generic": {
|
||||
"basic_auth": {1: {New: func() any { return &BasicAuth{} }, Validate: func(x any) error { return Validate.Struct(x) }}},
|
||||
"oauth2": {1: {New: func() any { return &OAuth2Credential{} }, Validate: func(x any) error { return Validate.Struct(x) }}},
|
||||
},
|
||||
}
|
||||
|
||||
// Scope shapes per provider/scopeKind/version
|
||||
|
||||
var ScopeRegistry = map[string]map[string]map[int]ScopeDef{
|
||||
"aws": {
|
||||
"provider": {1: {New: func() any { return &AWSProviderScope{} }, Validate: func(any) error { return nil }, Specificity: 0}},
|
||||
"service": {1: {New: func() any { return &AWSServiceScope{} }, Validate: func(x any) error { return Validate.Struct(x) }, Specificity: 1}},
|
||||
"resource": {1: {New: func() any { return &AWSResourceScope{} }, Validate: func(x any) error { return Validate.Struct(x) }, Specificity: 2}},
|
||||
},
|
||||
}
|
||||
|
||||
/*** API DTOs used by swagger ***/
|
||||
|
||||
// CreateCredentialRequest represents the POST /credentials payload
|
||||
type CreateCredentialRequest struct {
|
||||
Provider string `json:"provider" validate:"required,oneof=aws cloudflare hetzner digitalocean generic"`
|
||||
Kind string `json:"kind" validate:"required"` // aws_access_key, api_token, basic_auth, oauth2
|
||||
SchemaVersion int `json:"schema_version" validate:"required,gte=1"` // secret schema version
|
||||
Name string `json:"name" validate:"omitempty,max=100"` // human label
|
||||
ScopeKind string `json:"scope_kind" validate:"required,oneof=provider service resource"`
|
||||
ScopeVersion int `json:"scope_version" validate:"required,gte=1"` // scope schema version
|
||||
Scope RawJSON `json:"scope" validate:"required" swaggertype:"object"` // {"service":"route53"} or {"arn":"..."}
|
||||
AccountID string `json:"account_id,omitempty" validate:"omitempty,max=32"`
|
||||
Region string `json:"region,omitempty" validate:"omitempty,max=32"`
|
||||
Secret RawJSON `json:"secret" validate:"required" swaggertype:"object"` // encrypted later
|
||||
}
|
||||
|
||||
// UpdateCredentialRequest represents PATCH /credentials/{id}
|
||||
type UpdateCredentialRequest struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
AccountID *string `json:"account_id,omitempty"`
|
||||
Region *string `json:"region,omitempty"`
|
||||
ScopeKind *string `json:"scope_kind,omitempty"`
|
||||
ScopeVersion *int `json:"scope_version,omitempty"`
|
||||
Scope *RawJSON `json:"scope,omitempty" swaggertype:"object"`
|
||||
Secret *RawJSON `json:"secret,omitempty" swaggertype:"object"` // set if rotating
|
||||
|
||||
}
|
||||
|
||||
// CredentialOut is what we return (no secrets)
|
||||
type CredentialOut struct {
|
||||
ID string `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
Kind string `json:"kind"`
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
Name string `json:"name"`
|
||||
ScopeKind string `json:"scope_kind"`
|
||||
ScopeVersion int `json:"scope_version"`
|
||||
Scope RawJSON `json:"scope" swaggertype:"object"`
|
||||
AccountID string `json:"account_id,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
@@ -57,6 +57,6 @@ type PageJob struct {
|
||||
type EnqueueRequest struct {
|
||||
Queue string `json:"queue" example:"default"`
|
||||
Type string `json:"type" example:"email.send"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
Payload json.RawMessage `json:"payload" swaggertype:"object"`
|
||||
RunAt *time.Time `json:"run_at" example:"2025-11-05T08:00:00Z"`
|
||||
}
|
||||
|
||||
65
internal/handlers/version.go
Normal file
65
internal/handlers/version.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
|
||||
"github.com/glueops/autoglue/internal/utils"
|
||||
"github.com/glueops/autoglue/internal/version"
|
||||
)
|
||||
|
||||
type VersionResponse struct {
|
||||
Version string `json:"version" example:"1.4.2"`
|
||||
Commit string `json:"commit" example:"a1b2c3d"`
|
||||
Built string `json:"built" example:"2025-11-08T12:34:56Z"`
|
||||
BuiltBy string `json:"builtBy" example:"ci"`
|
||||
Go string `json:"go" example:"go1.23.3"`
|
||||
GOOS string `json:"goOS" example:"linux"`
|
||||
GOARCH string `json:"goArch" example:"amd64"`
|
||||
VCS string `json:"vcs,omitempty" example:"git"`
|
||||
Revision string `json:"revision,omitempty" example:"a1b2c3d4e5f6abcdef"`
|
||||
CommitTime string `json:"commitTime,omitempty" example:"2025-11-08T12:31:00Z"`
|
||||
Modified *bool `json:"modified,omitempty" example:"false"`
|
||||
}
|
||||
|
||||
// Version godoc
|
||||
//
|
||||
// @Summary Service version information
|
||||
// @Description Returns build/runtime metadata for the running service.
|
||||
// @Tags Meta
|
||||
// @ID Version // operationId
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} VersionResponse
|
||||
// @Router /version [get]
|
||||
func Version(w http.ResponseWriter, r *http.Request) {
|
||||
resp := VersionResponse{
|
||||
Version: version.Version,
|
||||
Commit: version.Commit,
|
||||
Built: version.Date,
|
||||
BuiltBy: version.BuiltBy,
|
||||
Go: runtime.Version(),
|
||||
GOOS: runtime.GOOS,
|
||||
GOARCH: runtime.GOARCH,
|
||||
}
|
||||
|
||||
if bi, ok := debug.ReadBuildInfo(); ok {
|
||||
for _, s := range bi.Settings {
|
||||
switch s.Key {
|
||||
case "vcs":
|
||||
resp.VCS = s.Value
|
||||
case "vcs.revision":
|
||||
resp.Revision = s.Value
|
||||
case "vcs.time":
|
||||
resp.CommitTime = s.Value
|
||||
case "vcs.modified":
|
||||
if b, err := strconv.ParseBool(s.Value); err == nil {
|
||||
resp.Modified = &b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
utils.WriteJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ type Cluster struct {
|
||||
Status string `json:"status"`
|
||||
CaptainDomain string `gorm:"not null" json:"captain_domain"`
|
||||
ClusterLoadBalancer string `json:"cluster_load_balancer"`
|
||||
ControlLoadBalancer string `json:"control_load_balancer"`
|
||||
RandomToken string `json:"random_token"`
|
||||
CertificateKey string `json:"certificate_key"`
|
||||
EncryptedKubeconfig string `gorm:"type:text" json:"-"`
|
||||
|
||||
29
internal/models/credential.go
Normal file
29
internal/models/credential.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
type Credential struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
|
||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id"`
|
||||
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
|
||||
Provider string `gorm:"type:varchar(50);not null;uniqueIndex:uniq_org_provider_scopekind_scope,priority:2;index:idx_provider_kind"`
|
||||
Kind string `gorm:"type:varchar(50);not null;index:idx_provider_kind;index:idx_kind_scope"`
|
||||
ScopeKind string `gorm:"type:varchar(20);not null;uniqueIndex:uniq_org_provider_scopekind_scope,priority:3"`
|
||||
Scope datatypes.JSON `gorm:"type:jsonb;not null;default:'{}';index:idx_kind_scope"`
|
||||
ScopeFingerprint string `gorm:"type:char(64);not null;uniqueIndex:uniq_org_provider_scopekind_scope,priority:4;index"`
|
||||
SchemaVersion int `gorm:"not null;default:1"`
|
||||
Name string `gorm:"type:varchar(100);not null;default:''"`
|
||||
ScopeVersion int `gorm:"not null;default:1"`
|
||||
AccountID string `gorm:"type:varchar(32)"`
|
||||
Region string `gorm:"type:varchar(32)"`
|
||||
EncryptedData string `gorm:"not null"`
|
||||
IV string `gorm:"not null"`
|
||||
Tag string `gorm:"not null"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty" gorm:"type:timestamptz;column:created_at;not null;default:now()" format:"date-time"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"type:timestamptz;autoUpdateTime;column:updated_at;not null;default:now()" format:"date-time"`
|
||||
}
|
||||
20
internal/models/dns.go
Normal file
20
internal/models/dns.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Dns struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
|
||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null;uniqueIndex:idx_credentials_org_provider" json:"organization_id"`
|
||||
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
|
||||
ClusterID *uuid.UUID `gorm:"type:uuid" json:"cluster_id,omitempty"`
|
||||
Cluster *Cluster `gorm:"foreignKey:ClusterID" json:"cluster,omitempty"`
|
||||
Type string `gorm:"not null" json:"type,omitempty"`
|
||||
Name string `gorm:"not null" json:"name,omitempty"`
|
||||
Content string `gorm:"not null" json:"content,omitempty"`
|
||||
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()"`
|
||||
}
|
||||
@@ -12,7 +12,7 @@ type NodePool struct {
|
||||
Annotations []Annotation `gorm:"many2many:node_annotations;constraint:OnDelete:CASCADE" json:"annotations,omitempty"`
|
||||
Labels []Label `gorm:"many2many:node_labels;constraint:OnDelete:CASCADE" json:"labels,omitempty"`
|
||||
Taints []Taint `gorm:"many2many:node_taints;constraint:OnDelete:CASCADE" json:"taints,omitempty"`
|
||||
//Clusters []Cluster `gorm:"many2many:cluster_node_pools;constraint:OnDelete:CASCADE" json:"clusters,omitempty"`
|
||||
Clusters []Cluster `gorm:"many2many:cluster_node_pools;constraint:OnDelete:CASCADE" json:"clusters,omitempty"`
|
||||
//Topology string `gorm:"not null,default:'stacked'" json:"topology,omitempty"` // stacked or external
|
||||
Role string `gorm:"not null,default:'worker'" json:"role,omitempty"` // master, worker, or etcd (etcd only if topology = external
|
||||
}
|
||||
|
||||
79
internal/web/dist/assets/index-52pog1DZ.js
vendored
Normal file
79
internal/web/dist/assets/index-52pog1DZ.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
internal/web/dist/assets/index-52pog1DZ.js.br
vendored
Normal file
BIN
internal/web/dist/assets/index-52pog1DZ.js.br
vendored
Normal file
Binary file not shown.
BIN
internal/web/dist/assets/index-52pog1DZ.js.gz
vendored
Normal file
BIN
internal/web/dist/assets/index-52pog1DZ.js.gz
vendored
Normal file
Binary file not shown.
1
internal/web/dist/assets/index-52pog1DZ.js.map
vendored
Normal file
1
internal/web/dist/assets/index-52pog1DZ.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
internal/web/dist/assets/index-tX4seA_J.css
vendored
Normal file
2
internal/web/dist/assets/index-tX4seA_J.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
internal/web/dist/assets/index-tX4seA_J.css.br
vendored
Normal file
BIN
internal/web/dist/assets/index-tX4seA_J.css.br
vendored
Normal file
Binary file not shown.
BIN
internal/web/dist/assets/index-tX4seA_J.css.gz
vendored
Normal file
BIN
internal/web/dist/assets/index-tX4seA_J.css.gz
vendored
Normal file
Binary file not shown.
4
internal/web/dist/assets/react-B75e6Si-.js
vendored
Normal file
4
internal/web/dist/assets/react-B75e6Si-.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
internal/web/dist/assets/react-B75e6Si-.js.br
vendored
Normal file
BIN
internal/web/dist/assets/react-B75e6Si-.js.br
vendored
Normal file
Binary file not shown.
BIN
internal/web/dist/assets/react-B75e6Si-.js.gz
vendored
Normal file
BIN
internal/web/dist/assets/react-B75e6Si-.js.gz
vendored
Normal file
Binary file not shown.
1
internal/web/dist/assets/react-B75e6Si-.js.map
vendored
Normal file
1
internal/web/dist/assets/react-B75e6Si-.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
15
internal/web/dist/index.html
vendored
Normal file
15
internal/web/dist/index.html
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AutoGlue</title>
|
||||
<script type="module" crossorigin src="/assets/index-52pog1DZ.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/react-B75e6Si-.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-tX4seA_J.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
internal/web/dist/index.html.br
vendored
Normal file
BIN
internal/web/dist/index.html.br
vendored
Normal file
Binary file not shown.
BIN
internal/web/dist/index.html.gz
vendored
Normal file
BIN
internal/web/dist/index.html.gz
vendored
Normal file
Binary file not shown.
2
internal/web/dist/vite.svg
vendored
Normal file
2
internal/web/dist/vite.svg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88"
|
||||
height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -56,7 +56,7 @@
|
||||
"react-router-dom": "^7.9.5",
|
||||
"recharts": "2.15.4",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^4.1.12"
|
||||
@@ -65,12 +65,10 @@
|
||||
"@eslint/js": "9.39.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.7.0",
|
||||
"@types/node": "24.10.0",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-dom": "19.2.2",
|
||||
"@vitejs/plugin-react": "5.1.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"eslint-plugin-react-hooks": "7.0.1",
|
||||
"eslint-plugin-react-refresh": "0.4.24",
|
||||
"globals": "16.5.0",
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 { JobsPage } from "@/pages/jobs/jobs-page.tsx"
|
||||
import { LabelsPage } from "@/pages/labels/labels-page.tsx"
|
||||
import { MePage } from "@/pages/me/me-page.tsx"
|
||||
@@ -33,6 +34,7 @@ export default function App() {
|
||||
<Route path="/labels" element={<LabelsPage />} />
|
||||
<Route path="/annotations" element={<AnnotationPage />} />
|
||||
<Route path="/node-pools" element={<NodePoolsPage />} />
|
||||
<Route path="/credentials" element={<CredentialPage />} />
|
||||
|
||||
<Route path="/admin/jobs" element={<JobsPage />} />
|
||||
</Route>
|
||||
|
||||
@@ -18,7 +18,12 @@ export const archerAdminApi = {
|
||||
return await archerAdmin.adminListArcherJobs(params)
|
||||
})
|
||||
},
|
||||
enqueue: (body: { queue: string; type: string; payload?: unknown; run_at?: string }) => {
|
||||
enqueue: (body: {
|
||||
queue: string
|
||||
type: string
|
||||
payload?: object | undefined
|
||||
run_at?: string
|
||||
}) => {
|
||||
return withRefresh(async () => {
|
||||
return await archerAdmin.adminEnqueueArcherJob({ body })
|
||||
})
|
||||
|
||||
32
ui/src/api/credentials.ts
Normal file
32
ui/src/api/credentials.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { withRefresh } from "@/api/with-refresh.ts"
|
||||
import type { DtoCreateCredentialRequest, DtoUpdateCredentialRequest } from "@/sdk"
|
||||
import { makeCredentialsApi } from "@/sdkClient.ts"
|
||||
|
||||
const credentials = makeCredentialsApi()
|
||||
|
||||
export const credentialsApi = {
|
||||
listCredentials: () =>
|
||||
withRefresh(async () => {
|
||||
return await credentials.listCredentials()
|
||||
}),
|
||||
createCredential: async (body: DtoCreateCredentialRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await credentials.createCredential({ body })
|
||||
}),
|
||||
getCredential: async (id: string) =>
|
||||
withRefresh(async () => {
|
||||
return await credentials.getCredential({ id })
|
||||
}),
|
||||
deleteCredential: async (id: string) =>
|
||||
withRefresh(async () => {
|
||||
await credentials.deleteCredential({ id })
|
||||
}),
|
||||
updateCredential: async (id: string, body: DtoUpdateCredentialRequest) =>
|
||||
withRefresh(async () => {
|
||||
return await credentials.updateCredential({ id, body })
|
||||
}),
|
||||
revealCredential: async (id: string) =>
|
||||
withRefresh(async () => {
|
||||
return await credentials.revealCredential({ id })
|
||||
}),
|
||||
}
|
||||
15
ui/src/api/footer.ts
Normal file
15
ui/src/api/footer.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const metaApi = {
|
||||
footer: async () => {
|
||||
const res = await fetch("/api/v1/version", { cache: "no-store" })
|
||||
if (!res.ok) throw new Error("failed to fetch version")
|
||||
return (await res.json()) as {
|
||||
built: string
|
||||
builtBy: string
|
||||
commit: string
|
||||
go: string
|
||||
goArch: string
|
||||
goOS: string
|
||||
version: string
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { withRefresh } from "@/api/with-refresh.ts"
|
||||
import type { DtoCreateLabelRequest, DtoUpdateLabelRequest } from "@/sdk"
|
||||
import { makeLabelsApi } from "@/sdkClient.ts"
|
||||
|
||||
|
||||
const labels = makeLabelsApi()
|
||||
|
||||
export const labelsApi = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { meApi } from "@/api/me.ts"
|
||||
import { orgStore } from "@/auth/org.ts"
|
||||
import { authStore } from "@/auth/store.ts"
|
||||
import { Footer } from "@/layouts/footer.tsx"
|
||||
import { adminNav, mainNav, orgNav, userNav } from "@/layouts/nav-config.ts"
|
||||
import { OrgSwitcher } from "@/layouts/org-switcher.tsx"
|
||||
import { Topbar } from "@/layouts/topbar.tsx"
|
||||
@@ -171,11 +171,12 @@ export const AppShell = () => {
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
|
||||
<SidebarInset className="min-h-screen">
|
||||
<SidebarInset className="flex min-h-screen flex-col">
|
||||
<Topbar />
|
||||
<main className="p-4">
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
|
||||
135
ui/src/layouts/footer.tsx
Normal file
135
ui/src/layouts/footer.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { memo, useMemo } from "react"
|
||||
import { metaApi } from "@/api/footer"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { Clipboard, ExternalLink, GitCommit, Info } from "lucide-react"
|
||||
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
|
||||
type VersionInfo = {
|
||||
built: string // ISO string or "unknown"
|
||||
builtBy: string
|
||||
commit: string
|
||||
go: string
|
||||
goArch: string
|
||||
goOS: string
|
||||
version: string
|
||||
}
|
||||
|
||||
function shortCommit(c?: string) {
|
||||
return c && c !== "none" ? c.slice(0, 7) : "none"
|
||||
}
|
||||
|
||||
function formatBuilt(built: string) {
|
||||
if (!built || built === "unknown") return "unknown"
|
||||
const d = new Date(built)
|
||||
return isNaN(+d) ? built : d.toLocaleString()
|
||||
}
|
||||
|
||||
function asClipboardText(v?: VersionInfo) {
|
||||
if (!v) return ""
|
||||
return `v${v.version} (${shortCommit(v.commit)}) • built ${v.built} • ${v.go} ${v.goOS}/${v.goArch}`
|
||||
}
|
||||
|
||||
export const Footer = memo(function Footer({ className }: { className?: string }) {
|
||||
const footerQ = useQuery({
|
||||
queryKey: ["footer"],
|
||||
queryFn: () => metaApi.footer() as Promise<VersionInfo>,
|
||||
staleTime: 60_000,
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
const data = footerQ.data
|
||||
|
||||
const copyText = useMemo(() => asClipboardText(data), [data])
|
||||
|
||||
return (
|
||||
<footer className="bg-background text-muted-foreground w-full border-t px-3 py-2 text-xs sm:text-sm">
|
||||
<div className="mx-auto flex max-w-screen-2xl items-center justify-between">
|
||||
{/* Left: brand / copyright */}
|
||||
<div className="flex items-center gap-2 text-xs sm:text-sm">
|
||||
<span>© {new Date().getFullYear()} GlueOps</span>
|
||||
<Separator orientation="vertical" className="hidden h-4 sm:block" />
|
||||
<span className="hidden sm:block">All systems nominal.</span>
|
||||
</div>
|
||||
|
||||
{/* Right: version/meta */}
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm">
|
||||
{footerQ.isLoading ? (
|
||||
<span className="animate-pulse">loading version…</span>
|
||||
) : footerQ.isError ? (
|
||||
<span className="text-destructive">version unavailable</span>
|
||||
) : data ? (
|
||||
<TooltipProvider>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="secondary" className="font-mono">
|
||||
{data.version}
|
||||
</Badge>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<GitCommit className="h-3.5 w-3.5" />
|
||||
<span className="font-mono">{shortCommit(data.commit)}</span>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
<div className="font-mono text-xs">{data.commit}</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Info className="h-3.5 w-3.5" />
|
||||
<span>{data.go}</span>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
<div className="font-mono text-xs">
|
||||
{data.goOS}/{data.goArch}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Separator orientation="vertical" className="hidden h-4 sm:block" />
|
||||
|
||||
<span className="hidden sm:inline">
|
||||
built <span className="font-mono">{formatBuilt(data.built)}</span>
|
||||
</span>
|
||||
|
||||
<Separator orientation="vertical" className="hidden h-4 sm:block" />
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => {
|
||||
navigator.clipboard?.writeText(copyText).catch(() => {})
|
||||
}}
|
||||
title="Copy version details"
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<a
|
||||
href="/api/v1/version"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-1 text-xs underline-offset-4 hover:underline"
|
||||
title="Open raw version JSON"
|
||||
>
|
||||
JSON <ExternalLink className="h-3.5 w-3.5" />
|
||||
</a>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
})
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ComponentIcon,
|
||||
FileKey2Icon,
|
||||
KeyRound,
|
||||
LockKeyholeIcon,
|
||||
ServerIcon,
|
||||
SprayCanIcon,
|
||||
TagsIcon,
|
||||
@@ -28,6 +29,7 @@ export const mainNav: NavItem[] = [
|
||||
{ to: "/taints", label: "Taints", icon: SprayCanIcon },
|
||||
{ to: "/servers", label: "Servers", icon: ServerIcon },
|
||||
{ to: "/ssh", label: "SSH Keys", icon: FileKey2Icon },
|
||||
{ to: "/credentials", label: "Credentials", icon: LockKeyholeIcon },
|
||||
]
|
||||
|
||||
export const orgNav: NavItem[] = [
|
||||
|
||||
1386
ui/src/pages/credentials/credential-page.tsx
Normal file
1386
ui/src/pages/credentials/credential-page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -145,8 +145,12 @@ export const JobsPage: FC = () => {
|
||||
|
||||
// Mutations
|
||||
const enqueueM = useMutation({
|
||||
mutationFn: (body: { queue: string; type: string; payload?: unknown; run_at?: string }) =>
|
||||
archerAdminApi.enqueue(body),
|
||||
mutationFn: (body: {
|
||||
queue: string
|
||||
type: string
|
||||
payload?: object | undefined
|
||||
run_at?: string
|
||||
}) => archerAdminApi.enqueue(body),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }),
|
||||
})
|
||||
const retryM = useMutation({
|
||||
@@ -462,7 +466,7 @@ function EnqueueDialog({
|
||||
onSubmit: (body: {
|
||||
queue: string
|
||||
type: string
|
||||
payload?: unknown
|
||||
payload?: object | undefined
|
||||
run_at?: string
|
||||
}) => Promise<unknown>
|
||||
submitting?: boolean
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useForm } from "react-hook-form"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
|
||||
|
||||
import { Button } from "@/components/ui/button.tsx"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx"
|
||||
import {
|
||||
|
||||
@@ -5,9 +5,11 @@ import {
|
||||
ArcherAdminApi,
|
||||
AuthApi,
|
||||
Configuration,
|
||||
CredentialsApi,
|
||||
LabelsApi,
|
||||
MeApi,
|
||||
MeAPIKeysApi,
|
||||
MetaApi,
|
||||
NodePoolsApi,
|
||||
OrgsApi,
|
||||
ServersApi,
|
||||
@@ -108,3 +110,11 @@ export function makeArcherAdminApi() {
|
||||
export function makeNodePoolApi() {
|
||||
return makeApiClient(NodePoolsApi)
|
||||
}
|
||||
|
||||
export function makeMetaApi() {
|
||||
return makeApiClient(MetaApi)
|
||||
}
|
||||
|
||||
export function makeCredentialsApi() {
|
||||
return makeApiClient(CredentialsApi)
|
||||
}
|
||||
|
||||
355
ui/yarn.lock
355
ui/yarn.lock
@@ -584,42 +584,42 @@
|
||||
"@babel/types" "^7.26.0"
|
||||
semver "^7.5.2"
|
||||
|
||||
"@inquirer/ansi@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/ansi/-/ansi-1.0.1.tgz#994f7dd16a00c547a7b110e04bf4f4eca1857929"
|
||||
integrity sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==
|
||||
"@inquirer/ansi@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/ansi/-/ansi-1.0.2.tgz#674a4c4d81ad460695cb2a1fc69d78cd187f337e"
|
||||
integrity sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==
|
||||
|
||||
"@inquirer/confirm@^5.0.0":
|
||||
version "5.1.19"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.19.tgz#bf28b420898999eb7479ab55623a3fbaf1453ff4"
|
||||
integrity sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==
|
||||
version "5.1.20"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.20.tgz#8e85662584f162b8b9f6a7c9edcb430fd79f56ad"
|
||||
integrity sha512-HDGiWh2tyRZa0M1ZnEIUCQro25gW/mN8ODByicQrbR1yHx4hT+IOpozCMi5TgBtUdklLwRI2mv14eNpftDluEw==
|
||||
dependencies:
|
||||
"@inquirer/core" "^10.3.0"
|
||||
"@inquirer/type" "^3.0.9"
|
||||
"@inquirer/core" "^10.3.1"
|
||||
"@inquirer/type" "^3.0.10"
|
||||
|
||||
"@inquirer/core@^10.3.0":
|
||||
version "10.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.3.0.tgz#342e4fd62cbd33ea62089364274995dbec1f2ffe"
|
||||
integrity sha512-Uv2aPPPSK5jeCplQmQ9xadnFx2Zhj9b5Dj7bU6ZeCdDNNY11nhYy4btcSdtDguHqCT2h5oNeQTcUNSGGLA7NTA==
|
||||
"@inquirer/core@^10.3.1":
|
||||
version "10.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.3.1.tgz#09bba1c6e0c45cfd3975c0c85c784c61b916baa8"
|
||||
integrity sha512-hzGKIkfomGFPgxKmnKEKeA+uCYBqC+TKtRx5LgyHRCrF6S2MliwRIjp3sUaWwVzMp7ZXVs8elB0Tfe682Rpg4w==
|
||||
dependencies:
|
||||
"@inquirer/ansi" "^1.0.1"
|
||||
"@inquirer/figures" "^1.0.14"
|
||||
"@inquirer/type" "^3.0.9"
|
||||
"@inquirer/ansi" "^1.0.2"
|
||||
"@inquirer/figures" "^1.0.15"
|
||||
"@inquirer/type" "^3.0.10"
|
||||
cli-width "^4.1.0"
|
||||
mute-stream "^2.0.0"
|
||||
mute-stream "^3.0.0"
|
||||
signal-exit "^4.1.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
yoctocolors-cjs "^2.1.2"
|
||||
yoctocolors-cjs "^2.1.3"
|
||||
|
||||
"@inquirer/figures@^1.0.14":
|
||||
version "1.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.14.tgz#12a7bfd344a83ae6cc5d6004b389ed11f6db6be4"
|
||||
integrity sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==
|
||||
"@inquirer/figures@^1.0.15":
|
||||
version "1.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.15.tgz#dbb49ed80df11df74268023b496ac5d9acd22b3a"
|
||||
integrity sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==
|
||||
|
||||
"@inquirer/type@^3.0.9":
|
||||
version "3.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.9.tgz#f7f9696e9276e4e1ae9332767afb9199992e31d9"
|
||||
integrity sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==
|
||||
"@inquirer/type@^3.0.10":
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.10.tgz#11ed564ec78432a200ea2601a212d24af8150d50"
|
||||
integrity sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==
|
||||
|
||||
"@isaacs/balanced-match@^4.0.1":
|
||||
version "4.0.1"
|
||||
@@ -668,9 +668,9 @@
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@modelcontextprotocol/sdk@^1.17.2":
|
||||
version "1.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.21.0.tgz#a4574443c02a8ce57e7ecbce823c6eaf04041927"
|
||||
integrity sha512-YFBsXJMFCyI1zP98u7gezMFKX4lgu/XpoZJk7ufI6UlFKXLj2hAMUuRlQX/nrmIPOmhRrG6tw2OQ2ZA/ZlXYpQ==
|
||||
version "1.21.1"
|
||||
resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.21.1.tgz#8fba02e7581d49cc9b047aab0cfd334043321fe5"
|
||||
integrity sha512-UyLFcJLDvUuZbGnaQqXFT32CpPpGj7VS19roLut6gkQVhb439xUzYWbsUvdI3ZPL+2hnFosuugtYWE0Mcs1rmQ==
|
||||
dependencies:
|
||||
ajv "^8.17.1"
|
||||
ajv-formats "^3.0.1"
|
||||
@@ -763,11 +763,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda"
|
||||
integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==
|
||||
|
||||
"@pkgr/core@^0.2.9":
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b"
|
||||
integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==
|
||||
|
||||
"@radix-ui/number@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.1.tgz#7b2c9225fbf1b126539551f5985769d0048d9090"
|
||||
@@ -1375,115 +1370,115 @@
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz#fa8249860113711ad3c8053bc79cb07c79b77f62"
|
||||
integrity sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz#0f44a2f8668ed87b040b6fe659358ac9239da4db"
|
||||
integrity sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==
|
||||
"@rollup/rollup-android-arm-eabi@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz#7131f3d364805067fd5596302aad9ebef1434b32"
|
||||
integrity sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==
|
||||
|
||||
"@rollup/rollup-android-arm64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz#25b9a01deef6518a948431564c987bcb205274f5"
|
||||
integrity sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==
|
||||
"@rollup/rollup-android-arm64@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz#7ede14d7fcf7c57821a2731c04b29ccc03145d82"
|
||||
integrity sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz#8a102869c88f3780c7d5e6776afd3f19084ecd7f"
|
||||
integrity sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==
|
||||
"@rollup/rollup-darwin-arm64@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz#d59bf9ed582b38838e86a17f91720c17db6575b9"
|
||||
integrity sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==
|
||||
|
||||
"@rollup/rollup-darwin-x64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz#8e526417cd6f54daf1d0c04cf361160216581956"
|
||||
integrity sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==
|
||||
"@rollup/rollup-darwin-x64@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz#a76278d9b9da9f84ea7909a14d93b915d5bbe01e"
|
||||
integrity sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==
|
||||
|
||||
"@rollup/rollup-freebsd-arm64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz#0e7027054493f3409b1f219a3eac5efd128ef899"
|
||||
integrity sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==
|
||||
"@rollup/rollup-freebsd-arm64@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz#1a94821a1f565b9eaa74187632d482e4c59a1707"
|
||||
integrity sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==
|
||||
|
||||
"@rollup/rollup-freebsd-x64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz#72b204a920139e9ec3d331bd9cfd9a0c248ccb10"
|
||||
integrity sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==
|
||||
"@rollup/rollup-freebsd-x64@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz#aad2274680106b2b6549b1e35e5d3a7a9f1f16af"
|
||||
integrity sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz#ab1b522ebe5b7e06c99504cc38f6cd8b808ba41c"
|
||||
integrity sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz#100fe4306399ffeec47318a3c9b8c0e5e8b07ddb"
|
||||
integrity sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz#f8cc30b638f1ee7e3d18eac24af47ea29d9beb00"
|
||||
integrity sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz#b84634952604b950e18fa11fddebde898c5928d8"
|
||||
integrity sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz#7af37a9e85f25db59dc8214172907b7e146c12cc"
|
||||
integrity sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==
|
||||
"@rollup/rollup-linux-arm64-gnu@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz#dad6f2fb41c2485f29a98e40e9bd78253255dbf3"
|
||||
integrity sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz#a623eb0d3617c03b7a73716eb85c6e37b776f7e0"
|
||||
integrity sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==
|
||||
"@rollup/rollup-linux-arm64-musl@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz#0f3f77c8ce9fbf982f8a8378b70a73dc6704a706"
|
||||
integrity sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz#76ea038b549c5c6c5f0d062942627c4066642ee2"
|
||||
integrity sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==
|
||||
"@rollup/rollup-linux-loong64-gnu@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz#870bb94e9dad28bb3124ba49bd733deaa6aa2635"
|
||||
integrity sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz#d9a4c3f0a3492bc78f6fdfe8131ac61c7359ccd5"
|
||||
integrity sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==
|
||||
"@rollup/rollup-linux-ppc64-gnu@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz#188427d11abefc6c9926e3870b3e032170f5577c"
|
||||
integrity sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz#87ab033eebd1a9a1dd7b60509f6333ec1f82d994"
|
||||
integrity sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz#9dec6eadbbb5abd3b76fe624dc4f006913ff4a7f"
|
||||
integrity sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz#bda3eb67e1c993c1ba12bc9c2f694e7703958d9f"
|
||||
integrity sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==
|
||||
"@rollup/rollup-linux-riscv64-musl@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz#b26ba1c80b6f104dc5bd83ed83181fc0411a0c38"
|
||||
integrity sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz#f7bc10fbe096ab44694233dc42a2291ed5453d4b"
|
||||
integrity sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==
|
||||
"@rollup/rollup-linux-s390x-gnu@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz#dc83647189b68ad8d56a956a6fcaa4ee9c728190"
|
||||
integrity sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz#a151cb1234cc9b2cf5e8cfc02aa91436b8f9e278"
|
||||
integrity sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==
|
||||
"@rollup/rollup-linux-x64-gnu@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz#42c3b8c94e9de37bd103cb2e26fb715118ef6459"
|
||||
integrity sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz#7859e196501cc3b3062d45d2776cfb4d2f3a9350"
|
||||
integrity sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==
|
||||
"@rollup/rollup-linux-x64-musl@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz#d0e216ee1ea16bfafe35681b899b6a05258988e5"
|
||||
integrity sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==
|
||||
|
||||
"@rollup/rollup-openharmony-arm64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz#85d0df7233734df31e547c1e647d2a5300b3bf30"
|
||||
integrity sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==
|
||||
"@rollup/rollup-openharmony-arm64@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz#3acd0157cb8976f659442bfd8a99aca46f8a2931"
|
||||
integrity sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz#e62357d00458db17277b88adbf690bb855cac937"
|
||||
integrity sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==
|
||||
"@rollup/rollup-win32-arm64-msvc@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz#3eb9e7d4d0e1d2e0850c4ee9aa2d0ddf89a8effa"
|
||||
integrity sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz#fc7cd40f44834a703c1f1c3fe8bcc27ce476cd50"
|
||||
integrity sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==
|
||||
"@rollup/rollup-win32-ia32-msvc@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz#d69280bc6680fe19e0956e965811946d542f6365"
|
||||
integrity sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz#1a22acfc93c64a64a48c42672e857ee51774d0d3"
|
||||
integrity sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==
|
||||
"@rollup/rollup-win32-x64-gnu@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz#d182ce91e342bad9cbb8b284cf33ac542b126ead"
|
||||
integrity sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz#1657f56326bbe0ac80eedc9f9c18fc1ddd24e107"
|
||||
integrity sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==
|
||||
"@rollup/rollup-win32-x64-msvc@4.53.2":
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz#d9ab606437fd072b2cb7df7e54bcdc7f1ccbe8b4"
|
||||
integrity sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==
|
||||
|
||||
"@sec-ant/readable-stream@^0.4.1":
|
||||
version "0.4.1"
|
||||
@@ -1736,12 +1731,12 @@
|
||||
dependencies:
|
||||
undici-types "~7.16.0"
|
||||
|
||||
"@types/react-dom@^19.2.2":
|
||||
"@types/react-dom@19.2.2":
|
||||
version "19.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.2.tgz#a4cc874797b7ddc9cb180ef0d5dc23f596fc2332"
|
||||
integrity sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==
|
||||
|
||||
"@types/react@^19.2.2":
|
||||
"@types/react@19.2.2":
|
||||
version "19.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.2.tgz#ba123a75d4c2a51158697160a4ea2ff70aa6bf36"
|
||||
integrity sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==
|
||||
@@ -2371,9 +2366,9 @@ ee-first@1.1.1:
|
||||
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
|
||||
|
||||
electron-to-chromium@^1.5.238:
|
||||
version "1.5.245"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.245.tgz#81aea81adf1e06b6f703b4b35ac6d543421d0fd9"
|
||||
integrity sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==
|
||||
version "1.5.249"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.249.tgz#e4fc3a3e60bb347361e4e876bb31903a9132a447"
|
||||
integrity sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==
|
||||
|
||||
embla-carousel-react@^8.6.0:
|
||||
version "8.6.0"
|
||||
@@ -2492,19 +2487,6 @@ escape-string-regexp@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
eslint-config-prettier@10.1.8:
|
||||
version "10.1.8"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97"
|
||||
integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==
|
||||
|
||||
eslint-plugin-prettier@5.5.4:
|
||||
version "5.5.4"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz#9d61c4ea11de5af704d4edf108c82ccfa7f2e61c"
|
||||
integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==
|
||||
dependencies:
|
||||
prettier-linter-helpers "^1.0.0"
|
||||
synckit "^0.11.7"
|
||||
|
||||
eslint-plugin-react-hooks@7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz#66e258db58ece50723ef20cc159f8aa908219169"
|
||||
@@ -2715,11 +2697,6 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-diff@^1.1.2:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
|
||||
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
|
||||
|
||||
fast-equals@^5.0.1:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.3.2.tgz#75a9c7b1c2f627851349a2db94327d79b774ce83"
|
||||
@@ -3487,9 +3464,9 @@ ms@^2.1.3:
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
msw@^2.10.4:
|
||||
version "2.12.0"
|
||||
resolved "https://registry.yarnpkg.com/msw/-/msw-2.12.0.tgz#92b62b42e2b81aa843151129c513e8c94b13c165"
|
||||
integrity sha512-jzf2eVnd8+iWXN74dccLrHUw3i3hFVvNVQRWS4vBl2KxaUt7Tdur0Eyda/DODGFkZDu2P5MXaeLe/9Qx8PZkrg==
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/msw/-/msw-2.12.1.tgz#b3dee99d7692e92581234b4060b9a9250f5d998e"
|
||||
integrity sha512-arzsi9IZjjByiEw21gSUP82qHM8zkV69nNpWV6W4z72KiLvsDWoOp678ORV6cNfU/JGhlX0SsnD4oXo9gI6I2A==
|
||||
dependencies:
|
||||
"@inquirer/confirm" "^5.0.0"
|
||||
"@mswjs/interceptors" "^0.40.0"
|
||||
@@ -3510,10 +3487,10 @@ msw@^2.10.4:
|
||||
until-async "^3.0.2"
|
||||
yargs "^17.7.2"
|
||||
|
||||
mute-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b"
|
||||
integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==
|
||||
mute-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-3.0.0.tgz#cd8014dd2acb72e1e91bb67c74f0019e620ba2d1"
|
||||
integrity sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==
|
||||
|
||||
nanoid@^3.3.11:
|
||||
version "3.3.11"
|
||||
@@ -3754,13 +3731,6 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prettier-linter-helpers@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
|
||||
integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier-plugin-tailwindcss@0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.1.tgz#0cb15246668788e62b5b752868f5e01f0ce7eec9"
|
||||
@@ -4013,34 +3983,34 @@ reusify@^1.0.4:
|
||||
integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
|
||||
|
||||
rollup@^4.43.0:
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.5.tgz#96982cdcaedcdd51b12359981f240f94304ec235"
|
||||
integrity sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==
|
||||
version "4.53.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.53.2.tgz#98e73ee51e119cb9d88b07d026c959522416420a"
|
||||
integrity sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==
|
||||
dependencies:
|
||||
"@types/estree" "1.0.8"
|
||||
optionalDependencies:
|
||||
"@rollup/rollup-android-arm-eabi" "4.52.5"
|
||||
"@rollup/rollup-android-arm64" "4.52.5"
|
||||
"@rollup/rollup-darwin-arm64" "4.52.5"
|
||||
"@rollup/rollup-darwin-x64" "4.52.5"
|
||||
"@rollup/rollup-freebsd-arm64" "4.52.5"
|
||||
"@rollup/rollup-freebsd-x64" "4.52.5"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.52.5"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.52.5"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.52.5"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.52.5"
|
||||
"@rollup/rollup-linux-loong64-gnu" "4.52.5"
|
||||
"@rollup/rollup-linux-ppc64-gnu" "4.52.5"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.52.5"
|
||||
"@rollup/rollup-linux-riscv64-musl" "4.52.5"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.52.5"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.52.5"
|
||||
"@rollup/rollup-linux-x64-musl" "4.52.5"
|
||||
"@rollup/rollup-openharmony-arm64" "4.52.5"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.52.5"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.52.5"
|
||||
"@rollup/rollup-win32-x64-gnu" "4.52.5"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.52.5"
|
||||
"@rollup/rollup-android-arm-eabi" "4.53.2"
|
||||
"@rollup/rollup-android-arm64" "4.53.2"
|
||||
"@rollup/rollup-darwin-arm64" "4.53.2"
|
||||
"@rollup/rollup-darwin-x64" "4.53.2"
|
||||
"@rollup/rollup-freebsd-arm64" "4.53.2"
|
||||
"@rollup/rollup-freebsd-x64" "4.53.2"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.53.2"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.53.2"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.53.2"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.53.2"
|
||||
"@rollup/rollup-linux-loong64-gnu" "4.53.2"
|
||||
"@rollup/rollup-linux-ppc64-gnu" "4.53.2"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.53.2"
|
||||
"@rollup/rollup-linux-riscv64-musl" "4.53.2"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.53.2"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.53.2"
|
||||
"@rollup/rollup-linux-x64-musl" "4.53.2"
|
||||
"@rollup/rollup-openharmony-arm64" "4.53.2"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.53.2"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.53.2"
|
||||
"@rollup/rollup-win32-x64-gnu" "4.53.2"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.53.2"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
router@^2.2.0:
|
||||
@@ -4329,17 +4299,10 @@ supports-color@^7.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
synckit@^0.11.7:
|
||||
version "0.11.11"
|
||||
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0"
|
||||
integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==
|
||||
dependencies:
|
||||
"@pkgr/core" "^0.2.9"
|
||||
|
||||
tailwind-merge@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-3.3.1.tgz#a7e7db7c714f6020319e626ecfb7e7dac8393a4b"
|
||||
integrity sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==
|
||||
tailwind-merge@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-3.4.0.tgz#5a264e131a096879965f1175d11f8c36e6b64eca"
|
||||
integrity sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==
|
||||
|
||||
tailwindcss@4.1.17, tailwindcss@^4.1.17:
|
||||
version "4.1.17"
|
||||
@@ -4654,7 +4617,7 @@ yocto-queue@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
yoctocolors-cjs@^2.1.2:
|
||||
yoctocolors-cjs@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa"
|
||||
integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==
|
||||
|
||||
Reference in New Issue
Block a user