mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-24 13:14:23 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 404b3d6246 | |||
| 021b0982f4 | |||
| 7b590957e1 | |||
| c9c3cda651 | |||
| 1efb4c4a3b | |||
| 05cb4ac108 | |||
| 6eac4d6598 | |||
| f03a2314c6 | |||
| 2c444b1eba | |||
| aea2e2c05a | |||
| d90bf53eaf | |||
| 14c52366da | |||
| 6737c42601 | |||
| 19988af768 | |||
| d20fcd2c38 |
@@ -37,15 +37,15 @@ jobs:
|
||||
repository: mulgadc/predastore
|
||||
path: predastore
|
||||
|
||||
- name: Setup Go Workspace
|
||||
run: go work init ./hive ./viperblock ./predastore
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache-dependency-path: "hive/go.sum"
|
||||
go-version-file: "hive/go.mod"
|
||||
|
||||
- name: Setup Go Workspace
|
||||
run: go work init ./hive ./viperblock ./predastore
|
||||
|
||||
- name: Build Hive
|
||||
run: make -C hive build
|
||||
|
||||
@@ -77,15 +77,15 @@ jobs:
|
||||
repository: mulgadc/predastore
|
||||
path: predastore
|
||||
|
||||
- name: Setup Go Workspace
|
||||
run: go work init ./hive ./viperblock ./predastore
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache-dependency-path: "hive/go.sum"
|
||||
go-version-file: "hive/go.mod"
|
||||
|
||||
- name: Setup Go Workspace
|
||||
run: go work init ./hive ./viperblock ./predastore
|
||||
|
||||
- name: Run Tests with Race Detector
|
||||
run: make -C hive test-race
|
||||
|
||||
@@ -111,15 +111,15 @@ jobs:
|
||||
repository: mulgadc/predastore
|
||||
path: predastore
|
||||
|
||||
- name: Setup Go Workspace
|
||||
run: go work init ./hive ./viperblock ./predastore
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache-dependency-path: "hive/go.sum"
|
||||
go-version-file: "hive/go.mod"
|
||||
|
||||
- name: Setup Go Workspace
|
||||
run: go work init ./hive ./viperblock ./predastore
|
||||
|
||||
- name: Check Format
|
||||
run: make -C hive check-format
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ sudo apt install nbdkit nbdkit-plugin-dev pkg-config qemu-system qemu-utils qemu
|
||||
|
||||
**Note:** OVN and Open vSwitch are required for VPC networking (virtual switches, routers, DHCP, Geneve overlay). The packages are installed above, but the setup and configuration step (`setup-ovn.sh`) is covered later — see the [Setup OVN](#setup-ovn) section below.
|
||||
|
||||
Ensure the Go toolkit is installed for version 1.26.0 or higher. Recommended to install the latest directly from [https://go.dev/dl/](https://go.dev/dl/).
|
||||
Ensure the Go toolkit is installed for version 1.26.1 or higher. Recommended to install the latest directly from [https://go.dev/dl/](https://go.dev/dl/).
|
||||
|
||||
Confirm Go is correctly installed, and set in your $PATH.
|
||||
|
||||
|
||||
@@ -115,9 +115,9 @@ install-system:
|
||||
ovn-central ovn-host openvswitch-switch
|
||||
|
||||
install-go:
|
||||
@echo -e "\n....Installing Go 1.26.0 for $(ARCH) ($(GO_ARCH))...."
|
||||
@echo -e "\n....Installing Go 1.26.1 for $(ARCH) ($(GO_ARCH))...."
|
||||
@if [ ! -d "/usr/local/go" ]; then \
|
||||
curl -L https://go.dev/dl/go1.26.0.linux-$(GO_ARCH).tar.gz | tar -C /usr/local -xz; \
|
||||
curl -L https://go.dev/dl/go1.26.1.linux-$(GO_ARCH).tar.gz | tar -C /usr/local -xz; \
|
||||
else \
|
||||
echo "Go already installed in /usr/local/go"; \
|
||||
fi
|
||||
|
||||
@@ -195,8 +195,8 @@ func init() {
|
||||
accountCreateCmd.MarkFlagRequired("name")
|
||||
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
configDir := fmt.Sprintf("%s/hive/config", homeDir)
|
||||
hiveDir := fmt.Sprintf("%s/hive/", homeDir)
|
||||
configDir := filepath.Join(homeDir, "hive", "config")
|
||||
hiveDir := filepath.Join(homeDir, "hive")
|
||||
|
||||
rootCmd.PersistentFlags().String("config-dir", configDir, "Configuration directory")
|
||||
rootCmd.PersistentFlags().String("hive-dir", hiveDir, "Hive base directory")
|
||||
@@ -628,6 +628,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
hiveRoot = filepath.Join(homeDir, "hive")
|
||||
}
|
||||
hiveRoot = filepath.Clean(hiveRoot)
|
||||
|
||||
// Determine if this is a multi-node formation
|
||||
isMultiNode := nodes >= 2 && bindIP != "0.0.0.0"
|
||||
|
||||
+5
-4
@@ -25,6 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/pterm/pterm"
|
||||
@@ -161,9 +162,9 @@ func runGetNodes(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
// Parse responses into a map by node name
|
||||
respondedNodes := make(map[string]config.NodeStatusResponse)
|
||||
respondedNodes := make(map[string]types.NodeStatusResponse)
|
||||
for _, data := range responses {
|
||||
var resp config.NodeStatusResponse
|
||||
var resp types.NodeStatusResponse
|
||||
if err := json.Unmarshal(data, &resp); err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -235,14 +236,14 @@ func runGetVMs(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
type vmRow struct {
|
||||
config.VMInfo
|
||||
types.VMInfo
|
||||
Node string
|
||||
Host string
|
||||
}
|
||||
|
||||
var allVMs []vmRow
|
||||
for _, data := range responses {
|
||||
var resp config.NodeVMsResponse
|
||||
var resp types.NodeVMsResponse
|
||||
if err := json.Unmarshal(data, &resp); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
# Generated by Hive admin init
|
||||
|
||||
# Server configuration
|
||||
{{ if .BindIP }}
|
||||
{{- if .BindIP }}
|
||||
listen: {{.BindIP}}:4222
|
||||
{{ else }}
|
||||
{{- else }}
|
||||
listen: 0.0.0.0:4222
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if and .ClusterBindIP .ClusterRoutes }}
|
||||
|
||||
{{ if and .ClusterBindIP .ClusterRoutes }}
|
||||
cluster {
|
||||
name: {{ .ClusterName }}
|
||||
listen: {{.ClusterBindIP}}:4248
|
||||
@@ -20,8 +20,7 @@ cluster {
|
||||
{{- end }}
|
||||
]
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{- end }}
|
||||
|
||||
server_name: hive-nats-{{.Node}}
|
||||
|
||||
@@ -49,9 +48,9 @@ max_payload: 1MB
|
||||
max_pending: 64MB
|
||||
|
||||
# Monitoring
|
||||
{{ if .BindIP }}
|
||||
{{- if .BindIP }}
|
||||
http: {{.BindIP}}:8222
|
||||
{{ else }}
|
||||
{{- else }}
|
||||
http_port: 8222
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ debug = false
|
||||
[rs]
|
||||
data = 2
|
||||
parity = 1
|
||||
{{range .Nodes}}
|
||||
{{- range .Nodes}}
|
||||
|
||||
[[db]]
|
||||
id = {{.ID}}
|
||||
host = "{{.Host}}"
|
||||
@@ -22,14 +23,16 @@ secret_access_key = "{{$.SecretKey}}"
|
||||
{{- if eq .ID 1}}
|
||||
leader = true
|
||||
{{- end}}
|
||||
{{end}}
|
||||
{{- end}}
|
||||
{{- range .Nodes}}
|
||||
|
||||
[[nodes]]
|
||||
id = {{.ID}}
|
||||
host = "{{.Host}}"
|
||||
port = 9991
|
||||
path = "distributed/nodes/node-{{.ID}}/"
|
||||
{{end}}
|
||||
{{- end}}
|
||||
|
||||
[[buckets]]
|
||||
name = "predastore"
|
||||
region = "{{.Region}}"
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -62,9 +62,9 @@ func runTopNodes(cmd *cobra.Command, args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
respondedNodes := make(map[string]config.NodeStatusResponse)
|
||||
respondedNodes := make(map[string]types.NodeStatusResponse)
|
||||
for _, data := range responses {
|
||||
var resp config.NodeStatusResponse
|
||||
var resp types.NodeStatusResponse
|
||||
if err := json.Unmarshal(data, &resp); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/mulgadc/hive
|
||||
|
||||
go 1.26.0
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.55.8
|
||||
@@ -8,8 +8,8 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kdomanski/iso9660 v0.4.0
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/mulgadc/predastore v1.0.2
|
||||
github.com/mulgadc/viperblock v1.0.16
|
||||
github.com/mulgadc/predastore v1.0.3
|
||||
github.com/mulgadc/viperblock v1.0.17
|
||||
github.com/nats-io/nats-server/v2 v2.12.4
|
||||
github.com/nats-io/nats.go v1.49.0
|
||||
github.com/ovn-kubernetes/libovsdb v0.8.1
|
||||
|
||||
@@ -291,10 +291,10 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mulgadc/predastore v1.0.2 h1:7BRE5a0geTShZGHWaXAdkrH3MfhcJ5KnRmhY5V8P5Hw=
|
||||
github.com/mulgadc/predastore v1.0.2/go.mod h1:RQyt9BROgyPfqC6TU69PuZ7RlHRDzZr11RYHL7eQwgk=
|
||||
github.com/mulgadc/viperblock v1.0.16 h1:eMICuMGjsHqmKsBLDBYwdWzoncPBCHCoPFY8f5VtM00=
|
||||
github.com/mulgadc/viperblock v1.0.16/go.mod h1:qck/WYWiXjGRJzoNM99JsFX/z00sZOmf9PM7umKYq3w=
|
||||
github.com/mulgadc/predastore v1.0.3 h1:qkuOEUH9B98QzVByn5osKeXm7SSPgk+ardQ3MibPSok=
|
||||
github.com/mulgadc/predastore v1.0.3/go.mod h1:x3Z6Ts/PONpmVx/R6t/hkHDJ3+0xRieYS3BuuVaNgyA=
|
||||
github.com/mulgadc/viperblock v1.0.17 h1:bGRVlXgcqnmH+Xxon03W/ZsbXqu/EE5+/VG3J4E3EBU=
|
||||
github.com/mulgadc/viperblock v1.0.17/go.mod h1:w9V0DjUH5hMXbXsiLbDr9lpqzXBGhEiYB1BeV6TYBkw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
|
||||
+31
-210
@@ -1,14 +1,11 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
@@ -40,45 +37,6 @@ type Config struct {
|
||||
WalDir string `mapstructure:"wal_dir"`
|
||||
}
|
||||
|
||||
// NodeBaseDir returns the BaseDir for the current node, or "" if the config
|
||||
// is nil, the node name is unset, or the node is not found in the Nodes map.
|
||||
func (cc *ClusterConfig) NodeBaseDir() string {
|
||||
if cc == nil || cc.Node == "" {
|
||||
slog.Warn("NodeBaseDir: no config or node name set, using global PID path")
|
||||
return ""
|
||||
}
|
||||
node, ok := cc.Nodes[cc.Node]
|
||||
if !ok {
|
||||
slog.Error("NodeBaseDir: node not found in config", "node", cc.Node)
|
||||
return ""
|
||||
}
|
||||
if node.BaseDir == "" {
|
||||
slog.Warn("NodeBaseDir: BaseDir is empty for node, using global PID path", "node", cc.Node)
|
||||
}
|
||||
return node.BaseDir
|
||||
}
|
||||
|
||||
// AllServices is the default service list when Services is empty (backward compat).
|
||||
var AllServices = []string{"nats", "predastore", "viperblock", "daemon", "awsgw", "vpcd", "ui"}
|
||||
|
||||
// HasService reports whether the node runs the named service.
|
||||
// An empty Services list means all services (backward compat).
|
||||
func (c Config) HasService(name string) bool {
|
||||
services := c.Services
|
||||
if len(services) == 0 {
|
||||
services = AllServices
|
||||
}
|
||||
return slices.Contains(services, name)
|
||||
}
|
||||
|
||||
// GetServices returns the configured service list, defaulting to AllServices.
|
||||
func (c Config) GetServices() []string {
|
||||
if len(c.Services) == 0 {
|
||||
return AllServices
|
||||
}
|
||||
return c.Services
|
||||
}
|
||||
|
||||
type AWSGWConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
TLSKey string `mapstructure:"tlskey"`
|
||||
@@ -134,180 +92,43 @@ type NATSSub struct {
|
||||
Subject string `mapstructure:"subject"`
|
||||
}
|
||||
|
||||
type EBSRequests struct {
|
||||
Requests []EBSRequest `mapstructure:"ebs_requests"`
|
||||
Mu sync.Mutex `json:"-"`
|
||||
}
|
||||
|
||||
type EBSRequest struct {
|
||||
Name string
|
||||
VolType string
|
||||
Boot bool
|
||||
EFI bool
|
||||
CloudInit bool
|
||||
DeleteOnTermination bool
|
||||
NBDURI string // NBD URI - socket path (nbd:unix:/path.sock) or TCP (nbd://host:port)
|
||||
DeviceName string // AWS API device name (e.g. /dev/sdf) for hot-plugged volumes
|
||||
}
|
||||
|
||||
// NBDTransport defines the transport type for NBD connections
|
||||
type NBDTransport string
|
||||
|
||||
const (
|
||||
// NBDTransportSocket uses Unix domain sockets (faster, local only)
|
||||
NBDTransportSocket NBDTransport = "socket"
|
||||
// NBDTransportTCP uses TCP connections (required for remote/DPU scenarios)
|
||||
NBDTransportTCP NBDTransport = "tcp"
|
||||
)
|
||||
|
||||
type EBSMountResponse struct {
|
||||
URI string
|
||||
Mounted bool
|
||||
Error string
|
||||
}
|
||||
|
||||
type EBSUnMountResponse struct {
|
||||
Volume string
|
||||
Mounted bool
|
||||
Error string
|
||||
}
|
||||
|
||||
type EBSSyncRequest struct {
|
||||
Volume string
|
||||
}
|
||||
|
||||
type EBSSyncResponse struct {
|
||||
Volume string
|
||||
Synced bool
|
||||
Error string
|
||||
}
|
||||
|
||||
type EBSDeleteRequest struct {
|
||||
Volume string
|
||||
}
|
||||
|
||||
type EBSDeleteResponse struct {
|
||||
Volume string
|
||||
Success bool
|
||||
Error string
|
||||
}
|
||||
|
||||
type EBSSnapshotRequest struct {
|
||||
Volume string
|
||||
SnapshotID string
|
||||
}
|
||||
|
||||
type EBSSnapshotResponse struct {
|
||||
SnapshotID string
|
||||
Success bool
|
||||
Error string
|
||||
}
|
||||
|
||||
// EC2, TODO: Move to vm.go or more applicable place
|
||||
type EC2StartInstancesRequest struct {
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
type EC2StartInstancesResponse struct {
|
||||
InstanceID string
|
||||
Status string
|
||||
Error string
|
||||
}
|
||||
|
||||
// TODO: Make a generic function for the response
|
||||
func (ec2StartInstanceResponse EC2StartInstancesResponse) Respond(msg *nats.Msg) {
|
||||
|
||||
response, err := json.Marshal(ec2StartInstanceResponse)
|
||||
if err != nil {
|
||||
slog.Error("Failed to marshal response", "err", err)
|
||||
return
|
||||
// NodeBaseDir returns the BaseDir for the current node, or "" if the config
|
||||
// is nil, the node name is unset, or the node is not found in the Nodes map.
|
||||
func (cc *ClusterConfig) NodeBaseDir() string {
|
||||
if cc == nil || cc.Node == "" {
|
||||
slog.Warn("NodeBaseDir: no config or node name set, using global PID path")
|
||||
return ""
|
||||
}
|
||||
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to NATS request", "err", err)
|
||||
node, ok := cc.Nodes[cc.Node]
|
||||
if !ok {
|
||||
slog.Error("NodeBaseDir: node not found in config", "node", cc.Node)
|
||||
return ""
|
||||
}
|
||||
|
||||
if node.BaseDir == "" {
|
||||
slog.Warn("NodeBaseDir: BaseDir is empty for node, using global PID path", "node", cc.Node)
|
||||
}
|
||||
return node.BaseDir
|
||||
}
|
||||
|
||||
// Cluster manager API types
|
||||
type NodeJoinRequest struct {
|
||||
Node string `json:"node"`
|
||||
Region string `json:"region"`
|
||||
AZ string `json:"az"`
|
||||
DataDir string `json:"data_dir"`
|
||||
DaemonHost string `json:"daemon_host"` // Host:port where this node's daemon is accessible
|
||||
// AllServices is the default service list when Services is empty (backward compat).
|
||||
var AllServices = []string{"nats", "predastore", "viperblock", "daemon", "awsgw", "vpcd", "ui"}
|
||||
|
||||
// HasService reports whether the node runs the named service.
|
||||
// An empty Services list means all services (backward compat).
|
||||
func (c Config) HasService(name string) bool {
|
||||
services := c.Services
|
||||
if len(services) == 0 {
|
||||
services = AllServices
|
||||
}
|
||||
return slices.Contains(services, name)
|
||||
}
|
||||
|
||||
// SharedClusterData contains only the shared cluster information (no node-specific top-level fields)
|
||||
type SharedClusterData struct {
|
||||
Epoch uint64 `json:"epoch" toml:"epoch"`
|
||||
Version string `json:"version" toml:"version"`
|
||||
Nodes map[string]Config `json:"nodes" toml:"nodes"`
|
||||
}
|
||||
|
||||
type NodeJoinResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
SharedData *SharedClusterData `json:"shared_data,omitempty"`
|
||||
ConfigHash string `json:"config_hash,omitempty"`
|
||||
JoiningNode string `json:"joining_node,omitempty"` // The node name that is joining
|
||||
// CA distribution for per-node certificate generation
|
||||
CACert string `json:"ca_cert,omitempty"` // PEM-encoded CA certificate
|
||||
CAKey string `json:"ca_key,omitempty"` // PEM-encoded CA private key
|
||||
// Predastore config distribution for multi-node clusters
|
||||
PredastoreConfig string `json:"predastore_config,omitempty"` // Full predastore.toml content
|
||||
}
|
||||
|
||||
type NodeHealthResponse struct {
|
||||
Node string `json:"node"`
|
||||
Status string `json:"status"`
|
||||
ConfigHash string `json:"config_hash"`
|
||||
Epoch uint64 `json:"epoch"`
|
||||
Uptime int64 `json:"uptime"`
|
||||
Services []string `json:"services"`
|
||||
ServiceHealth map[string]string `json:"service_health,omitempty"`
|
||||
}
|
||||
|
||||
// NodeStatusResponse is returned by the hive.node.status NATS topic (fan-out).
|
||||
type NodeStatusResponse struct {
|
||||
Node string `json:"node"`
|
||||
Status string `json:"status"`
|
||||
Host string `json:"host"`
|
||||
Region string `json:"region"`
|
||||
AZ string `json:"az"`
|
||||
Uptime int64 `json:"uptime"`
|
||||
Services []string `json:"services"`
|
||||
TotalVCPU int `json:"total_vcpu"`
|
||||
TotalMemGB float64 `json:"total_mem_gb"`
|
||||
AllocVCPU int `json:"alloc_vcpu"`
|
||||
AllocMemGB float64 `json:"alloc_mem_gb"`
|
||||
VMCount int `json:"vm_count"`
|
||||
InstanceTypes []InstanceTypeCap `json:"instance_types"`
|
||||
}
|
||||
|
||||
// InstanceTypeCap describes available capacity for one instance type on a node.
|
||||
type InstanceTypeCap struct {
|
||||
Name string `json:"name"`
|
||||
VCPU int `json:"vcpu"`
|
||||
MemoryGB float64 `json:"memory_gb"`
|
||||
Available int `json:"available"`
|
||||
}
|
||||
|
||||
// VMInfo describes a single VM for the cluster stats CLI.
|
||||
type VMInfo struct {
|
||||
InstanceID string `json:"instance_id"`
|
||||
Status string `json:"status"`
|
||||
InstanceType string `json:"instance_type"`
|
||||
VCPU int `json:"vcpu"`
|
||||
MemoryGB float64 `json:"memory_gb"`
|
||||
LaunchTime int64 `json:"launch_time"`
|
||||
}
|
||||
|
||||
// NodeVMsResponse is returned by the hive.node.vms NATS topic (fan-out).
|
||||
type NodeVMsResponse struct {
|
||||
Node string `json:"node"`
|
||||
Host string `json:"host"`
|
||||
VMs []VMInfo `json:"vms"`
|
||||
// GetServices returns the configured service list, defaulting to AllServices.
|
||||
func (c Config) GetServices() []string {
|
||||
if len(c.Services) == 0 {
|
||||
return AllServices
|
||||
}
|
||||
return c.Services
|
||||
}
|
||||
|
||||
// LoadConfig loads the configuration from file and environment variables
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -304,13 +300,6 @@ func TestNodeBaseDir_EmptyBaseDir(t *testing.T) {
|
||||
assert.Equal(t, "", cc.NodeBaseDir())
|
||||
}
|
||||
|
||||
// Tests for type constants
|
||||
|
||||
func TestNBDTransportConstants(t *testing.T) {
|
||||
assert.Equal(t, NBDTransport("socket"), NBDTransportSocket)
|
||||
assert.Equal(t, NBDTransport("tcp"), NBDTransportTCP)
|
||||
}
|
||||
|
||||
// Tests for ViperblockConfig
|
||||
|
||||
func TestLoadConfig_ViperblockShardWAL_Explicit(t *testing.T) {
|
||||
@@ -380,77 +369,3 @@ shardwal = true
|
||||
require.NotNil(t, n.Viperblock.ShardWAL)
|
||||
assert.True(t, *n.Viperblock.ShardWAL)
|
||||
}
|
||||
|
||||
// TestEC2StartInstancesResponseRespond verifies Respond marshals and sends the response via NATS.
|
||||
func TestEC2StartInstancesResponseRespond(t *testing.T) {
|
||||
ns, err := server.NewServer(&server.Options{
|
||||
Host: "127.0.0.1",
|
||||
Port: -1,
|
||||
NoLog: true,
|
||||
NoSigs: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
go ns.Start()
|
||||
require.True(t, ns.ReadyForConnections(5*time.Second))
|
||||
defer ns.Shutdown()
|
||||
|
||||
nc, err := nats.Connect(ns.ClientURL())
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
resp EC2StartInstancesResponse
|
||||
}{
|
||||
{
|
||||
name: "successful start",
|
||||
resp: EC2StartInstancesResponse{
|
||||
InstanceID: "i-abc123",
|
||||
Status: "pending",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "start with error",
|
||||
resp: EC2StartInstancesResponse{
|
||||
InstanceID: "i-def456",
|
||||
Status: "failed",
|
||||
Error: "instance not found",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
subject := "test.ec2.start"
|
||||
|
||||
sub, err := nc.SubscribeSync(subject)
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
inbox := nc.NewRespInbox()
|
||||
replySub, err := nc.SubscribeSync(inbox)
|
||||
require.NoError(t, err)
|
||||
defer replySub.Unsubscribe()
|
||||
require.NoError(t, nc.Flush())
|
||||
|
||||
err = nc.PublishRequest(subject, inbox, []byte("{}"))
|
||||
require.NoError(t, err)
|
||||
|
||||
msg, err := sub.NextMsg(2 * time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
tt.resp.Respond(msg)
|
||||
|
||||
reply, err := replySub.NextMsg(2 * time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
var decoded EC2StartInstancesResponse
|
||||
err = json.Unmarshal(reply.Data, &decoded)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.resp.InstanceID, decoded.InstanceID)
|
||||
assert.Equal(t, tt.resp.Status, decoded.Status)
|
||||
assert.Equal(t, tt.resp.Error, decoded.Error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+43
-21
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/mulgadc/hive/hive/instancetypes"
|
||||
"github.com/mulgadc/hive/hive/objectstore"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/mulgadc/viperblock/viperblock"
|
||||
@@ -284,7 +285,7 @@ func (rm *ResourceManager) GetAvailableInstanceTypeInfos(showCapacity bool) []*e
|
||||
}
|
||||
|
||||
// GetResourceStats returns current resource allocation stats for the node status response.
|
||||
func (rm *ResourceManager) GetResourceStats() (totalVCPU int, totalMemGB float64, allocVCPU int, allocMemGB float64, caps []config.InstanceTypeCap) {
|
||||
func (rm *ResourceManager) GetResourceStats() (totalVCPU int, totalMemGB float64, allocVCPU int, allocMemGB float64, caps []types.InstanceTypeCap) {
|
||||
rm.mu.RLock()
|
||||
defer rm.mu.RUnlock()
|
||||
|
||||
@@ -388,6 +389,7 @@ func (d *Daemon) subscribeAll() error {
|
||||
{"ec2.start", d.handleEC2StartStoppedInstance, "hive-workers"},
|
||||
{"ec2.terminate", d.handleEC2TerminateStoppedInstance, "hive-workers"},
|
||||
{"ec2.DescribeStoppedInstances", d.handleEC2DescribeStoppedInstances, "hive-workers"},
|
||||
{"ec2.DescribeTerminatedInstances", d.handleEC2DescribeTerminatedInstances, "hive-workers"},
|
||||
// these 2 fan out to all nodes and gateway aggregates the results
|
||||
{"ec2.DescribeInstances", d.handleEC2DescribeInstances, ""},
|
||||
{"ec2.DescribeInstanceTypes", d.handleEC2DescribeInstanceTypes, ""},
|
||||
@@ -603,6 +605,10 @@ func (d *Daemon) initJetStream() error {
|
||||
err = d.jsManager.InitClusterStateBucket()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = d.jsManager.InitTerminatedInstanceBucket()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
slog.Info("JetStream KV stores initialized successfully", "replicas", 1, "attempts", attempt, "elapsed", time.Since(start).Round(time.Second))
|
||||
break
|
||||
@@ -721,23 +727,31 @@ func (d *Daemon) checkPredastoreReady() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// migrateStoppedToSharedKV writes a stopped instance to shared KV and removes
|
||||
// migrateInstanceToKV writes an instance to the given KV write function and removes
|
||||
// it from the local instance map. Returns true if migration succeeded.
|
||||
func (d *Daemon) migrateStoppedToSharedKV(instance *vm.VM) bool {
|
||||
func (d *Daemon) migrateInstanceToKV(instance *vm.VM, writeFn func(string, *vm.VM) error, label string) bool {
|
||||
if d.jsManager == nil {
|
||||
return false
|
||||
}
|
||||
instance.LastNode = d.node
|
||||
if err := d.jsManager.WriteStoppedInstance(instance.ID, instance); err != nil {
|
||||
slog.Error("Failed to migrate stopped instance to shared KV",
|
||||
"instance", instance.ID, "err", err)
|
||||
if err := writeFn(instance.ID, instance); err != nil {
|
||||
slog.Error("Failed to migrate instance to KV",
|
||||
"instance", instance.ID, "bucket", label, "err", err)
|
||||
return false
|
||||
}
|
||||
delete(d.Instances.VMS, instance.ID)
|
||||
slog.Info("Migrated stopped instance to shared KV", "instance", instance.ID)
|
||||
slog.Info("Migrated instance to KV", "instance", instance.ID, "bucket", label)
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *Daemon) migrateStoppedToSharedKV(instance *vm.VM) bool {
|
||||
return d.migrateInstanceToKV(instance, d.jsManager.WriteStoppedInstance, "stopped")
|
||||
}
|
||||
|
||||
func (d *Daemon) migrateTerminatedToKV(instance *vm.VM) bool {
|
||||
return d.migrateInstanceToKV(instance, d.jsManager.WriteTerminatedInstance, "terminated")
|
||||
}
|
||||
|
||||
// maxConcurrentRecovery limits how many VMs are relaunched in parallel during recovery.
|
||||
const maxConcurrentRecovery = 2
|
||||
|
||||
@@ -782,7 +796,11 @@ func (d *Daemon) restoreInstances() {
|
||||
instance := d.Instances.VMS[i]
|
||||
|
||||
if instance.Status == vm.StateTerminated {
|
||||
slog.Info("Instance state is terminated, skipping", "instance", instance.ID)
|
||||
if !d.migrateTerminatedToKV(instance) {
|
||||
// KV write failed — still remove from local map. The VM is gone;
|
||||
// keeping it here would make it retry forever on every restart.
|
||||
delete(d.Instances.VMS, instance.ID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -838,6 +856,10 @@ func (d *Daemon) restoreInstances() {
|
||||
continue
|
||||
}
|
||||
|
||||
if instance.Status == vm.StateTerminated && d.migrateTerminatedToKV(instance) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := d.WriteState(); err != nil {
|
||||
slog.Error("Failed to persist state, will retry on next restart",
|
||||
"instance", instance.ID, "error", err)
|
||||
@@ -951,7 +973,7 @@ func (d *Daemon) awaitShutdown() {
|
||||
// computeConfigHash computes SHA256 hash of the shared cluster config (excluding node-specific fields)
|
||||
func (d *Daemon) computeConfigHash() (string, error) {
|
||||
// Only hash the shared cluster data, not the node-specific top-level field
|
||||
sharedData := config.SharedClusterData{
|
||||
sharedData := types.SharedClusterData{
|
||||
Epoch: d.clusterConfig.Epoch,
|
||||
Version: d.clusterConfig.Version,
|
||||
Nodes: d.clusterConfig.Nodes,
|
||||
@@ -1038,7 +1060,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
status = "starting"
|
||||
}
|
||||
|
||||
response := config.NodeHealthResponse{
|
||||
response := types.NodeHealthResponse{
|
||||
Node: d.node,
|
||||
Status: status,
|
||||
ConfigHash: configHash,
|
||||
@@ -1054,11 +1076,11 @@ func (d *Daemon) ClusterManager() error {
|
||||
|
||||
// Join endpoint - accepts new nodes joining the cluster
|
||||
r.Post("/join", func(w http.ResponseWriter, r *http.Request) {
|
||||
var req config.NodeJoinRequest
|
||||
var req types.NodeJoinRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(400)
|
||||
_ = json.NewEncoder(w).Encode(config.NodeJoinResponse{
|
||||
_ = json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: false,
|
||||
Message: "invalid request body",
|
||||
})
|
||||
@@ -1071,7 +1093,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
if req.Node == "" || req.Region == "" || req.AZ == "" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(400)
|
||||
_ = json.NewEncoder(w).Encode(config.NodeJoinResponse{
|
||||
_ = json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: false,
|
||||
Message: "node, region, and az are required",
|
||||
})
|
||||
@@ -1082,7 +1104,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
if _, exists := d.clusterConfig.Nodes[req.Node]; exists {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(409)
|
||||
_ = json.NewEncoder(w).Encode(config.NodeJoinResponse{
|
||||
_ = json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("node %s already exists in cluster", req.Node),
|
||||
})
|
||||
@@ -1115,7 +1137,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
slog.Error("Failed to save cluster config", "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(500)
|
||||
_ = json.NewEncoder(w).Encode(config.NodeJoinResponse{
|
||||
_ = json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: false,
|
||||
Message: "failed to save cluster config",
|
||||
})
|
||||
@@ -1137,7 +1159,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
}
|
||||
|
||||
// Send only shared cluster data (exclude node-specific top-level fields)
|
||||
sharedData := &config.SharedClusterData{
|
||||
sharedData := &types.SharedClusterData{
|
||||
Epoch: d.clusterConfig.Epoch,
|
||||
Version: d.clusterConfig.Version,
|
||||
Nodes: d.clusterConfig.Nodes,
|
||||
@@ -1171,7 +1193,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(config.NodeJoinResponse{
|
||||
_ = json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("node %s successfully joined cluster", req.Node),
|
||||
SharedData: sharedData,
|
||||
@@ -1352,7 +1374,7 @@ func (d *Daemon) stopInstance(instances map[string]*vm.VM, deleteVolume bool) er
|
||||
// to stop viperblockd processes. S3 data cleanup happens via DeleteVolume
|
||||
// on the parent root volume (which deletes -efi/ and -cloudinit/ prefixes).
|
||||
if ebsRequest.EFI || ebsRequest.CloudInit {
|
||||
ebsDeleteData, err := json.Marshal(config.EBSDeleteRequest{Volume: ebsRequest.Name})
|
||||
ebsDeleteData, err := json.Marshal(types.EBSDeleteRequest{Volume: ebsRequest.Name})
|
||||
if err != nil {
|
||||
slog.Error("Failed to marshal ebs.delete request for internal volume", "name", ebsRequest.Name, "err", err)
|
||||
continue
|
||||
@@ -2042,7 +2064,7 @@ func (d *Daemon) MountVolumes(instance *vm.VM) error {
|
||||
}
|
||||
|
||||
// Unmarshal the response
|
||||
var ebsMountResponse config.EBSMountResponse
|
||||
var ebsMountResponse types.EBSMountResponse
|
||||
err = json.Unmarshal(reply.Data, &ebsMountResponse)
|
||||
|
||||
if err != nil {
|
||||
@@ -2072,7 +2094,7 @@ func (d *Daemon) MountVolumes(instance *vm.VM) error {
|
||||
|
||||
// rollbackEBSMount sends an ebs.unmount request to undo a previously successful ebs.mount.
|
||||
// Rollback failures are logged but not propagated; callers treat this as best-effort cleanup.
|
||||
func (d *Daemon) rollbackEBSMount(req config.EBSRequest) {
|
||||
func (d *Daemon) rollbackEBSMount(req types.EBSRequest) {
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
slog.Error("rollbackEBSMount: failed to marshal unmount request", "volume", req.Name, "err", err)
|
||||
@@ -2083,7 +2105,7 @@ func (d *Daemon) rollbackEBSMount(req config.EBSRequest) {
|
||||
slog.Error("rollbackEBSMount: ebs.unmount NATS request failed", "volume", req.Name, "err", err)
|
||||
return
|
||||
}
|
||||
var resp config.EBSUnMountResponse
|
||||
var resp types.EBSUnMountResponse
|
||||
if err := json.Unmarshal(msg.Data, &resp); err != nil {
|
||||
slog.Error("rollbackEBSMount: failed to unmarshal response", "volume", req.Name, "err", err)
|
||||
return
|
||||
|
||||
@@ -8,8 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/nats-io/nats.go"
|
||||
@@ -57,10 +56,10 @@ func handleNATSRequest[I any, O any](msg *nats.Msg, serviceFn func(*I, string) (
|
||||
|
||||
// handleEC2Events processes incoming EC2 instance events (start, stop, terminate, attach-volume)
|
||||
func (d *Daemon) handleEC2Events(msg *nats.Msg) {
|
||||
var command qmp.Command
|
||||
var command types.EC2InstanceCommand
|
||||
|
||||
if err := json.Unmarshal(msg.Data, &command); err != nil {
|
||||
slog.Error("Error unmarshaling QMP command", "err", err)
|
||||
slog.Error("Error unmarshaling EC2 instance command", "err", err)
|
||||
respondWithError(msg, awserrors.ErrorServerInternal)
|
||||
return
|
||||
}
|
||||
@@ -92,49 +91,8 @@ func (d *Daemon) handleEC2Events(msg *nats.Msg) {
|
||||
case command.Attributes.StopInstance, command.Attributes.TerminateInstance:
|
||||
d.handleStopOrTerminateInstance(msg, command, instance)
|
||||
default:
|
||||
d.handleQMPCommand(msg, command, instance)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) handleQMPCommand(msg *nats.Msg, command qmp.Command, instance *vm.VM) {
|
||||
resp, err := d.SendQMPCommand(instance.QMPClient, command.QMPCommand, instance.ID)
|
||||
if err != nil {
|
||||
slog.Error("Failed to send QMP command", "err", err)
|
||||
slog.Warn("Unhandled EC2 instance command", "id", command.ID, "attributes", command.Attributes)
|
||||
respondWithError(msg, awserrors.ErrorServerInternal)
|
||||
return
|
||||
}
|
||||
|
||||
slog.Debug("RAW QMP Response", "resp", string(resp.Return))
|
||||
|
||||
// Unmarshal the response
|
||||
target, ok := qmp.CommandResponseTypes[command.QMPCommand.Execute]
|
||||
if !ok {
|
||||
slog.Warn("Unhandled QMP command", "cmd", command.QMPCommand.Execute)
|
||||
if err := msg.Respond(resp.Return); err != nil {
|
||||
slog.Error("Failed to respond to NATS request", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp.Return, target); err != nil {
|
||||
slog.Error("Failed to unmarshal QMP response", "cmd", command.QMPCommand.Execute, "err", err)
|
||||
if err := msg.Respond(resp.Return); err != nil {
|
||||
slog.Error("Failed to respond to NATS request", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update attributes and respond
|
||||
d.Instances.Mu.Lock()
|
||||
instance.Attributes = command.Attributes
|
||||
d.Instances.Mu.Unlock()
|
||||
|
||||
if err := d.WriteState(); err != nil {
|
||||
slog.Error("Failed to write state to disk", "err", err)
|
||||
}
|
||||
|
||||
if err := msg.Respond(resp.Return); err != nil {
|
||||
slog.Error("Failed to respond to NATS request", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +111,7 @@ func (d *Daemon) handleHealthCheck(msg *nats.Msg) {
|
||||
status = "starting"
|
||||
}
|
||||
|
||||
response := config.NodeHealthResponse{
|
||||
response := types.NodeHealthResponse{
|
||||
Node: d.node,
|
||||
Status: status,
|
||||
ConfigHash: configHash,
|
||||
@@ -165,15 +123,10 @@ func (d *Daemon) handleHealthCheck(msg *nats.Msg) {
|
||||
slog.Debug("Health check responded", "node", d.node, "epoch", d.clusterConfig.Epoch)
|
||||
}
|
||||
|
||||
// NodeDiscoverResponse is the response for node discovery requests
|
||||
type NodeDiscoverResponse struct {
|
||||
Node string `json:"node"`
|
||||
}
|
||||
|
||||
// handleNodeDiscover responds to node discovery requests with this node's ID
|
||||
// Used by the gateway to dynamically discover active hive nodes in the cluster
|
||||
func (d *Daemon) handleNodeDiscover(msg *nats.Msg) {
|
||||
response := NodeDiscoverResponse{
|
||||
response := types.NodeDiscoverResponse{
|
||||
Node: d.node,
|
||||
}
|
||||
|
||||
@@ -204,7 +157,7 @@ func (d *Daemon) handleNodeStatus(msg *nats.Msg) {
|
||||
}
|
||||
d.Instances.Mu.Unlock()
|
||||
|
||||
resp := config.NodeStatusResponse{
|
||||
resp := types.NodeStatusResponse{
|
||||
Node: d.node,
|
||||
Status: "Ready",
|
||||
Host: d.daemonIP(),
|
||||
@@ -227,9 +180,9 @@ func (d *Daemon) handleNodeStatus(msg *nats.Msg) {
|
||||
// Used by the CLI: hive get vms.
|
||||
func (d *Daemon) handleNodeVMs(msg *nats.Msg) {
|
||||
d.Instances.Mu.Lock()
|
||||
vms := make([]config.VMInfo, 0, len(d.Instances.VMS))
|
||||
vms := make([]types.VMInfo, 0, len(d.Instances.VMS))
|
||||
for _, v := range d.Instances.VMS {
|
||||
info := config.VMInfo{
|
||||
info := types.VMInfo{
|
||||
InstanceID: v.ID,
|
||||
Status: string(v.Status),
|
||||
InstanceType: v.InstanceType,
|
||||
@@ -246,7 +199,7 @@ func (d *Daemon) handleNodeVMs(msg *nats.Msg) {
|
||||
}
|
||||
d.Instances.Mu.Unlock()
|
||||
|
||||
resp := config.NodeVMsResponse{
|
||||
resp := types.NodeVMsResponse{
|
||||
Node: d.node,
|
||||
Host: d.daemonIP(),
|
||||
VMs: vms,
|
||||
|
||||
@@ -11,8 +11,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/nats-io/nats.go"
|
||||
@@ -306,7 +305,7 @@ func (d *Daemon) handleEC2RunInstances(msg *nats.Msg) {
|
||||
slog.Info("handleEC2RunInstances completed", "requested", launchCount, "created", len(instances), "launched", successCount)
|
||||
}
|
||||
|
||||
func (d *Daemon) handleStartInstance(msg *nats.Msg, command qmp.Command, instance *vm.VM) {
|
||||
func (d *Daemon) handleStartInstance(msg *nats.Msg, command types.EC2InstanceCommand, instance *vm.VM) {
|
||||
slog.Info("Starting instance", "id", command.ID)
|
||||
|
||||
// Validate instance is in stopped state
|
||||
@@ -360,7 +359,7 @@ func (d *Daemon) handleStartInstance(msg *nats.Msg, command qmp.Command, instanc
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) handleStopOrTerminateInstance(msg *nats.Msg, command qmp.Command, instance *vm.VM) {
|
||||
func (d *Daemon) handleStopOrTerminateInstance(msg *nats.Msg, command types.EC2InstanceCommand, instance *vm.VM) {
|
||||
isTerminate := command.Attributes.TerminateInstance
|
||||
action := "Stopping"
|
||||
initialState := vm.StateStopping
|
||||
@@ -399,7 +398,7 @@ func (d *Daemon) handleStopOrTerminateInstance(msg *nats.Msg, command qmp.Comman
|
||||
}
|
||||
|
||||
// Run cleanup in goroutine to not block NATS
|
||||
go func(inst *vm.VM, attrs qmp.Attributes) {
|
||||
go func(inst *vm.VM, attrs types.EC2CommandAttributes) {
|
||||
stopErr := d.stopInstance(map[string]*vm.VM{inst.ID: inst}, isTerminate)
|
||||
|
||||
if stopErr != nil {
|
||||
@@ -418,44 +417,50 @@ func (d *Daemon) handleStopOrTerminateInstance(msg *nats.Msg, command qmp.Comman
|
||||
}
|
||||
slog.Info("Instance "+string(finalState), "id", inst.ID)
|
||||
|
||||
// For stop (not terminate): release ownership to shared KV so any
|
||||
// daemon can pick up the next StartInstance request.
|
||||
if !isTerminate && d.jsManager != nil {
|
||||
|
||||
// Write to shared KV first — if daemon crashes after this but
|
||||
// before local cleanup, restoreInstances handles the overlap.
|
||||
if err := d.jsManager.WriteStoppedInstance(inst.ID, inst); err != nil {
|
||||
slog.Error("Failed to write stopped instance to shared KV, keeping local ownership",
|
||||
"instanceId", inst.ID, "err", err)
|
||||
} else {
|
||||
// Unsubscribe from per-instance NATS topic
|
||||
d.mu.Lock()
|
||||
if sub, ok := d.natsSubscriptions[inst.ID]; ok {
|
||||
if err := sub.Unsubscribe(); err != nil {
|
||||
slog.Error("Failed to unsubscribe stopped instance", "instanceId", inst.ID, "err", err)
|
||||
}
|
||||
delete(d.natsSubscriptions, inst.ID)
|
||||
}
|
||||
d.mu.Unlock()
|
||||
|
||||
// Remove from local instance map
|
||||
d.Instances.Mu.Lock()
|
||||
delete(d.Instances.VMS, inst.ID)
|
||||
d.Instances.Mu.Unlock()
|
||||
|
||||
// Persist local state without the stopped instance
|
||||
if err := d.WriteState(); err != nil {
|
||||
slog.Error("Failed to persist state after releasing stopped instance, re-adding to local map for consistency",
|
||||
if d.jsManager != nil {
|
||||
if isTerminate {
|
||||
// Write to terminated KV bucket (auto-expires after 1 hour via TTL).
|
||||
// On failure, continue cleanup — VM is already stopped, keeping it
|
||||
// in the local map just delays data loss to the next restart.
|
||||
if err := d.jsManager.WriteTerminatedInstance(inst.ID, inst); err != nil {
|
||||
slog.Error("Failed to write terminated instance to KV, instance will not appear in DescribeInstances",
|
||||
"instanceId", inst.ID, "err", err)
|
||||
// Re-add to local map so in-memory state matches on-disk state.
|
||||
// On next restart, restoreInstances will retry the migration.
|
||||
d.Instances.Mu.Lock()
|
||||
d.Instances.VMS[inst.ID] = inst
|
||||
d.Instances.Mu.Unlock()
|
||||
} else {
|
||||
slog.Info("Released stopped instance ownership to shared KV",
|
||||
"instanceId", inst.ID, "lastNode", d.node)
|
||||
}
|
||||
} else {
|
||||
// Write to shared KV first — if daemon crashes after this but
|
||||
// before local cleanup, restoreInstances handles the overlap.
|
||||
if err := d.jsManager.WriteStoppedInstance(inst.ID, inst); err != nil {
|
||||
slog.Error("Failed to write stopped instance to shared KV, keeping local ownership",
|
||||
"instanceId", inst.ID, "err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Unsubscribe from per-instance NATS topic
|
||||
d.mu.Lock()
|
||||
if sub, ok := d.natsSubscriptions[inst.ID]; ok {
|
||||
if err := sub.Unsubscribe(); err != nil {
|
||||
slog.Error("Failed to unsubscribe instance", "instanceId", inst.ID, "err", err)
|
||||
}
|
||||
delete(d.natsSubscriptions, inst.ID)
|
||||
}
|
||||
d.mu.Unlock()
|
||||
|
||||
// Remove from local instance map
|
||||
d.Instances.Mu.Lock()
|
||||
delete(d.Instances.VMS, inst.ID)
|
||||
d.Instances.Mu.Unlock()
|
||||
|
||||
// Persist local state without the instance
|
||||
if err := d.WriteState(); err != nil {
|
||||
slog.Error("Failed to persist state after releasing instance, re-adding to local map for consistency",
|
||||
"instanceId", inst.ID, "err", err)
|
||||
d.Instances.Mu.Lock()
|
||||
d.Instances.VMS[inst.ID] = inst
|
||||
d.Instances.Mu.Unlock()
|
||||
} else {
|
||||
slog.Info("Released instance ownership to KV",
|
||||
"instanceId", inst.ID, "state", string(finalState), "lastNode", d.node)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -685,7 +690,7 @@ func (d *Daemon) handleEC2StartStoppedInstance(msg *nats.Msg) {
|
||||
// Add instance to local map and clear stop attribute before launch
|
||||
d.Instances.Mu.Lock()
|
||||
d.Instances.VMS[instance.ID] = instance
|
||||
instance.Attributes = qmp.Attributes{StartInstance: true}
|
||||
instance.Attributes = types.EC2CommandAttributes{StartInstance: true}
|
||||
d.Instances.Mu.Unlock()
|
||||
|
||||
// Launch the instance infrastructure (QEMU, QMP, NATS subscriptions)
|
||||
@@ -780,7 +785,7 @@ func (d *Daemon) handleEC2TerminateStoppedInstance(msg *nats.Msg) {
|
||||
for _, ebsRequest := range instance.EBSRequests.Requests {
|
||||
// Internal volumes (EFI, cloud-init) are always cleaned up via ebs.delete
|
||||
if ebsRequest.EFI || ebsRequest.CloudInit {
|
||||
ebsDeleteData, err := json.Marshal(config.EBSDeleteRequest{Volume: ebsRequest.Name})
|
||||
ebsDeleteData, err := json.Marshal(types.EBSDeleteRequest{Volume: ebsRequest.Name})
|
||||
if err != nil {
|
||||
slog.Error("handleEC2TerminateStoppedInstance: failed to marshal ebs.delete request", "name", ebsRequest.Name, "err", err)
|
||||
continue
|
||||
@@ -810,13 +815,26 @@ func (d *Daemon) handleEC2TerminateStoppedInstance(msg *nats.Msg) {
|
||||
}
|
||||
instance.EBSRequests.Mu.Unlock()
|
||||
|
||||
// Remove from shared KV
|
||||
if err := d.jsManager.DeleteStoppedInstance(req.InstanceID); err != nil {
|
||||
slog.Error("handleEC2TerminateStoppedInstance: failed to delete from shared KV", "instanceId", req.InstanceID, "err", err)
|
||||
// Write to terminated KV bucket FIRST so the instance is visible in DescribeInstances.
|
||||
// If this fails, the instance remains in the stopped bucket (safe to retry).
|
||||
instance.Status = vm.StateTerminated
|
||||
if err := d.jsManager.WriteTerminatedInstance(req.InstanceID, instance); err != nil {
|
||||
slog.Error("handleEC2TerminateStoppedInstance: failed to write to terminated KV, aborting", "instanceId", req.InstanceID, "err", err)
|
||||
respondWithError(msg, awserrors.ErrorServerInternal)
|
||||
return
|
||||
}
|
||||
|
||||
// Now safe to remove from shared stopped KV — instance already exists in terminated bucket.
|
||||
// Retry once on failure to avoid duplicate entries in DescribeInstances.
|
||||
if err := d.jsManager.DeleteStoppedInstance(req.InstanceID); err != nil {
|
||||
slog.Warn("handleEC2TerminateStoppedInstance: first stopped KV delete failed, retrying",
|
||||
"instanceId", req.InstanceID, "err", err)
|
||||
if retryErr := d.jsManager.DeleteStoppedInstance(req.InstanceID); retryErr != nil {
|
||||
slog.Error("handleEC2TerminateStoppedInstance: stopped KV delete failed after retry, instance may appear in both buckets",
|
||||
"instanceId", req.InstanceID, "err", retryErr)
|
||||
}
|
||||
}
|
||||
|
||||
slog.Info("Terminated stopped instance from shared KV", "instanceId", req.InstanceID)
|
||||
|
||||
if err := msg.Respond(fmt.Appendf(nil, `{"status":"terminated","instanceId":"%s"}`, req.InstanceID)); err != nil {
|
||||
@@ -826,16 +844,24 @@ func (d *Daemon) handleEC2TerminateStoppedInstance(msg *nats.Msg) {
|
||||
|
||||
// handleEC2DescribeStoppedInstances returns stopped instances from shared KV.
|
||||
func (d *Daemon) handleEC2DescribeStoppedInstances(msg *nats.Msg) {
|
||||
d.describeInstancesFromKV(msg, d.jsManager.ListStoppedInstances, 80, "stopped", "handleEC2DescribeStoppedInstances")
|
||||
}
|
||||
|
||||
// handleEC2DescribeTerminatedInstances returns terminated instances from the terminated KV bucket.
|
||||
func (d *Daemon) handleEC2DescribeTerminatedInstances(msg *nats.Msg) {
|
||||
d.describeInstancesFromKV(msg, d.jsManager.ListTerminatedInstances, 48, "terminated", "handleEC2DescribeTerminatedInstances")
|
||||
}
|
||||
|
||||
// describeInstancesFromKV is a shared helper for DescribeStopped/TerminatedInstances handlers.
|
||||
// It lists instances from a KV bucket, filters by account/instance ID, and responds with reservations.
|
||||
func (d *Daemon) describeInstancesFromKV(msg *nats.Msg, listFn func() ([]*vm.VM, error), fallbackCode int64, fallbackName, handlerName string) {
|
||||
if d.jsManager == nil {
|
||||
respondWithError(msg, awserrors.ErrorServerInternal)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract account ID from NATS header for scoping
|
||||
accountID := utils.AccountIDFromMsg(msg)
|
||||
|
||||
// Parse optional filters from the request
|
||||
describeInput := &ec2.DescribeInstancesInput{}
|
||||
if len(msg.Data) > 0 {
|
||||
if errResp := utils.UnmarshalJsonPayload(describeInput, msg.Data); errResp != nil {
|
||||
@@ -846,67 +872,61 @@ func (d *Daemon) handleEC2DescribeStoppedInstances(msg *nats.Msg) {
|
||||
}
|
||||
}
|
||||
|
||||
// Build instance ID filter
|
||||
instanceIDFilter := make(map[string]bool)
|
||||
if len(describeInput.InstanceIds) > 0 {
|
||||
for _, id := range describeInput.InstanceIds {
|
||||
if id != nil {
|
||||
instanceIDFilter[*id] = true
|
||||
}
|
||||
for _, id := range describeInput.InstanceIds {
|
||||
if id != nil {
|
||||
instanceIDFilter[*id] = true
|
||||
}
|
||||
}
|
||||
|
||||
instances, err := d.jsManager.ListStoppedInstances()
|
||||
instances, err := listFn()
|
||||
if err != nil {
|
||||
slog.Error("handleEC2DescribeStoppedInstances: failed to list stopped instances", "err", err)
|
||||
slog.Error(handlerName+": failed to list instances", "err", err)
|
||||
respondWithError(msg, awserrors.ErrorServerInternal)
|
||||
return
|
||||
}
|
||||
|
||||
// Group instances by reservation ID
|
||||
reservationMap := make(map[string]*ec2.Reservation)
|
||||
|
||||
for _, instance := range instances {
|
||||
// Skip instances not owned by the caller's account.
|
||||
// Pre-Phase4 instances (empty AccountID) are only visible to root.
|
||||
if !isInstanceVisible(accountID, instance.AccountID) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply instance ID filter
|
||||
if len(instanceIDFilter) > 0 && !instanceIDFilter[instance.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
if instance.Reservation != nil && instance.Instance != nil {
|
||||
resID := ""
|
||||
if instance.Reservation.ReservationId != nil {
|
||||
resID = *instance.Reservation.ReservationId
|
||||
}
|
||||
|
||||
if _, exists := reservationMap[resID]; !exists {
|
||||
reservation := &ec2.Reservation{}
|
||||
reservation.SetReservationId(resID)
|
||||
if instance.Reservation.OwnerId != nil {
|
||||
reservation.SetOwnerId(*instance.Reservation.OwnerId)
|
||||
}
|
||||
reservation.Instances = []*ec2.Instance{}
|
||||
reservationMap[resID] = reservation
|
||||
}
|
||||
|
||||
// Update the instance state
|
||||
instanceCopy := *instance.Instance
|
||||
instanceCopy.State = &ec2.InstanceState{}
|
||||
if info, ok := vm.EC2StateCodes[instance.Status]; ok {
|
||||
instanceCopy.State.SetCode(info.Code)
|
||||
instanceCopy.State.SetName(info.Name)
|
||||
} else {
|
||||
instanceCopy.State.SetCode(80)
|
||||
instanceCopy.State.SetName("stopped")
|
||||
}
|
||||
|
||||
reservationMap[resID].Instances = append(reservationMap[resID].Instances, &instanceCopy)
|
||||
if instance.Reservation == nil || instance.Instance == nil {
|
||||
slog.Warn(handlerName+": skipping instance with nil Reservation/Instance (data integrity issue)",
|
||||
"instanceId", instance.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
resID := ""
|
||||
if instance.Reservation.ReservationId != nil {
|
||||
resID = *instance.Reservation.ReservationId
|
||||
}
|
||||
|
||||
if _, exists := reservationMap[resID]; !exists {
|
||||
reservation := &ec2.Reservation{}
|
||||
reservation.SetReservationId(resID)
|
||||
if instance.Reservation.OwnerId != nil {
|
||||
reservation.SetOwnerId(*instance.Reservation.OwnerId)
|
||||
}
|
||||
reservation.Instances = []*ec2.Instance{}
|
||||
reservationMap[resID] = reservation
|
||||
}
|
||||
|
||||
instanceCopy := *instance.Instance
|
||||
instanceCopy.State = &ec2.InstanceState{}
|
||||
if info, ok := vm.EC2StateCodes[instance.Status]; ok {
|
||||
instanceCopy.State.SetCode(info.Code)
|
||||
instanceCopy.State.SetName(info.Name)
|
||||
} else {
|
||||
instanceCopy.State.SetCode(fallbackCode)
|
||||
instanceCopy.State.SetName(fallbackName)
|
||||
}
|
||||
|
||||
reservationMap[resID].Instances = append(reservationMap[resID].Instances, &instanceCopy)
|
||||
}
|
||||
|
||||
reservations := make([]*ec2.Reservation, 0, len(reservationMap))
|
||||
@@ -914,12 +934,8 @@ func (d *Daemon) handleEC2DescribeStoppedInstances(msg *nats.Msg) {
|
||||
reservations = append(reservations, reservation)
|
||||
}
|
||||
|
||||
output := &ec2.DescribeInstancesOutput{
|
||||
Reservations: reservations,
|
||||
}
|
||||
|
||||
respondWithJSON(msg, output)
|
||||
slog.Info("handleEC2DescribeStoppedInstances completed", "count", len(reservations))
|
||||
respondWithJSON(msg, &ec2.DescribeInstancesOutput{Reservations: reservations})
|
||||
slog.Info(handlerName+" completed", "count", len(reservations))
|
||||
}
|
||||
|
||||
// handleEC2ModifyInstanceAttribute modifies attributes of a stopped instance in shared KV.
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
handlers_ec2_vpc "github.com/mulgadc/hive/hive/handlers/ec2/vpc"
|
||||
"github.com/mulgadc/hive/hive/objectstore"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/mulgadc/viperblock/viperblock"
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
@@ -80,6 +81,8 @@ func createFullTestDaemonWithJetStream(t *testing.T, natsURL string) *Daemon {
|
||||
require.NoError(t, err)
|
||||
err = daemon.jsManager.InitKVBucket()
|
||||
require.NoError(t, err)
|
||||
err = daemon.jsManager.InitTerminatedInstanceBucket()
|
||||
require.NoError(t, err)
|
||||
|
||||
return daemon
|
||||
}
|
||||
@@ -329,7 +332,7 @@ func TestHandleHealthCheck(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, reply)
|
||||
|
||||
var resp config.NodeHealthResponse
|
||||
var resp types.NodeHealthResponse
|
||||
err = json.Unmarshal(reply.Data, &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -363,7 +366,7 @@ func TestHandleNodeDiscover(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, reply)
|
||||
|
||||
var resp NodeDiscoverResponse
|
||||
var resp types.NodeDiscoverResponse
|
||||
err = json.Unmarshal(reply.Data, &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -552,9 +555,9 @@ func TestHandleEC2Events_StopInstance(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
cmd := qmp.Command{
|
||||
cmd := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{StopInstance: true},
|
||||
Attributes: types.EC2CommandAttributes{StopInstance: true},
|
||||
}
|
||||
cmdData, _ := json.Marshal(cmd)
|
||||
|
||||
@@ -598,9 +601,9 @@ func TestHandleEC2Events_TerminateInstance(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
cmd := qmp.Command{
|
||||
cmd := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{TerminateInstance: true},
|
||||
Attributes: types.EC2CommandAttributes{TerminateInstance: true},
|
||||
}
|
||||
cmdData, _ := json.Marshal(cmd)
|
||||
|
||||
@@ -629,9 +632,9 @@ func TestHandleEC2Events_InstanceNotFound(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
cmd := qmp.Command{
|
||||
cmd := types.EC2InstanceCommand{
|
||||
ID: "i-nonexistent",
|
||||
Attributes: qmp.Attributes{StopInstance: true},
|
||||
Attributes: types.EC2CommandAttributes{StopInstance: true},
|
||||
}
|
||||
cmdData, _ := json.Marshal(cmd)
|
||||
|
||||
@@ -1483,12 +1486,12 @@ func TestAttachVolume_ZoneMismatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: &qmp.AttachVolumeData{
|
||||
AttachVolumeData: &types.AttachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
},
|
||||
}
|
||||
@@ -1940,7 +1943,7 @@ func TestHandleNodeStatus(t *testing.T) {
|
||||
reply, err := daemon.natsConn.Request("hive.node.status.test", nil, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
var resp config.NodeStatusResponse
|
||||
var resp types.NodeStatusResponse
|
||||
err = json.Unmarshal(reply.Data, &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1966,7 +1969,7 @@ func TestHandleNodeStatus_NoVMs(t *testing.T) {
|
||||
reply, err := daemon.natsConn.Request("hive.node.status.empty", nil, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
var resp config.NodeStatusResponse
|
||||
var resp types.NodeStatusResponse
|
||||
err = json.Unmarshal(reply.Data, &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2007,7 +2010,7 @@ func TestHandleNodeVMs(t *testing.T) {
|
||||
reply, err := daemon.natsConn.Request("hive.node.vms.test", nil, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
var resp config.NodeVMsResponse
|
||||
var resp types.NodeVMsResponse
|
||||
err = json.Unmarshal(reply.Data, &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2016,7 +2019,7 @@ func TestHandleNodeVMs(t *testing.T) {
|
||||
assert.Len(t, resp.VMs, 2)
|
||||
|
||||
// Build a lookup by instance ID
|
||||
vmsByID := make(map[string]config.VMInfo)
|
||||
vmsByID := make(map[string]types.VMInfo)
|
||||
for _, v := range resp.VMs {
|
||||
vmsByID[v.InstanceID] = v
|
||||
}
|
||||
@@ -2044,7 +2047,7 @@ func TestHandleNodeVMs_Empty(t *testing.T) {
|
||||
reply, err := daemon.natsConn.Request("hive.node.vms.empty", nil, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
var resp config.NodeVMsResponse
|
||||
var resp types.NodeVMsResponse
|
||||
err = json.Unmarshal(reply.Data, &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2071,7 +2074,7 @@ func TestHandleNodeVMs_UnknownInstanceType(t *testing.T) {
|
||||
reply, err := daemon.natsConn.Request("hive.node.vms.unknown", nil, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
var resp config.NodeVMsResponse
|
||||
var resp types.NodeVMsResponse
|
||||
err = json.Unmarshal(reply.Data, &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2547,7 +2550,7 @@ func TestHandleEC2TerminateStoppedInstance_WithVolumes(t *testing.T) {
|
||||
},
|
||||
}
|
||||
// Add EFI, CloudInit, and a user volume with DeleteOnTermination
|
||||
stoppedVM.EBSRequests.Requests = []config.EBSRequest{
|
||||
stoppedVM.EBSRequests.Requests = []types.EBSRequest{
|
||||
{Name: "vol-efi-001", EFI: true},
|
||||
{Name: "vol-ci-001", CloudInit: true},
|
||||
{Name: "vol-user-001", DeleteOnTermination: true},
|
||||
@@ -2754,9 +2757,9 @@ func TestAttachVolume_MissingVolumeData(t *testing.T) {
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// AttachVolume with nil AttachVolumeData
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: nil,
|
||||
@@ -2793,12 +2796,12 @@ func TestAttachVolume_InstanceNotRunning(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: &qmp.AttachVolumeData{
|
||||
AttachVolumeData: &types.AttachVolumeData{
|
||||
VolumeID: "vol-test-123",
|
||||
},
|
||||
}
|
||||
@@ -2834,12 +2837,12 @@ func TestAttachVolume_VolumeNotFound(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: &qmp.AttachVolumeData{
|
||||
AttachVolumeData: &types.AttachVolumeData{
|
||||
VolumeID: "vol-nonexistent-999",
|
||||
},
|
||||
}
|
||||
@@ -2896,12 +2899,12 @@ func TestAttachVolume_VolumeInUse(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: &qmp.AttachVolumeData{
|
||||
AttachVolumeData: &types.AttachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
},
|
||||
}
|
||||
@@ -2939,9 +2942,9 @@ func TestDetachVolume_MissingVolumeData(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: nil,
|
||||
@@ -2978,12 +2981,12 @@ func TestDetachVolume_InstanceNotRunning(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: "vol-test-123",
|
||||
},
|
||||
}
|
||||
@@ -3019,12 +3022,12 @@ func TestDetachVolume_VolumeNotAttached(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: "vol-not-attached-999",
|
||||
},
|
||||
}
|
||||
@@ -3051,7 +3054,7 @@ func TestDetachVolume_BootVolumeRejected(t *testing.T) {
|
||||
Instance: &ec2.Instance{},
|
||||
QMPClient: &qmp.QMPClient{},
|
||||
}
|
||||
instance.EBSRequests.Requests = []config.EBSRequest{
|
||||
instance.EBSRequests.Requests = []types.EBSRequest{
|
||||
{Name: "vol-boot-001", Boot: true, DeviceName: "/dev/sda1"},
|
||||
}
|
||||
daemon.Instances.VMS[instanceID] = instance
|
||||
@@ -3063,12 +3066,12 @@ func TestDetachVolume_BootVolumeRejected(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: "vol-boot-001",
|
||||
},
|
||||
}
|
||||
@@ -3095,7 +3098,7 @@ func TestDetachVolume_DeviceMismatch(t *testing.T) {
|
||||
Instance: &ec2.Instance{},
|
||||
QMPClient: &qmp.QMPClient{},
|
||||
}
|
||||
instance.EBSRequests.Requests = []config.EBSRequest{
|
||||
instance.EBSRequests.Requests = []types.EBSRequest{
|
||||
{Name: "vol-mismatch-001", DeviceName: "/dev/sdf"},
|
||||
}
|
||||
daemon.Instances.VMS[instanceID] = instance
|
||||
@@ -3107,12 +3110,12 @@ func TestDetachVolume_DeviceMismatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: "vol-mismatch-001",
|
||||
Device: "/dev/sdg",
|
||||
},
|
||||
@@ -3211,3 +3214,134 @@ func TestHandleEC2DescribeInstances_MalformedInstanceID(t *testing.T) {
|
||||
|
||||
assert.Contains(t, string(reply.Data), "InvalidInstanceID.Malformed")
|
||||
}
|
||||
|
||||
// --- Terminated instance handler tests ---
|
||||
|
||||
func TestHandleEC2DescribeTerminatedInstances_ReturnsTerminatedInstances(t *testing.T) {
|
||||
daemon := createFullTestDaemonWithJetStream(t, sharedJSNATSURL)
|
||||
|
||||
terminatedVM := &vm.VM{
|
||||
ID: "i-describe-term-001",
|
||||
Status: vm.StateTerminated,
|
||||
InstanceType: getTestInstanceType(),
|
||||
LastNode: "node-1",
|
||||
AccountID: testAccountID,
|
||||
Reservation: &ec2.Reservation{
|
||||
ReservationId: aws.String("r-term-001"),
|
||||
OwnerId: aws.String("123456789012"),
|
||||
},
|
||||
Instance: &ec2.Instance{
|
||||
InstanceId: aws.String("i-describe-term-001"),
|
||||
InstanceType: aws.String(getTestInstanceType()),
|
||||
},
|
||||
}
|
||||
err := daemon.jsManager.WriteTerminatedInstance(terminatedVM.ID, terminatedVM)
|
||||
require.NoError(t, err)
|
||||
|
||||
sub, err := daemon.natsConn.QueueSubscribe("ec2.DescribeTerminatedInstances", "hive-workers", daemon.handleEC2DescribeTerminatedInstances)
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
input := &ec2.DescribeInstancesInput{}
|
||||
reqData, _ := json.Marshal(input)
|
||||
reply, err := natsRequest(daemon.natsConn, "ec2.DescribeTerminatedInstances", reqData, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
var output ec2.DescribeInstancesOutput
|
||||
err = json.Unmarshal(reply.Data, &output)
|
||||
require.NoError(t, err)
|
||||
|
||||
found := false
|
||||
for _, res := range output.Reservations {
|
||||
for _, inst := range res.Instances {
|
||||
if inst.InstanceId != nil && *inst.InstanceId == "i-describe-term-001" {
|
||||
found = true
|
||||
assert.Equal(t, "terminated", *inst.State.Name)
|
||||
assert.Equal(t, int64(48), *inst.State.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "Should find terminated instance in DescribeTerminatedInstances output")
|
||||
|
||||
_ = daemon.jsManager.DeleteTerminatedInstance(terminatedVM.ID)
|
||||
}
|
||||
|
||||
func TestHandleEC2DescribeTerminatedInstances_WithFilter(t *testing.T) {
|
||||
daemon := createFullTestDaemonWithJetStream(t, sharedJSNATSURL)
|
||||
|
||||
for _, id := range []string{"i-tfilter-001", "i-tfilter-002"} {
|
||||
v := &vm.VM{
|
||||
ID: id,
|
||||
Status: vm.StateTerminated,
|
||||
LastNode: "node-1",
|
||||
AccountID: testAccountID,
|
||||
Reservation: &ec2.Reservation{
|
||||
ReservationId: aws.String("r-tfilter"),
|
||||
OwnerId: aws.String("123456789012"),
|
||||
},
|
||||
Instance: &ec2.Instance{
|
||||
InstanceId: aws.String(id),
|
||||
InstanceType: aws.String(getTestInstanceType()),
|
||||
},
|
||||
}
|
||||
require.NoError(t, daemon.jsManager.WriteTerminatedInstance(v.ID, v))
|
||||
}
|
||||
|
||||
sub, err := daemon.natsConn.QueueSubscribe("ec2.DescribeTerminatedInstances", "hive-workers", daemon.handleEC2DescribeTerminatedInstances)
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Filter for only one instance
|
||||
input := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String("i-tfilter-001")},
|
||||
}
|
||||
reqData, _ := json.Marshal(input)
|
||||
reply, err := natsRequest(daemon.natsConn, "ec2.DescribeTerminatedInstances", reqData, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
var output ec2.DescribeInstancesOutput
|
||||
require.NoError(t, json.Unmarshal(reply.Data, &output))
|
||||
|
||||
instanceCount := 0
|
||||
for _, res := range output.Reservations {
|
||||
instanceCount += len(res.Instances)
|
||||
}
|
||||
assert.Equal(t, 1, instanceCount, "Filter should return only the requested instance")
|
||||
|
||||
_ = daemon.jsManager.DeleteTerminatedInstance("i-tfilter-001")
|
||||
_ = daemon.jsManager.DeleteTerminatedInstance("i-tfilter-002")
|
||||
}
|
||||
|
||||
func TestHandleEC2TerminateStoppedInstance_WritesToTerminatedKV(t *testing.T) {
|
||||
daemon := createFullTestDaemonWithJetStream(t, sharedJSNATSURL)
|
||||
|
||||
stoppedVM := &vm.VM{
|
||||
ID: "i-stop-to-term-001",
|
||||
Status: vm.StateStopped,
|
||||
InstanceType: getTestInstanceType(),
|
||||
AccountID: testAccountID,
|
||||
}
|
||||
require.NoError(t, daemon.jsManager.WriteStoppedInstance(stoppedVM.ID, stoppedVM))
|
||||
|
||||
sub, err := daemon.natsConn.QueueSubscribe("ec2.terminate", "hive-workers", daemon.handleEC2TerminateStoppedInstance)
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
reqData, _ := json.Marshal(terminateStoppedInstanceRequest{InstanceID: stoppedVM.ID})
|
||||
reply, err := natsRequest(daemon.natsConn, "ec2.terminate", reqData, 30*time.Second)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(reply.Data), "terminated")
|
||||
|
||||
// Verify instance is in terminated KV
|
||||
terminatedInst, err := daemon.jsManager.LoadTerminatedInstance(stoppedVM.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, terminatedInst, "terminated instance should exist in terminated KV")
|
||||
assert.Equal(t, vm.StateTerminated, terminatedInst.Status)
|
||||
|
||||
// Verify instance is removed from stopped KV
|
||||
stoppedInst, err := daemon.jsManager.LoadStoppedInstance(stoppedVM.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, stoppedInst, "instance should be removed from stopped KV")
|
||||
|
||||
_ = daemon.jsManager.DeleteTerminatedInstance(stoppedVM.ID)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/nats-io/nats.go"
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// Phase 1: ebs.mount via NATS (rolls back with ebs.unmount)
|
||||
// Phase 2: QMP blockdev-add (rolls back Phase 1)
|
||||
// Phase 3: QMP device_add (rolls back Phase 2 + Phase 1)
|
||||
func (d *Daemon) handleAttachVolume(msg *nats.Msg, command qmp.Command, instance *vm.VM) {
|
||||
func (d *Daemon) handleAttachVolume(msg *nats.Msg, command types.EC2InstanceCommand, instance *vm.VM) {
|
||||
slog.Info("Attaching volume to instance", "instanceId", command.ID)
|
||||
|
||||
// Validate AttachVolumeData
|
||||
@@ -89,7 +89,7 @@ func (d *Daemon) handleAttachVolume(msg *nats.Msg, command qmp.Command, instance
|
||||
}
|
||||
|
||||
// Create EBS mount request
|
||||
ebsRequest := config.EBSRequest{
|
||||
ebsRequest := types.EBSRequest{
|
||||
Name: volumeID,
|
||||
DeviceName: device,
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func (d *Daemon) handleAttachVolume(msg *nats.Msg, command qmp.Command, instance
|
||||
return
|
||||
}
|
||||
|
||||
var mountResp config.EBSMountResponse
|
||||
var mountResp types.EBSMountResponse
|
||||
if err := json.Unmarshal(mountReply.Data, &mountResp); err != nil {
|
||||
slog.Error("AttachVolume: failed to unmarshal mount response", "err", err)
|
||||
respondWithError(msg, awserrors.ErrorServerInternal)
|
||||
@@ -294,7 +294,7 @@ func (d *Daemon) handleAttachVolume(msg *nats.Msg, command qmp.Command, instance
|
||||
// Phase 1: QMP device_del (remove guest device)
|
||||
// Phase 2: QMP blockdev-del (remove block node)
|
||||
// Phase 3: ebs.unmount NATS (stop NBD server)
|
||||
func (d *Daemon) handleDetachVolume(msg *nats.Msg, command qmp.Command, instance *vm.VM) {
|
||||
func (d *Daemon) handleDetachVolume(msg *nats.Msg, command types.EC2InstanceCommand, instance *vm.VM) {
|
||||
slog.Info("Detaching volume from instance", "instanceId", command.ID)
|
||||
|
||||
// Validate DetachVolumeData
|
||||
@@ -321,7 +321,7 @@ func (d *Daemon) handleDetachVolume(msg *nats.Msg, command qmp.Command, instance
|
||||
|
||||
// Find the volume in EBSRequests
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
var ebsReq config.EBSRequest
|
||||
var ebsReq types.EBSRequest
|
||||
found := false
|
||||
for _, req := range instance.EBSRequests.Requests {
|
||||
if req.Name == volumeID {
|
||||
@@ -481,7 +481,7 @@ func (d *Daemon) handleEC2ModifyVolume(msg *nats.Msg) {
|
||||
|
||||
// Notify viperblockd to reload state after volume modification (e.g. resize)
|
||||
if modifyVolumeInput.VolumeId != nil {
|
||||
syncData, err := json.Marshal(config.EBSSyncRequest{Volume: *modifyVolumeInput.VolumeId})
|
||||
syncData, err := json.Marshal(types.EBSSyncRequest{Volume: *modifyVolumeInput.VolumeId})
|
||||
if err != nil {
|
||||
slog.Error("failed to marshal ebs.sync request", "volumeId", *modifyVolumeInput.VolumeId, "err", err)
|
||||
} else {
|
||||
|
||||
+111
-110
@@ -20,6 +20,7 @@ import (
|
||||
handlers_ec2_volume "github.com/mulgadc/hive/hive/handlers/ec2/volume"
|
||||
"github.com/mulgadc/hive/hive/objectstore"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
@@ -861,20 +862,20 @@ func TestDaemon_BootAllocation(t *testing.T) {
|
||||
InstanceType: getTestInstanceType(),
|
||||
Status: vm.StateRunning,
|
||||
AccountID: testAccountID,
|
||||
Attributes: qmp.Attributes{StopInstance: false},
|
||||
Attributes: types.EC2CommandAttributes{StopInstance: false},
|
||||
},
|
||||
"i-stopped": {
|
||||
ID: "i-stopped",
|
||||
InstanceType: getTestInstanceType(),
|
||||
Status: vm.StateStopped,
|
||||
AccountID: testAccountID,
|
||||
Attributes: qmp.Attributes{StopInstance: true},
|
||||
Attributes: types.EC2CommandAttributes{StopInstance: true},
|
||||
},
|
||||
"i-terminated": {
|
||||
ID: "i-terminated",
|
||||
InstanceType: getTestInstanceType(),
|
||||
Status: vm.StateTerminated,
|
||||
Attributes: qmp.Attributes{StopInstance: false},
|
||||
Attributes: types.EC2CommandAttributes{StopInstance: false},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1618,7 +1619,7 @@ func TestResourceManager_ConcurrentAccess(t *testing.T) {
|
||||
|
||||
// TestEBSRequest_DeleteOnTermination_DefaultFalse verifies the default value
|
||||
func TestEBSRequest_DeleteOnTermination_DefaultFalse(t *testing.T) {
|
||||
req := config.EBSRequest{
|
||||
req := types.EBSRequest{
|
||||
Name: "vol-test",
|
||||
Boot: true,
|
||||
}
|
||||
@@ -1627,7 +1628,7 @@ func TestEBSRequest_DeleteOnTermination_DefaultFalse(t *testing.T) {
|
||||
|
||||
// TestEBSRequest_DeleteOnTermination_SetTrue verifies the field can be set
|
||||
func TestEBSRequest_DeleteOnTermination_SetTrue(t *testing.T) {
|
||||
req := config.EBSRequest{
|
||||
req := types.EBSRequest{
|
||||
Name: "vol-test",
|
||||
Boot: true,
|
||||
DeleteOnTermination: true,
|
||||
@@ -1697,7 +1698,7 @@ func TestGenerateVolumes_DeleteOnTermination_FromBlockDeviceMapping(t *testing.T
|
||||
"deleteOnTermination should match expected value")
|
||||
|
||||
// Verify the flag is correctly assigned to an EBSRequest
|
||||
ebsReq := config.EBSRequest{
|
||||
ebsReq := types.EBSRequest{
|
||||
Name: "vol-test",
|
||||
Boot: true,
|
||||
DeleteOnTermination: deleteOnTermination,
|
||||
@@ -1722,12 +1723,12 @@ func TestStopInstance_DeleteOnTermination_VolumeDeletion(t *testing.T) {
|
||||
|
||||
// Mock ebs.unmount subscriber
|
||||
unmountSub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
var req config.EBSRequest
|
||||
var req types.EBSRequest
|
||||
json.Unmarshal(msg.Data, &req)
|
||||
mu.Lock()
|
||||
unmountedVolumes[req.Name] = true
|
||||
mu.Unlock()
|
||||
resp := config.EBSUnMountResponse{Volume: req.Name, Mounted: false}
|
||||
resp := types.EBSUnMountResponse{Volume: req.Name, Mounted: false}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -1736,12 +1737,12 @@ func TestStopInstance_DeleteOnTermination_VolumeDeletion(t *testing.T) {
|
||||
|
||||
// Mock ebs.delete subscriber
|
||||
deleteSub, err := daemon.natsConn.Subscribe("ebs.delete", func(msg *nats.Msg) {
|
||||
var req config.EBSDeleteRequest
|
||||
var req types.EBSDeleteRequest
|
||||
json.Unmarshal(msg.Data, &req)
|
||||
mu.Lock()
|
||||
ebsDeletedVolumes[req.Volume] = true
|
||||
mu.Unlock()
|
||||
resp := config.EBSDeleteResponse{Volume: req.Volume, Success: true}
|
||||
resp := types.EBSDeleteResponse{Volume: req.Volume, Success: true}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -1760,8 +1761,8 @@ func TestStopInstance_DeleteOnTermination_VolumeDeletion(t *testing.T) {
|
||||
Status: vm.StateRunning,
|
||||
AccountID: testAccountID,
|
||||
QMPClient: &qmp.QMPClient{}, // nil encoder/decoder => QMP will fail, which is fine
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: "vol-root",
|
||||
Boot: true,
|
||||
@@ -1820,12 +1821,12 @@ func TestStopInstance_DeleteOnTermination_False_SkipsVolumeDeletion(t *testing.T
|
||||
|
||||
// Mock ebs.unmount subscriber
|
||||
unmountSub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
var req config.EBSRequest
|
||||
var req types.EBSRequest
|
||||
json.Unmarshal(msg.Data, &req)
|
||||
mu.Lock()
|
||||
unmountedVolumes[req.Name] = true
|
||||
mu.Unlock()
|
||||
resp := config.EBSUnMountResponse{Volume: req.Name, Mounted: false}
|
||||
resp := types.EBSUnMountResponse{Volume: req.Name, Mounted: false}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -1834,12 +1835,12 @@ func TestStopInstance_DeleteOnTermination_False_SkipsVolumeDeletion(t *testing.T
|
||||
|
||||
// Mock ebs.delete subscriber
|
||||
deleteSub, err := daemon.natsConn.Subscribe("ebs.delete", func(msg *nats.Msg) {
|
||||
var req config.EBSDeleteRequest
|
||||
var req types.EBSDeleteRequest
|
||||
json.Unmarshal(msg.Data, &req)
|
||||
mu.Lock()
|
||||
ebsDeletedVolumes[req.Volume] = true
|
||||
mu.Unlock()
|
||||
resp := config.EBSDeleteResponse{Volume: req.Volume, Success: true}
|
||||
resp := types.EBSDeleteResponse{Volume: req.Volume, Success: true}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -1858,8 +1859,8 @@ func TestStopInstance_DeleteOnTermination_False_SkipsVolumeDeletion(t *testing.T
|
||||
Status: vm.StateRunning,
|
||||
AccountID: testAccountID,
|
||||
QMPClient: &qmp.QMPClient{},
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: "vol-keep",
|
||||
Boot: true,
|
||||
@@ -1918,7 +1919,7 @@ func TestStopInstance_NoDelete_OnStop(t *testing.T) {
|
||||
|
||||
// Mock ebs.unmount subscriber
|
||||
unmountSub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
resp := config.EBSUnMountResponse{Mounted: false}
|
||||
resp := types.EBSUnMountResponse{Mounted: false}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -1927,12 +1928,12 @@ func TestStopInstance_NoDelete_OnStop(t *testing.T) {
|
||||
|
||||
// Mock ebs.delete subscriber - should NOT be called
|
||||
deleteSub, err := daemon.natsConn.Subscribe("ebs.delete", func(msg *nats.Msg) {
|
||||
var req config.EBSDeleteRequest
|
||||
var req types.EBSDeleteRequest
|
||||
json.Unmarshal(msg.Data, &req)
|
||||
mu.Lock()
|
||||
ebsDeletedVolumes[req.Volume] = true
|
||||
mu.Unlock()
|
||||
resp := config.EBSDeleteResponse{Volume: req.Volume, Success: true}
|
||||
resp := types.EBSDeleteResponse{Volume: req.Volume, Success: true}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -1945,8 +1946,8 @@ func TestStopInstance_NoDelete_OnStop(t *testing.T) {
|
||||
Status: vm.StateRunning,
|
||||
AccountID: testAccountID,
|
||||
QMPClient: &qmp.QMPClient{},
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: "vol-stop-root",
|
||||
Boot: true,
|
||||
@@ -2010,9 +2011,9 @@ func TestHandleEC2Events_AttachVolume(t *testing.T) {
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
t.Run("MissingAttachVolumeData", func(t *testing.T) {
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
// No AttachVolumeData
|
||||
@@ -2036,12 +2037,12 @@ func TestHandleEC2Events_AttachVolume(t *testing.T) {
|
||||
instance.Status = vm.StateStopped
|
||||
daemon.Instances.Mu.Unlock()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: &qmp.AttachVolumeData{
|
||||
AttachVolumeData: &types.AttachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
},
|
||||
}
|
||||
@@ -2063,12 +2064,12 @@ func TestHandleEC2Events_AttachVolume(t *testing.T) {
|
||||
|
||||
t.Run("VolumeNotFound", func(t *testing.T) {
|
||||
// volumeService.GetVolumeConfig will fail since we have no S3 backend
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: &qmp.AttachVolumeData{
|
||||
AttachVolumeData: &types.AttachVolumeData{
|
||||
VolumeID: "vol-nonexistent",
|
||||
Device: "/dev/sdf",
|
||||
},
|
||||
@@ -2089,7 +2090,7 @@ func TestHandleEC2Events_AttachVolume(t *testing.T) {
|
||||
// TestEBSRequest_JSON_Serialization verifies DeleteOnTermination survives JSON round-trip
|
||||
// (important for JetStream state persistence)
|
||||
func TestEBSRequest_JSON_Serialization(t *testing.T) {
|
||||
original := config.EBSRequest{
|
||||
original := types.EBSRequest{
|
||||
Name: "vol-test-json",
|
||||
Boot: true,
|
||||
DeleteOnTermination: true,
|
||||
@@ -2099,7 +2100,7 @@ func TestEBSRequest_JSON_Serialization(t *testing.T) {
|
||||
data, err := json.Marshal(original)
|
||||
require.NoError(t, err)
|
||||
|
||||
var restored config.EBSRequest
|
||||
var restored types.EBSRequest
|
||||
err = json.Unmarshal(data, &restored)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2136,8 +2137,8 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
},
|
||||
},
|
||||
QMPClient: &qmp.QMPClient{}, // nil encoder/decoder
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: volumeID,
|
||||
DeviceName: "/dev/sdf",
|
||||
@@ -2156,9 +2157,9 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
t.Run("MissingDetachVolumeData", func(t *testing.T) {
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
// No DetachVolumeData
|
||||
@@ -2180,12 +2181,12 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
instance.Status = vm.StateStopped
|
||||
daemon.Instances.Mu.Unlock()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
},
|
||||
}
|
||||
@@ -2206,12 +2207,12 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("VolumeNotAttached", func(t *testing.T) {
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: "vol-nonexistent",
|
||||
},
|
||||
}
|
||||
@@ -2231,18 +2232,18 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
|
||||
// Add a boot volume to the instance
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, config.EBSRequest{
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, types.EBSRequest{
|
||||
Name: bootVolumeID,
|
||||
Boot: true,
|
||||
})
|
||||
instance.EBSRequests.Mu.Unlock()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: bootVolumeID,
|
||||
},
|
||||
}
|
||||
@@ -2266,18 +2267,18 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
efiVolumeID := "vol-efi-protected"
|
||||
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, config.EBSRequest{
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, types.EBSRequest{
|
||||
Name: efiVolumeID,
|
||||
EFI: true,
|
||||
})
|
||||
instance.EBSRequests.Mu.Unlock()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: efiVolumeID,
|
||||
},
|
||||
}
|
||||
@@ -2300,18 +2301,18 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
ciVolumeID := "vol-cloudinit-protected"
|
||||
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, config.EBSRequest{
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, types.EBSRequest{
|
||||
Name: ciVolumeID,
|
||||
CloudInit: true,
|
||||
})
|
||||
instance.EBSRequests.Mu.Unlock()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: ciVolumeID,
|
||||
},
|
||||
}
|
||||
@@ -2331,12 +2332,12 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("DeviceMismatch", func(t *testing.T) {
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
Device: "/dev/sdg", // actual is /dev/sdf
|
||||
},
|
||||
@@ -2355,12 +2356,12 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
|
||||
t.Run("QMPDeviceDelFails_NoForce", func(t *testing.T) {
|
||||
// With nil QMPClient encoder/decoder, SendQMPCommand returns error.
|
||||
// Without force=true, this should return ServerInternal.
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
Force: false,
|
||||
},
|
||||
@@ -2479,8 +2480,8 @@ func TestDetachVolume_SuccessPath(t *testing.T) {
|
||||
},
|
||||
},
|
||||
QMPClient: qmpClient,
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: "vol-root",
|
||||
Boot: true,
|
||||
@@ -2499,10 +2500,10 @@ func TestDetachVolume_SuccessPath(t *testing.T) {
|
||||
// Subscribe a mock ebs.unmount handler
|
||||
ebsUnmountCalled := make(chan string, 1)
|
||||
ebsSub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
var req config.EBSRequest
|
||||
var req types.EBSRequest
|
||||
json.Unmarshal(msg.Data, &req)
|
||||
ebsUnmountCalled <- req.Name
|
||||
resp := config.EBSUnMountResponse{Volume: req.Name, Mounted: false}
|
||||
resp := types.EBSUnMountResponse{Volume: req.Name, Mounted: false}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -2516,12 +2517,12 @@ func TestDetachVolume_SuccessPath(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
},
|
||||
}
|
||||
@@ -2627,8 +2628,8 @@ func TestDetachVolume_ForceFlag(t *testing.T) {
|
||||
},
|
||||
},
|
||||
QMPClient: qmpClient,
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: volumeID,
|
||||
DeviceName: "/dev/sdf",
|
||||
@@ -2641,7 +2642,7 @@ func TestDetachVolume_ForceFlag(t *testing.T) {
|
||||
|
||||
// Mock ebs.unmount
|
||||
ebsSub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
resp := config.EBSUnMountResponse{Mounted: false}
|
||||
resp := types.EBSUnMountResponse{Mounted: false}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -2655,12 +2656,12 @@ func TestDetachVolume_ForceFlag(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
Force: true,
|
||||
},
|
||||
@@ -2741,8 +2742,8 @@ func TestDetachVolume_BlockdevDelFailure(t *testing.T) {
|
||||
},
|
||||
},
|
||||
QMPClient: qmpClient,
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: volumeID,
|
||||
DeviceName: "/dev/sdf",
|
||||
@@ -2760,12 +2761,12 @@ func TestDetachVolume_BlockdevDelFailure(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
},
|
||||
}
|
||||
@@ -2833,8 +2834,8 @@ func TestDetachVolume_SuccessWithDeviceMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
QMPClient: qmpClient,
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: volumeID,
|
||||
DeviceName: "/dev/sdh",
|
||||
@@ -2846,7 +2847,7 @@ func TestDetachVolume_SuccessWithDeviceMatch(t *testing.T) {
|
||||
daemon.Instances.VMS[instanceID] = instance
|
||||
|
||||
ebsSub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
resp := config.EBSUnMountResponse{Mounted: false}
|
||||
resp := types.EBSUnMountResponse{Mounted: false}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -2860,12 +2861,12 @@ func TestDetachVolume_SuccessWithDeviceMatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
Device: "/dev/sdh", // matches actual device
|
||||
},
|
||||
@@ -2909,8 +2910,8 @@ func TestAttachVolume_ReplacesStaleEBSRequest(t *testing.T) {
|
||||
AccountID: testAccountID,
|
||||
Instance: &ec2.Instance{},
|
||||
QMPClient: qmpClient,
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{
|
||||
Name: volumeID,
|
||||
DeviceName: "/dev/sdf", // stale entry from before stop
|
||||
@@ -2923,7 +2924,7 @@ func TestAttachVolume_ReplacesStaleEBSRequest(t *testing.T) {
|
||||
|
||||
// Mock ebs.mount to return success with a new NBDURI
|
||||
ebsSub, err := daemon.natsConn.Subscribe("ebs.node-1.mount", func(msg *nats.Msg) {
|
||||
resp := config.EBSMountResponse{URI: "nbd://new:2222"}
|
||||
resp := types.EBSMountResponse{URI: "nbd://new:2222"}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -2937,12 +2938,12 @@ func TestAttachVolume_ReplacesStaleEBSRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: &qmp.AttachVolumeData{
|
||||
AttachVolumeData: &types.AttachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
Device: "/dev/sdg", // new device
|
||||
},
|
||||
@@ -2966,7 +2967,7 @@ func TestAttachVolume_ReplacesStaleEBSRequest(t *testing.T) {
|
||||
|
||||
// Direct unit test: simulate what the fixed attach handler does
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
newReq := config.EBSRequest{
|
||||
newReq := types.EBSRequest{
|
||||
Name: volumeID,
|
||||
DeviceName: "/dev/sdg",
|
||||
NBDURI: "nbd://new:2222",
|
||||
@@ -3309,17 +3310,17 @@ func TestRollbackEBSMount_Success(t *testing.T) {
|
||||
|
||||
// Mock ebs.unmount subscriber that returns success
|
||||
sub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
var req config.EBSRequest
|
||||
var req types.EBSRequest
|
||||
json.Unmarshal(msg.Data, &req)
|
||||
unmountCalled <- req.Name
|
||||
resp := config.EBSUnMountResponse{Volume: req.Name, Mounted: false}
|
||||
resp := types.EBSUnMountResponse{Volume: req.Name, Mounted: false}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
ebsReq := config.EBSRequest{
|
||||
ebsReq := types.EBSRequest{
|
||||
Name: "vol-rollback-test",
|
||||
DeviceName: "/dev/sdf",
|
||||
}
|
||||
@@ -3343,14 +3344,14 @@ func TestRollbackEBSMount_UnmountError(t *testing.T) {
|
||||
|
||||
// Mock ebs.unmount subscriber that returns an error
|
||||
sub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
resp := config.EBSUnMountResponse{Error: "unmount failed: device busy"}
|
||||
resp := types.EBSUnMountResponse{Error: "unmount failed: device busy"}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
ebsReq := config.EBSRequest{Name: "vol-rollback-err"}
|
||||
ebsReq := types.EBSRequest{Name: "vol-rollback-err"}
|
||||
|
||||
// Should not panic — errors are logged but not propagated
|
||||
daemon.rollbackEBSMount(ebsReq)
|
||||
@@ -3364,14 +3365,14 @@ func TestRollbackEBSMount_StillMounted(t *testing.T) {
|
||||
daemon := createTestDaemon(t, natsURL)
|
||||
|
||||
sub, err := daemon.natsConn.Subscribe("ebs.node-1.unmount", func(msg *nats.Msg) {
|
||||
resp := config.EBSUnMountResponse{Mounted: true} // still mounted
|
||||
resp := types.EBSUnMountResponse{Mounted: true} // still mounted
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
ebsReq := config.EBSRequest{Name: "vol-still-mounted"}
|
||||
ebsReq := types.EBSRequest{Name: "vol-still-mounted"}
|
||||
|
||||
// Should not panic
|
||||
daemon.rollbackEBSMount(ebsReq)
|
||||
@@ -3385,7 +3386,7 @@ func TestRollbackEBSMount_NATSTimeout(t *testing.T) {
|
||||
daemon := createTestDaemon(t, natsURL)
|
||||
|
||||
// No ebs.unmount subscriber — will timeout
|
||||
ebsReq := config.EBSRequest{Name: "vol-timeout"}
|
||||
ebsReq := types.EBSRequest{Name: "vol-timeout"}
|
||||
|
||||
// Should not panic, just log the timeout
|
||||
daemon.rollbackEBSMount(ebsReq)
|
||||
@@ -3454,9 +3455,9 @@ func TestStopTerminate_IncorrectInstanceState(t *testing.T) {
|
||||
instance.Status = vm.StateStopped
|
||||
daemon.Instances.Mu.Unlock()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
StopInstance: true,
|
||||
},
|
||||
}
|
||||
@@ -3477,9 +3478,9 @@ func TestStopTerminate_IncorrectInstanceState(t *testing.T) {
|
||||
instance.Status = vm.StateTerminated
|
||||
daemon.Instances.Mu.Unlock()
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
TerminateInstance: true,
|
||||
},
|
||||
}
|
||||
@@ -3531,8 +3532,8 @@ func TestNextAvailableDevice(t *testing.T) {
|
||||
name: "existing EBSRequests skipped",
|
||||
instance: &vm.VM{
|
||||
Instance: &ec2.Instance{},
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{Name: "vol-1", DeviceName: "/dev/sdf"},
|
||||
},
|
||||
},
|
||||
@@ -3547,8 +3548,8 @@ func TestNextAvailableDevice(t *testing.T) {
|
||||
{DeviceName: aws.String("/dev/sdf")},
|
||||
},
|
||||
},
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{Name: "vol-1", DeviceName: "/dev/sdg"},
|
||||
},
|
||||
},
|
||||
@@ -3586,8 +3587,8 @@ func TestNextAvailableDevice(t *testing.T) {
|
||||
{
|
||||
name: "empty DeviceName in EBSRequests ignored",
|
||||
instance: &vm.VM{
|
||||
EBSRequests: config.EBSRequests{
|
||||
Requests: []config.EBSRequest{
|
||||
EBSRequests: types.EBSRequests{
|
||||
Requests: []types.EBSRequest{
|
||||
{DeviceName: ""},
|
||||
{DeviceName: "/dev/sdf"},
|
||||
},
|
||||
|
||||
+236
-22
@@ -21,15 +21,20 @@ const (
|
||||
InstanceStatePrefix = "node."
|
||||
// StoppedInstancePrefix is the key prefix for stopped instances in shared KV
|
||||
StoppedInstancePrefix = "instance."
|
||||
// TerminatedInstanceBucket is the name of the KV bucket for terminated instances (auto-expiry via TTL)
|
||||
TerminatedInstanceBucket = "hive-terminated-instances"
|
||||
// TerminatedInstancePrefix is the key prefix for terminated instances
|
||||
TerminatedInstancePrefix = "terminated."
|
||||
)
|
||||
|
||||
// JetStreamManager manages JetStream KV store operations for instance state
|
||||
type JetStreamManager struct {
|
||||
js nats.JetStreamContext
|
||||
kv nats.KeyValue // hive-instance-state
|
||||
clusterKV nats.KeyValue // hive-cluster-state
|
||||
replicas int
|
||||
kvMu sync.Mutex // protects kv during recovery
|
||||
js nats.JetStreamContext
|
||||
kv nats.KeyValue // hive-instance-state
|
||||
clusterKV nats.KeyValue // hive-cluster-state
|
||||
terminatedKV nats.KeyValue // hive-terminated-instances
|
||||
replicas int
|
||||
kvMu sync.Mutex // protects kv during recovery
|
||||
}
|
||||
|
||||
// NewJetStreamManager creates a new JetStreamManager from a NATS connection.
|
||||
@@ -101,6 +106,34 @@ func (m *JetStreamManager) InitClusterStateBucket() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitTerminatedInstanceBucket initializes the terminated-instances KV bucket with a 1-hour TTL.
|
||||
// JetStream automatically purges keys after 1 hour, matching AWS behavior for terminated instances.
|
||||
func (m *JetStreamManager) InitTerminatedInstanceBucket() error {
|
||||
kv, err := m.js.KeyValue(TerminatedInstanceBucket)
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrBucketNotFound) {
|
||||
slog.Debug("Creating JetStream KV bucket", "bucket", TerminatedInstanceBucket, "replicas", m.replicas)
|
||||
kv, err = m.js.CreateKeyValue(&nats.KeyValueConfig{
|
||||
Bucket: TerminatedInstanceBucket,
|
||||
Description: "Terminated instances (auto-expire after 1 hour)",
|
||||
History: 1,
|
||||
Replicas: m.replicas,
|
||||
TTL: 1 * time.Hour,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
slog.Debug("Connected to existing JetStream KV bucket", "bucket", TerminatedInstanceBucket)
|
||||
}
|
||||
|
||||
m.terminatedKV = kv
|
||||
return nil
|
||||
}
|
||||
|
||||
// isStreamUnavailable checks if an error indicates the underlying JetStream stream
|
||||
// was lost or is unreachable. This can happen during NATS cluster formation when
|
||||
// streams created with low replication are disrupted by node join/catchup operations.
|
||||
@@ -120,18 +153,18 @@ func isStreamUnavailable(err error) bool {
|
||||
return strings.Contains(err.Error(), "stream not found")
|
||||
}
|
||||
|
||||
// recoverKVBucket attempts to reconnect to or re-create the instance-state KV bucket
|
||||
// after the underlying JetStream stream was lost during cluster formation.
|
||||
// Returns the recovered KV handle directly so callers avoid a racy re-read of m.kv.
|
||||
func (m *JetStreamManager) recoverKVBucket() (nats.KeyValue, error) {
|
||||
// recoverBucket attempts to reconnect to or re-create a KV bucket after the
|
||||
// underlying JetStream stream was lost during cluster formation.
|
||||
// Returns the recovered KV handle directly so callers avoid a racy re-read.
|
||||
func (m *JetStreamManager) recoverBucket(cfg *nats.KeyValueConfig, field *nats.KeyValue) (nats.KeyValue, error) {
|
||||
m.kvMu.Lock()
|
||||
defer m.kvMu.Unlock()
|
||||
|
||||
// Try to reconnect to existing bucket first (another goroutine may have recovered it)
|
||||
kv, err := m.js.KeyValue(InstanceStateBucket)
|
||||
kv, err := m.js.KeyValue(cfg.Bucket)
|
||||
if err == nil {
|
||||
m.kv = kv
|
||||
slog.Info("Reconnected to KV bucket", "bucket", InstanceStateBucket)
|
||||
*field = kv
|
||||
slog.Info("Reconnected to KV bucket", "bucket", cfg.Bucket)
|
||||
return kv, nil
|
||||
}
|
||||
|
||||
@@ -140,23 +173,35 @@ func (m *JetStreamManager) recoverKVBucket() (nats.KeyValue, error) {
|
||||
}
|
||||
|
||||
// Bucket truly doesn't exist — recreate it
|
||||
slog.Warn("KV bucket stream lost, recreating", "bucket", InstanceStateBucket, "replicas", m.replicas)
|
||||
kv, err = m.js.CreateKeyValue(&nats.KeyValueConfig{
|
||||
Bucket: InstanceStateBucket,
|
||||
Description: "Hive instance state storage",
|
||||
History: 1,
|
||||
Replicas: m.replicas,
|
||||
})
|
||||
slog.Warn("KV bucket stream lost, recreating", "bucket", cfg.Bucket, "replicas", m.replicas)
|
||||
cfg.History = 1
|
||||
cfg.Replicas = m.replicas
|
||||
kv, err = m.js.CreateKeyValue(cfg)
|
||||
if err != nil {
|
||||
slog.Error("Failed to recreate KV bucket", "bucket", InstanceStateBucket, "err", err)
|
||||
slog.Error("Failed to recreate KV bucket", "bucket", cfg.Bucket, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.kv = kv
|
||||
slog.Info("KV bucket recreated successfully", "bucket", InstanceStateBucket)
|
||||
*field = kv
|
||||
slog.Info("KV bucket recreated successfully", "bucket", cfg.Bucket)
|
||||
return kv, nil
|
||||
}
|
||||
|
||||
func (m *JetStreamManager) recoverKVBucket() (nats.KeyValue, error) {
|
||||
return m.recoverBucket(&nats.KeyValueConfig{
|
||||
Bucket: InstanceStateBucket,
|
||||
Description: "Hive instance state storage",
|
||||
}, &m.kv)
|
||||
}
|
||||
|
||||
func (m *JetStreamManager) recoverTerminatedKVBucket() (nats.KeyValue, error) {
|
||||
return m.recoverBucket(&nats.KeyValueConfig{
|
||||
Bucket: TerminatedInstanceBucket,
|
||||
Description: "Terminated instances (auto-expire after 1 hour)",
|
||||
TTL: 1 * time.Hour,
|
||||
}, &m.terminatedKV)
|
||||
}
|
||||
|
||||
// Heartbeat represents a daemon's periodic health status published to cluster KV.
|
||||
type Heartbeat struct {
|
||||
Node string `json:"node"`
|
||||
@@ -475,6 +520,20 @@ func (m *JetStreamManager) UpdateReplicas(newReplicas int) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Also update the terminated-instances bucket if it exists
|
||||
if m.terminatedKV != nil {
|
||||
terminatedStreamName := "KV_" + TerminatedInstanceBucket
|
||||
terminatedInfo, err := m.js.StreamInfo(terminatedStreamName)
|
||||
if err == nil && terminatedInfo.Config.Replicas < newReplicas {
|
||||
terminatedInfo.Config.Replicas = newReplicas
|
||||
if _, err := m.js.UpdateStream(&terminatedInfo.Config); err != nil {
|
||||
slog.Warn("Failed to update terminated-instances bucket replicas", "error", err)
|
||||
} else {
|
||||
slog.Info("Updated terminated-instances bucket replicas", "bucket", TerminatedInstanceBucket, "newReplicas", newReplicas)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -637,3 +696,158 @@ func (m *JetStreamManager) ListStoppedInstances() ([]*vm.VM, error) {
|
||||
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
// WriteTerminatedInstance writes a terminated instance to the terminated KV bucket.
|
||||
// The entry will auto-expire after the bucket's TTL (1 hour).
|
||||
func (m *JetStreamManager) WriteTerminatedInstance(instanceID string, instance *vm.VM) error {
|
||||
if m.terminatedKV == nil {
|
||||
return errors.New("terminated instance KV bucket not initialized")
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(instance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := TerminatedInstancePrefix + instanceID
|
||||
_, err = m.terminatedKV.Put(key, jsonData)
|
||||
if err != nil {
|
||||
if isStreamUnavailable(err) {
|
||||
slog.Warn("KV stream unavailable, attempting recovery", "operation", "WriteTerminatedInstance", "key", key, "err", err)
|
||||
kv, recoverErr := m.recoverTerminatedKVBucket()
|
||||
if recoverErr != nil {
|
||||
return err
|
||||
}
|
||||
if _, retryErr := kv.Put(key, jsonData); retryErr != nil {
|
||||
return retryErr
|
||||
}
|
||||
slog.Debug("Wrote terminated instance to JetStream KV (after recovery)", "key", key, "instanceId", instanceID)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("Wrote terminated instance to JetStream KV", "key", key, "instanceId", instanceID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListTerminatedInstances returns all terminated instances from the terminated KV bucket.
|
||||
func (m *JetStreamManager) ListTerminatedInstances() ([]*vm.VM, error) {
|
||||
if m.terminatedKV == nil {
|
||||
return nil, errors.New("terminated instance KV bucket not initialized")
|
||||
}
|
||||
|
||||
keys, err := m.terminatedKV.Keys()
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrNoKeysFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if isStreamUnavailable(err) {
|
||||
slog.Warn("KV stream unavailable, attempting recovery", "operation", "ListTerminatedInstances", "err", err)
|
||||
kv, recoverErr := m.recoverTerminatedKVBucket()
|
||||
if recoverErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys, err = kv.Keys()
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrNoKeysFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var instances []*vm.VM
|
||||
for _, key := range keys {
|
||||
if !strings.HasPrefix(key, TerminatedInstancePrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
entry, err := m.terminatedKV.Get(key)
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrKeyNotFound) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var instance vm.VM
|
||||
if err := json.Unmarshal(entry.Value(), &instance); err != nil {
|
||||
slog.Error("Failed to unmarshal terminated instance", "key", key, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
instances = append(instances, &instance)
|
||||
}
|
||||
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
// DeleteTerminatedInstance removes a terminated instance from the terminated KV bucket.
|
||||
func (m *JetStreamManager) DeleteTerminatedInstance(instanceID string) error {
|
||||
if m.terminatedKV == nil {
|
||||
return errors.New("terminated instance KV bucket not initialized")
|
||||
}
|
||||
|
||||
key := TerminatedInstancePrefix + instanceID
|
||||
err := m.terminatedKV.Delete(key)
|
||||
if err != nil && !errors.Is(err, nats.ErrKeyNotFound) {
|
||||
if isStreamUnavailable(err) {
|
||||
slog.Warn("KV stream unavailable, attempting recovery", "operation", "DeleteTerminatedInstance", "key", key, "err", err)
|
||||
kv, recoverErr := m.recoverTerminatedKVBucket()
|
||||
if recoverErr != nil {
|
||||
return err
|
||||
}
|
||||
if retryErr := kv.Delete(key); retryErr != nil && !errors.Is(retryErr, nats.ErrKeyNotFound) {
|
||||
return retryErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("Deleted terminated instance from JetStream KV", "key", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadTerminatedInstance loads a single terminated instance from the terminated KV bucket.
|
||||
// Returns nil, nil if the key does not exist.
|
||||
func (m *JetStreamManager) LoadTerminatedInstance(instanceID string) (*vm.VM, error) {
|
||||
if m.terminatedKV == nil {
|
||||
return nil, errors.New("terminated instance KV bucket not initialized")
|
||||
}
|
||||
|
||||
key := TerminatedInstancePrefix + instanceID
|
||||
entry, err := m.terminatedKV.Get(key)
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrKeyNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if isStreamUnavailable(err) {
|
||||
slog.Warn("KV stream unavailable, attempting recovery", "operation", "LoadTerminatedInstance", "key", key, "err", err)
|
||||
kv, recoverErr := m.recoverTerminatedKVBucket()
|
||||
if recoverErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry, err = kv.Get(key)
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrKeyNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var instance vm.VM
|
||||
if err := json.Unmarshal(entry.Value(), &instance); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instance, nil
|
||||
}
|
||||
|
||||
@@ -962,3 +962,249 @@ func TestIsStreamUnavailable(t *testing.T) {
|
||||
assert.False(t, isStreamUnavailable(errors.New("some other error")))
|
||||
assert.False(t, isStreamUnavailable(nats.ErrKeyNotFound))
|
||||
}
|
||||
|
||||
// --- Terminated instance KV tests ---
|
||||
|
||||
func TestJetStreamManager_WriteAndLoadTerminatedInstance(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
testVM := &vm.VM{
|
||||
ID: "i-term-001",
|
||||
Status: vm.StateTerminated,
|
||||
InstanceType: "t3.micro",
|
||||
LastNode: "node-1",
|
||||
}
|
||||
|
||||
err = jsm.WriteTerminatedInstance(testVM.ID, testVM)
|
||||
require.NoError(t, err)
|
||||
|
||||
loaded, err := jsm.LoadTerminatedInstance(testVM.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loaded)
|
||||
assert.Equal(t, testVM.ID, loaded.ID)
|
||||
assert.Equal(t, vm.StateTerminated, loaded.Status)
|
||||
assert.Equal(t, "t3.micro", loaded.InstanceType)
|
||||
assert.Equal(t, "node-1", loaded.LastNode)
|
||||
|
||||
_ = jsm.DeleteTerminatedInstance(testVM.ID)
|
||||
}
|
||||
|
||||
func TestJetStreamManager_LoadTerminatedInstance_NotFound(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
loaded, err := jsm.LoadTerminatedInstance("i-nonexistent")
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, loaded)
|
||||
}
|
||||
|
||||
func TestJetStreamManager_DeleteTerminatedInstance(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
testVM := &vm.VM{
|
||||
ID: "i-term-del",
|
||||
Status: vm.StateTerminated,
|
||||
}
|
||||
|
||||
err = jsm.WriteTerminatedInstance(testVM.ID, testVM)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = jsm.DeleteTerminatedInstance(testVM.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
loaded, err := jsm.LoadTerminatedInstance(testVM.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, loaded)
|
||||
|
||||
// Delete non-existent should not error
|
||||
err = jsm.DeleteTerminatedInstance("i-does-not-exist")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestJetStreamManager_ListTerminatedInstances(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
vms := []*vm.VM{
|
||||
{ID: "i-tlist-001", Status: vm.StateTerminated, InstanceType: "t3.micro"},
|
||||
{ID: "i-tlist-002", Status: vm.StateTerminated, InstanceType: "t3.small"},
|
||||
}
|
||||
|
||||
for _, v := range vms {
|
||||
require.NoError(t, jsm.WriteTerminatedInstance(v.ID, v))
|
||||
}
|
||||
defer func() {
|
||||
for _, v := range vms {
|
||||
_ = jsm.DeleteTerminatedInstance(v.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
instances, err := jsm.ListTerminatedInstances()
|
||||
require.NoError(t, err)
|
||||
// Use >= because other tests may leave entries in the shared bucket
|
||||
assert.GreaterOrEqual(t, len(instances), 2)
|
||||
|
||||
idSet := map[string]bool{}
|
||||
for _, inst := range instances {
|
||||
idSet[inst.ID] = true
|
||||
}
|
||||
assert.True(t, idSet["i-tlist-001"])
|
||||
assert.True(t, idSet["i-tlist-002"])
|
||||
}
|
||||
|
||||
func TestJetStreamManager_WriteLoadDeleteTerminatedInstance_RoundTrip(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
testVM := &vm.VM{
|
||||
ID: "i-trt-001",
|
||||
Status: vm.StateTerminated,
|
||||
}
|
||||
|
||||
// Write
|
||||
require.NoError(t, jsm.WriteTerminatedInstance(testVM.ID, testVM))
|
||||
|
||||
// Load — should find it
|
||||
loaded, err := jsm.LoadTerminatedInstance(testVM.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loaded)
|
||||
assert.Equal(t, vm.StateTerminated, loaded.Status)
|
||||
|
||||
// Delete
|
||||
require.NoError(t, jsm.DeleteTerminatedInstance(testVM.ID))
|
||||
|
||||
// Load — should be gone
|
||||
loaded, err = jsm.LoadTerminatedInstance(testVM.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, loaded)
|
||||
}
|
||||
|
||||
func TestJetStreamManager_TerminatedInstance_KVNotInitialized(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
// Don't call InitTerminatedInstanceBucket
|
||||
|
||||
err = jsm.WriteTerminatedInstance("i-test", &vm.VM{})
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = jsm.LoadTerminatedInstance("i-test")
|
||||
assert.Error(t, err)
|
||||
|
||||
err = jsm.DeleteTerminatedInstance("i-test")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = jsm.ListTerminatedInstances()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// --- Terminated KV bucket recovery tests ---
|
||||
|
||||
// deleteTerminatedInstanceBucket deletes the underlying JetStream stream for the terminated-instances KV bucket.
|
||||
func deleteTerminatedInstanceBucket(t *testing.T, nc *nats.Conn) {
|
||||
t.Helper()
|
||||
js, err := nc.JetStream()
|
||||
require.NoError(t, err)
|
||||
err = js.DeleteStream("KV_" + TerminatedInstanceBucket)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestJetStreamManager_WriteTerminatedInstance_RecoverAfterStreamLost(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
deleteTerminatedInstanceBucket(t, nc)
|
||||
|
||||
testVM := &vm.VM{ID: "i-term-recover", Status: vm.StateTerminated, InstanceType: "t3.micro"}
|
||||
err = jsm.WriteTerminatedInstance(testVM.ID, testVM)
|
||||
require.NoError(t, err, "WriteTerminatedInstance should recover after stream loss")
|
||||
|
||||
loaded, err := jsm.LoadTerminatedInstance(testVM.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loaded)
|
||||
assert.Equal(t, "i-term-recover", loaded.ID)
|
||||
|
||||
_ = jsm.DeleteTerminatedInstance(testVM.ID)
|
||||
}
|
||||
|
||||
func TestJetStreamManager_LoadTerminatedInstance_RecoverAfterStreamLost(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
deleteTerminatedInstanceBucket(t, nc)
|
||||
|
||||
loaded, err := jsm.LoadTerminatedInstance("i-nonexistent")
|
||||
require.NoError(t, err, "LoadTerminatedInstance should recover after stream loss")
|
||||
assert.Nil(t, loaded)
|
||||
}
|
||||
|
||||
func TestJetStreamManager_DeleteTerminatedInstance_RecoverAfterStreamLost(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
deleteTerminatedInstanceBucket(t, nc)
|
||||
|
||||
err = jsm.DeleteTerminatedInstance("i-any")
|
||||
require.NoError(t, err, "DeleteTerminatedInstance should recover after stream loss")
|
||||
}
|
||||
|
||||
func TestJetStreamManager_ListTerminatedInstances_RecoverAfterStreamLost(t *testing.T) {
|
||||
nc, err := nats.Connect(sharedJSNATSURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, jsm.InitTerminatedInstanceBucket())
|
||||
|
||||
deleteTerminatedInstanceBucket(t, nc)
|
||||
|
||||
instances, err := jsm.ListTerminatedInstances()
|
||||
require.NoError(t, err, "ListTerminatedInstances should recover after stream loss")
|
||||
assert.Empty(t, instances)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
)
|
||||
|
||||
// errInsufficientCapacity is returned by allocateForLaunch when MinCount
|
||||
@@ -38,7 +38,7 @@ func canAllocateCount(availVCPU, allocVCPU int, availMem, allocMem float64,
|
||||
|
||||
// resourceStatsForType computes the InstanceTypeCap for a single instance type
|
||||
// given the remaining host resources. Pure function — no locks or side effects.
|
||||
func resourceStatsForType(remainVCPU int, remainMem float64, it *ec2.InstanceTypeInfo) config.InstanceTypeCap {
|
||||
func resourceStatsForType(remainVCPU int, remainMem float64, it *ec2.InstanceTypeInfo) types.InstanceTypeCap {
|
||||
vCPUs := instanceTypeVCPUs(it)
|
||||
memGB := float64(instanceTypeMemoryMiB(it)) / 1024.0
|
||||
|
||||
@@ -54,7 +54,7 @@ func resourceStatsForType(remainVCPU int, remainMem float64, it *ec2.InstanceTyp
|
||||
name = *it.InstanceType
|
||||
}
|
||||
|
||||
return config.InstanceTypeCap{
|
||||
return types.InstanceTypeCap{
|
||||
Name: name,
|
||||
VCPU: int(vCPUs),
|
||||
MemoryGB: memGB,
|
||||
|
||||
+81
-23
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/nats-io/nats.go"
|
||||
@@ -62,6 +62,7 @@ func createDaemonWithJetStream(t *testing.T) *Daemon {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, daemon.jsManager.InitKVBucket())
|
||||
require.NoError(t, daemon.jsManager.InitClusterStateBucket())
|
||||
require.NoError(t, daemon.jsManager.InitTerminatedInstanceBucket())
|
||||
|
||||
return daemon
|
||||
}
|
||||
@@ -642,15 +643,20 @@ func TestRestoreInstances_ShuttingDownFinalizedToTerminated(t *testing.T) {
|
||||
daemon.Instances.VMS = make(map[string]*vm.VM)
|
||||
simulateCleanRestore(t, daemon)
|
||||
|
||||
instance, ok := daemon.Instances.VMS["i-restore-shut"]
|
||||
require.True(t, ok, "instance should be loaded from state")
|
||||
assert.Equal(t, vm.StateTerminated, instance.Status,
|
||||
// shutting-down→terminated should be migrated to terminated KV
|
||||
_, ok := daemon.Instances.VMS["i-restore-shut"]
|
||||
assert.False(t, ok, "terminated instance should be removed from local map")
|
||||
|
||||
terminatedInst, err := daemon.jsManager.LoadTerminatedInstance("i-restore-shut")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, terminatedInst, "terminated instance should exist in terminated KV")
|
||||
assert.Equal(t, vm.StateTerminated, terminatedInst.Status,
|
||||
"shutting-down instance should be finalized to terminated on recovery")
|
||||
}
|
||||
|
||||
// TestRestoreInstances_TerminatedSkipped verifies that terminated instances
|
||||
// are loaded but not relaunched or modified.
|
||||
func TestRestoreInstances_TerminatedSkipped(t *testing.T) {
|
||||
// TestRestoreInstances_TerminatedRemoved verifies that terminated instances
|
||||
// are removed from per-node state on restore (no migration, just cleanup).
|
||||
func TestRestoreInstances_TerminatedRemoved(t *testing.T) {
|
||||
daemon := createDaemonWithJetStream(t)
|
||||
|
||||
daemon.Instances.VMS["i-restore-term"] = &vm.VM{
|
||||
@@ -662,10 +668,8 @@ func TestRestoreInstances_TerminatedSkipped(t *testing.T) {
|
||||
daemon.Instances.VMS = make(map[string]*vm.VM)
|
||||
simulateCleanRestore(t, daemon)
|
||||
|
||||
instance, ok := daemon.Instances.VMS["i-restore-term"]
|
||||
require.True(t, ok, "terminated instance should still be loaded")
|
||||
assert.Equal(t, vm.StateTerminated, instance.Status,
|
||||
"terminated instance should remain terminated")
|
||||
_, ok := daemon.Instances.VMS["i-restore-term"]
|
||||
assert.False(t, ok, "terminated instance should be removed from local map")
|
||||
}
|
||||
|
||||
// TestRestoreInstances_UserStoppedMigratedToSharedKV verifies that instances
|
||||
@@ -676,7 +680,7 @@ func TestRestoreInstances_UserStoppedMigratedToSharedKV(t *testing.T) {
|
||||
daemon.Instances.VMS["i-restore-userstop"] = &vm.VM{
|
||||
ID: "i-restore-userstop",
|
||||
Status: vm.StateStopped,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
StopInstance: true,
|
||||
},
|
||||
}
|
||||
@@ -770,7 +774,7 @@ func TestRestoreInstances_MixedStates(t *testing.T) {
|
||||
}
|
||||
daemon.Instances.VMS["i-mix-stopped"] = &vm.VM{
|
||||
ID: "i-mix-stopped", Status: vm.StateStopped,
|
||||
Attributes: qmp.Attributes{StopInstance: true},
|
||||
Attributes: types.EC2CommandAttributes{StopInstance: true},
|
||||
}
|
||||
require.NoError(t, daemon.WriteState())
|
||||
|
||||
@@ -785,10 +789,17 @@ func TestRestoreInstances_MixedStates(t *testing.T) {
|
||||
require.NotNil(t, stoppedFromStopping, "stopping→stopped should exist in shared KV")
|
||||
assert.Equal(t, vm.StateStopped, stoppedFromStopping.Status)
|
||||
|
||||
assert.Equal(t, vm.StateTerminated, daemon.Instances.VMS["i-mix-shutting"].Status,
|
||||
"shutting-down should finalize to terminated")
|
||||
assert.Equal(t, vm.StateTerminated, daemon.Instances.VMS["i-mix-terminated"].Status,
|
||||
"terminated should remain terminated")
|
||||
// shutting-down should finalize to terminated and migrate to terminated KV
|
||||
assert.Nil(t, daemon.Instances.VMS["i-mix-shutting"],
|
||||
"shutting-down→terminated should be migrated to terminated KV")
|
||||
terminatedFromShutting, err2 := daemon.jsManager.LoadTerminatedInstance("i-mix-shutting")
|
||||
require.NoError(t, err2)
|
||||
require.NotNil(t, terminatedFromShutting, "shutting-down→terminated should exist in terminated KV")
|
||||
assert.Equal(t, vm.StateTerminated, terminatedFromShutting.Status)
|
||||
|
||||
// already-terminated should be removed from local map (no migration, just cleanup)
|
||||
assert.Nil(t, daemon.Instances.VMS["i-mix-terminated"],
|
||||
"terminated should be removed from local map")
|
||||
|
||||
assert.Nil(t, daemon.Instances.VMS["i-mix-stopped"],
|
||||
"user-stopped should be migrated to shared KV")
|
||||
@@ -824,10 +835,16 @@ func TestRestoreInstances_StatePersistsAfterRecovery(t *testing.T) {
|
||||
require.NotNil(t, stoppedInst)
|
||||
assert.Equal(t, vm.StateStopped, stoppedInst.Status)
|
||||
|
||||
assert.Equal(t, vm.StateTerminated, daemon.Instances.VMS["i-persist-shut"].Status)
|
||||
// shutting-down→terminated should be migrated to terminated KV
|
||||
assert.Nil(t, daemon.Instances.VMS["i-persist-shut"],
|
||||
"terminated instance should be removed from local map")
|
||||
terminatedInst, err := daemon.jsManager.LoadTerminatedInstance("i-persist-shut")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, terminatedInst)
|
||||
assert.Equal(t, vm.StateTerminated, terminatedInst.Status)
|
||||
|
||||
// Second restart: stopped instance is in shared KV (not per-node state),
|
||||
// so local map won't have it. Terminated instance persists in per-node state.
|
||||
// terminated instance is in terminated KV (not per-node state).
|
||||
daemon.Instances.VMS = make(map[string]*vm.VM)
|
||||
simulateCleanRestore(t, daemon)
|
||||
|
||||
@@ -837,8 +854,49 @@ func TestRestoreInstances_StatePersistsAfterRecovery(t *testing.T) {
|
||||
require.NotNil(t, stoppedInst2, "stopped instance should persist in shared KV through second restart")
|
||||
assert.Equal(t, vm.StateStopped, stoppedInst2.Status)
|
||||
|
||||
assert.Equal(t, vm.StateTerminated, daemon.Instances.VMS["i-persist-shut"].Status,
|
||||
"terminated state should persist through second restart")
|
||||
// Terminated instance should still be in terminated KV (within TTL window)
|
||||
terminatedInst2, err := daemon.jsManager.LoadTerminatedInstance("i-persist-shut")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, terminatedInst2, "terminated instance should persist in terminated KV through second restart")
|
||||
assert.Equal(t, vm.StateTerminated, terminatedInst2.Status)
|
||||
}
|
||||
|
||||
// TestMigrateInstanceToKV verifies the unified migration helper works for both stopped and terminated paths.
|
||||
func TestMigrateInstanceToKV(t *testing.T) {
|
||||
daemon := createDaemonWithJetStream(t)
|
||||
|
||||
// Test stopped migration
|
||||
stoppedVM := &vm.VM{ID: "i-migrate-stop", Status: vm.StateStopped}
|
||||
daemon.Instances.VMS[stoppedVM.ID] = stoppedVM
|
||||
assert.True(t, daemon.migrateStoppedToSharedKV(stoppedVM))
|
||||
assert.Nil(t, daemon.Instances.VMS[stoppedVM.ID], "should be removed from local map")
|
||||
loaded, err := daemon.jsManager.LoadStoppedInstance(stoppedVM.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loaded)
|
||||
assert.Equal(t, daemon.node, loaded.LastNode)
|
||||
|
||||
// Test terminated migration
|
||||
terminatedVM := &vm.VM{ID: "i-migrate-term", Status: vm.StateTerminated}
|
||||
daemon.Instances.VMS[terminatedVM.ID] = terminatedVM
|
||||
assert.True(t, daemon.migrateTerminatedToKV(terminatedVM))
|
||||
assert.Nil(t, daemon.Instances.VMS[terminatedVM.ID], "should be removed from local map")
|
||||
loadedTerm, err := daemon.jsManager.LoadTerminatedInstance(terminatedVM.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loadedTerm)
|
||||
assert.Equal(t, daemon.node, loadedTerm.LastNode)
|
||||
}
|
||||
|
||||
// TestMigrateInstanceToKV_NoJetStream verifies graceful failure when JetStream is nil.
|
||||
func TestMigrateInstanceToKV_NoJetStream(t *testing.T) {
|
||||
daemon := createDaemonWithJetStream(t)
|
||||
daemon.jsManager = nil
|
||||
|
||||
vm1 := &vm.VM{ID: "i-no-js", Status: vm.StateStopped}
|
||||
daemon.Instances.VMS[vm1.ID] = vm1
|
||||
assert.False(t, daemon.migrateStoppedToSharedKV(vm1))
|
||||
assert.False(t, daemon.migrateTerminatedToKV(vm1))
|
||||
// Instance should still be in local map since migration failed
|
||||
assert.NotNil(t, daemon.Instances.VMS[vm1.ID])
|
||||
}
|
||||
|
||||
// TestStatePersistence_RoundTrip verifies that all instance fields survive
|
||||
@@ -850,7 +908,7 @@ func TestStatePersistence_RoundTrip(t *testing.T) {
|
||||
ID: "i-roundtrip",
|
||||
Status: vm.StateRunning,
|
||||
InstanceType: "t3.micro",
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
StopInstance: false,
|
||||
TerminateInstance: false,
|
||||
},
|
||||
@@ -881,7 +939,7 @@ func TestRestoreInstances_StoppedInstanceMigratedToSharedKV(t *testing.T) {
|
||||
ID: instanceID,
|
||||
InstanceType: "t3.micro",
|
||||
Status: vm.StateStopped,
|
||||
Attributes: qmp.Attributes{StopInstance: true},
|
||||
Attributes: types.EC2CommandAttributes{StopInstance: true},
|
||||
}
|
||||
require.NoError(t, daemon.WriteState())
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
@@ -105,27 +106,22 @@ func DescribeInstances(input *ec2.DescribeInstancesInput, natsConn *nats.Conn, e
|
||||
}
|
||||
}
|
||||
|
||||
// Query stopped instances from shared KV (via queue group — only one daemon handles it)
|
||||
stoppedReqMsg := nats.NewMsg("ec2.DescribeStoppedInstances")
|
||||
stoppedReqMsg.Data = jsonData
|
||||
stoppedReqMsg.Header.Set(utils.AccountIDHeader, accountID)
|
||||
stoppedMsg, err := natsConn.RequestMsg(stoppedReqMsg, 3*time.Second)
|
||||
if err != nil {
|
||||
slog.Warn("DescribeInstances: Failed to query stopped instances (may not be available)", "err", err)
|
||||
} else {
|
||||
responseError, parseErr := utils.ValidateErrorPayload(stoppedMsg.Data)
|
||||
if parseErr != nil {
|
||||
slog.Warn("DescribeInstances: Stopped instances query returned error", "code", responseError.Code)
|
||||
} else {
|
||||
var stoppedOutput ec2.DescribeInstancesOutput
|
||||
if err := json.Unmarshal(stoppedMsg.Data, &stoppedOutput); err != nil {
|
||||
slog.Error("DescribeInstances: Failed to unmarshal stopped instances response", "err", err)
|
||||
} else if stoppedOutput.Reservations != nil {
|
||||
allReservations = append(allReservations, stoppedOutput.Reservations...)
|
||||
slog.Info("DescribeInstances: Collected stopped instance reservations", "count", len(stoppedOutput.Reservations))
|
||||
// Query stopped and terminated instances in parallel (both use queue groups — single responder each)
|
||||
var kvMu sync.Mutex
|
||||
var kvWg sync.WaitGroup
|
||||
for _, topic := range []string{"ec2.DescribeStoppedInstances", "ec2.DescribeTerminatedInstances"} {
|
||||
kvWg.Add(1)
|
||||
go func(topic string) {
|
||||
defer kvWg.Done()
|
||||
reservations := queryInstanceBucket(natsConn, topic, jsonData, accountID)
|
||||
if len(reservations) > 0 {
|
||||
kvMu.Lock()
|
||||
allReservations = append(allReservations, reservations...)
|
||||
kvMu.Unlock()
|
||||
}
|
||||
}
|
||||
}(topic)
|
||||
}
|
||||
kvWg.Wait()
|
||||
|
||||
// Build final aggregated response
|
||||
output := &ec2.DescribeInstancesOutput{
|
||||
@@ -135,3 +131,28 @@ func DescribeInstances(input *ec2.DescribeInstancesInput, natsConn *nats.Conn, e
|
||||
slog.Info("DescribeInstances: Aggregated response", "total_reservations", len(allReservations))
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// queryInstanceBucket sends a NATS request to a describe topic and returns the reservations.
|
||||
func queryInstanceBucket(natsConn *nats.Conn, topic string, jsonData []byte, accountID string) []*ec2.Reservation {
|
||||
reqMsg := nats.NewMsg(topic)
|
||||
reqMsg.Data = jsonData
|
||||
reqMsg.Header.Set(utils.AccountIDHeader, accountID)
|
||||
msg, err := natsConn.RequestMsg(reqMsg, 3*time.Second)
|
||||
if err != nil {
|
||||
slog.Warn("DescribeInstances: Failed to query instance bucket", "topic", topic, "err", err)
|
||||
return nil
|
||||
}
|
||||
if responseError, parseErr := utils.ValidateErrorPayload(msg.Data); parseErr != nil {
|
||||
slog.Warn("DescribeInstances: Instance bucket query returned error", "topic", topic, "code", responseError.Code)
|
||||
return nil
|
||||
}
|
||||
var output ec2.DescribeInstancesOutput
|
||||
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
||||
slog.Error("DescribeInstances: Failed to unmarshal instance bucket response", "topic", topic, "err", err)
|
||||
return nil
|
||||
}
|
||||
if len(output.Reservations) > 0 {
|
||||
slog.Info("DescribeInstances: Collected reservations from bucket", "topic", topic, "count", len(output.Reservations))
|
||||
}
|
||||
return output.Reservations
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
@@ -31,15 +31,9 @@ func StopInstances(input *ec2.StopInstancesInput, natsConn *nats.Conn, accountID
|
||||
}
|
||||
instanceID := *instanceIDPtr
|
||||
|
||||
// Build the QMP command to stop the instance
|
||||
// Note: system_powerdown with stop_instance=true prevents auto-restart on daemon boot
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
QMPCommand: qmp.QMPCommand{
|
||||
Execute: "system_powerdown",
|
||||
Arguments: map[string]any{},
|
||||
},
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
StopInstance: true, // Don't auto-restart on daemon boot
|
||||
TerminateInstance: false,
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -18,12 +18,11 @@ func TestStopInstances_Success(t *testing.T) {
|
||||
instanceID := "i-0123456789abcdef0"
|
||||
|
||||
nc.Subscribe("ec2.cmd."+instanceID, func(msg *nats.Msg) {
|
||||
var cmd qmp.Command
|
||||
var cmd types.EC2InstanceCommand
|
||||
err := json.Unmarshal(msg.Data, &cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, instanceID, cmd.ID)
|
||||
assert.Equal(t, "system_powerdown", cmd.QMPCommand.Execute)
|
||||
assert.True(t, cmd.Attributes.StopInstance)
|
||||
assert.False(t, cmd.Attributes.TerminateInstance)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
@@ -36,15 +36,9 @@ func TerminateInstances(input *ec2.TerminateInstancesInput, natsConn *nats.Conn,
|
||||
}
|
||||
instanceID := *instanceIDPtr
|
||||
|
||||
// Build the QMP command to terminate the instance
|
||||
// Note: stop_instance=true prevents restart on daemon/node restart
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
QMPCommand: qmp.QMPCommand{
|
||||
Execute: "system_powerdown",
|
||||
Arguments: map[string]any{},
|
||||
},
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
StopInstance: true, // Prevent restart on daemon/node restart
|
||||
TerminateInstance: true,
|
||||
},
|
||||
@@ -68,7 +62,11 @@ func TerminateInstances(input *ec2.TerminateInstancesInput, natsConn *nats.Conn,
|
||||
if errors.Is(err, nats.ErrNoResponders) {
|
||||
slog.Info("TerminateInstances: No responder on per-instance topic, trying ec2.terminate", "instance_id", instanceID)
|
||||
|
||||
terminateReq, _ := json.Marshal(terminateStoppedInstanceRequest{InstanceID: instanceID})
|
||||
terminateReq, err := json.Marshal(terminateStoppedInstanceRequest{InstanceID: instanceID})
|
||||
if err != nil {
|
||||
slog.Error("TerminateInstances: Failed to marshal terminate request", "instance_id", instanceID, "err", err)
|
||||
continue
|
||||
}
|
||||
terminateReqMsg := nats.NewMsg("ec2.terminate")
|
||||
terminateReqMsg.Data = terminateReq
|
||||
terminateReqMsg.Header.Set(utils.AccountIDHeader, accountID)
|
||||
@@ -80,6 +78,13 @@ func TerminateInstances(input *ec2.TerminateInstancesInput, natsConn *nats.Conn,
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check if instance is already terminated (idempotent, matches AWS behavior)
|
||||
if isAlreadyTerminated(natsConn, instanceID, accountID) {
|
||||
slog.Info("TerminateInstances: Instance already terminated", "instance_id", instanceID)
|
||||
stateChanges = append(stateChanges, newStateChange(instanceID, 48, "terminated", 48, "terminated"))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
slog.Error("TerminateInstances: Failed to send command", "instance_id", instanceID, "err", err)
|
||||
@@ -105,3 +110,36 @@ func TerminateInstances(input *ec2.TerminateInstancesInput, natsConn *nats.Conn,
|
||||
slog.Info("TerminateInstances: Completed", "total_instances", len(stateChanges))
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// isAlreadyTerminated checks if an instance exists in the terminated KV bucket.
|
||||
func isAlreadyTerminated(natsConn *nats.Conn, instanceID, accountID string) bool {
|
||||
describeInput := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{&instanceID},
|
||||
}
|
||||
reqData, err := json.Marshal(describeInput)
|
||||
if err != nil {
|
||||
slog.Warn("isAlreadyTerminated: failed to marshal request", "instanceId", instanceID, "err", err)
|
||||
return false
|
||||
}
|
||||
reqMsg := nats.NewMsg("ec2.DescribeTerminatedInstances")
|
||||
reqMsg.Data = reqData
|
||||
reqMsg.Header.Set(utils.AccountIDHeader, accountID)
|
||||
msg, err := natsConn.RequestMsg(reqMsg, 3*time.Second)
|
||||
if err != nil {
|
||||
slog.Warn("isAlreadyTerminated: failed to query terminated instances", "instanceId", instanceID, "err", err)
|
||||
return false
|
||||
}
|
||||
var output ec2.DescribeInstancesOutput
|
||||
if unmarshalErr := json.Unmarshal(msg.Data, &output); unmarshalErr != nil {
|
||||
slog.Warn("isAlreadyTerminated: failed to unmarshal response", "instanceId", instanceID, "err", unmarshalErr)
|
||||
return false
|
||||
}
|
||||
for _, res := range output.Reservations {
|
||||
for _, inst := range res.Instances {
|
||||
if inst.InstanceId != nil && *inst.InstanceId == instanceID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -18,12 +18,11 @@ func TestTerminateInstances_Success(t *testing.T) {
|
||||
instanceID := "i-0123456789abcdef0"
|
||||
|
||||
nc.Subscribe("ec2.cmd."+instanceID, func(msg *nats.Msg) {
|
||||
var cmd qmp.Command
|
||||
var cmd types.EC2InstanceCommand
|
||||
err := json.Unmarshal(msg.Data, &cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, instanceID, cmd.ID)
|
||||
assert.Equal(t, "system_powerdown", cmd.QMPCommand.Execute)
|
||||
assert.True(t, cmd.Attributes.StopInstance)
|
||||
assert.True(t, cmd.Attributes.TerminateInstance)
|
||||
|
||||
@@ -161,7 +160,7 @@ func TestTerminateInstances_VerifiesQMPAttributes(t *testing.T) {
|
||||
_, nc := startTestNATSServer(t)
|
||||
|
||||
instanceID := "i-verify"
|
||||
var receivedCmd qmp.Command
|
||||
var receivedCmd types.EC2InstanceCommand
|
||||
|
||||
nc.Subscribe("ec2.cmd."+instanceID, func(msg *nats.Msg) {
|
||||
json.Unmarshal(msg.Data, &receivedCmd)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
@@ -48,12 +48,12 @@ func AttachVolume(input *ec2.AttachVolumeInput, natsConn *nats.Conn, accountID s
|
||||
device = *input.Device
|
||||
}
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
AttachVolume: true,
|
||||
},
|
||||
AttachVolumeData: &qmp.AttachVolumeData{
|
||||
AttachVolumeData: &types.AttachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
Device: device,
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
handlers_ec2_volume "github.com/mulgadc/hive/hive/handlers/ec2/volume"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
@@ -66,12 +66,12 @@ func DetachVolume(input *ec2.DetachVolumeInput, natsConn *nats.Conn, accountID s
|
||||
device = *input.Device
|
||||
}
|
||||
|
||||
command := qmp.Command{
|
||||
command := types.EC2InstanceCommand{
|
||||
ID: instanceID,
|
||||
Attributes: qmp.Attributes{
|
||||
Attributes: types.EC2CommandAttributes{
|
||||
DetachVolume: true,
|
||||
},
|
||||
DetachVolumeData: &qmp.DetachVolumeData{
|
||||
DetachVolumeData: &types.DetachVolumeData{
|
||||
VolumeID: volumeID,
|
||||
Device: device,
|
||||
Force: input.Force != nil && *input.Force,
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/gateway/policy"
|
||||
handlers_iam "github.com/mulgadc/hive/hive/handlers/iam"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
@@ -347,11 +348,6 @@ func ParseArgsToStruct(input *any, args map[string]string) (err error) {
|
||||
|
||||
}
|
||||
|
||||
// NodeDiscoverResponse is the response from a node discovery request
|
||||
type NodeDiscoverResponse struct {
|
||||
Node string `json:"node"`
|
||||
}
|
||||
|
||||
// DiscoverActiveNodes discovers the number of active hive daemon nodes in the cluster
|
||||
// by publishing a discovery request and counting unique responses.
|
||||
// Returns the number of active nodes (minimum 1 if fallback is needed).
|
||||
@@ -399,7 +395,7 @@ func (gw *GatewayConfig) DiscoverActiveNodes() int {
|
||||
break
|
||||
}
|
||||
|
||||
var response NodeDiscoverResponse
|
||||
var response types.NodeDiscoverResponse
|
||||
if err := json.Unmarshal(msg.Data, &response); err != nil {
|
||||
slog.Debug("DiscoverActiveNodes: Failed to unmarshal response", "err", err)
|
||||
continue
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
handlers_iam "github.com/mulgadc/hive/hive/handlers/iam"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -312,7 +313,7 @@ func TestDiscoverActiveNodes_WithResponders(t *testing.T) {
|
||||
for _, nodeName := range []string{"node-1", "node-2"} {
|
||||
name := nodeName
|
||||
_, err := nc.Subscribe("hive.nodes.discover", func(msg *nats.Msg) {
|
||||
resp := NodeDiscoverResponse{Node: name}
|
||||
resp := types.NodeDiscoverResponse{Node: name}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -352,7 +353,7 @@ func TestDiscoverActiveNodes_DuplicateNodes(t *testing.T) {
|
||||
|
||||
for range 2 {
|
||||
_, err := nc.Subscribe("hive.nodes.discover", func(msg *nats.Msg) {
|
||||
resp := NodeDiscoverResponse{Node: "same-node"}
|
||||
resp := types.NodeDiscoverResponse{Node: "same-node"}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
handlers_ec2_vpc "github.com/mulgadc/hive/hive/handlers/ec2/vpc"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
@@ -341,4 +341,3 @@ func (s *IGWServiceImpl) recordToEC2(record *IGWRecord) *ec2.InternetGateway {
|
||||
|
||||
return igw
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
handlers_ec2_snapshot "github.com/mulgadc/hive/hive/handlers/ec2/snapshot"
|
||||
"github.com/mulgadc/hive/hive/objectstore"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/viperblock/viperblock"
|
||||
vbs3 "github.com/mulgadc/viperblock/viperblock/backends/s3"
|
||||
@@ -398,7 +399,7 @@ func (s *ImageServiceImpl) snapshotRunningVolume(volumeID, snapshotID string) er
|
||||
return errors.New(awserrors.ErrorServerInternal)
|
||||
}
|
||||
|
||||
snapReq := config.EBSSnapshotRequest{Volume: volumeID, SnapshotID: snapshotID}
|
||||
snapReq := types.EBSSnapshotRequest{Volume: volumeID, SnapshotID: snapshotID}
|
||||
snapData, err := json.Marshal(snapReq)
|
||||
if err != nil {
|
||||
slog.Error("snapshotRunningVolume: failed to marshal snapshot request", "volumeId", volumeID, "err", err)
|
||||
@@ -411,7 +412,7 @@ func (s *ImageServiceImpl) snapshotRunningVolume(volumeID, snapshotID string) er
|
||||
return errors.New(awserrors.ErrorServerInternal)
|
||||
}
|
||||
|
||||
var snapResp config.EBSSnapshotResponse
|
||||
var snapResp types.EBSSnapshotResponse
|
||||
if err := json.Unmarshal(msg.Data, &snapResp); err != nil {
|
||||
slog.Error("snapshotRunningVolume: failed to unmarshal response", "err", err)
|
||||
return errors.New(awserrors.ErrorServerInternal)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/objectstore"
|
||||
hivetypes "github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/mulgadc/viperblock/types"
|
||||
@@ -324,7 +325,7 @@ func (s *InstanceServiceImpl) prepareRootVolume(input *ec2.RunInstancesInput, im
|
||||
|
||||
// Append root volume to instance
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, config.EBSRequest{
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, hivetypes.EBSRequest{
|
||||
Name: imageId,
|
||||
Boot: true,
|
||||
DeleteOnTermination: deleteOnTermination,
|
||||
@@ -466,7 +467,7 @@ func (s *InstanceServiceImpl) prepareEFIVolume(imageId string, volumeConfig vipe
|
||||
}
|
||||
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, config.EBSRequest{
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, hivetypes.EBSRequest{
|
||||
Name: efiVb.VolumeName,
|
||||
Boot: false,
|
||||
EFI: true,
|
||||
@@ -542,7 +543,7 @@ func (s *InstanceServiceImpl) prepareCloudInitVolume(input *ec2.RunInstancesInpu
|
||||
}
|
||||
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, config.EBSRequest{
|
||||
instance.EBSRequests.Requests = append(instance.EBSRequests.Requests, hivetypes.EBSRequest{
|
||||
Name: cloudInitVolumeName,
|
||||
Boot: false,
|
||||
CloudInit: true,
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/objectstore"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/viperblock/viperblock"
|
||||
"github.com/nats-io/nats.go"
|
||||
@@ -217,7 +218,7 @@ func (s *SnapshotServiceImpl) CreateSnapshot(input *ec2.CreateSnapshotInput, acc
|
||||
// This sends a NATS message to the EBS daemon that owns the volume, which
|
||||
// calls vb.CreateSnapshot() on the live viperblock instance.
|
||||
if s.natsConn != nil {
|
||||
snapReq := config.EBSSnapshotRequest{Volume: volumeID, SnapshotID: snapshotID}
|
||||
snapReq := types.EBSSnapshotRequest{Volume: volumeID, SnapshotID: snapshotID}
|
||||
snapData, err := json.Marshal(snapReq)
|
||||
if err != nil {
|
||||
slog.Error("CreateSnapshot: failed to marshal ebs.snapshot request", "volumeId", volumeID, "err", err)
|
||||
@@ -232,7 +233,7 @@ func (s *SnapshotServiceImpl) CreateSnapshot(input *ec2.CreateSnapshotInput, acc
|
||||
slog.Error("CreateSnapshot: ebs.snapshot NATS request failed", "volumeId", volumeID, "snapshotId", snapshotID, "err", err)
|
||||
return nil, errors.New(awserrors.ErrorServerInternal)
|
||||
} else {
|
||||
var snapResp config.EBSSnapshotResponse
|
||||
var snapResp types.EBSSnapshotResponse
|
||||
if err := json.Unmarshal(msg.Data, &snapResp); err != nil {
|
||||
slog.Error("CreateSnapshot: failed to unmarshal ebs.snapshot response", "volumeId", volumeID, "snapshotId", snapshotID, "err", err)
|
||||
return nil, errors.New(awserrors.ErrorServerInternal)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/objectstore"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/viperblock/viperblock"
|
||||
s3backend "github.com/mulgadc/viperblock/viperblock/backends/s3"
|
||||
@@ -776,7 +777,7 @@ func (s *VolumeServiceImpl) DeleteVolume(input *ec2.DeleteVolumeInput, accountID
|
||||
|
||||
// Notify viperblockd to stop nbdkit/WAL syncer (best-effort)
|
||||
if s.natsConn != nil {
|
||||
deleteReq := config.EBSDeleteRequest{Volume: volumeID}
|
||||
deleteReq := types.EBSDeleteRequest{Volume: volumeID}
|
||||
deleteData, err := json.Marshal(deleteReq)
|
||||
if err != nil {
|
||||
slog.Error("DeleteVolume failed to marshal ebs.delete request", "volumeId", volumeID, "err", err)
|
||||
@@ -785,7 +786,7 @@ func (s *VolumeServiceImpl) DeleteVolume(input *ec2.DeleteVolumeInput, accountID
|
||||
if err != nil {
|
||||
slog.Warn("ebs.delete notification failed (volume may not be mounted)", "volumeId", volumeID, "err", err)
|
||||
} else {
|
||||
var deleteResp config.EBSDeleteResponse
|
||||
var deleteResp types.EBSDeleteResponse
|
||||
if json.Unmarshal(msg.Data, &deleteResp) == nil && deleteResp.Error != "" {
|
||||
slog.Error("ebs.delete returned error", "volumeId", volumeID, "err", deleteResp.Error)
|
||||
return nil, errors.New(awserrors.ErrorServerInternal)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/hive/hive/objectstore"
|
||||
"github.com/mulgadc/hive/hive/types"
|
||||
"github.com/mulgadc/viperblock/viperblock"
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/nats-io/nats.go"
|
||||
@@ -1357,7 +1358,7 @@ func TestDeleteVolume_WithNATSNotification(t *testing.T) {
|
||||
|
||||
// Subscribe to ebs.delete and reply with success
|
||||
sub, err := nc.Subscribe("ebs.delete", func(msg *nats.Msg) {
|
||||
resp := config.EBSDeleteResponse{Volume: volumeID, Success: true}
|
||||
resp := types.EBSDeleteResponse{Volume: volumeID, Success: true}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
@@ -1496,7 +1497,7 @@ func TestDeleteVolume_NATSErrorResponse(t *testing.T) {
|
||||
|
||||
// Subscribe and respond with an error
|
||||
sub, err := nc.Subscribe("ebs.delete", func(msg *nats.Msg) {
|
||||
resp := config.EBSDeleteResponse{Volume: volumeID, Error: "volume still mounted"}
|
||||
resp := types.EBSDeleteResponse{Volume: volumeID, Error: "volume still mounted"}
|
||||
data, _ := json.Marshal(resp)
|
||||
msg.Respond(data)
|
||||
})
|
||||
|
||||
@@ -20,34 +20,6 @@ var CommandResponseTypes = map[string]unmarshalTarget{
|
||||
"query-status": &Status{},
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
ID string `json:"id"`
|
||||
QMPCommand QMPCommand `json:"command"`
|
||||
Attributes Attributes `json:"attributes"`
|
||||
AttachVolumeData *AttachVolumeData `json:"attach_volume_data,omitempty"`
|
||||
DetachVolumeData *DetachVolumeData `json:"detach_volume_data,omitempty"`
|
||||
}
|
||||
|
||||
type Attributes struct {
|
||||
StopInstance bool `json:"stop_instance"`
|
||||
TerminateInstance bool `json:"delete_instance"`
|
||||
StartInstance bool `json:"start_instance"`
|
||||
AttachVolume bool `json:"attach_volume"`
|
||||
DetachVolume bool `json:"detach_volume"`
|
||||
}
|
||||
|
||||
type AttachVolumeData struct {
|
||||
VolumeID string `json:"volume_id"`
|
||||
Device string `json:"device,omitempty"`
|
||||
}
|
||||
|
||||
type DetachVolumeData struct {
|
||||
VolumeID string `json:"volume_id"`
|
||||
Device string `json:"device,omitempty"`
|
||||
Force bool `json:"force,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
type Status struct {
|
||||
Status string `json:"status"`
|
||||
Singlestep bool `json:"singlestep"`
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as B,y as $,z as C,v as F,j as s,t as K,A as b,C as v,L}from"./index.js";import{u as A}from"./useSuspenseQuery.js";import{B as E}from"./back-link.js";import{E as O}from"./error-banner.js";import{a as S}from"./s3.js";import{U,F as R,O as I}from"./upload-button.js";import"./alert-dialog.js";import"./download.js";import"./trash-2.js";function J(){const e=B.c(24),{bucket:n,_splat:k}=$.useParams(),r=C(k||""),{data:m}=A(F(n,r)),a=S();let d;e[0]!==m.Contents?(d=m.Contents||[],e[0]=m.Contents,e[1]=d):d=e[1];const o=d;let p;e[2]!==m.CommonPrefixes?(p=m.CommonPrefixes||[],e[2]=m.CommonPrefixes,e[3]=p):p=e[3];const i=p,u=r.split("/").filter(Boolean),y=[{name:n,path:`/s3/ls/${n}`},...u.map((t,l)=>{const c=`${u.slice(0,l+1).join("/")}/`;return{name:t,path:`/s3/ls/${n}/${c}`}})],P=u.length>1?`/s3/ls/${n}/${u.slice(0,-1).join("/")}/`:`/s3/ls/${n}`;let f;e[4]!==P?(f=s.jsx(E,{to:P,children:"Back"}),e[4]=P,e[5]=f):f=e[5];let x;e[6]!==n||e[7]!==r||e[8]!==a.isPending||e[9]!==a.mutateAsync?(x=s.jsx(U,{bucket:n,isPending:a.isPending,onUpload:a.mutateAsync,prefix:r}),e[6]=n,e[7]=r,e[8]=a.isPending,e[9]=a.mutateAsync,e[10]=x):x=e[10];let h;e[11]!==a.error?(h=a.error&&s.jsx(O,{error:a.error,msg:"Failed to upload object"}),e[11]=a.error,e[12]=h):h=e[12];let g;e[13]!==n||e[14]!==i||e[15]!==r?(g=i.length>0&&s.jsxs("div",{className:"mb-6",children:[s.jsx("h2",{className:"mb-3 font-semibold text-lg",children:"Folders"}),s.jsx("div",{className:"space-y-2",children:i.map(t=>{if(!t.Prefix)return null;const l=K(b(t.Prefix,r)),c=v(t.Prefix,r);return s.jsx(R,{bucketName:n,folderName:l,fullPrefix:c},t.Prefix)})})]}),e[13]=n,e[14]=i,e[15]=r,e[16]=g):g=e[16];let j;e[17]!==n||e[18]!==o||e[19]!==r?(j=o.length>0&&s.jsxs("div",{className:"space-y-2",children:[s.jsx("h2",{className:"mb-3 font-semibold text-lg",children:"Objects"}),o.map(t=>{if(!t.Key)return null;const l=b(t.Key,r),c=v(t.Key,r);return s.jsx(I,{bucketName:n,displayName:l,fullKey:c,object:t},t.Key)})]}),e[17]=n,e[18]=o,e[19]=r,e[20]=j):j=e[20];let N;return e[21]!==i.length||e[22]!==o.length?(N=o.length===0&&i.length===0&&s.jsx("p",{className:"text-muted-foreground",children:"No objects found in this folder."}),e[21]=i.length,e[22]=o.length,e[23]=N):N=e[23],s.jsxs(s.Fragment,{children:[f,s.jsx("div",{className:"mb-6",children:s.jsxs("div",{className:"mb-2 flex items-center justify-between",children:[s.jsx("div",{className:"flex items-center gap-2 text-muted-foreground text-sm",children:y.map((t,l)=>s.jsxs("div",{className:"flex items-center gap-2",children:[l>0&&s.jsx("span",{children:"/"}),l===y.length-1?s.jsx("span",{className:"text-foreground",children:t.name}):s.jsx(L,{className:"hover:text-foreground",to:t.path,children:t.name})]},t.path))}),x]})}),h,g,j,N]})}export{J as component};
|
||||
import{c as B,F as $,G as F,y as C,j as s,x as K,H as b,I as k,L}from"./index.js";import{u as E}from"./useSuspenseQuery.js";import{B as O}from"./back-link.js";import{E as S}from"./error-banner.js";import{a as U}from"./s3.js";import{U as A,F as I,O as R}from"./upload-button.js";import"./delete-confirmation-dialog.js";import"./alert-dialog.js";import"./download.js";import"./trash-2.js";function V(){const e=B.c(24),{bucket:n,_splat:v}=$.useParams(),r=F(v||""),{data:m}=E(C(n,r)),a=U();let d;e[0]!==m.Contents?(d=m.Contents||[],e[0]=m.Contents,e[1]=d):d=e[1];const o=d;let p;e[2]!==m.CommonPrefixes?(p=m.CommonPrefixes||[],e[2]=m.CommonPrefixes,e[3]=p):p=e[3];const i=p,u=r.split("/").filter(Boolean),y=[{name:n,path:`/s3/ls/${n}`},...u.map((t,l)=>{const c=`${u.slice(0,l+1).join("/")}/`;return{name:t,path:`/s3/ls/${n}/${c}`}})],P=u.length>1?`/s3/ls/${n}/${u.slice(0,-1).join("/")}/`:`/s3/ls/${n}`;let x;e[4]!==P?(x=s.jsx(O,{to:P,children:"Back"}),e[4]=P,e[5]=x):x=e[5];let f;e[6]!==n||e[7]!==r||e[8]!==a.isPending||e[9]!==a.mutateAsync?(f=s.jsx(A,{bucket:n,isPending:a.isPending,onUpload:a.mutateAsync,prefix:r}),e[6]=n,e[7]=r,e[8]=a.isPending,e[9]=a.mutateAsync,e[10]=f):f=e[10];let h;e[11]!==a.error?(h=a.error&&s.jsx(S,{error:a.error,msg:"Failed to upload object"}),e[11]=a.error,e[12]=h):h=e[12];let g;e[13]!==n||e[14]!==i||e[15]!==r?(g=i.length>0&&s.jsxs("div",{className:"mb-6",children:[s.jsx("h2",{className:"mb-3 font-semibold text-lg",children:"Folders"}),s.jsx("div",{className:"space-y-2",children:i.map(t=>{if(!t.Prefix)return null;const l=K(b(t.Prefix,r)),c=k(t.Prefix,r);return s.jsx(I,{bucketName:n,folderName:l,fullPrefix:c},t.Prefix)})})]}),e[13]=n,e[14]=i,e[15]=r,e[16]=g):g=e[16];let j;e[17]!==n||e[18]!==o||e[19]!==r?(j=o.length>0&&s.jsxs("div",{className:"space-y-2",children:[s.jsx("h2",{className:"mb-3 font-semibold text-lg",children:"Objects"}),o.map(t=>{if(!t.Key)return null;const l=b(t.Key,r),c=k(t.Key,r);return s.jsx(R,{bucketName:n,displayName:l,fullKey:c,object:t},t.Key)})]}),e[17]=n,e[18]=o,e[19]=r,e[20]=j):j=e[20];let N;return e[21]!==i.length||e[22]!==o.length?(N=o.length===0&&i.length===0&&s.jsx("p",{className:"text-muted-foreground",children:"No objects found in this folder."}),e[21]=i.length,e[22]=o.length,e[23]=N):N=e[23],s.jsxs(s.Fragment,{children:[x,s.jsx("div",{className:"mb-6",children:s.jsxs("div",{className:"mb-2 flex items-center justify-between",children:[s.jsx("div",{className:"flex items-center gap-2 text-muted-foreground text-sm",children:y.map((t,l)=>s.jsxs("div",{className:"flex items-center gap-2",children:[l>0&&s.jsx("span",{children:"/"}),l===y.length-1?s.jsx("span",{className:"text-foreground",children:t.name}):s.jsx(L,{className:"hover:text-foreground",to:t.path,children:t.name})]},t.path))}),f]})}),h,g,j,N]})}export{V as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{ak as D,u as g,al as b,p as f,r as v,j as e,B as C,L as I}from"./index.js";import{u as d}from"./useSuspenseQuery.js";import{B as u}from"./back-link.js";import{D as l,a as t}from"./detail-row.js";import{E as V}from"./error-banner.js";import{P as S}from"./page-heading.js";import{S as y}from"./state-badge.js";import{g as P,f as A,b as k,a as B,c as w,d as N,e as T,h as O}from"./alert-dialog.js";import{g as H}from"./ec2.js";import{T as R}from"./trash-2.js";function q(){const{id:r}=D.useParams(),x=g(),{data:p}=d(b(r)),{data:m}=d(f),s=p.Vpcs?.[0],n=H(),[j,i]=v.useState(!1),o=m.Subnets?.filter(a=>a.VpcId===r)??[],h=async()=>{try{await n.mutateAsync(r),x({to:"/ec2/describe-vpcs"})}finally{i(!1)}};if(!s?.VpcId)return e.jsxs(e.Fragment,{children:[e.jsx(u,{to:"/ec2/describe-vpcs",children:"Back to VPCs"}),e.jsx("p",{className:"text-muted-foreground",children:"VPC not found."})]});const c=s.Tags?.find(a=>a.Key==="Name")?.Value;return e.jsxs(e.Fragment,{children:[e.jsx(u,{to:"/ec2/describe-vpcs",children:"Back to VPCs"}),n.error&&e.jsx(V,{error:n.error,msg:"Failed to delete VPC"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(S,{actions:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(C,{onClick:()=>i(!0),size:"sm",variant:"destructive",children:[e.jsx(R,{className:"size-4"}),"Delete"]}),e.jsx(y,{state:s.State})]}),subtitle:"VPC Details",title:c?`${s.VpcId} (${c})`:s.VpcId}),e.jsxs(l,{children:[e.jsx(l.Header,{children:"VPC Information"}),e.jsxs(l.Content,{children:[e.jsx(t,{label:"VPC ID",value:s.VpcId}),e.jsx(t,{label:"State",value:s.State}),e.jsx(t,{label:"CIDR Block",value:s.CidrBlock}),e.jsx(t,{label:"Is Default",value:s.IsDefault?"Yes":"No"}),e.jsx(t,{label:"DHCP Options ID",value:s.DhcpOptionsId}),e.jsx(t,{label:"Instance Tenancy",value:s.InstanceTenancy}),e.jsx(t,{label:"Owner ID",value:s.OwnerId})]})]}),o.length>0&&e.jsxs(l,{children:[e.jsxs(l.Header,{children:["Subnets (",o.length,")"]}),o.map(a=>a.SubnetId?e.jsxs(l.Content,{children:[e.jsx(t,{label:"Subnet ID",value:e.jsx(I,{className:"text-primary hover:underline",params:{id:a.SubnetId},to:"/ec2/describe-subnets/$id",children:a.SubnetId})}),e.jsx(t,{label:"CIDR Block",value:a.CidrBlock}),e.jsx(t,{label:"Availability Zone",value:a.AvailabilityZone}),e.jsx(t,{label:"State",value:a.State})]},a.SubnetId):null)]})]}),e.jsx(P,{onOpenChange:i,open:j,children:e.jsxs(A,{children:[e.jsxs(k,{children:[e.jsx(B,{children:"Delete VPC"}),e.jsxs(w,{children:['Are you sure you want to delete the VPC "',s.VpcId,'"? This action cannot be undone. The VPC must have no associated subnets or internet gateways.']})]}),e.jsxs(N,{children:[e.jsx(T,{children:"Cancel"}),e.jsx(O,{disabled:n.isPending,onClick:h,children:n.isPending?"Deleting…":"Delete"})]})]})})]})}export{q as component};
|
||||
import{aJ as D,u as f,aK as v,v as b,r as I,j as e,B as C,L as g}from"./index.js";import{u as d}from"./useSuspenseQuery.js";import{B as u}from"./back-link.js";import{D as V}from"./delete-confirmation-dialog.js";import{D as n,a}from"./detail-row.js";import{E as S}from"./error-banner.js";import{P as y}from"./page-heading.js";import{S as P}from"./state-badge.js";import{g as B}from"./ec2.js";import{T as k}from"./trash-2.js";import"./alert-dialog.js";function Q(){const{id:l}=D.useParams(),m=f(),{data:p}=d(v(l)),{data:x}=d(b),s=p.Vpcs?.[0],r=B(),[j,i]=I.useState(!1),o=x.Subnets?.filter(t=>t.VpcId===l)??[],h=async()=>{try{await r.mutateAsync(l),m({to:"/ec2/describe-vpcs"})}finally{i(!1)}};if(!s?.VpcId)return e.jsxs(e.Fragment,{children:[e.jsx(u,{to:"/ec2/describe-vpcs",children:"Back to VPCs"}),e.jsx("p",{className:"text-muted-foreground",children:"VPC not found."})]});const c=s.Tags?.find(t=>t.Key==="Name")?.Value;return e.jsxs(e.Fragment,{children:[e.jsx(u,{to:"/ec2/describe-vpcs",children:"Back to VPCs"}),r.error&&e.jsx(S,{error:r.error,msg:"Failed to delete VPC"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(y,{actions:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(C,{onClick:()=>i(!0),size:"sm",variant:"destructive",children:[e.jsx(k,{className:"size-4"}),"Delete"]}),e.jsx(P,{state:s.State})]}),subtitle:"VPC Details",title:c?`${s.VpcId} (${c})`:s.VpcId}),e.jsxs(n,{children:[e.jsx(n.Header,{children:"VPC Information"}),e.jsxs(n.Content,{children:[e.jsx(a,{label:"VPC ID",value:s.VpcId}),e.jsx(a,{label:"State",value:s.State}),e.jsx(a,{label:"CIDR Block",value:s.CidrBlock}),e.jsx(a,{label:"Is Default",value:s.IsDefault?"Yes":"No"}),e.jsx(a,{label:"DHCP Options ID",value:s.DhcpOptionsId}),e.jsx(a,{label:"Instance Tenancy",value:s.InstanceTenancy}),e.jsx(a,{label:"Owner ID",value:s.OwnerId})]})]}),o.length>0&&e.jsxs(n,{children:[e.jsxs(n.Header,{children:["Subnets (",o.length,")"]}),o.map(t=>t.SubnetId?e.jsxs(n.Content,{children:[e.jsx(a,{label:"Subnet ID",value:e.jsx(g,{className:"text-primary hover:underline",params:{id:t.SubnetId},to:"/ec2/describe-subnets/$id",children:t.SubnetId})}),e.jsx(a,{label:"CIDR Block",value:t.CidrBlock}),e.jsx(a,{label:"Availability Zone",value:t.AvailabilityZone}),e.jsx(a,{label:"State",value:t.State})]},t.SubnetId):null)]})]}),e.jsx(V,{description:`Are you sure you want to delete the VPC "${s.VpcId}"? This action cannot be undone. The VPC must have no associated subnets or internet gateways.`,isPending:r.isPending,onConfirm:h,onOpenChange:i,open:j,title:"Delete VPC"})]})}export{Q as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as T,am as q,u as D,j as s,an as J,B as Q}from"./index.js";import{a as K,u as U}from"./zod.js";import{u as W}from"./useSuspenseQuery.js";import{B as H}from"./back-link.js";import{E as X}from"./error-banner.js";import{P as Y}from"./page-heading.js";import{F as Z,a as ee,b as se}from"./field.js";import{I as te}from"./input.js";import{h as ie}from"./ec2.js";import{m as re}from"./ec22.js";function xe(){const e=T.c(58),{id:R}=q.useParams(),o=D();let S;e[0]!==R?(S=J(R),e[0]=R,e[1]=S):S=e[1];const{data:L}=W(S),A=L.Volumes?.[0],t=A?.VolumeId,r=ie(),i=A?.Size??1;let a;e[2]!==i?(a=K(re.refine(l=>l.size>i,{message:`New size must be greater than current size (${i} GiB)`,path:["size"]})),e[2]=i,e[3]=a):a=e[3];let n;e[4]!==i?(n={size:i},e[4]=i,e[5]=n):n=e[5];let B;e[6]!==a||e[7]!==n?(B={resolver:a,defaultValues:n},e[6]=a,e[7]=n,e[8]=B):B=e[8];const{handleSubmit:k,register:E,formState:O}=U(B),{errors:V,isSubmitting:$}=O;if(!t){let l;return e[9]===Symbol.for("react.memo_cache_sentinel")?(l=s.jsxs(s.Fragment,{children:[s.jsx(H,{to:"/ec2/describe-volumes",children:"Back to volumes"}),s.jsx("p",{className:"text-muted-foreground",children:"Volume not found."})]}),e[9]=l):l=e[9],l}let F;e[10]!==r||e[11]!==o||e[12]!==t?(F=async l=>{await r.mutateAsync({...l,volumeId:t}),o({to:"/ec2/describe-volumes/$id",params:{id:t}})},e[10]=r,e[11]=o,e[12]=t,e[13]=F):F=e[13];const w=F;let m,c;e[14]!==t?(m=s.jsx(H,{params:{id:t},to:"/ec2/describe-volumes/$id",children:"Back to volume"}),c=s.jsx(Y,{subtitle:t,title:"Resize Volume"}),e[14]=t,e[15]=m,e[16]=c):(m=e[15],c=e[16]);let u;e[17]!==r.error?(u=r.error&&s.jsx(X,{error:r.error,msg:"Failed to resize volume"}),e[17]=r.error,e[18]=u):u=e[18];let d;e[19]!==k||e[20]!==w?(d=k(w),e[19]=k,e[20]=w,e[21]=d):d=e[21];let f;e[22]!==i?(f=s.jsx("div",{className:"space-y-2",children:s.jsxs("p",{className:"text-muted-foreground text-sm",children:["Current size: ",i," GiB"]})}),e[22]=i,e[23]=f):f=e[23];let N;e[24]===Symbol.for("react.memo_cache_sentinel")?(N=s.jsx(ee,{children:s.jsx("label",{htmlFor:"size",children:"New size (GiB)"})}),e[24]=N):N=e[24];const I=!!V.size,_=i+1;let p;e[25]!==E?(p=E("size",{valueAsNumber:!0}),e[25]=E,e[26]=p):p=e[26];let x;e[27]!==I||e[28]!==_||e[29]!==p?(x=s.jsx(te,{"aria-invalid":I,id:"size",min:_,type:"number",...p}),e[27]=I,e[28]=_,e[29]=p,e[30]=x):x=e[30];let h;e[31]!==V.size?(h=s.jsx(Z,{errors:[V.size]}),e[31]=V.size,e[32]=h):h=e[32];let b;e[33]!==x||e[34]!==h?(b=s.jsxs(se,{children:[N,x,h]}),e[33]=x,e[34]=h,e[35]=b):b=e[35];const C=$||r.isPending;let j;e[36]!==o||e[37]!==t?(j=()=>o({to:"/ec2/describe-volumes/$id",params:{id:t}}),e[36]=o,e[37]=t,e[38]=j):j=e[38];let z;e[39]!==C||e[40]!==j?(z=s.jsx(Q,{disabled:C,onClick:j,type:"button",variant:"outline",children:"Cancel"}),e[39]=C,e[40]=j,e[41]=z):z=e[41];const G=$||r.isPending,M=$||r.isPending?"Resizing…":"Resize Volume";let g;e[42]!==G||e[43]!==M?(g=s.jsx(Q,{disabled:G,type:"submit",children:M}),e[42]=G,e[43]=M,e[44]=g):g=e[44];let v;e[45]!==z||e[46]!==g?(v=s.jsxs("div",{className:"flex gap-2",children:[z,g]}),e[45]=z,e[46]=g,e[47]=v):v=e[47];let y;e[48]!==f||e[49]!==b||e[50]!==v||e[51]!==d?(y=s.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:d,children:[f,b,v]}),e[48]=f,e[49]=b,e[50]=v,e[51]=d,e[52]=y):y=e[52];let P;return e[53]!==y||e[54]!==m||e[55]!==c||e[56]!==u?(P=s.jsxs(s.Fragment,{children:[m,c,u,y]}),e[53]=y,e[54]=m,e[55]=c,e[56]=u,e[57]=P):P=e[57],P}export{xe as component};
|
||||
import{c as A,aL as G,u as C,j as s,aM as Q}from"./index.js";import{a as H,u as O}from"./zod.js";import{u as T}from"./useSuspenseQuery.js";import{B as L}from"./back-link.js";import{E as q}from"./error-banner.js";import{F as D}from"./form-actions.js";import{P as J}from"./page-heading.js";import{F as K,a as U,b as W}from"./field.js";import{I as X}from"./input.js";import{h as Y}from"./ec2.js";import{m as Z}from"./ec22.js";function ue(){const e=A.c(53),{id:N}=G.useParams(),l=C();let v;e[0]!==N?(v=Q(N),e[0]=N,e[1]=v):v=e[1];const{data:M}=T(v),I=M.Volumes?.[0],t=I?.VolumeId,r=Y(),i=I?.Size??1;let m;e[2]!==i?(m=H(Z.refine(o=>o.size>i,{message:`New size must be greater than current size (${i} GiB)`,path:["size"]})),e[2]=i,e[3]=m):m=e[3];let a;e[4]!==i?(a={size:i},e[4]=i,e[5]=a):a=e[5];let y;e[6]!==m||e[7]!==a?(y={resolver:m,defaultValues:a},e[6]=m,e[7]=a,e[8]=y):y=e[8];const{handleSubmit:P,register:R,formState:_}=O(y),{errors:F,isSubmitting:E}=_;if(!t){let o;return e[9]===Symbol.for("react.memo_cache_sentinel")?(o=s.jsxs(s.Fragment,{children:[s.jsx(L,{to:"/ec2/describe-volumes",children:"Back to volumes"}),s.jsx("p",{className:"text-muted-foreground",children:"Volume not found."})]}),e[9]=o):o=e[9],o}let S;e[10]!==r||e[11]!==l||e[12]!==t?(S=async o=>{await r.mutateAsync({...o,volumeId:t}),l({to:"/ec2/describe-volumes/$id",params:{id:t}})},e[10]=r,e[11]=l,e[12]=t,e[13]=S):S=e[13];const $=S;let n,c;e[14]!==t?(n=s.jsx(L,{params:{id:t},to:"/ec2/describe-volumes/$id",children:"Back to volume"}),c=s.jsx(J,{subtitle:t,title:"Resize Volume"}),e[14]=t,e[15]=n,e[16]=c):(n=e[15],c=e[16]);let u;e[17]!==r.error?(u=r.error&&s.jsx(q,{error:r.error,msg:"Failed to resize volume"}),e[17]=r.error,e[18]=u):u=e[18];let d;e[19]!==P||e[20]!==$?(d=P($),e[19]=P,e[20]=$,e[21]=d):d=e[21];let f;e[22]!==i?(f=s.jsx("div",{className:"space-y-2",children:s.jsxs("p",{className:"text-muted-foreground text-sm",children:["Current size: ",i," GiB"]})}),e[22]=i,e[23]=f):f=e[23];let V;e[24]===Symbol.for("react.memo_cache_sentinel")?(V=s.jsx(U,{children:s.jsx("label",{htmlFor:"size",children:"New size (GiB)"})}),e[24]=V):V=e[24];const k=!!F.size,w=i+1;let p;e[25]!==R?(p=R("size",{valueAsNumber:!0}),e[25]=R,e[26]=p):p=e[26];let x;e[27]!==k||e[28]!==w||e[29]!==p?(x=s.jsx(X,{"aria-invalid":k,id:"size",min:w,type:"number",...p}),e[27]=k,e[28]=w,e[29]=p,e[30]=x):x=e[30];let z;e[31]!==F.size?(z=s.jsx(K,{errors:[F.size]}),e[31]=F.size,e[32]=z):z=e[32];let b;e[33]!==x||e[34]!==z?(b=s.jsxs(W,{children:[V,x,z]}),e[33]=x,e[34]=z,e[35]=b):b=e[35];let g;e[36]!==l||e[37]!==t?(g=()=>l({to:"/ec2/describe-volumes/$id",params:{id:t}}),e[36]=l,e[37]=t,e[38]=g):g=e[38];let h;e[39]!==E||e[40]!==r.isPending||e[41]!==g?(h=s.jsx(D,{isPending:r.isPending,isSubmitting:E,onCancel:g,pendingLabel:"Resizing\\u2026",submitLabel:"Resize Volume"}),e[39]=E,e[40]=r.isPending,e[41]=g,e[42]=h):h=e[42];let j;e[43]!==f||e[44]!==b||e[45]!==h||e[46]!==d?(j=s.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:d,children:[f,b,h]}),e[43]=f,e[44]=b,e[45]=h,e[46]=d,e[47]=j):j=e[47];let B;return e[48]!==j||e[49]!==n||e[50]!==c||e[51]!==u?(B=s.jsxs(s.Fragment,{children:[n,c,u,j]}),e[48]=j,e[49]=n,e[50]=c,e[51]=u,e[52]=B):B=e[52],B}export{ue as component};
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{az as x,u as j,aA as p,r as h,j as e,B as D,L as g}from"./index.js";import{u as S}from"./useSuspenseQuery.js";import{B as o}from"./back-link.js";import{D as l,a as s}from"./detail-row.js";import{E as f}from"./error-banner.js";import{P as v}from"./page-heading.js";import{S as A}from"./state-badge.js";import{g as I,f as y,b as C,a as B,c as P,d as k,e as N,h as w}from"./alert-dialog.js";import{l as F}from"./ec2.js";import{T as L}from"./trash-2.js";function Q(){const{id:r}=x.useParams(),c=j(),{data:u}=S(p(r)),t=u.Subnets?.[0],a=F(),[d,n]=h.useState(!1),b=async()=>{try{await a.mutateAsync(r),c({to:"/ec2/describe-subnets"})}finally{n(!1)}};if(!t?.SubnetId)return e.jsxs(e.Fragment,{children:[e.jsx(o,{to:"/ec2/describe-subnets",children:"Back to Subnets"}),e.jsx("p",{className:"text-muted-foreground",children:"Subnet not found."})]});const i=t.Tags?.find(m=>m.Key==="Name")?.Value;return e.jsxs(e.Fragment,{children:[e.jsx(o,{to:"/ec2/describe-subnets",children:"Back to Subnets"}),a.error&&e.jsx(f,{error:a.error,msg:"Failed to delete subnet"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(v,{actions:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(D,{onClick:()=>n(!0),size:"sm",variant:"destructive",children:[e.jsx(L,{className:"size-4"}),"Delete"]}),e.jsx(A,{state:t.State})]}),subtitle:"Subnet Details",title:i?`${t.SubnetId} (${i})`:t.SubnetId}),e.jsxs(l,{children:[e.jsx(l.Header,{children:"Subnet Information"}),e.jsxs(l.Content,{children:[e.jsx(s,{label:"Subnet ID",value:t.SubnetId}),e.jsx(s,{label:"VPC ID",value:t.VpcId?e.jsx(g,{className:"text-primary hover:underline",params:{id:t.VpcId},to:"/ec2/describe-vpcs/$id",children:t.VpcId}):"—"}),e.jsx(s,{label:"State",value:t.State}),e.jsx(s,{label:"CIDR Block",value:t.CidrBlock}),e.jsx(s,{label:"Availability Zone",value:t.AvailabilityZone}),e.jsx(s,{label:"Available IP Count",value:t.AvailableIpAddressCount?.toString()}),e.jsx(s,{label:"Map Public IP on Launch",value:t.MapPublicIpOnLaunch?"Yes":"No"}),e.jsx(s,{label:"Default for AZ",value:t.DefaultForAz?"Yes":"No"})]})]})]}),e.jsx(I,{onOpenChange:n,open:d,children:e.jsxs(y,{children:[e.jsxs(C,{children:[e.jsx(B,{children:"Delete Subnet"}),e.jsxs(P,{children:['Are you sure you want to delete the subnet "',t.SubnetId,'"? This action cannot be undone.']})]}),e.jsxs(k,{children:[e.jsx(N,{children:"Cancel"}),e.jsx(w,{disabled:a.isPending,onClick:b,children:a.isPending?"Deleting…":"Delete"})]})]})})]})}export{Q as component};
|
||||
import{cX as p,u as x,cY as j,r as f,j as e,B as S,L as h}from"./index.js";import{u as D}from"./useSuspenseQuery.js";import{B as l}from"./back-link.js";import{D as v}from"./delete-confirmation-dialog.js";import{D as o,a as s}from"./detail-row.js";import{E as g}from"./error-banner.js";import{P as I}from"./page-heading.js";import{S as y}from"./state-badge.js";import{l as C}from"./ec2.js";import{T as B}from"./trash-2.js";import"./alert-dialog.js";function $(){const{id:r}=p.useParams(),u=x(),{data:c}=D(j(r)),t=c.Subnets?.[0],a=C(),[d,n]=f.useState(!1),m=async()=>{try{await a.mutateAsync(r),u({to:"/ec2/describe-subnets"})}finally{n(!1)}};if(!t?.SubnetId)return e.jsxs(e.Fragment,{children:[e.jsx(l,{to:"/ec2/describe-subnets",children:"Back to Subnets"}),e.jsx("p",{className:"text-muted-foreground",children:"Subnet not found."})]});const i=t.Tags?.find(b=>b.Key==="Name")?.Value;return e.jsxs(e.Fragment,{children:[e.jsx(l,{to:"/ec2/describe-subnets",children:"Back to Subnets"}),a.error&&e.jsx(g,{error:a.error,msg:"Failed to delete subnet"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(I,{actions:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(S,{onClick:()=>n(!0),size:"sm",variant:"destructive",children:[e.jsx(B,{className:"size-4"}),"Delete"]}),e.jsx(y,{state:t.State})]}),subtitle:"Subnet Details",title:i?`${t.SubnetId} (${i})`:t.SubnetId}),e.jsxs(o,{children:[e.jsx(o.Header,{children:"Subnet Information"}),e.jsxs(o.Content,{children:[e.jsx(s,{label:"Subnet ID",value:t.SubnetId}),e.jsx(s,{label:"VPC ID",value:t.VpcId?e.jsx(h,{className:"text-primary hover:underline",params:{id:t.VpcId},to:"/ec2/describe-vpcs/$id",children:t.VpcId}):"—"}),e.jsx(s,{label:"State",value:t.State}),e.jsx(s,{label:"CIDR Block",value:t.CidrBlock}),e.jsx(s,{label:"Availability Zone",value:t.AvailabilityZone}),e.jsx(s,{label:"Available IP Count",value:t.AvailableIpAddressCount?.toString()}),e.jsx(s,{label:"Map Public IP on Launch",value:t.MapPublicIpOnLaunch?"Yes":"No"}),e.jsx(s,{label:"Default for AZ",value:t.DefaultForAz?"Yes":"No"})]})]})]}),e.jsx(v,{description:`Are you sure you want to delete the subnet "${t.SubnetId}"? This action cannot be undone.`,isPending:a.isPending,onConfirm:m,onOpenChange:n,open:d,title:"Delete Subnet"})]})}export{$ as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{k as z,aB as E,u as V,aC as O,e as L,r as l,j as e,ay as H,B as u,L as M}from"./index.js";import{u as m}from"./useSuspenseQuery.js";import{B as x}from"./back-link.js";import{D as c,a as t}from"./detail-row.js";import{E as j}from"./error-banner.js";import{P as Q}from"./page-heading.js";import{S as _}from"./state-badge.js";import{g,f as y,b as D,a as S,c as f,d as C,e as b,h as v}from"./alert-dialog.js";import{b as $,a as G}from"./field.js";import{I as Y}from"./input.js";import{m as q,n as J}from"./ec2.js";import{T as K}from"./trash-2.js";const U=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],W=z("copy",U);function de(){const{id:n}=E.useParams(),I=V(),{data:k}=m(O(n)),{data:A}=m(L),s=k.Snapshots?.[0],a=q(),o=J(),[w,i]=l.useState(!1),[B,r]=l.useState(!1),[d,p]=l.useState(""),N=A.Regions?.[0]?.RegionName??"ap-southeast-2",h=s?.State==="completed",P=async()=>{try{await a.mutateAsync(n),I({to:"/ec2/describe-snapshots"})}finally{i(!1)}},T=async()=>{try{await o.mutateAsync({sourceSnapshotId:n,sourceRegion:N,description:d||void 0}),r(!1),p("")}catch{}};if(!s?.SnapshotId)return e.jsxs(e.Fragment,{children:[e.jsx(x,{to:"/ec2/describe-snapshots",children:"Back to snapshots"}),e.jsx("p",{className:"text-muted-foreground",children:"Snapshot not found."})]});const F=H(s.StartTime);return e.jsxs(e.Fragment,{children:[e.jsx(x,{to:"/ec2/describe-snapshots",children:"Back to snapshots"}),a.error&&e.jsx(j,{error:a.error,msg:"Failed to delete snapshot"}),o.error&&e.jsx(j,{error:o.error,msg:"Failed to copy snapshot"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(Q,{actions:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(u,{disabled:!h,onClick:()=>i(!0),size:"sm",variant:"destructive",children:[e.jsx(K,{className:"size-4"}),"Delete"]}),e.jsxs(u,{disabled:!h,onClick:()=>r(!0),size:"sm",variant:"outline",children:[e.jsx(W,{className:"size-4"}),"Copy Snapshot"]}),e.jsx(_,{state:s.State})]}),subtitle:"EC2 Snapshot Details",title:s.SnapshotId}),e.jsxs(c,{children:[e.jsx(c.Header,{children:"Snapshot Information"}),e.jsxs(c.Content,{children:[e.jsx(t,{label:"Snapshot ID",value:s.SnapshotId}),e.jsx(t,{label:"Volume ID",value:s.VolumeId?e.jsx(M,{className:"text-primary hover:underline",params:{id:s.VolumeId},to:"/ec2/describe-volumes/$id",children:s.VolumeId}):void 0}),e.jsx(t,{label:"Volume Size",value:`${s.VolumeSize} GiB`}),e.jsx(t,{label:"State",value:s.State}),e.jsx(t,{label:"Description",value:s.Description}),e.jsx(t,{label:"Progress",value:s.Progress}),e.jsx(t,{label:"Start Time",value:F}),e.jsx(t,{label:"Owner ID",value:s.OwnerId}),e.jsx(t,{label:"Encrypted",value:s.Encrypted?"Yes":"No"})]})]})]}),e.jsx(g,{onOpenChange:i,open:w,children:e.jsxs(y,{children:[e.jsxs(D,{children:[e.jsx(S,{children:"Delete Snapshot"}),e.jsxs(f,{children:['Are you sure you want to delete the snapshot "',s.SnapshotId,'"? This action cannot be undone.']})]}),e.jsxs(C,{children:[e.jsx(b,{children:"Cancel"}),e.jsx(v,{disabled:a.isPending,onClick:P,children:a.isPending?"Deleting…":"Delete"})]})]})}),e.jsx(g,{onOpenChange:r,open:B,children:e.jsxs(y,{children:[e.jsxs(D,{children:[e.jsx(S,{children:"Copy Snapshot"}),e.jsx(f,{children:"Create a copy of this snapshot in the current region."})]}),e.jsxs($,{children:[e.jsx(G,{children:e.jsx("label",{htmlFor:"copyDescription",children:"Description (optional)"})}),e.jsx(Y,{id:"copyDescription",onChange:R=>p(R.target.value),placeholder:"Description for the snapshot copy",value:d})]}),e.jsxs(C,{children:[e.jsx(b,{children:"Cancel"}),e.jsx(v,{disabled:o.isPending,onClick:T,children:o.isPending?"Copying…":"Copy Snapshot"})]})]})})]})}export{de as component};
|
||||
import{J as w,cZ as k,u as N,c_ as B,f as P,r as l,j as e,i as T,B as m,L as F}from"./index.js";import{u}from"./useSuspenseQuery.js";import{B as x}from"./back-link.js";import{D as R}from"./delete-confirmation-dialog.js";import{D as c,a as t}from"./detail-row.js";import{E as g}from"./error-banner.js";import{P as z}from"./page-heading.js";import{S as E}from"./state-badge.js";import{g as V,f as O,b as L,a as H,c as M,d as Q,e as _,h as $}from"./alert-dialog.js";import{b as G,a as J}from"./field.js";import{I as Y}from"./input.js";import{m as Z,n as q}from"./ec2.js";import{T as K}from"./trash-2.js";const U=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],W=w("copy",U);function he(){const{id:n}=k.useParams(),j=N(),{data:D}=u(B(n)),{data:y}=u(P),s=D.Snapshots?.[0],a=Z(),o=q(),[S,i]=l.useState(!1),[f,r]=l.useState(!1),[p,d]=l.useState(""),C=y.Regions?.[0]?.RegionName??"ap-southeast-2",h=s?.State==="completed",v=async()=>{try{await a.mutateAsync(n),j({to:"/ec2/describe-snapshots"})}finally{i(!1)}},b=async()=>{try{await o.mutateAsync({sourceSnapshotId:n,sourceRegion:C,description:p||void 0}),r(!1),d("")}catch{}};if(!s?.SnapshotId)return e.jsxs(e.Fragment,{children:[e.jsx(x,{to:"/ec2/describe-snapshots",children:"Back to snapshots"}),e.jsx("p",{className:"text-muted-foreground",children:"Snapshot not found."})]});const I=T(s.StartTime);return e.jsxs(e.Fragment,{children:[e.jsx(x,{to:"/ec2/describe-snapshots",children:"Back to snapshots"}),a.error&&e.jsx(g,{error:a.error,msg:"Failed to delete snapshot"}),o.error&&e.jsx(g,{error:o.error,msg:"Failed to copy snapshot"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(z,{actions:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(m,{disabled:!h,onClick:()=>i(!0),size:"sm",variant:"destructive",children:[e.jsx(K,{className:"size-4"}),"Delete"]}),e.jsxs(m,{disabled:!h,onClick:()=>r(!0),size:"sm",variant:"outline",children:[e.jsx(W,{className:"size-4"}),"Copy Snapshot"]}),e.jsx(E,{state:s.State})]}),subtitle:"EC2 Snapshot Details",title:s.SnapshotId}),e.jsxs(c,{children:[e.jsx(c.Header,{children:"Snapshot Information"}),e.jsxs(c.Content,{children:[e.jsx(t,{label:"Snapshot ID",value:s.SnapshotId}),e.jsx(t,{label:"Volume ID",value:s.VolumeId?e.jsx(F,{className:"text-primary hover:underline",params:{id:s.VolumeId},to:"/ec2/describe-volumes/$id",children:s.VolumeId}):void 0}),e.jsx(t,{label:"Volume Size",value:`${s.VolumeSize} GiB`}),e.jsx(t,{label:"State",value:s.State}),e.jsx(t,{label:"Description",value:s.Description}),e.jsx(t,{label:"Progress",value:s.Progress}),e.jsx(t,{label:"Start Time",value:I}),e.jsx(t,{label:"Owner ID",value:s.OwnerId}),e.jsx(t,{label:"Encrypted",value:s.Encrypted?"Yes":"No"})]})]})]}),e.jsx(R,{description:`Are you sure you want to delete the snapshot "${s.SnapshotId}"? This action cannot be undone.`,isPending:a.isPending,onConfirm:v,onOpenChange:i,open:S,title:"Delete Snapshot"}),e.jsx(V,{onOpenChange:r,open:f,children:e.jsxs(O,{children:[e.jsxs(L,{children:[e.jsx(H,{children:"Copy Snapshot"}),e.jsx(M,{children:"Create a copy of this snapshot in the current region."})]}),e.jsxs(G,{children:[e.jsx(J,{children:e.jsx("label",{htmlFor:"copyDescription",children:"Description (optional)"})}),e.jsx(Y,{id:"copyDescription",onChange:A=>d(A.target.value),placeholder:"Description for the snapshot copy",value:p})]}),e.jsxs(Q,{children:[e.jsx(_,{children:"Cancel"}),e.jsx($,{disabled:o.isPending,onClick:b,children:o.isPending?"Copying…":"Copy Snapshot"})]})]})})]})}export{he as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aD as x,u as p,aE as h,r as j,j as e,ay as g,B as D}from"./index.js";import{u as K}from"./useSuspenseQuery.js";import{B as o}from"./back-link.js";import{D as s,a as r}from"./detail-row.js";import{E as f}from"./error-banner.js";import{P as b}from"./page-heading.js";import{g as P,f as k,b as T,a as A,c as C,d as v,e as N,h as w}from"./alert-dialog.js";import{o as B}from"./ec2.js";import{T as E}from"./trash-2.js";function M(){const{id:l}=x.useParams(),c=p(),{data:d}=K(h(l)),a=d.KeyPairs?.[0],i=B(),[y,t]=j.useState(!1),m=async()=>{try{await i.mutateAsync(l),c({to:"/ec2/describe-key-pairs"})}finally{t(!1)}};if(!a?.KeyPairId)return e.jsxs(e.Fragment,{children:[e.jsx(o,{to:"/ec2/describe-key-pairs",children:"Back to key pairs"}),e.jsx("p",{className:"text-muted-foreground",children:"Key pair not found."})]});const u=g(a.CreateTime);return e.jsxs(e.Fragment,{children:[e.jsx(o,{to:"/ec2/describe-key-pairs",children:"Back to key pairs"}),i.error&&e.jsx(f,{error:i.error,msg:"Failed to delete key pair"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(b,{actions:e.jsxs(D,{onClick:()=>t(!0),size:"sm",variant:"destructive",children:[e.jsx(E,{className:"size-4"}),"Delete"]}),subtitle:"EC2 Key Pair Details",title:a.KeyName??""}),e.jsxs(s,{children:[e.jsx(s.Header,{children:"Key Pair Information"}),e.jsxs(s.Content,{children:[e.jsx(r,{label:"Key Name",value:a.KeyName}),e.jsx(r,{label:"Key Pair ID",value:a.KeyPairId}),e.jsx(r,{label:"Key Type",value:a.KeyType}),e.jsx(r,{label:"Key Fingerprint",value:a.KeyFingerprint}),e.jsx(r,{label:"Creation Date",value:u})]})]}),a.Tags&&a.Tags.length>0&&e.jsxs(s,{children:[e.jsx(s.Header,{children:"Tags"}),e.jsx(s.Content,{children:a.Tags.map(n=>e.jsx(r,{label:n.Key||"",value:n.Value},n.Key))})]})]}),e.jsx(P,{onOpenChange:t,open:y,children:e.jsxs(k,{children:[e.jsxs(T,{children:[e.jsx(A,{children:"Delete Key Pair"}),e.jsxs(C,{children:['Are you sure you want to delete the key pair "',a.KeyName,'"? This action cannot be undone. Any instances using this key pair will no longer be able to be accessed with it.']})]}),e.jsxs(v,{children:[e.jsx(N,{children:"Cancel"}),e.jsx(w,{disabled:i.isPending,onClick:m,children:i.isPending?"Deleting…":"Delete"})]})]})})]})}export{M as component};
|
||||
import{c$ as p,u as x,d0 as j,r as h,j as e,i as g,B as K}from"./index.js";import{u as f}from"./useSuspenseQuery.js";import{B as l}from"./back-link.js";import{D}from"./delete-confirmation-dialog.js";import{D as s,a as r}from"./detail-row.js";import{E as P}from"./error-banner.js";import{P as b}from"./page-heading.js";import{o as k}from"./ec2.js";import{T}from"./trash-2.js";import"./alert-dialog.js";function R(){const{id:o}=p.useParams(),c=x(),{data:m}=f(j(o)),a=m.KeyPairs?.[0],t=k(),[y,i]=h.useState(!1),d=async()=>{try{await t.mutateAsync(o),c({to:"/ec2/describe-key-pairs"})}finally{i(!1)}};if(!a?.KeyPairId)return e.jsxs(e.Fragment,{children:[e.jsx(l,{to:"/ec2/describe-key-pairs",children:"Back to key pairs"}),e.jsx("p",{className:"text-muted-foreground",children:"Key pair not found."})]});const u=g(a.CreateTime);return e.jsxs(e.Fragment,{children:[e.jsx(l,{to:"/ec2/describe-key-pairs",children:"Back to key pairs"}),t.error&&e.jsx(P,{error:t.error,msg:"Failed to delete key pair"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(b,{actions:e.jsxs(K,{onClick:()=>i(!0),size:"sm",variant:"destructive",children:[e.jsx(T,{className:"size-4"}),"Delete"]}),subtitle:"EC2 Key Pair Details",title:a.KeyName??""}),e.jsxs(s,{children:[e.jsx(s.Header,{children:"Key Pair Information"}),e.jsxs(s.Content,{children:[e.jsx(r,{label:"Key Name",value:a.KeyName}),e.jsx(r,{label:"Key Pair ID",value:a.KeyPairId}),e.jsx(r,{label:"Key Type",value:a.KeyType}),e.jsx(r,{label:"Key Fingerprint",value:a.KeyFingerprint}),e.jsx(r,{label:"Creation Date",value:u})]})]}),a.Tags&&a.Tags.length>0&&e.jsxs(s,{children:[e.jsx(s.Header,{children:"Tags"}),e.jsx(s.Content,{children:a.Tags.map(n=>e.jsx(r,{label:n.Key||"",value:n.Value},n.Key))})]})]}),e.jsx(D,{description:`Are you sure you want to delete the key pair "${a.KeyName}"? This action cannot be undone. Any instances using this key pair will no longer be able to be accessed with it.`,isPending:t.isPending,onConfirm:d,onOpenChange:i,open:y,title:"Delete Key Pair"})]})}export{R as component};
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{c as f,cz as u,j as t,aH as p}from"./index.js";import{u as x}from"./useSuspenseQuery.js";import{B as d}from"./back-link.js";import{P as I}from"./page-heading.js";import{S as j}from"./state-badge.js";import{A as h}from"./ami-details.js";import"./detail-row.js";function _(){const e=f.c(14),{id:n}=u.useParams();let o;e[0]!==n?(o=p(n),e[0]=n,e[1]=o):o=e[1];const{data:g}=x(o),s=g?.Images?.[0];if(!s?.ImageId){let l;return e[2]===Symbol.for("react.memo_cache_sentinel")?(l=t.jsxs(t.Fragment,{children:[t.jsx(d,{to:"/ec2/describe-images",children:"Back to images"}),t.jsx("p",{className:"text-muted-foreground",children:"Image not found."})]}),e[2]=l):l=e[2],l}let r;e[3]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(d,{to:"/ec2/describe-images",children:"Back to images"}),e[3]=r):r=e[3];let a;e[4]!==s.State?(a=t.jsx(j,{state:s.State}),e[4]=s.State,e[5]=a):a=e[5];let i;e[6]!==s.ImageId||e[7]!==a?(i=t.jsx(I,{actions:a,subtitle:"EC2 Image Details",title:s.ImageId}),e[6]=s.ImageId,e[7]=a,e[8]=i):i=e[8];let m;e[9]!==s?(m=t.jsx(h,{image:s,showExtendedDetails:!0}),e[9]=s,e[10]=m):m=e[10];let c;return e[11]!==i||e[12]!==m?(c=t.jsxs(t.Fragment,{children:[r,t.jsxs("div",{className:"space-y-6",children:[i,m]})]}),e[11]=i,e[12]=m,e[13]=c):c=e[13],c}export{_ as component};
|
||||
import{c as f,d4 as u,j as t,d3 as p}from"./index.js";import{u as x}from"./useSuspenseQuery.js";import{B as d}from"./back-link.js";import{P as I}from"./page-heading.js";import{S as j}from"./state-badge.js";import{A as h}from"./ami-details.js";import"./detail-row.js";function _(){const e=f.c(14),{id:n}=u.useParams();let o;e[0]!==n?(o=p(n),e[0]=n,e[1]=o):o=e[1];const{data:g}=x(o),s=g?.Images?.[0];if(!s?.ImageId){let l;return e[2]===Symbol.for("react.memo_cache_sentinel")?(l=t.jsxs(t.Fragment,{children:[t.jsx(d,{to:"/ec2/describe-images",children:"Back to images"}),t.jsx("p",{className:"text-muted-foreground",children:"Image not found."})]}),e[2]=l):l=e[2],l}let r;e[3]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(d,{to:"/ec2/describe-images",children:"Back to images"}),e[3]=r):r=e[3];let a;e[4]!==s.State?(a=t.jsx(j,{state:s.State}),e[4]=s.State,e[5]=a):a=e[5];let i;e[6]!==s.ImageId||e[7]!==a?(i=t.jsx(I,{actions:a,subtitle:"EC2 Image Details",title:s.ImageId}),e[6]=s.ImageId,e[7]=a,e[8]=i):i=e[8];let m;e[9]!==s?(m=t.jsx(h,{image:s,showExtendedDetails:!0}),e[9]=s,e[10]=m):m=e[10];let c;return e[11]!==i||e[12]!==m?(c=t.jsxs(t.Fragment,{children:[r,t.jsxs("div",{className:"space-y-6",children:[i,m]})]}),e[11]=i,e[12]=m,e[13]=c):c=e[13],c}export{_ as component};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import{c as f,j as e,av as j,u as P,aw as h,r as v,ax as g,B as N,i as b}from"./index.js";import{u}from"./useSuspenseQuery.js";import{B as p}from"./back-link.js";import{D as C}from"./delete-confirmation-dialog.js";import{D as l,a as n}from"./detail-row.js";import{E as w}from"./error-banner.js";import{P as A}from"./page-heading.js";import{h as I}from"./iam.js";import{T as B}from"./trash-2.js";import"./alert-dialog.js";function R(r){const t=f.c(4),{document:a}=r;let s;try{let i;t[0]!==a?(i=JSON.stringify(JSON.parse(a),null,2),t[0]=a,t[1]=i):i=t[1],s=i}catch{s=a}let o;return t[2]!==s?(o=e.jsx("pre",{className:"overflow-auto rounded-md border bg-muted p-4 font-mono text-sm",children:s}),t[2]=s,t[3]=o):o=t[3],o}function J(){const{policyArn:r}=j.useParams(),t=decodeURIComponent(r),a=P(),{data:s}=u(h(t)),o=s.Policy,i=I(),[y,c]=v.useState(!1),x=o?.DefaultVersionId??"v1",{data:m}=u(g(t,x)),d=m?.PolicyVersion?.Document?decodeURIComponent(m.PolicyVersion.Document):null,D=async()=>{try{await i.mutateAsync(t),a({to:"/iam/list-policies"})}finally{c(!1)}};return o?e.jsxs(e.Fragment,{children:[e.jsx(p,{to:"/iam/list-policies",children:"Back to policies"}),i.error&&e.jsx(w,{error:i.error,msg:"Failed to delete policy"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(A,{actions:e.jsxs(N,{onClick:()=>c(!0),size:"sm",variant:"destructive",children:[e.jsx(B,{className:"size-4"}),"Delete"]}),subtitle:"IAM Policy Details",title:o.PolicyName??""}),e.jsxs(l,{children:[e.jsx(l.Header,{children:"Policy Information"}),e.jsxs(l.Content,{children:[e.jsx(n,{label:"Policy Name",value:o.PolicyName}),e.jsx(n,{label:"Policy ID",value:o.PolicyId}),e.jsx(n,{label:"ARN",value:o.Arn}),e.jsx(n,{label:"Path",value:o.Path}),e.jsx(n,{label:"Description",value:o.Description||"-"}),e.jsx(n,{label:"Created",value:b(o.CreateDate)})]})]}),d&&e.jsxs(l,{children:[e.jsx(l.Header,{children:"Policy Document"}),e.jsx("div",{className:"p-4",children:e.jsx(R,{document:d})})]})]}),e.jsx(C,{description:`Are you sure you want to delete the policy "${o.PolicyName}"? This action cannot be undone.`,isPending:i.isPending,onConfirm:D,onOpenChange:c,open:y,title:"Delete Policy"})]}):e.jsxs(e.Fragment,{children:[e.jsx(p,{to:"/iam/list-policies",children:"Back to policies"}),e.jsx("p",{className:"text-muted-foreground",children:"Policy not found."})]})}export{J as component};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{c as g,ay as k,j as t,L as M}from"./index.js";import{D as T,a}from"./detail-row.js";function C(s){const e=g.c(58),{image:l,showExtendedDetails:P}=s,i=P===void 0?!1:P;let E;e[0]!==l.CreationDate?(E=k(l.CreationDate),e[0]=l.CreationDate,e[1]=E):E=e[1];const R=E,S=i?"AMI Information":"AMI Details";let o;e[2]!==S?(o=t.jsx(T.Header,{children:S}),e[2]=S,e[3]=o):o=e[3];let r;e[4]!==l.ImageId||e[5]!==i?(r=l.ImageId&&!i?t.jsx(M,{className:"text-primary hover:underline",params:{id:l.ImageId},to:"/ec2/describe-images/$id",children:l.ImageId}):l.ImageId,e[4]=l.ImageId,e[5]=i,e[6]=r):r=e[6];let c;e[7]!==r?(c=t.jsx(a,{label:"AMI ID",value:r}),e[7]=r,e[8]=c):c=e[8];let n;e[9]!==l.Name?(n=t.jsx(a,{label:"AMI Name",value:l.Name}),e[9]=l.Name,e[10]=n):n=e[10];let u;e[11]!==l.Description?(u=t.jsx(a,{label:"Description",value:l.Description}),e[11]=l.Description,e[12]=u):u=e[12];let m;e[13]!==l.Architecture?(m=t.jsx(a,{label:"Architecture",value:l.Architecture}),e[13]=l.Architecture,e[14]=m):m=e[14];let v;e[15]!==l.VirtualizationType?(v=t.jsx(a,{label:"Virtualization Type",value:l.VirtualizationType}),e[15]=l.VirtualizationType,e[16]=v):v=e[16];const A=l.Platform||"Linux";let D;e[17]!==A?(D=t.jsx(a,{label:"Platform",value:A}),e[17]=A,e[18]=D):D=e[18];let d;e[19]!==l.PlatformDetails?(d=t.jsx(a,{label:"Platform Details",value:l.PlatformDetails}),e[19]=l.PlatformDetails,e[20]=d):d=e[20];let x;e[21]!==l.ImageType?(x=t.jsx(a,{label:"Image Type",value:l.ImageType}),e[21]=l.ImageType,e[22]=x):x=e[22];let b;e[23]!==l.RootDeviceType?(b=t.jsx(a,{label:"Root Device Type",value:l.RootDeviceType}),e[23]=l.RootDeviceType,e[24]=b):b=e[24];let j;e[25]!==l.RootDeviceName?(j=t.jsx(a,{label:"Root Device Name",value:l.RootDeviceName}),e[25]=l.RootDeviceName,e[26]=j):j=e[26];let f;e[27]!==l.OwnerId?(f=t.jsx(a,{label:"Owner",value:l.OwnerId}),e[27]=l.OwnerId,e[28]=f):f=e[28];let I;e[29]!==l.State?(I=t.jsx(a,{label:"State",value:l.State}),e[29]=l.State,e[30]=I):I=e[30];let h;e[31]!==R||e[32]!==l.Public||e[33]!==i?(h=i&&t.jsxs(t.Fragment,{children:[t.jsx(a,{label:"Creation Date",value:R}),t.jsx(a,{label:"Public",value:l.Public?"Yes":"No"})]}),e[31]=R,e[32]=l.Public,e[33]=i,e[34]=h):h=e[34];let p;e[35]!==v||e[36]!==D||e[37]!==d||e[38]!==x||e[39]!==b||e[40]!==j||e[41]!==f||e[42]!==I||e[43]!==h||e[44]!==c||e[45]!==n||e[46]!==u||e[47]!==m?(p=t.jsxs(T.Content,{children:[c,n,u,m,v,D,d,x,b,j,f,I,h]}),e[35]=v,e[36]=D,e[37]=d,e[38]=x,e[39]=b,e[40]=j,e[41]=f,e[42]=I,e[43]=h,e[44]=c,e[45]=n,e[46]=u,e[47]=m,e[48]=p):p=e[48];let N;e[49]!==p||e[50]!==o?(N=t.jsxs(T,{children:[o,p]}),e[49]=p,e[50]=o,e[51]=N):N=e[51];let y;e[52]!==l.BlockDeviceMappings||e[53]!==i?(y=i&&l.BlockDeviceMappings&&l.BlockDeviceMappings.length>0&&l.BlockDeviceMappings.map(w),e[52]=l.BlockDeviceMappings,e[53]=i,e[54]=y):y=e[54];let V;return e[55]!==N||e[56]!==y?(V=t.jsxs(t.Fragment,{children:[N,y]}),e[55]=N,e[56]=y,e[57]=V):V=e[57],V}function w(s){return t.jsxs(T,{children:[t.jsxs(T.Header,{children:["Block Device: ",s.DeviceName]}),t.jsxs(T.Content,{children:[t.jsx(a,{label:"Device Name",value:s.DeviceName}),s.Ebs&&t.jsxs(t.Fragment,{children:[t.jsx(a,{label:"Volume Size",value:s.Ebs.VolumeSize?`${s.Ebs.VolumeSize} GB`:void 0}),t.jsx(a,{label:"Volume Type",value:s.Ebs.VolumeType}),t.jsx(a,{label:"Delete On Termination",value:s.Ebs.DeleteOnTermination?"Yes":"No"}),t.jsx(a,{label:"Encrypted",value:s.Ebs.Encrypted?"Yes":"No"}),s.Ebs.SnapshotId&&t.jsx(a,{label:"Snapshot ID",value:t.jsx(M,{className:"text-primary hover:underline",params:{id:s.Ebs.SnapshotId},to:"/ec2/describe-snapshots/$id",children:s.Ebs.SnapshotId})})]}),s.VirtualName&&t.jsx(a,{label:"Virtual Name",value:s.VirtualName})]})]},s.DeviceName)}export{C as A};
|
||||
import{c as g,i as k,j as t,L as M}from"./index.js";import{D as T,a}from"./detail-row.js";function C(s){const e=g.c(58),{image:l,showExtendedDetails:P}=s,i=P===void 0?!1:P;let E;e[0]!==l.CreationDate?(E=k(l.CreationDate),e[0]=l.CreationDate,e[1]=E):E=e[1];const R=E,S=i?"AMI Information":"AMI Details";let o;e[2]!==S?(o=t.jsx(T.Header,{children:S}),e[2]=S,e[3]=o):o=e[3];let r;e[4]!==l.ImageId||e[5]!==i?(r=l.ImageId&&!i?t.jsx(M,{className:"text-primary hover:underline",params:{id:l.ImageId},to:"/ec2/describe-images/$id",children:l.ImageId}):l.ImageId,e[4]=l.ImageId,e[5]=i,e[6]=r):r=e[6];let c;e[7]!==r?(c=t.jsx(a,{label:"AMI ID",value:r}),e[7]=r,e[8]=c):c=e[8];let n;e[9]!==l.Name?(n=t.jsx(a,{label:"AMI Name",value:l.Name}),e[9]=l.Name,e[10]=n):n=e[10];let u;e[11]!==l.Description?(u=t.jsx(a,{label:"Description",value:l.Description}),e[11]=l.Description,e[12]=u):u=e[12];let m;e[13]!==l.Architecture?(m=t.jsx(a,{label:"Architecture",value:l.Architecture}),e[13]=l.Architecture,e[14]=m):m=e[14];let v;e[15]!==l.VirtualizationType?(v=t.jsx(a,{label:"Virtualization Type",value:l.VirtualizationType}),e[15]=l.VirtualizationType,e[16]=v):v=e[16];const A=l.Platform||"Linux";let D;e[17]!==A?(D=t.jsx(a,{label:"Platform",value:A}),e[17]=A,e[18]=D):D=e[18];let d;e[19]!==l.PlatformDetails?(d=t.jsx(a,{label:"Platform Details",value:l.PlatformDetails}),e[19]=l.PlatformDetails,e[20]=d):d=e[20];let x;e[21]!==l.ImageType?(x=t.jsx(a,{label:"Image Type",value:l.ImageType}),e[21]=l.ImageType,e[22]=x):x=e[22];let b;e[23]!==l.RootDeviceType?(b=t.jsx(a,{label:"Root Device Type",value:l.RootDeviceType}),e[23]=l.RootDeviceType,e[24]=b):b=e[24];let j;e[25]!==l.RootDeviceName?(j=t.jsx(a,{label:"Root Device Name",value:l.RootDeviceName}),e[25]=l.RootDeviceName,e[26]=j):j=e[26];let f;e[27]!==l.OwnerId?(f=t.jsx(a,{label:"Owner",value:l.OwnerId}),e[27]=l.OwnerId,e[28]=f):f=e[28];let I;e[29]!==l.State?(I=t.jsx(a,{label:"State",value:l.State}),e[29]=l.State,e[30]=I):I=e[30];let h;e[31]!==R||e[32]!==l.Public||e[33]!==i?(h=i&&t.jsxs(t.Fragment,{children:[t.jsx(a,{label:"Creation Date",value:R}),t.jsx(a,{label:"Public",value:l.Public?"Yes":"No"})]}),e[31]=R,e[32]=l.Public,e[33]=i,e[34]=h):h=e[34];let p;e[35]!==v||e[36]!==D||e[37]!==d||e[38]!==x||e[39]!==b||e[40]!==j||e[41]!==f||e[42]!==I||e[43]!==h||e[44]!==c||e[45]!==n||e[46]!==u||e[47]!==m?(p=t.jsxs(T.Content,{children:[c,n,u,m,v,D,d,x,b,j,f,I,h]}),e[35]=v,e[36]=D,e[37]=d,e[38]=x,e[39]=b,e[40]=j,e[41]=f,e[42]=I,e[43]=h,e[44]=c,e[45]=n,e[46]=u,e[47]=m,e[48]=p):p=e[48];let N;e[49]!==p||e[50]!==o?(N=t.jsxs(T,{children:[o,p]}),e[49]=p,e[50]=o,e[51]=N):N=e[51];let y;e[52]!==l.BlockDeviceMappings||e[53]!==i?(y=i&&l.BlockDeviceMappings&&l.BlockDeviceMappings.length>0&&l.BlockDeviceMappings.map(w),e[52]=l.BlockDeviceMappings,e[53]=i,e[54]=y):y=e[54];let V;return e[55]!==N||e[56]!==y?(V=t.jsxs(t.Fragment,{children:[N,y]}),e[55]=N,e[56]=y,e[57]=V):V=e[57],V}function w(s){return t.jsxs(T,{children:[t.jsxs(T.Header,{children:["Block Device: ",s.DeviceName]}),t.jsxs(T.Content,{children:[t.jsx(a,{label:"Device Name",value:s.DeviceName}),s.Ebs&&t.jsxs(t.Fragment,{children:[t.jsx(a,{label:"Volume Size",value:s.Ebs.VolumeSize?`${s.Ebs.VolumeSize} GB`:void 0}),t.jsx(a,{label:"Volume Type",value:s.Ebs.VolumeType}),t.jsx(a,{label:"Delete On Termination",value:s.Ebs.DeleteOnTermination?"Yes":"No"}),t.jsx(a,{label:"Encrypted",value:s.Ebs.Encrypted?"Yes":"No"}),s.Ebs.SnapshotId&&t.jsx(a,{label:"Snapshot ID",value:t.jsx(M,{className:"text-primary hover:underline",params:{id:s.Ebs.SnapshotId},to:"/ec2/describe-snapshots/$id",children:s.Ebs.SnapshotId})})]}),s.VirtualName&&t.jsx(a,{label:"Virtual Name",value:s.VirtualName})]})]},s.DeviceName)}export{C as A};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{k as o,c as r,j as l,L as a}from"./index.js";const m=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],x=o("arrow-left",m);function p(i){const e=r.c(7);let s,t;e[0]!==i?({children:s,...t}=i,e[0]=i,e[1]=s,e[2]=t):(s=e[1],t=e[2]);let n;e[3]===Symbol.for("react.memo_cache_sentinel")?(n=l.jsx(x,{className:"size-4"}),e[3]=n):n=e[3];let c;return e[4]!==s||e[5]!==t?(c=l.jsxs(a,{className:"mb-6 inline-flex items-center gap-2 text-sm",...t,children:[n,s]}),e[4]=s,e[5]=t,e[6]=c):c=e[6],c}export{p as B};
|
||||
import{J as o,c as r,j as l,L as a}from"./index.js";const m=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],x=o("arrow-left",m);function p(i){const e=r.c(7);let s,t;e[0]!==i?({children:s,...t}=i,e[0]=i,e[1]=s,e[2]=t):(s=e[1],t=e[2]);let n;e[3]===Symbol.for("react.memo_cache_sentinel")?(n=l.jsx(x,{className:"size-4"}),e[3]=n):n=e[3];let c;return e[4]!==s||e[5]!==t?(c=l.jsxs(a,{className:"mb-6 inline-flex items-center gap-2 text-sm",...t,children:[n,s]}),e[4]=s,e[5]=t,e[6]=c):c=e[6],c}export{p as B};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{k as c}from"./index.js";const e=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],t=c("check",e);export{t as C};
|
||||
import{J as c}from"./index.js";const e=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],t=c("check",e);export{t as C};
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{o as M,f as $,c as I,u as R,j as t,B as E}from"./index.js";import{a as A,u as H}from"./zod.js";import{B as L}from"./back-link.js";import{E as O}from"./error-banner.js";import{P as T}from"./page-heading.js";import{F as q,a as D,b as G}from"./field.js";import{I as J}from"./input.js";import{u as K}from"./s3.js";const Q=M({bucketName:$().min(3,"Bucket name must be at least 3 characters").max(63,"Bucket name must be at most 63 characters").regex(/^[a-z0-9][a-z0-9.-]*[a-z0-9]$/,"Must start and end with a lowercase letter or number").regex(/^[a-z0-9.-]+$/,"Only lowercase letters, numbers, hyphens, and periods allowed").refine(e=>!e.includes(".."),"Must not contain consecutive periods").refine(e=>!(e.includes(".-")||e.includes("-.")),"Must not contain adjacent period and hyphen")});function se(){const e=I.c(40),r=R(),s=K();let p;e[0]===Symbol.for("react.memo_cache_sentinel")?(p={resolver:A(Q)},e[0]=p):p=e[0];const{handleSubmit:y,register:N,formState:P}=H(p),{errors:x,isSubmitting:S}=P;let h;e[1]!==s||e[2]!==r?(h=async z=>{await s.mutateAsync(z),r({to:"/s3/ls"})},e[1]=s,e[2]=r,e[3]=h):h=e[3];const F=h;let k,j;e[4]===Symbol.for("react.memo_cache_sentinel")?(k=t.jsx(L,{to:"/s3/ls",children:"Back to buckets"}),j=t.jsx(T,{title:"Create Bucket"}),e[4]=k,e[5]=j):(k=e[4],j=e[5]);let a;e[6]!==s.error?(a=s.error&&t.jsx(O,{error:s.error,msg:"Failed to create bucket"}),e[6]=s.error,e[7]=a):a=e[7];let i;e[8]!==y||e[9]!==F?(i=y(F),e[8]=y,e[9]=F,e[10]=i):i=e[10];let g;e[11]===Symbol.for("react.memo_cache_sentinel")?(g=t.jsx(D,{children:t.jsx("label",{htmlFor:"bucketName",children:"Bucket Name"})}),e[11]=g):g=e[11];const v=!!x.bucketName;let l;e[12]!==N?(l=N("bucketName"),e[12]=N,e[13]=l):l=e[13];let n;e[14]!==v||e[15]!==l?(n=t.jsx(J,{"aria-invalid":v,id:"bucketName",placeholder:"my-bucket",type:"text",...l}),e[14]=v,e[15]=l,e[16]=n):n=e[16];let o;e[17]!==x.bucketName?(o=t.jsx(q,{errors:[x.bucketName]}),e[17]=x.bucketName,e[18]=o):o=e[18];let c;e[19]!==n||e[20]!==o?(c=t.jsxs(G,{children:[g,n,o]}),e[19]=n,e[20]=o,e[21]=c):c=e[21];const C=S||s.isPending;let m;e[22]!==r?(m=()=>r({to:"/s3/ls"}),e[22]=r,e[23]=m):m=e[23];let u;e[24]!==C||e[25]!==m?(u=t.jsx(E,{disabled:C,onClick:m,type:"button",variant:"outline",children:"Cancel"}),e[24]=C,e[25]=m,e[26]=u):u=e[26];const w=S||s.isPending,_=S||s.isPending?"Creating…":"Create Bucket";let d;e[27]!==w||e[28]!==_?(d=t.jsx(E,{disabled:w,type:"submit",children:_}),e[27]=w,e[28]=_,e[29]=d):d=e[29];let f;e[30]!==u||e[31]!==d?(f=t.jsxs("div",{className:"flex gap-2",children:[u,d]}),e[30]=u,e[31]=d,e[32]=f):f=e[32];let b;e[33]!==c||e[34]!==f||e[35]!==i?(b=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:i,children:[c,f]}),e[33]=c,e[34]=f,e[35]=i,e[36]=b):b=e[36];let B;return e[37]!==b||e[38]!==a?(B=t.jsxs(t.Fragment,{children:[k,j,a,b]}),e[37]=b,e[38]=a,e[39]=B):B=e[39],B}export{se as component};
|
||||
import{o as C,k as P,c as _,u as v,j as t}from"./index.js";import{a as E,u as z}from"./zod.js";import{B as M}from"./back-link.js";import{E as L}from"./error-banner.js";import{F as $}from"./form-actions.js";import{P as A}from"./page-heading.js";import{F as I,a as R,b as H}from"./field.js";import{I as O}from"./input.js";import{u as T}from"./s3.js";const q=C({bucketName:P().min(3,"Bucket name must be at least 3 characters").max(63,"Bucket name must be at most 63 characters").regex(/^[a-z0-9][a-z0-9.-]*[a-z0-9]$/,"Must start and end with a lowercase letter or number").regex(/^[a-z0-9.-]+$/,"Only lowercase letters, numbers, hyphens, and periods allowed").refine(e=>!e.includes(".."),"Must not contain consecutive periods").refine(e=>!(e.includes(".-")||e.includes("-.")),"Must not contain adjacent period and hyphen")});function Y(){const e=_.c(35),r=v(),s=T();let f;e[0]===Symbol.for("react.memo_cache_sentinel")?(f={resolver:E(q)},e[0]=f):f=e[0];const{handleSubmit:j,register:B,formState:S}=z(f),{errors:b,isSubmitting:y}=S;let p;e[1]!==s||e[2]!==r?(p=async w=>{await s.mutateAsync(w),r({to:"/s3/ls"})},e[1]=s,e[2]=r,e[3]=p):p=e[3];const N=p;let k,x;e[4]===Symbol.for("react.memo_cache_sentinel")?(k=t.jsx(M,{to:"/s3/ls",children:"Back to buckets"}),x=t.jsx(A,{title:"Create Bucket"}),e[4]=k,e[5]=x):(k=e[4],x=e[5]);let a;e[6]!==s.error?(a=s.error&&t.jsx(L,{error:s.error,msg:"Failed to create bucket"}),e[6]=s.error,e[7]=a):a=e[7];let i;e[8]!==j||e[9]!==N?(i=j(N),e[8]=j,e[9]=N,e[10]=i):i=e[10];let h;e[11]===Symbol.for("react.memo_cache_sentinel")?(h=t.jsx(R,{children:t.jsx("label",{htmlFor:"bucketName",children:"Bucket Name"})}),e[11]=h):h=e[11];const F=!!b.bucketName;let o;e[12]!==B?(o=B("bucketName"),e[12]=B,e[13]=o):o=e[13];let n;e[14]!==F||e[15]!==o?(n=t.jsx(O,{"aria-invalid":F,id:"bucketName",placeholder:"my-bucket",type:"text",...o}),e[14]=F,e[15]=o,e[16]=n):n=e[16];let c;e[17]!==b.bucketName?(c=t.jsx(I,{errors:[b.bucketName]}),e[17]=b.bucketName,e[18]=c):c=e[18];let l;e[19]!==n||e[20]!==c?(l=t.jsxs(H,{children:[h,n,c]}),e[19]=n,e[20]=c,e[21]=l):l=e[21];let m;e[22]!==r?(m=()=>r({to:"/s3/ls"}),e[22]=r,e[23]=m):m=e[23];let u;e[24]!==s.isPending||e[25]!==y||e[26]!==m?(u=t.jsx($,{isPending:s.isPending,isSubmitting:y,onCancel:m,pendingLabel:"Creating\\u2026",submitLabel:"Create Bucket"}),e[24]=s.isPending,e[25]=y,e[26]=m,e[27]=u):u=e[27];let d;e[28]!==l||e[29]!==u||e[30]!==i?(d=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:i,children:[l,u]}),e[28]=l,e[29]=u,e[30]=i,e[31]=d):d=e[31];let g;return e[32]!==d||e[33]!==a?(g=t.jsxs(t.Fragment,{children:[k,x,a,d]}),e[32]=d,e[33]=a,e[34]=g):g=e[34],g}export{Y as component};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{c as V,u as H,j as t}from"./index.js";import{a as Y,u as $}from"./zod.js";import{B as q}from"./back-link.js";import{E as z}from"./error-banner.js";import{F as G}from"./form-actions.js";import{P as K}from"./page-heading.js";import{F as k,a as I,b as R}from"./field.js";import{I as J}from"./input.js";import{T as Q}from"./textarea.js";import{a as W}from"./iam.js";import{a as X}from"./iam2.js";const Z=JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Action:"*",Resource:"*"}]},null,2);function pe(){const e=V.c(58),s=H(),o=W();let S;e[0]===Symbol.for("react.memo_cache_sentinel")?(S={resolver:Y(X),defaultValues:{policyDocument:Z}},e[0]=S):S=e[0];const{handleSubmit:L,register:l,formState:M}=$(S),{errors:i,isSubmitting:T}=M;let F;e[1]!==o||e[2]!==s?(F=async U=>{await o.mutateAsync(U),s({to:"/iam/list-policies"})},e[1]=o,e[2]=s,e[3]=F):F=e[3];const w=F;let D,_;e[4]===Symbol.for("react.memo_cache_sentinel")?(D=t.jsx(q,{to:"/iam/list-policies",children:"Back to policies"}),_=t.jsx(K,{title:"Create Policy"}),e[4]=D,e[5]=_):(D=e[4],_=e[5]);let r;e[6]!==o.error?(r=o.error&&t.jsx(z,{error:o.error,msg:"Failed to create policy"}),e[6]=o.error,e[7]=r):r=e[7];let c;e[8]!==L||e[9]!==w?(c=L(w),e[8]=L,e[9]=w,e[10]=c):c=e[10];let C;e[11]===Symbol.for("react.memo_cache_sentinel")?(C=t.jsx(I,{children:t.jsx("label",{htmlFor:"policyName",children:"Policy Name"})}),e[11]=C):C=e[11];const B=!!i.policyName;let a;e[12]!==l?(a=l("policyName"),e[12]=l,e[13]=a):a=e[13];let n;e[14]!==B||e[15]!==a?(n=t.jsx(J,{"aria-invalid":B,id:"policyName",placeholder:"my-policy...",...a}),e[14]=B,e[15]=a,e[16]=n):n=e[16];let m;e[17]!==i.policyName?(m=t.jsx(k,{errors:[i.policyName]}),e[17]=i.policyName,e[18]=m):m=e[18];let p;e[19]!==n||e[20]!==m?(p=t.jsxs(R,{children:[C,n,m]}),e[19]=n,e[20]=m,e[21]=p):p=e[21];let E;e[22]===Symbol.for("react.memo_cache_sentinel")?(E=t.jsx(I,{children:t.jsx("label",{htmlFor:"description",children:"Description (optional)"})}),e[22]=E):E=e[22];let f;e[23]!==l?(f=l("description"),e[23]=l,e[24]=f):f=e[24];let d;e[25]!==f?(d=t.jsx(J,{id:"description",placeholder:"A brief description of this policy...",...f}),e[25]=f,e[26]=d):d=e[26];let y;e[27]!==i.description?(y=t.jsx(k,{errors:[i.description]}),e[27]=i.description,e[28]=y):y=e[28];let u;e[29]!==d||e[30]!==y?(u=t.jsxs(R,{children:[E,d,y]}),e[29]=d,e[30]=y,e[31]=u):u=e[31];let A;e[32]===Symbol.for("react.memo_cache_sentinel")?(A=t.jsx(I,{children:t.jsx("label",{htmlFor:"policyDocument",children:"Policy Document (JSON)"})}),e[32]=A):A=e[32];const O=!!i.policyDocument;let x;e[33]!==l?(x=l("policyDocument"),e[33]=l,e[34]=x):x=e[34];let h;e[35]!==O||e[36]!==x?(h=t.jsx(Q,{"aria-invalid":O,className:"font-mono text-sm",id:"policyDocument",rows:15,...x}),e[35]=O,e[36]=x,e[37]=h):h=e[37];let j;e[38]!==i.policyDocument?(j=t.jsx(k,{errors:[i.policyDocument]}),e[38]=i.policyDocument,e[39]=j):j=e[39];let b;e[40]!==h||e[41]!==j?(b=t.jsxs(R,{children:[A,h,j]}),e[40]=h,e[41]=j,e[42]=b):b=e[42];let g;e[43]!==s?(g=()=>s({to:"/iam/list-policies"}),e[43]=s,e[44]=g):g=e[44];let N;e[45]!==o.isPending||e[46]!==T||e[47]!==g?(N=t.jsx(G,{isPending:o.isPending,isSubmitting:T,onCancel:g,pendingLabel:"Creating...",submitLabel:"Create Policy"}),e[45]=o.isPending,e[46]=T,e[47]=g,e[48]=N):N=e[48];let P;e[49]!==p||e[50]!==u||e[51]!==b||e[52]!==N||e[53]!==c?(P=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:c,children:[p,u,b,N]}),e[49]=p,e[50]=u,e[51]=b,e[52]=N,e[53]=c,e[54]=P):P=e[54];let v;return e[55]!==P||e[56]!==r?(v=t.jsxs(t.Fragment,{children:[D,_,r,P]}),e[55]=P,e[56]=r,e[57]=v):v=e[57],v}export{pe as component};
|
||||
@@ -1 +1 @@
|
||||
import{c as q,u as J,i as K,R as U,j as t,B as G}from"./index.js";import{a as W,u as X,C as Y}from"./zod.js";import{u as Z}from"./useSuspenseQuery.js";import{B as ee}from"./back-link.js";import{E as te}from"./error-banner.js";import{P as se}from"./page-heading.js";import{F as le,b as H,a as L}from"./field.js";import{S as oe,a as re,b as ie,c as ae,d as ne}from"./select.js";import{T as ce}from"./textarea.js";import{c as me}from"./ec2.js";import{d as de}from"./ec22.js";import"./check.js";function ye(){const e=q.c(51),l=J(),{data:B}=Z(K),s=me();let j;e[0]!==B.Volumes?(j=B.Volumes??[],e[0]=B.Volumes,e[1]=j):j=e[1];const b=j,{volumeId:M}=U.useSearch(),E=M??b[0]?.VolumeId??"";let v;e[2]===Symbol.for("react.memo_cache_sentinel")?(v=W(de),e[2]=v):v=e[2];let I;e[3]!==E?(I={resolver:v,defaultValues:{volumeId:E,description:""}},e[3]=E,e[4]=I):I=e[4];const{control:P,handleSubmit:k,register:N,formState:O}=X(I),{errors:o,isSubmitting:R}=O;let g;e[5]!==s||e[6]!==l?(g=async z=>{await s.mutateAsync(z),l({to:"/ec2/describe-snapshots"})},e[5]=s,e[6]=l,e[7]=g):g=e[7];const T=g;let V,C;e[8]===Symbol.for("react.memo_cache_sentinel")?(V=t.jsx(ee,{to:"/ec2/describe-snapshots",children:"Back to snapshots"}),C=t.jsx(se,{title:"Create Snapshot"}),e[8]=V,e[9]=C):(V=e[8],C=e[9]);let r;e[10]!==s.error?(r=s.error&&t.jsx(te,{error:s.error,msg:"Failed to create snapshot"}),e[10]=s.error,e[11]=r):r=e[11];let i;e[12]!==k||e[13]!==T?(i=k(T),e[12]=k,e[13]=T,e[14]=i):i=e[14];let y;e[15]===Symbol.for("react.memo_cache_sentinel")?(y=t.jsx(L,{children:t.jsx("label",{htmlFor:"volumeId",children:"Volume"})}),e[15]=y):y=e[15];let a;e[16]!==o.volumeId||e[17]!==b?(a=z=>{const{field:A}=z;return t.jsxs(oe,{onValueChange:$=>A.onChange($),value:A.value??"",children:[t.jsx(re,{"aria-invalid":!!o.volumeId,className:"w-full",id:"volumeId",children:t.jsx(ie,{})}),t.jsx(ae,{children:b.map(ue)})]})},e[16]=o.volumeId,e[17]=b,e[18]=a):a=e[18];let n;e[19]!==P||e[20]!==a?(n=t.jsx(Y,{control:P,name:"volumeId",render:a}),e[19]=P,e[20]=a,e[21]=n):n=e[21];let c;e[22]!==o.volumeId?(c=t.jsx(le,{errors:[o.volumeId]}),e[22]=o.volumeId,e[23]=c):c=e[23];let m;e[24]!==n||e[25]!==c?(m=t.jsxs(H,{children:[y,n,c]}),e[24]=n,e[25]=c,e[26]=m):m=e[26];let F;e[27]===Symbol.for("react.memo_cache_sentinel")?(F=t.jsx(L,{children:t.jsx("label",{htmlFor:"description",children:"Description (optional)"})}),e[27]=F):F=e[27];let d;e[28]!==N?(d=N("description"),e[28]=N,e[29]=d):d=e[29];let u;e[30]!==d?(u=t.jsxs(H,{children:[F,t.jsx(ce,{id:"description",placeholder:"Snapshot description",...d})]}),e[30]=d,e[31]=u):u=e[31];const w=R||s.isPending;let f;e[32]!==l?(f=()=>l({to:"/ec2/describe-snapshots"}),e[32]=l,e[33]=f):f=e[33];let p;e[34]!==w||e[35]!==f?(p=t.jsx(G,{disabled:w,onClick:f,type:"button",variant:"outline",children:"Cancel"}),e[34]=w,e[35]=f,e[36]=p):p=e[36];const D=R||s.isPending,Q=R||s.isPending?"Creating…":"Create Snapshot";let h;e[37]!==D||e[38]!==Q?(h=t.jsx(G,{disabled:D,type:"submit",children:Q}),e[37]=D,e[38]=Q,e[39]=h):h=e[39];let x;e[40]!==p||e[41]!==h?(x=t.jsxs("div",{className:"flex gap-2",children:[p,h]}),e[40]=p,e[41]=h,e[42]=x):x=e[42];let S;e[43]!==m||e[44]!==u||e[45]!==x||e[46]!==i?(S=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:i,children:[m,u,x]}),e[43]=m,e[44]=u,e[45]=x,e[46]=i,e[47]=S):S=e[47];let _;return e[48]!==S||e[49]!==r?(_=t.jsxs(t.Fragment,{children:[V,C,r,S]}),e[48]=S,e[49]=r,e[50]=_):_=e[50],_}function ue(e){return t.jsxs(ne,{value:e.VolumeId??"",children:[e.VolumeId," (",e.Size," GiB)"]},e.VolumeId)}export{ye as component};
|
||||
import{c as z,u as G,n as H,R as M,j as t}from"./index.js";import{a as O,u as $,C as q}from"./zod.js";import{u as J}from"./useSuspenseQuery.js";import{B as K}from"./back-link.js";import{E as U}from"./error-banner.js";import{F as W}from"./form-actions.js";import{P as X}from"./page-heading.js";import{F as Y,b as L,a as N}from"./field.js";import{S as Z,a as ee,b as te,c as se,d as oe}from"./select.js";import{T as re}from"./textarea.js";import{c as le}from"./ec2.js";import{d as ie}from"./ec22.js";import"./check.js";function Ie(){const e=z.c(46),o=G(),{data:y}=J(H),s=le();let x;e[0]!==y.Volumes?(x=y.Volumes??[],e[0]=y.Volumes,e[1]=x):x=e[1];const S=x,{volumeId:A}=M.useSearch(),_=A??S[0]?.VolumeId??"";let j;e[2]===Symbol.for("react.memo_cache_sentinel")?(j=O(ie),e[2]=j):j=e[2];let b;e[3]!==_?(b={resolver:j,defaultValues:{volumeId:_,description:""}},e[3]=_,e[4]=b):b=e[4];const{control:P,handleSubmit:B,register:E,formState:D}=$(b),{errors:r,isSubmitting:R}=D;let g;e[5]!==s||e[6]!==o?(g=async k=>{await s.mutateAsync(k),o({to:"/ec2/describe-snapshots"})},e[5]=s,e[6]=o,e[7]=g):g=e[7];const T=g;let I,v;e[8]===Symbol.for("react.memo_cache_sentinel")?(I=t.jsx(K,{to:"/ec2/describe-snapshots",children:"Back to snapshots"}),v=t.jsx(X,{title:"Create Snapshot"}),e[8]=I,e[9]=v):(I=e[8],v=e[9]);let l;e[10]!==s.error?(l=s.error&&t.jsx(U,{error:s.error,msg:"Failed to create snapshot"}),e[10]=s.error,e[11]=l):l=e[11];let i;e[12]!==B||e[13]!==T?(i=B(T),e[12]=B,e[13]=T,e[14]=i):i=e[14];let V;e[15]===Symbol.for("react.memo_cache_sentinel")?(V=t.jsx(N,{children:t.jsx("label",{htmlFor:"volumeId",children:"Volume"})}),e[15]=V):V=e[15];let a;e[16]!==r.volumeId||e[17]!==S?(a=k=>{const{field:w}=k;return t.jsxs(Z,{onValueChange:Q=>w.onChange(Q),value:w.value??"",children:[t.jsx(ee,{"aria-invalid":!!r.volumeId,className:"w-full",id:"volumeId",children:t.jsx(te,{})}),t.jsx(se,{children:S.map(ae)})]})},e[16]=r.volumeId,e[17]=S,e[18]=a):a=e[18];let n;e[19]!==P||e[20]!==a?(n=t.jsx(q,{control:P,name:"volumeId",render:a}),e[19]=P,e[20]=a,e[21]=n):n=e[21];let m;e[22]!==r.volumeId?(m=t.jsx(Y,{errors:[r.volumeId]}),e[22]=r.volumeId,e[23]=m):m=e[23];let c;e[24]!==n||e[25]!==m?(c=t.jsxs(L,{children:[V,n,m]}),e[24]=n,e[25]=m,e[26]=c):c=e[26];let C;e[27]===Symbol.for("react.memo_cache_sentinel")?(C=t.jsx(N,{children:t.jsx("label",{htmlFor:"description",children:"Description (optional)"})}),e[27]=C):C=e[27];let d;e[28]!==E?(d=E("description"),e[28]=E,e[29]=d):d=e[29];let u;e[30]!==d?(u=t.jsxs(L,{children:[C,t.jsx(re,{id:"description",placeholder:"Snapshot description",...d})]}),e[30]=d,e[31]=u):u=e[31];let f;e[32]!==o?(f=()=>o({to:"/ec2/describe-snapshots"}),e[32]=o,e[33]=f):f=e[33];let p;e[34]!==s.isPending||e[35]!==R||e[36]!==f?(p=t.jsx(W,{isPending:s.isPending,isSubmitting:R,onCancel:f,pendingLabel:"Creating\\u2026",submitLabel:"Create Snapshot"}),e[34]=s.isPending,e[35]=R,e[36]=f,e[37]=p):p=e[37];let h;e[38]!==c||e[39]!==u||e[40]!==p||e[41]!==i?(h=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:i,children:[c,u,p]}),e[38]=c,e[39]=u,e[40]=p,e[41]=i,e[42]=h):h=e[42];let F;return e[43]!==h||e[44]!==l?(F=t.jsxs(t.Fragment,{children:[I,v,l,h]}),e[43]=h,e[44]=l,e[45]=F):F=e[45],F}function ae(e){return t.jsxs(oe,{value:e.VolumeId??"",children:[e.VolumeId," (",e.Size," GiB)"]},e.VolumeId)}export{Ie as component};
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{c as oe,u as ce,h as de,g as me,j as t,B as Y}from"./index.js";import{a as ue,u as fe,C as ee}from"./zod.js";import{u as te}from"./useSuspenseQuery.js";import{B as pe}from"./back-link.js";import{E as be}from"./error-banner.js";import{P as he}from"./page-heading.js";import{F as se,a as U,b as W}from"./field.js";import{I as xe}from"./input.js";import{S as le,a as ie,b as ne,c as re,d as X}from"./select.js";import{b as je}from"./ec2.js";import{b as ve}from"./ec22.js";import"./check.js";function Ee(){const e=oe.c(66),s=ce(),{data:Q}=te(de),{data:R}=te(me),l=je();let V;e[0]!==Q.Vpcs?(V=Q.Vpcs??[],e[0]=Q.Vpcs,e[1]=V):V=e[1];const k=V;let _;e[2]!==R.AvailabilityZones?(_=R.AvailabilityZones??[],e[2]=R.AvailabilityZones,e[3]=_):_=e[3];const T=_;let Z;e[4]===Symbol.for("react.memo_cache_sentinel")?(Z=ue(ve),e[4]=Z):Z=e[4];const O=k[0]?.VpcId??"";let F;e[5]!==O?(F={resolver:Z,defaultValues:{vpcId:O,cidrBlock:"10.0.1.0/24",availabilityZone:""}},e[5]=O,e[6]=F):F=e[6];const{control:r,handleSubmit:H,register:K,formState:ae}=fe(F),{errors:i,isSubmitting:L}=ae;let N;e[7]!==l||e[8]!==s?(N=async B=>{const n=(await l.mutateAsync(B)).Subnet?.SubnetId;s(n?{to:"/ec2/describe-subnets/$id",params:{id:n}}:{to:"/ec2/describe-subnets"})},e[7]=l,e[8]=s,e[9]=N):N=e[9];const M=N;let A,P;e[10]===Symbol.for("react.memo_cache_sentinel")?(A=t.jsx(pe,{to:"/ec2/describe-subnets",children:"Back to Subnets"}),P=t.jsx(he,{title:"Create Subnet"}),e[10]=A,e[11]=P):(A=e[10],P=e[11]);let a;e[12]!==l.error?(a=l.error&&t.jsx(be,{error:l.error,msg:"Failed to create subnet"}),e[12]=l.error,e[13]=a):a=e[13];let o;e[14]!==H||e[15]!==M?(o=H(M),e[14]=H,e[15]=M,e[16]=o):o=e[16];let $;e[17]===Symbol.for("react.memo_cache_sentinel")?($=t.jsx(U,{children:t.jsx("label",{htmlFor:"vpcId",children:"VPC"})}),e[17]=$):$=e[17];let c;e[18]!==i.vpcId||e[19]!==k?(c=B=>{const{field:C}=B;return t.jsxs(le,{onValueChange:n=>C.onChange(n),value:C.value??"",children:[t.jsx(ie,{"aria-invalid":!!i.vpcId,className:"w-full",id:"vpcId",children:t.jsx(ne,{})}),t.jsx(re,{children:k.map(ye)})]})},e[18]=i.vpcId,e[19]=k,e[20]=c):c=e[20];let d;e[21]!==r||e[22]!==c?(d=t.jsx(ee,{control:r,name:"vpcId",render:c}),e[21]=r,e[22]=c,e[23]=d):d=e[23];let m;e[24]!==i.vpcId?(m=t.jsx(se,{errors:[i.vpcId]}),e[24]=i.vpcId,e[25]=m):m=e[25];let u;e[26]!==d||e[27]!==m?(u=t.jsxs(W,{children:[$,d,m]}),e[26]=d,e[27]=m,e[28]=u):u=e[28];let E;e[29]===Symbol.for("react.memo_cache_sentinel")?(E=t.jsx(U,{children:t.jsx("label",{htmlFor:"cidrBlock",children:"CIDR Block"})}),e[29]=E):E=e[29];const q=!!i.cidrBlock;let f;e[30]!==K?(f=K("cidrBlock"),e[30]=K,e[31]=f):f=e[31];let p;e[32]!==q||e[33]!==f?(p=t.jsx(xe,{"aria-invalid":q,id:"cidrBlock",placeholder:"10.0.1.0/24",...f}),e[32]=q,e[33]=f,e[34]=p):p=e[34];let b;e[35]!==i.cidrBlock?(b=t.jsx(se,{errors:[i.cidrBlock]}),e[35]=i.cidrBlock,e[36]=b):b=e[36];let h;e[37]!==p||e[38]!==b?(h=t.jsxs(W,{children:[E,p,b]}),e[37]=p,e[38]=b,e[39]=h):h=e[39];let w;e[40]===Symbol.for("react.memo_cache_sentinel")?(w=t.jsx(U,{children:t.jsx("label",{htmlFor:"availabilityZone",children:"Availability Zone (optional)"})}),e[40]=w):w=e[40];let x;e[41]!==T?(x=B=>{const{field:C}=B;return t.jsxs(le,{onValueChange:n=>C.onChange(n==="none"?"":n),value:C.value||"none",children:[t.jsx(ie,{className:"w-full",id:"availabilityZone",children:t.jsx(ne,{})}),t.jsxs(re,{children:[t.jsx(X,{value:"none",children:"none"}),T.map(Se)]})]})},e[41]=T,e[42]=x):x=e[42];let j;e[43]!==r||e[44]!==x?(j=t.jsxs(W,{children:[w,t.jsx(ee,{control:r,name:"availabilityZone",render:x})]}),e[43]=r,e[44]=x,e[45]=j):j=e[45];const z=L||l.isPending;let v;e[46]!==s?(v=()=>s({to:"/ec2/describe-subnets"}),e[46]=s,e[47]=v):v=e[47];let S;e[48]!==z||e[49]!==v?(S=t.jsx(Y,{disabled:z,onClick:v,type:"button",variant:"outline",children:"Cancel"}),e[48]=z,e[49]=v,e[50]=S):S=e[50];const G=L||l.isPending,J=L||l.isPending?"Creating…":"Create Subnet";let y;e[51]!==G||e[52]!==J?(y=t.jsx(Y,{disabled:G,type:"submit",children:J}),e[51]=G,e[52]=J,e[53]=y):y=e[53];let g;e[54]!==S||e[55]!==y?(g=t.jsxs("div",{className:"flex gap-2",children:[S,y]}),e[54]=S,e[55]=y,e[56]=g):g=e[56];let I;e[57]!==o||e[58]!==u||e[59]!==h||e[60]!==j||e[61]!==g?(I=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:o,children:[u,h,j,g]}),e[57]=o,e[58]=u,e[59]=h,e[60]=j,e[61]=g,e[62]=I):I=e[62];let D;return e[63]!==I||e[64]!==a?(D=t.jsxs(t.Fragment,{children:[A,P,a,I]}),e[63]=I,e[64]=a,e[65]=D):D=e[65],D}function Se(e){return t.jsx(X,{value:e.ZoneName??"",children:e.ZoneName},e.ZoneName)}function ye(e){const s=e.Tags?.find(ge)?.Value;return t.jsx(X,{value:e.VpcId??"",children:s?`${e.VpcId} (${s})`:`${e.VpcId} (${e.CidrBlock})`},e.VpcId)}function ge(e){return e.Key==="Name"}export{Ee as component};
|
||||
import{c as se,u as le,m as ie,l as re,j as t}from"./index.js";import{a as ne,u as ae,C as G}from"./zod.js";import{u as J}from"./useSuspenseQuery.js";import{B as oe}from"./back-link.js";import{E as ce}from"./error-banner.js";import{F as de}from"./form-actions.js";import{P as me}from"./page-heading.js";import{F as U,a as M,b as q}from"./field.js";import{I as ue}from"./input.js";import{S as W,a as X,b as Y,c as ee,d as z}from"./select.js";import{b as fe}from"./ec2.js";import{b as pe}from"./ec22.js";import"./check.js";function Ne(){const e=se.c(61),s=le(),{data:w}=J(ie),{data:D}=J(re),l=fe();let C;e[0]!==w.Vpcs?(C=w.Vpcs??[],e[0]=w.Vpcs,e[1]=C):C=e[1];const V=C;let B;e[2]!==D.AvailabilityZones?(B=D.AvailabilityZones??[],e[2]=D.AvailabilityZones,e[3]=B):B=e[3];const L=B;let k;e[4]===Symbol.for("react.memo_cache_sentinel")?(k=ne(pe),e[4]=k):k=e[4];const Q=V[0]?.VpcId??"";let _;e[5]!==Q?(_={resolver:k,defaultValues:{vpcId:Q,cidrBlock:"10.0.1.0/24",availabilityZone:""}},e[5]=Q,e[6]=_):_=e[6];const{control:n,handleSubmit:R,register:T,formState:te}=ae(_),{errors:i,isSubmitting:O}=te;let F;e[7]!==l||e[8]!==s?(F=async I=>{const r=(await l.mutateAsync(I)).Subnet?.SubnetId;s(r?{to:"/ec2/describe-subnets/$id",params:{id:r}}:{to:"/ec2/describe-subnets"})},e[7]=l,e[8]=s,e[9]=F):F=e[9];const H=F;let Z,N;e[10]===Symbol.for("react.memo_cache_sentinel")?(Z=t.jsx(oe,{to:"/ec2/describe-subnets",children:"Back to Subnets"}),N=t.jsx(me,{title:"Create Subnet"}),e[10]=Z,e[11]=N):(Z=e[10],N=e[11]);let a;e[12]!==l.error?(a=l.error&&t.jsx(ce,{error:l.error,msg:"Failed to create subnet"}),e[12]=l.error,e[13]=a):a=e[13];let o;e[14]!==R||e[15]!==H?(o=R(H),e[14]=R,e[15]=H,e[16]=o):o=e[16];let A;e[17]===Symbol.for("react.memo_cache_sentinel")?(A=t.jsx(M,{children:t.jsx("label",{htmlFor:"vpcId",children:"VPC"})}),e[17]=A):A=e[17];let c;e[18]!==i.vpcId||e[19]!==V?(c=I=>{const{field:y}=I;return t.jsxs(W,{onValueChange:r=>y.onChange(r),value:y.value??"",children:[t.jsx(X,{"aria-invalid":!!i.vpcId,className:"w-full",id:"vpcId",children:t.jsx(Y,{})}),t.jsx(ee,{children:V.map(xe)})]})},e[18]=i.vpcId,e[19]=V,e[20]=c):c=e[20];let d;e[21]!==n||e[22]!==c?(d=t.jsx(G,{control:n,name:"vpcId",render:c}),e[21]=n,e[22]=c,e[23]=d):d=e[23];let m;e[24]!==i.vpcId?(m=t.jsx(U,{errors:[i.vpcId]}),e[24]=i.vpcId,e[25]=m):m=e[25];let u;e[26]!==d||e[27]!==m?(u=t.jsxs(q,{children:[A,d,m]}),e[26]=d,e[27]=m,e[28]=u):u=e[28];let P;e[29]===Symbol.for("react.memo_cache_sentinel")?(P=t.jsx(M,{children:t.jsx("label",{htmlFor:"cidrBlock",children:"CIDR Block"})}),e[29]=P):P=e[29];const K=!!i.cidrBlock;let f;e[30]!==T?(f=T("cidrBlock"),e[30]=T,e[31]=f):f=e[31];let p;e[32]!==K||e[33]!==f?(p=t.jsx(ue,{"aria-invalid":K,id:"cidrBlock",placeholder:"10.0.1.0/24",...f}),e[32]=K,e[33]=f,e[34]=p):p=e[34];let b;e[35]!==i.cidrBlock?(b=t.jsx(U,{errors:[i.cidrBlock]}),e[35]=i.cidrBlock,e[36]=b):b=e[36];let x;e[37]!==p||e[38]!==b?(x=t.jsxs(q,{children:[P,p,b]}),e[37]=p,e[38]=b,e[39]=x):x=e[39];let $;e[40]===Symbol.for("react.memo_cache_sentinel")?($=t.jsx(M,{children:t.jsx("label",{htmlFor:"availabilityZone",children:"Availability Zone (optional)"})}),e[40]=$):$=e[40];let h;e[41]!==L?(h=I=>{const{field:y}=I;return t.jsxs(W,{onValueChange:r=>y.onChange(r==="none"?"":r),value:y.value||"none",children:[t.jsx(X,{className:"w-full",id:"availabilityZone",children:t.jsx(Y,{})}),t.jsxs(ee,{children:[t.jsx(z,{value:"none",children:"none"}),L.map(be)]})]})},e[41]=L,e[42]=h):h=e[42];let j;e[43]!==n||e[44]!==h?(j=t.jsxs(q,{children:[$,t.jsx(G,{control:n,name:"availabilityZone",render:h})]}),e[43]=n,e[44]=h,e[45]=j):j=e[45];let v;e[46]!==s?(v=()=>s({to:"/ec2/describe-subnets"}),e[46]=s,e[47]=v):v=e[47];let S;e[48]!==l.isPending||e[49]!==O||e[50]!==v?(S=t.jsx(de,{isPending:l.isPending,isSubmitting:O,onCancel:v,pendingLabel:"Creating\\u2026",submitLabel:"Create Subnet"}),e[48]=l.isPending,e[49]=O,e[50]=v,e[51]=S):S=e[51];let g;e[52]!==o||e[53]!==u||e[54]!==x||e[55]!==j||e[56]!==S?(g=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:o,children:[u,x,j,S]}),e[52]=o,e[53]=u,e[54]=x,e[55]=j,e[56]=S,e[57]=g):g=e[57];let E;return e[58]!==g||e[59]!==a?(E=t.jsxs(t.Fragment,{children:[Z,N,a,g]}),e[58]=g,e[59]=a,e[60]=E):E=e[60],E}function be(e){return t.jsx(z,{value:e.ZoneName??"",children:e.ZoneName},e.ZoneName)}function xe(e){const s=e.Tags?.find(he)?.Value;return t.jsx(z,{value:e.VpcId??"",children:s?`${e.VpcId} (${s})`:`${e.VpcId} (${e.CidrBlock})`},e.VpcId)}function he(e){return e.Key==="Name"}export{Ne as component};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import{c as R,u as H,j as t}from"./index.js";import{a as M,u as T}from"./zod.js";import{B as $}from"./back-link.js";import{E as q}from"./error-banner.js";import{F as z}from"./form-actions.js";import{P as D}from"./page-heading.js";import{F as B,a as k,b as L}from"./field.js";import{I as w}from"./input.js";import{u as G}from"./iam.js";import{c as J}from"./iam2.js";function re(){const e=R.c(46),i=H(),r=G();let g;e[0]===Symbol.for("react.memo_cache_sentinel")?(g={resolver:M(J)},e[0]=g):g=e[0];const{handleSubmit:C,register:a,formState:A}=T(g),{errors:s,isSubmitting:U}=A;let F;e[1]!==r||e[2]!==i?(F=async I=>{await r.mutateAsync(I),i({to:"/iam/list-users"})},e[1]=r,e[2]=i,e[3]=F):F=e[3];const E=F;let N,S;e[4]===Symbol.for("react.memo_cache_sentinel")?(N=t.jsx($,{to:"/iam/list-users",children:"Back to users"}),S=t.jsx(D,{title:"Create User"}),e[4]=N,e[5]=S):(N=e[4],S=e[5]);let l;e[6]!==r.error?(l=r.error&&t.jsx(q,{error:r.error,msg:"Failed to create user"}),e[6]=r.error,e[7]=l):l=e[7];let o;e[8]!==C||e[9]!==E?(o=C(E),e[8]=C,e[9]=E,e[10]=o):o=e[10];let y;e[11]===Symbol.for("react.memo_cache_sentinel")?(y=t.jsx(k,{children:t.jsx("label",{htmlFor:"userName",children:"User Name"})}),e[11]=y):y=e[11];const v=!!s.userName;let m;e[12]!==a?(m=a("userName"),e[12]=a,e[13]=m):m=e[13];let n;e[14]!==v||e[15]!==m?(n=t.jsx(w,{"aria-invalid":v,id:"userName",placeholder:"my-user...",...m}),e[14]=v,e[15]=m,e[16]=n):n=e[16];let c;e[17]!==s.userName?(c=t.jsx(B,{errors:[s.userName]}),e[17]=s.userName,e[18]=c):c=e[18];let f;e[19]!==n||e[20]!==c?(f=t.jsxs(L,{children:[y,n,c]}),e[19]=n,e[20]=c,e[21]=f):f=e[21];let _;e[22]===Symbol.for("react.memo_cache_sentinel")?(_=t.jsx(k,{children:t.jsx("label",{htmlFor:"path",children:"Path (optional)"})}),e[22]=_):_=e[22];let u;e[23]!==a?(u=a("path"),e[23]=a,e[24]=u):u=e[24];let p;e[25]!==u?(p=t.jsx(w,{id:"path",placeholder:"/",...u}),e[25]=u,e[26]=p):p=e[26];let h;e[27]!==s.path?(h=t.jsx(B,{errors:[s.path]}),e[27]=s.path,e[28]=h):h=e[28];let d;e[29]!==p||e[30]!==h?(d=t.jsxs(L,{children:[_,p,h]}),e[29]=p,e[30]=h,e[31]=d):d=e[31];let x;e[32]!==i?(x=()=>i({to:"/iam/list-users"}),e[32]=i,e[33]=x):x=e[33];let j;e[34]!==r.isPending||e[35]!==U||e[36]!==x?(j=t.jsx(z,{isPending:r.isPending,isSubmitting:U,onCancel:x,pendingLabel:"Creating...",submitLabel:"Create User"}),e[34]=r.isPending,e[35]=U,e[36]=x,e[37]=j):j=e[37];let b;e[38]!==f||e[39]!==d||e[40]!==j||e[41]!==o?(b=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:o,children:[f,d,j]}),e[38]=f,e[39]=d,e[40]=j,e[41]=o,e[42]=b):b=e[42];let P;return e[43]!==b||e[44]!==l?(P=t.jsxs(t.Fragment,{children:[N,S,l,b]}),e[43]=b,e[44]=l,e[45]=P):P=e[45],P}export{re as component};
|
||||
@@ -1 +1 @@
|
||||
import{c as K,u as U,g as W,j as t,B as L}from"./index.js";import{a as X,u as Y,C as ee}from"./zod.js";import{u as te}from"./useSuspenseQuery.js";import{B as ie}from"./back-link.js";import{E as le}from"./error-banner.js";import{P as ae}from"./page-heading.js";import{F as M,a as O,b as $}from"./field.js";import{I as se}from"./input.js";import{S as re,a as oe,b as ne,c as me,d as ce}from"./select.js";import{a as ue}from"./ec2.js";import{a as fe}from"./ec22.js";import"./check.js";function Fe(){const e=K.c(57),a=U(),{data:V}=te(W),i=ue();let j;e[0]!==V.AvailabilityZones?(j=V.AvailabilityZones??[],e[0]=V.AvailabilityZones,e[1]=j):j=e[1];const Z=j,B=Z[0]?.ZoneName??"";let g;e[2]===Symbol.for("react.memo_cache_sentinel")?(g=X(fe),e[2]=g):g=e[2];let S;e[3]!==B?(S={resolver:g,defaultValues:{size:1,availabilityZone:B}},e[3]=B,e[4]=S):S=e[4];const{control:E,handleSubmit:P,register:k,formState:q}=Y(S),{errors:l,isSubmitting:w}=q;let C;e[5]!==i||e[6]!==a?(C=async G=>{await i.mutateAsync(G),a({to:"/ec2/describe-volumes"})},e[5]=i,e[6]=a,e[7]=C):C=e[7];const I=C;let z,F;e[8]===Symbol.for("react.memo_cache_sentinel")?(z=t.jsx(ie,{to:"/ec2/describe-volumes",children:"Back to volumes"}),F=t.jsx(ae,{title:"Create Volume"}),e[8]=z,e[9]=F):(z=e[8],F=e[9]);let s;e[10]!==i.error?(s=i.error&&t.jsx(le,{error:i.error,msg:"Failed to create volume"}),e[10]=i.error,e[11]=s):s=e[11];let r;e[12]!==P||e[13]!==I?(r=P(I),e[12]=P,e[13]=I,e[14]=r):r=e[14];let N;e[15]===Symbol.for("react.memo_cache_sentinel")?(N=t.jsx(O,{children:t.jsx("label",{htmlFor:"size",children:"Size (GiB)"})}),e[15]=N):N=e[15];const Q=!!l.size;let o;e[16]!==k?(o=k("size",{valueAsNumber:!0}),e[16]=k,e[17]=o):o=e[17];let n;e[18]!==Q||e[19]!==o?(n=t.jsx(se,{"aria-invalid":Q,id:"size",max:16384,min:1,type:"number",...o}),e[18]=Q,e[19]=o,e[20]=n):n=e[20];let m;e[21]!==l.size?(m=t.jsx(M,{errors:[l.size]}),e[21]=l.size,e[22]=m):m=e[22];let c;e[23]!==n||e[24]!==m?(c=t.jsxs($,{children:[N,n,m]}),e[23]=n,e[24]=m,e[25]=c):c=e[25];let _;e[26]===Symbol.for("react.memo_cache_sentinel")?(_=t.jsx(O,{children:t.jsx("label",{htmlFor:"availabilityZone",children:"Availability Zone"})}),e[26]=_):_=e[26];let u;e[27]!==Z||e[28]!==l.availabilityZone?(u=G=>{const{field:H}=G;return t.jsxs(re,{onValueChange:J=>H.onChange(J),value:H.value??"",children:[t.jsx(oe,{"aria-invalid":!!l.availabilityZone,className:"w-full",id:"availabilityZone",children:t.jsx(ne,{})}),t.jsx(me,{children:Z.map(de)})]})},e[27]=Z,e[28]=l.availabilityZone,e[29]=u):u=e[29];let f;e[30]!==E||e[31]!==u?(f=t.jsx(ee,{control:E,name:"availabilityZone",render:u}),e[30]=E,e[31]=u,e[32]=f):f=e[32];let d;e[33]!==l.availabilityZone?(d=t.jsx(M,{errors:[l.availabilityZone]}),e[33]=l.availabilityZone,e[34]=d):d=e[34];let b;e[35]!==f||e[36]!==d?(b=t.jsxs($,{children:[_,f,d]}),e[35]=f,e[36]=d,e[37]=b):b=e[37];const R=w||i.isPending;let v;e[38]!==a?(v=()=>a({to:"/ec2/describe-volumes"}),e[38]=a,e[39]=v):v=e[39];let x;e[40]!==R||e[41]!==v?(x=t.jsx(L,{disabled:R,onClick:v,type:"button",variant:"outline",children:"Cancel"}),e[40]=R,e[41]=v,e[42]=x):x=e[42];const T=w||i.isPending,D=w||i.isPending?"Creating…":"Create Volume";let y;e[43]!==T||e[44]!==D?(y=t.jsx(L,{disabled:T,type:"submit",children:D}),e[43]=T,e[44]=D,e[45]=y):y=e[45];let p;e[46]!==x||e[47]!==y?(p=t.jsxs("div",{className:"flex gap-2",children:[x,y]}),e[46]=x,e[47]=y,e[48]=p):p=e[48];let h;e[49]!==c||e[50]!==b||e[51]!==p||e[52]!==r?(h=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:r,children:[c,b,p]}),e[49]=c,e[50]=b,e[51]=p,e[52]=r,e[53]=h):h=e[53];let A;return e[54]!==h||e[55]!==s?(A=t.jsxs(t.Fragment,{children:[z,F,s,h]}),e[54]=h,e[55]=s,e[56]=A):A=e[56],A}function de(e){return t.jsx(ce,{value:e.ZoneName??"",children:e.ZoneName},e.ZoneName)}export{Fe as component};
|
||||
import{c as H,u as M,l as O,j as i}from"./index.js";import{a as $,u as q,C as J}from"./zod.js";import{u as K}from"./useSuspenseQuery.js";import{B as U}from"./back-link.js";import{E as W}from"./error-banner.js";import{F as X}from"./form-actions.js";import{P as Y}from"./page-heading.js";import{F as Q,a as R,b as T}from"./field.js";import{I as ee}from"./input.js";import{S as ie,a as te,b as le,c as ae,d as se}from"./select.js";import{a as re}from"./ec2.js";import{a as oe}from"./ec22.js";import"./check.js";function ge(){const e=H.c(52),a=M(),{data:_}=K(O),t=re();let y;e[0]!==_.AvailabilityZones?(y=_.AvailabilityZones??[],e[0]=_.AvailabilityZones,e[1]=y):y=e[1];const h=y,N=h[0]?.ZoneName??"";let j;e[2]===Symbol.for("react.memo_cache_sentinel")?(j=$(oe),e[2]=j):j=e[2];let Z;e[3]!==N?(Z={resolver:j,defaultValues:{size:1,availabilityZone:N}},e[3]=N,e[4]=Z):Z=e[4];const{control:V,handleSubmit:P,register:B,formState:D}=q(Z),{errors:l,isSubmitting:E}=D;let g;e[5]!==t||e[6]!==a?(g=async I=>{await t.mutateAsync(I),a({to:"/ec2/describe-volumes"})},e[5]=t,e[6]=a,e[7]=g):g=e[7];const k=g;let S,C;e[8]===Symbol.for("react.memo_cache_sentinel")?(S=i.jsx(U,{to:"/ec2/describe-volumes",children:"Back to volumes"}),C=i.jsx(Y,{title:"Create Volume"}),e[8]=S,e[9]=C):(S=e[8],C=e[9]);let s;e[10]!==t.error?(s=t.error&&i.jsx(W,{error:t.error,msg:"Failed to create volume"}),e[10]=t.error,e[11]=s):s=e[11];let r;e[12]!==P||e[13]!==k?(r=P(k),e[12]=P,e[13]=k,e[14]=r):r=e[14];let F;e[15]===Symbol.for("react.memo_cache_sentinel")?(F=i.jsx(R,{children:i.jsx("label",{htmlFor:"size",children:"Size (GiB)"})}),e[15]=F):F=e[15];const w=!!l.size;let o;e[16]!==B?(o=B("size",{valueAsNumber:!0}),e[16]=B,e[17]=o):o=e[17];let n;e[18]!==w||e[19]!==o?(n=i.jsx(ee,{"aria-invalid":w,id:"size",max:16384,min:1,type:"number",...o}),e[18]=w,e[19]=o,e[20]=n):n=e[20];let m;e[21]!==l.size?(m=i.jsx(Q,{errors:[l.size]}),e[21]=l.size,e[22]=m):m=e[22];let c;e[23]!==n||e[24]!==m?(c=i.jsxs(T,{children:[F,n,m]}),e[23]=n,e[24]=m,e[25]=c):c=e[25];let z;e[26]===Symbol.for("react.memo_cache_sentinel")?(z=i.jsx(R,{children:i.jsx("label",{htmlFor:"availabilityZone",children:"Availability Zone"})}),e[26]=z):z=e[26];let u;e[27]!==h||e[28]!==l.availabilityZone?(u=I=>{const{field:L}=I;return i.jsxs(ie,{onValueChange:G=>L.onChange(G),value:L.value??"",children:[i.jsx(te,{"aria-invalid":!!l.availabilityZone,className:"w-full",id:"availabilityZone",children:i.jsx(le,{})}),i.jsx(ae,{children:h.map(ne)})]})},e[27]=h,e[28]=l.availabilityZone,e[29]=u):u=e[29];let f;e[30]!==V||e[31]!==u?(f=i.jsx(J,{control:V,name:"availabilityZone",render:u}),e[30]=V,e[31]=u,e[32]=f):f=e[32];let d;e[33]!==l.availabilityZone?(d=i.jsx(Q,{errors:[l.availabilityZone]}),e[33]=l.availabilityZone,e[34]=d):d=e[34];let b;e[35]!==f||e[36]!==d?(b=i.jsxs(T,{children:[z,f,d]}),e[35]=f,e[36]=d,e[37]=b):b=e[37];let v;e[38]!==a?(v=()=>a({to:"/ec2/describe-volumes"}),e[38]=a,e[39]=v):v=e[39];let x;e[40]!==t.isPending||e[41]!==E||e[42]!==v?(x=i.jsx(X,{isPending:t.isPending,isSubmitting:E,onCancel:v,pendingLabel:"Creating\\u2026",submitLabel:"Create Volume"}),e[40]=t.isPending,e[41]=E,e[42]=v,e[43]=x):x=e[43];let p;e[44]!==c||e[45]!==b||e[46]!==x||e[47]!==r?(p=i.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:r,children:[c,b,x]}),e[44]=c,e[45]=b,e[46]=x,e[47]=r,e[48]=p):p=e[48];let A;return e[49]!==p||e[50]!==s?(A=i.jsxs(i.Fragment,{children:[S,C,s,p]}),e[49]=p,e[50]=s,e[51]=A):A=e[51],A}function ne(e){return i.jsx(se,{value:e.ZoneName??"",children:e.ZoneName},e.ZoneName)}export{ge as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as M,u as T,j as t,B as w}from"./index.js";import{a as q,u as z}from"./zod.js";import{B as G}from"./back-link.js";import{E as J}from"./error-banner.js";import{P as K}from"./page-heading.js";import{F as O,b as $,a as A}from"./field.js";import{I as D}from"./input.js";import{u as Q}from"./ec2.js";import{c as U}from"./ec22.js";function ce(){const e=M.c(46),r=T(),s=Q();let j;e[0]===Symbol.for("react.memo_cache_sentinel")?(j={resolver:q(U),defaultValues:{cidrBlock:"10.0.0.0/16",name:""}},e[0]=j):j=e[0];const{handleSubmit:V,register:l,formState:H}=z(j),{errors:B,isSubmitting:F}=H;let g;e[1]!==s||e[2]!==r?(g=async L=>{const R=(await s.mutateAsync(L)).Vpc?.VpcId;r(R?{to:"/ec2/describe-vpcs/$id",params:{id:R}}:{to:"/ec2/describe-vpcs"})},e[1]=s,e[2]=r,e[3]=g):g=e[3];const P=g;let k,v;e[4]===Symbol.for("react.memo_cache_sentinel")?(k=t.jsx(G,{to:"/ec2/describe-vpcs",children:"Back to VPCs"}),v=t.jsx(K,{title:"Create VPC"}),e[4]=k,e[5]=v):(k=e[4],v=e[5]);let i;e[6]!==s.error?(i=s.error&&t.jsx(J,{error:s.error,msg:"Failed to create VPC"}),e[6]=s.error,e[7]=i):i=e[7];let c;e[8]!==V||e[9]!==P?(c=V(P),e[8]=V,e[9]=P,e[10]=c):c=e[10];let C;e[11]===Symbol.for("react.memo_cache_sentinel")?(C=t.jsx(A,{children:t.jsx("label",{htmlFor:"cidrBlock",children:"CIDR Block"})}),e[11]=C):C=e[11];const _=!!B.cidrBlock;let o;e[12]!==l?(o=l("cidrBlock"),e[12]=l,e[13]=o):o=e[13];let a;e[14]!==_||e[15]!==o?(a=t.jsx(D,{"aria-invalid":_,id:"cidrBlock",placeholder:"10.0.0.0/16",...o}),e[14]=_,e[15]=o,e[16]=a):a=e[16];let n;e[17]!==B.cidrBlock?(n=t.jsx(O,{errors:[B.cidrBlock]}),e[17]=B.cidrBlock,e[18]=n):n=e[18];let m;e[19]!==a||e[20]!==n?(m=t.jsxs($,{children:[C,a,n]}),e[19]=a,e[20]=n,e[21]=m):m=e[21];let y;e[22]===Symbol.for("react.memo_cache_sentinel")?(y=t.jsx(A,{children:t.jsx("label",{htmlFor:"name",children:"Name (optional)"})}),e[22]=y):y=e[22];let d;e[23]!==l?(d=l("name"),e[23]=l,e[24]=d):d=e[24];let f;e[25]!==d?(f=t.jsxs($,{children:[y,t.jsx(D,{id:"name",placeholder:"my-vpc",...d})]}),e[25]=d,e[26]=f):f=e[26];const E=F||s.isPending;let p;e[27]!==r?(p=()=>r({to:"/ec2/describe-vpcs"}),e[27]=r,e[28]=p):p=e[28];let u;e[29]!==E||e[30]!==p?(u=t.jsx(w,{disabled:E,onClick:p,type:"button",variant:"outline",children:"Cancel"}),e[29]=E,e[30]=p,e[31]=u):u=e[31];const I=F||s.isPending,N=F||s.isPending?"Creating…":"Create VPC";let x;e[32]!==I||e[33]!==N?(x=t.jsx(w,{disabled:I,type:"submit",children:N}),e[32]=I,e[33]=N,e[34]=x):x=e[34];let h;e[35]!==u||e[36]!==x?(h=t.jsxs("div",{className:"flex gap-2",children:[u,x]}),e[35]=u,e[36]=x,e[37]=h):h=e[37];let b;e[38]!==m||e[39]!==f||e[40]!==h||e[41]!==c?(b=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:c,children:[m,f,h]}),e[38]=m,e[39]=f,e[40]=h,e[41]=c,e[42]=b):b=e[42];let S;return e[43]!==b||e[44]!==i?(S=t.jsxs(t.Fragment,{children:[k,v,i,b]}),e[43]=b,e[44]=i,e[45]=S):S=e[45],S}export{ce as component};
|
||||
import{c as R,u as w,j as t}from"./index.js";import{a as A,u as $}from"./zod.js";import{B as D}from"./back-link.js";import{E as H}from"./error-banner.js";import{F as M}from"./form-actions.js";import{P as T}from"./page-heading.js";import{F as q,b as _,a as E}from"./field.js";import{I}from"./input.js";import{u as z}from"./ec2.js";import{c as G}from"./ec22.js";function re(){const e=R.c(41),s=w(),r=z();let h;e[0]===Symbol.for("react.memo_cache_sentinel")?(h={resolver:A(G),defaultValues:{cidrBlock:"10.0.0.0/16",name:""}},e[0]=h):h=e[0];const{handleSubmit:v,register:i,formState:L}=$(h),{errors:b,isSubmitting:P}=L;let j;e[1]!==r||e[2]!==s?(j=async N=>{const y=(await r.mutateAsync(N)).Vpc?.VpcId;s(y?{to:"/ec2/describe-vpcs/$id",params:{id:y}}:{to:"/ec2/describe-vpcs"})},e[1]=r,e[2]=s,e[3]=j):j=e[3];const S=j;let g,B;e[4]===Symbol.for("react.memo_cache_sentinel")?(g=t.jsx(D,{to:"/ec2/describe-vpcs",children:"Back to VPCs"}),B=t.jsx(T,{title:"Create VPC"}),e[4]=g,e[5]=B):(g=e[4],B=e[5]);let l;e[6]!==r.error?(l=r.error&&t.jsx(H,{error:r.error,msg:"Failed to create VPC"}),e[6]=r.error,e[7]=l):l=e[7];let o;e[8]!==v||e[9]!==S?(o=v(S),e[8]=v,e[9]=S,e[10]=o):o=e[10];let k;e[11]===Symbol.for("react.memo_cache_sentinel")?(k=t.jsx(E,{children:t.jsx("label",{htmlFor:"cidrBlock",children:"CIDR Block"})}),e[11]=k):k=e[11];const V=!!b.cidrBlock;let c;e[12]!==i?(c=i("cidrBlock"),e[12]=i,e[13]=c):c=e[13];let a;e[14]!==V||e[15]!==c?(a=t.jsx(I,{"aria-invalid":V,id:"cidrBlock",placeholder:"10.0.0.0/16",...c}),e[14]=V,e[15]=c,e[16]=a):a=e[16];let n;e[17]!==b.cidrBlock?(n=t.jsx(q,{errors:[b.cidrBlock]}),e[17]=b.cidrBlock,e[18]=n):n=e[18];let m;e[19]!==a||e[20]!==n?(m=t.jsxs(_,{children:[k,a,n]}),e[19]=a,e[20]=n,e[21]=m):m=e[21];let C;e[22]===Symbol.for("react.memo_cache_sentinel")?(C=t.jsx(E,{children:t.jsx("label",{htmlFor:"name",children:"Name (optional)"})}),e[22]=C):C=e[22];let d;e[23]!==i?(d=i("name"),e[23]=i,e[24]=d):d=e[24];let f;e[25]!==d?(f=t.jsxs(_,{children:[C,t.jsx(I,{id:"name",placeholder:"my-vpc",...d})]}),e[25]=d,e[26]=f):f=e[26];let p;e[27]!==s?(p=()=>s({to:"/ec2/describe-vpcs"}),e[27]=s,e[28]=p):p=e[28];let u;e[29]!==r.isPending||e[30]!==P||e[31]!==p?(u=t.jsx(M,{isPending:r.isPending,isSubmitting:P,onCancel:p,pendingLabel:"Creating\\u2026",submitLabel:"Create VPC"}),e[29]=r.isPending,e[30]=P,e[31]=p,e[32]=u):u=e[32];let x;e[33]!==m||e[34]!==f||e[35]!==u||e[36]!==o?(x=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:o,children:[m,f,u]}),e[33]=m,e[34]=f,e[35]=u,e[36]=o,e[37]=x):x=e[37];let F;return e[38]!==x||e[39]!==l?(F=t.jsxs(t.Fragment,{children:[g,B,l,x]}),e[38]=x,e[39]=l,e[40]=F):F=e[40],F}export{re as component};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import{c as j,j as l}from"./index.js";import{a as p,c as A,b as C,e as u,d as b,h as E,f as R,g as _}from"./alert-dialog.js";function F(h){const e=j.c(19),{open:d,onOpenChange:f,title:D,description:g,isPending:r,onConfirm:m}=h;let t;e[0]!==D?(t=l.jsx(p,{children:D}),e[0]=D,e[1]=t):t=e[1];let i;e[2]!==g?(i=l.jsx(A,{children:g}),e[2]=g,e[3]=i):i=e[3];let s;e[4]!==t||e[5]!==i?(s=l.jsxs(C,{children:[t,i]}),e[4]=t,e[5]=i,e[6]=s):s=e[6];let a;e[7]===Symbol.for("react.memo_cache_sentinel")?(a=l.jsx(u,{children:"Cancel"}),e[7]=a):a=e[7];const x=r?"Deleting…":"Delete";let n;e[8]!==r||e[9]!==m||e[10]!==x?(n=l.jsxs(b,{children:[a,l.jsx(E,{disabled:r,onClick:m,children:x})]}),e[8]=r,e[9]=m,e[10]=x,e[11]=n):n=e[11];let o;e[12]!==s||e[13]!==n?(o=l.jsxs(R,{children:[s,n]}),e[12]=s,e[13]=n,e[14]=o):o=e[14];let c;return e[15]!==f||e[16]!==d||e[17]!==o?(c=l.jsx(_,{onOpenChange:f,open:d,children:o}),e[15]=f,e[16]=d,e[17]=o,e[18]=c):c=e[18],c}export{F as D};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{k as o}from"./index.js";const a=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],e=o("download",a);export{e as D};
|
||||
import{J as o}from"./index.js";const a=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],e=o("download",a);export{e as D};
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{o as i,f as e,ao as a,ap as s,aq as t}from"./index.js";const r=i({imageId:e("Please select an Image"),instanceType:e("Please select an instance type"),keyName:e("Please select a key pair"),subnetId:e().optional(),count:s("Instance count must be a whole number").min(1,"Instance count must be at least 1")}),c=i({keyName:e().min(1,"Key name is required").max(255,"Key name must be 255 characters or less").regex(/^[\w\s._\-:/()#,@[\]+=&;{}!$*]+$/,"Key name contains invalid characters")}),m=i({keyName:e().min(1,"Key name is required").max(255,"Key name must be 255 characters or less").regex(/^[\w\s._\-:/()#,@[\]+=&;{}!$*]+$/,"Key name contains invalid characters"),publicKeyMaterial:e().min(1,"Public key is required").refine(n=>n.trim().length>0,"Public key cannot be empty")}),l=i({size:a().int("Size must be a whole number").min(1,"Size must be at least 1 GiB").max(16384,"Size must be at most 16384 GiB"),availabilityZone:e().min(1,"Availability zone is required")}),u=i({size:a().int("Size must be a whole number").min(1,"Size must be at least 1 GiB")}),d=i({volumeId:e().min(1,"Volume is required"),description:e().optional()});i({sourceSnapshotId:e().min(1,"Source snapshot is required"),sourceRegion:e().min(1,"Source region is required"),description:e().optional()});i({volumeId:e().min(1,"Volume is required"),instanceId:e().min(1,"Instance is required"),device:e().optional()});i({volumeId:e().min(1,"Volume is required"),instanceId:e().optional(),force:t().optional()});i({instanceType:e().min(1,"Instance type is required")});i({name:e().min(1,"Name is required"),description:e().optional()});const b=i({vpcId:e().min(1,"VPC is required"),cidrBlock:e().min(1,"CIDR block is required").regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/,"Must be a valid CIDR block (e.g. 10.0.1.0/24)"),availabilityZone:e().optional()}),p=i({cidrBlock:e().min(1,"CIDR block is required").regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/,"Must be a valid CIDR block (e.g. 10.0.0.0/16)"),name:e().optional()});export{l as a,b,p as c,d,c as e,r as f,m as i,u as m};
|
||||
import{o as i,k as e,aN as a,aO as s,aP as t}from"./index.js";const r=i({imageId:e("Please select an Image"),instanceType:e("Please select an instance type"),keyName:e("Please select a key pair"),subnetId:e().optional(),count:s("Instance count must be a whole number").min(1,"Instance count must be at least 1")}),c=i({keyName:e().min(1,"Key name is required").max(255,"Key name must be 255 characters or less").regex(/^[\w\s._\-:/()#,@[\]+=&;{}!$*]+$/,"Key name contains invalid characters")}),m=i({keyName:e().min(1,"Key name is required").max(255,"Key name must be 255 characters or less").regex(/^[\w\s._\-:/()#,@[\]+=&;{}!$*]+$/,"Key name contains invalid characters"),publicKeyMaterial:e().min(1,"Public key is required").refine(n=>n.trim().length>0,"Public key cannot be empty")}),l=i({size:a().int("Size must be a whole number").min(1,"Size must be at least 1 GiB").max(16384,"Size must be at most 16384 GiB"),availabilityZone:e().min(1,"Availability zone is required")}),u=i({size:a().int("Size must be a whole number").min(1,"Size must be at least 1 GiB")}),d=i({volumeId:e().min(1,"Volume is required"),description:e().optional()});i({sourceSnapshotId:e().min(1,"Source snapshot is required"),sourceRegion:e().min(1,"Source region is required"),description:e().optional()});i({volumeId:e().min(1,"Volume is required"),instanceId:e().min(1,"Instance is required"),device:e().optional()});i({volumeId:e().min(1,"Volume is required"),instanceId:e().optional(),force:t().optional()});i({instanceType:e().min(1,"Instance type is required")});i({name:e().min(1,"Name is required"),description:e().optional()});const b=i({vpcId:e().min(1,"VPC is required"),cidrBlock:e().min(1,"CIDR block is required").regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/,"Must be a valid CIDR block (e.g. 10.0.1.0/24)"),availabilityZone:e().optional()}),p=i({cidrBlock:e().min(1,"CIDR block is required").regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/,"Must be a valid CIDR block (e.g. 10.0.0.0/16)"),name:e().optional()});export{l as a,b,p as c,d,c as e,r as f,m as i,u as m};
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{cs as d,ct as m,cu as c,cv as p,cw as l,aj as b,r as h,cx as v,cy as f,c as y,j as u}from"./index.js";var x=class extends d{#e;#i=void 0;#t;#s;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#r()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){const t=this.options;this.options=this.#e.defaultMutationOptions(e),m(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#t,observer:this}),t?.mutationKey&&this.options.mutationKey&&c(t.mutationKey)!==c(this.options.mutationKey)?this.reset():this.#t?.state.status==="pending"&&this.#t.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#t?.removeObserver(this)}onMutationUpdate(e){this.#r(),this.#o(e)}getCurrentResult(){return this.#i}reset(){this.#t?.removeObserver(this),this.#t=void 0,this.#r(),this.#o()}mutate(e,t){return this.#s=t,this.#t?.removeObserver(this),this.#t=this.#e.getMutationCache().build(this.#e,this.options),this.#t.addObserver(this),this.#t.execute(e)}#r(){const e=this.#t?.state??p();this.#i={...e,isPending:e.status==="pending",isSuccess:e.status==="success",isError:e.status==="error",isIdle:e.status==="idle",mutate:this.mutate,reset:this.reset}}#o(e){l.batch(()=>{if(this.#s&&this.hasListeners()){const t=this.#i.variables,r=this.#i.context,s={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type==="success"){try{this.#s.onSuccess?.(e.data,t,r,s)}catch(i){Promise.reject(i)}try{this.#s.onSettled?.(e.data,null,t,r,s)}catch(i){Promise.reject(i)}}else if(e?.type==="error"){try{this.#s.onError?.(e.error,t,r,s)}catch(i){Promise.reject(i)}try{this.#s.onSettled?.(void 0,e.error,t,r,s)}catch(i){Promise.reject(i)}}}this.listeners.forEach(t=>{t(this.#i)})})}};function E(e,t){const r=b(),[s]=h.useState(()=>new x(r,e));h.useEffect(()=>{s.setOptions(e)},[s,e]);const i=h.useSyncExternalStore(h.useCallback(o=>s.subscribe(l.batchCalls(o)),[s]),()=>s.getCurrentResult(),()=>s.getCurrentResult()),n=h.useCallback((o,a)=>{s.mutate(o,a).catch(v)},[s]);if(i.error&&f(s.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:n,mutateAsync:i.mutate}}function g(e){const t=y.c(7),{msg:r,error:s}=e;let i;s&&(i=s.name!=="Error"?s.name:s.message);let n;t[0]!==r?(n=r&&u.jsx("h2",{className:"text-destructive text-sm",children:r}),t[0]=r,t[1]=n):n=t[1];let o;t[2]!==i?(o=i&&u.jsx("p",{className:"text-destructive text-sm",children:i}),t[2]=i,t[3]=o):o=t[3];let a;return t[4]!==n||t[5]!==o?(a=u.jsxs("div",{className:"mb-6 max-w-4xl rounded-md border border-destructive bg-destructive/10 p-4",children:[n,o]}),t[4]=n,t[5]=o,t[6]=a):a=t[6],a}export{g as E,E as u};
|
||||
import{ba as d,bd as m,bp as c,bq as b,bn as l,aq as p,r as h,bf as v,bo as f,c as y,j as u}from"./index.js";var x=class extends d{#e;#i=void 0;#t;#s;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#r()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){const t=this.options;this.options=this.#e.defaultMutationOptions(e),m(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#t,observer:this}),t?.mutationKey&&this.options.mutationKey&&c(t.mutationKey)!==c(this.options.mutationKey)?this.reset():this.#t?.state.status==="pending"&&this.#t.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#t?.removeObserver(this)}onMutationUpdate(e){this.#r(),this.#o(e)}getCurrentResult(){return this.#i}reset(){this.#t?.removeObserver(this),this.#t=void 0,this.#r(),this.#o()}mutate(e,t){return this.#s=t,this.#t?.removeObserver(this),this.#t=this.#e.getMutationCache().build(this.#e,this.options),this.#t.addObserver(this),this.#t.execute(e)}#r(){const e=this.#t?.state??b();this.#i={...e,isPending:e.status==="pending",isSuccess:e.status==="success",isError:e.status==="error",isIdle:e.status==="idle",mutate:this.mutate,reset:this.reset}}#o(e){l.batch(()=>{if(this.#s&&this.hasListeners()){const t=this.#i.variables,r=this.#i.context,s={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type==="success"){try{this.#s.onSuccess?.(e.data,t,r,s)}catch(i){Promise.reject(i)}try{this.#s.onSettled?.(e.data,null,t,r,s)}catch(i){Promise.reject(i)}}else if(e?.type==="error"){try{this.#s.onError?.(e.error,t,r,s)}catch(i){Promise.reject(i)}try{this.#s.onSettled?.(void 0,e.error,t,r,s)}catch(i){Promise.reject(i)}}}this.listeners.forEach(t=>{t(this.#i)})})}};function E(e,t){const r=p(),[s]=h.useState(()=>new x(r,e));h.useEffect(()=>{s.setOptions(e)},[s,e]);const i=h.useSyncExternalStore(h.useCallback(o=>s.subscribe(l.batchCalls(o)),[s]),()=>s.getCurrentResult(),()=>s.getCurrentResult()),n=h.useCallback((o,a)=>{s.mutate(o,a).catch(v)},[s]);if(i.error&&f(s.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:n,mutateAsync:i.mutate}}function g(e){const t=y.c(7),{msg:r,error:s}=e;let i;s&&(i=s.name!=="Error"?s.name:s.message);let n;t[0]!==r?(n=r&&u.jsx("h2",{className:"text-destructive text-sm",children:r}),t[0]=r,t[1]=n):n=t[1];let o;t[2]!==i?(o=i&&u.jsx("p",{className:"text-destructive text-sm",children:i}),t[2]=i,t[3]=o):o=t[3];let a;return t[4]!==n||t[5]!==o?(a=u.jsxs("div",{className:"mb-6 max-w-4xl rounded-md border border-destructive bg-destructive/10 p-4",children:[n,o]}),t[4]=n,t[5]=o,t[6]=a):a=t[6],a}export{g as E,E as u};
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{c as x,j as c,B as a}from"./index.js";function f(r){const t=x.c(9),{isSubmitting:m,isPending:u,onCancel:l,submitLabel:p,pendingLabel:d}=r,e=m||u;let n;t[0]!==e||t[1]!==l?(n=c.jsx(a,{disabled:e,onClick:l,type:"button",variant:"outline",children:"Cancel"}),t[0]=e,t[1]=l,t[2]=n):n=t[2];const o=e?d:p;let s;t[3]!==e||t[4]!==o?(s=c.jsx(a,{disabled:e,type:"submit",children:o}),t[3]=e,t[4]=o,t[5]=s):s=t[5];let i;return t[6]!==n||t[7]!==s?(i=c.jsxs("div",{className:"flex gap-2",children:[n,s]}),t[6]=n,t[7]=s,t[8]=i):i=t[8],i}export{f as F};
|
||||
@@ -0,0 +1 @@
|
||||
import{N as a,ay as i,Q as o,az as d,aA as y,aB as C,aC as p,aD as A,aE as f,aF as P,aG as I,aH as U,c as r,aq as u,aI as l}from"./index.js";import{u as m}from"./error-banner.js";class K extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","AttachUserPolicy",{}).n("IAMClient","AttachUserPolicyCommand").sc(d).build(){}class D extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","CreateAccessKey",{}).n("IAMClient","CreateAccessKeyCommand").sc(y).build(){}class g extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","CreatePolicy",{}).n("IAMClient","CreatePolicyCommand").sc(C).build(){}class q extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","CreateUser",{}).n("IAMClient","CreateUserCommand").sc(p).build(){}class M extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","DeleteAccessKey",{}).n("IAMClient","DeleteAccessKeyCommand").sc(A).build(){}class S extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","DeletePolicy",{}).n("IAMClient","DeletePolicyCommand").sc(f).build(){}class $ extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","DeleteUser",{}).n("IAMClient","DeleteUserCommand").sc(P).build(){}class h extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","DetachUserPolicy",{}).n("IAMClient","DetachUserPolicyCommand").sc(I).build(){}class N extends a.classBuilder().ep(i).m(function(t,n,s,c){return[o(s,t.getEndpointParameterInstructions())]}).s("AWSIdentityManagementV20100508","UpdateAccessKey",{}).n("IAMClient","UpdateAccessKeyCommand").sc(U).build(){}function k(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:E,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","users"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function E(e){const t=new q({UserName:e.userName,Path:e.path||void 0});return l().send(t)}function z(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:v,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","users"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function v(e){const t=new $({UserName:e});return l().send(t)}function G(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:x,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","access-keys"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function x(e){const t=new D({UserName:e});return l().send(t)}function H(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:Q,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","access-keys"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function Q(e){const{userName:t,accessKeyId:n}=e,s=new M({UserName:t,AccessKeyId:n});return l().send(s)}function R(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:b,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","access-keys"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function b(e){const{userName:t,accessKeyId:n,status:s}=e,c=new N({UserName:t,AccessKeyId:n,Status:s});return l().send(c)}function j(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:B,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","policies"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function B(e){const t=new g({PolicyName:e.policyName,Description:e.description||void 0,PolicyDocument:e.policyDocument});return l().send(t)}function J(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:F,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","policies"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function F(e){const t=new S({PolicyArn:e});return l().send(t)}function L(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:w,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","attached-user-policies"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function w(e){const{userName:t,policyArn:n}=e,s=new K({UserName:t,PolicyArn:n});return l().send(s)}function O(){const e=r.c(2),t=u();let n;return e[0]!==t?(n={mutationFn:V,onSuccess:()=>{t.invalidateQueries({queryKey:["iam","attached-user-policies"]})}},e[0]=t,e[1]=n):n=e[1],m(n)}function V(e){const{userName:t,policyArn:n}=e,s=new h({UserName:t,PolicyArn:n});return l().send(s)}export{j as a,z as b,G as c,H as d,R as e,L as f,O as g,J as h,k as u};
|
||||
@@ -0,0 +1 @@
|
||||
import{o as r,k as e}from"./index.js";const c=r({userName:e().min(1,"User name is required").max(64,"User name must be 64 characters or less").regex(/^[\w+=,.@-]+$/,"User name contains invalid characters"),path:e().optional()}),i=r({policyName:e().min(1,"Policy name is required").max(128,"Policy name must be 128 characters or less"),description:e().optional(),policyDocument:e().min(1,"Policy document is required").refine(a=>{try{return JSON.parse(a),!0}catch{return!1}},{message:"Policy document must be valid JSON"})});export{i as a,c};
|
||||
@@ -1 +1 @@
|
||||
import{c as O,u as $,j as t,B as H}from"./index.js";import{a as q,u as G}from"./zod.js";import{B as J}from"./back-link.js";import{E as U}from"./error-banner.js";import{P as V}from"./page-heading.js";import{F as Q,a as R,b as z}from"./field.js";import{I as W}from"./input.js";import{T as X}from"./textarea.js";import{d as Y}from"./ec2.js";import{i as Z}from"./ec22.js";function me(){const e=O.c(53),s=$(),i=Y();let g;e[0]===Symbol.for("react.memo_cache_sentinel")?(g={resolver:q(Z)},e[0]=g):g=e[0];const{handleSubmit:_,register:l,formState:D}=G(g),{errors:r,isSubmitting:M}=D;let A;e[1]!==i||e[2]!==s?(A=async L=>{await i.mutateAsync(L),s({to:"/ec2/describe-key-pairs"})},e[1]=i,e[2]=s,e[3]=A):A=e[3];const v=A;let K,N;e[4]===Symbol.for("react.memo_cache_sentinel")?(K=t.jsx(J,{to:"/ec2/describe-key-pairs",children:"Back to key pairs"}),N=t.jsx(V,{title:"Import Key Pair"}),e[4]=K,e[5]=N):(K=e[4],N=e[5]);let a;e[6]!==i.error?(a=i.error&&t.jsx(U,{error:i.error,msg:"Failed to import key pair"}),e[6]=i.error,e[7]=a):a=e[7];let o;e[8]!==_||e[9]!==v?(o=_(v),e[8]=_,e[9]=v,e[10]=o):o=e[10];let P;e[11]===Symbol.for("react.memo_cache_sentinel")?(P=t.jsx(R,{children:t.jsx("label",{htmlFor:"keyName",children:"Key Pair Name"})}),e[11]=P):P=e[11];const I=!!r.keyName;let c;e[12]!==l?(c=l("keyName"),e[12]=l,e[13]=c):c=e[13];let n;e[14]!==I||e[15]!==c?(n=t.jsx(W,{"aria-invalid":I,id:"keyName",placeholder:"my-key-pair…",...c}),e[14]=I,e[15]=c,e[16]=n):n=e[16];let m;e[17]!==r.keyName?(m=t.jsx(Q,{errors:[r.keyName]}),e[17]=r.keyName,e[18]=m):m=e[18];let p;e[19]!==n||e[20]!==m?(p=t.jsxs(z,{children:[P,n,m]}),e[19]=n,e[20]=m,e[21]=p):p=e[21];let S,B;e[22]===Symbol.for("react.memo_cache_sentinel")?(S=t.jsx(R,{children:t.jsx("label",{htmlFor:"publicKeyMaterial",children:"Public Key"})}),B=t.jsx("p",{className:"text-muted-foreground text-xs",id:"publicKey-description",children:"Paste your OpenSSH public key (e.g. the contents of id_rsa.pub)"}),e[22]=S,e[23]=B):(S=e[22],B=e[23]);const E=!!r.publicKeyMaterial;let d;e[24]!==l?(d=l("publicKeyMaterial"),e[24]=l,e[25]=d):d=e[25];let f;e[26]!==E||e[27]!==d?(f=t.jsx(X,{"aria-invalid":E,id:"publicKeyMaterial",placeholder:"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC…",rows:8,...d}),e[26]=E,e[27]=d,e[28]=f):f=e[28];let y;e[29]!==r.publicKeyMaterial?(y=t.jsx(Q,{errors:[r.publicKeyMaterial]}),e[29]=r.publicKeyMaterial,e[30]=y):y=e[30];let u;e[31]!==f||e[32]!==y?(u=t.jsxs(z,{children:[S,B,f,y]}),e[31]=f,e[32]=y,e[33]=u):u=e[33];const C=M||i.isPending;let x;e[34]!==s?(x=()=>s({to:"/ec2/describe-key-pairs"}),e[34]=s,e[35]=x):x=e[35];let b;e[36]!==C||e[37]!==x?(b=t.jsx(H,{disabled:C,onClick:x,type:"button",variant:"outline",children:"Cancel"}),e[36]=C,e[37]=x,e[38]=b):b=e[38];const w=M||i.isPending,T=M||i.isPending?"Importing…":"Import Key Pair";let h;e[39]!==w||e[40]!==T?(h=t.jsx(H,{disabled:w,type:"submit",children:T}),e[39]=w,e[40]=T,e[41]=h):h=e[41];let j;e[42]!==b||e[43]!==h?(j=t.jsxs("div",{className:"flex gap-2",children:[b,h]}),e[42]=b,e[43]=h,e[44]=j):j=e[44];let k;e[45]!==p||e[46]!==u||e[47]!==j||e[48]!==o?(k=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:o,children:[p,u,j]}),e[45]=p,e[46]=u,e[47]=j,e[48]=o,e[49]=k):k=e[49];let F;return e[50]!==k||e[51]!==a?(F=t.jsxs(t.Fragment,{children:[K,N,a,k]}),e[50]=k,e[51]=a,e[52]=F):F=e[52],F}export{me as component};
|
||||
import{c as T,u as H,j as t}from"./index.js";import{a as Q,u as R}from"./zod.js";import{B as z}from"./back-link.js";import{E as D}from"./error-banner.js";import{F as O}from"./form-actions.js";import{P as $}from"./page-heading.js";import{F as E,a as v,b as w}from"./field.js";import{I as q}from"./input.js";import{T as G}from"./textarea.js";import{d as J}from"./ec2.js";import{i as U}from"./ec22.js";function le(){const e=T.c(48),s=H(),r=J();let j;e[0]===Symbol.for("react.memo_cache_sentinel")?(j={resolver:Q(U)},e[0]=j):j=e[0];const{handleSubmit:F,register:a,formState:C}=R(j),{errors:i,isSubmitting:_}=C;let k;e[1]!==r||e[2]!==s?(k=async L=>{await r.mutateAsync(L),s({to:"/ec2/describe-key-pairs"})},e[1]=r,e[2]=s,e[3]=k):k=e[3];const M=k;let g,A;e[4]===Symbol.for("react.memo_cache_sentinel")?(g=t.jsx(z,{to:"/ec2/describe-key-pairs",children:"Back to key pairs"}),A=t.jsx($,{title:"Import Key Pair"}),e[4]=g,e[5]=A):(g=e[4],A=e[5]);let l;e[6]!==r.error?(l=r.error&&t.jsx(D,{error:r.error,msg:"Failed to import key pair"}),e[6]=r.error,e[7]=l):l=e[7];let o;e[8]!==F||e[9]!==M?(o=F(M),e[8]=F,e[9]=M,e[10]=o):o=e[10];let K;e[11]===Symbol.for("react.memo_cache_sentinel")?(K=t.jsx(v,{children:t.jsx("label",{htmlFor:"keyName",children:"Key Pair Name"})}),e[11]=K):K=e[11];const B=!!i.keyName;let m;e[12]!==a?(m=a("keyName"),e[12]=a,e[13]=m):m=e[13];let n;e[14]!==B||e[15]!==m?(n=t.jsx(q,{"aria-invalid":B,id:"keyName",placeholder:"my-key-pair…",...m}),e[14]=B,e[15]=m,e[16]=n):n=e[16];let c;e[17]!==i.keyName?(c=t.jsx(E,{errors:[i.keyName]}),e[17]=i.keyName,e[18]=c):c=e[18];let p;e[19]!==n||e[20]!==c?(p=t.jsxs(w,{children:[K,n,c]}),e[19]=n,e[20]=c,e[21]=p):p=e[21];let P,N;e[22]===Symbol.for("react.memo_cache_sentinel")?(P=t.jsx(v,{children:t.jsx("label",{htmlFor:"publicKeyMaterial",children:"Public Key"})}),N=t.jsx("p",{className:"text-muted-foreground text-xs",id:"publicKey-description",children:"Paste your OpenSSH public key (e.g. the contents of id_rsa.pub)"}),e[22]=P,e[23]=N):(P=e[22],N=e[23]);const I=!!i.publicKeyMaterial;let f;e[24]!==a?(f=a("publicKeyMaterial"),e[24]=a,e[25]=f):f=e[25];let y;e[26]!==I||e[27]!==f?(y=t.jsx(G,{"aria-invalid":I,id:"publicKeyMaterial",placeholder:"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC…",rows:8,...f}),e[26]=I,e[27]=f,e[28]=y):y=e[28];let d;e[29]!==i.publicKeyMaterial?(d=t.jsx(E,{errors:[i.publicKeyMaterial]}),e[29]=i.publicKeyMaterial,e[30]=d):d=e[30];let u;e[31]!==y||e[32]!==d?(u=t.jsxs(w,{children:[P,N,y,d]}),e[31]=y,e[32]=d,e[33]=u):u=e[33];let b;e[34]!==s?(b=()=>s({to:"/ec2/describe-key-pairs"}),e[34]=s,e[35]=b):b=e[35];let x;e[36]!==r.isPending||e[37]!==_||e[38]!==b?(x=t.jsx(O,{isPending:r.isPending,isSubmitting:_,onCancel:b,pendingLabel:"Importing…",submitLabel:"Import Key Pair"}),e[36]=r.isPending,e[37]=_,e[38]=b,e[39]=x):x=e[39];let h;e[40]!==p||e[41]!==u||e[42]!==x||e[43]!==o?(h=t.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:o,children:[p,u,x]}),e[40]=p,e[41]=u,e[42]=x,e[43]=o,e[44]=h):h=e[44];let S;return e[45]!==h||e[46]!==l?(S=t.jsxs(t.Fragment,{children:[g,A,l,h]}),e[45]=h,e[46]=l,e[47]=S):S=e[47],S}export{le as component};
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+40
-40
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{c as m,m as l,j as t,L as n,B as c}from"./index.js";import{u as p}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as y}from"./page-heading.js";function P(){const e=m.c(4),{data:s}=p(l);let r;if(e[0]!==s.KeyPairs){const a=(s.KeyPairs||[]).toSorted(f);let i;e[2]===Symbol.for("react.memo_cache_sentinel")?(i=t.jsx(n,{to:"/ec2/import-key-pair",children:t.jsx(c,{variant:"outline",children:"Import Key Pair"})}),e[2]=i):i=e[2];let o;e[3]===Symbol.for("react.memo_cache_sentinel")?(o=t.jsx(y,{actions:t.jsxs("div",{className:"flex gap-2",children:[i,t.jsx(n,{to:"/ec2/create-key-pair",children:t.jsx(c,{children:"Create Key Pair"})})]}),title:"EC2 Key Pairs"}),e[3]=o):o=e[3],r=t.jsxs(t.Fragment,{children:[o,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(u)}):t.jsx("p",{className:"text-muted-foreground",children:"No key pairs found."})]}),e[0]=s.KeyPairs,e[1]=r}else r=e[1];return r}function u(e){return e.KeyPairId?t.jsx(d,{params:{id:e.KeyPairId},subtitle:`${e.KeyPairId} • ${e.KeyType}`,title:e.KeyName??"",to:"/ec2/describe-key-pairs/$id"},e.KeyPairId):null}function f(e,s){const r=e.KeyName?.toLowerCase()??"",a=s.KeyName?.toLowerCase()??"";return r.localeCompare(a)}export{P as component};
|
||||
import{c as o,v as c,j as t,L as u,B as m}from"./index.js";import{u as l}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as p}from"./page-heading.js";import{S}from"./state-badge.js";function $(){const e=o.c(5),{data:s}=l(c);let n;e[0]!==s.Subnets?(n=s.Subnets||[],e[0]=s.Subnets,e[1]=n):n=e[1];const a=n;let r;e[2]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(p,{actions:t.jsx(u,{to:"/ec2/create-subnet",children:t.jsx(m,{children:"Create Subnet"})}),title:"Subnets"}),e[2]=r):r=e[2];let i;return e[3]!==a?(i=t.jsxs(t.Fragment,{children:[r,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(f)}):t.jsx("p",{className:"text-muted-foreground",children:"No subnets found."})]}),e[3]=a,e[4]=i):i=e[4],i}function f(e){if(!e.SubnetId)return null;const s=e.Tags?.find(b)?.Value;return t.jsx(d,{badge:t.jsx(S,{state:e.State}),params:{id:e.SubnetId},subtitle:`${e.CidrBlock??""} • ${e.AvailabilityZone??""}`,title:s?`${e.SubnetId} (${s})`:e.SubnetId,to:"/ec2/describe-subnets/$id"},e.SubnetId)}function b(e){return e.Key==="Name"}export{$ as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as u,x as f,j as t,L as I,B as x}from"./index.js";import{u as g}from"./useSuspenseQuery.js";import{L as h}from"./list-card.js";import{P as j}from"./page-heading.js";import{S}from"./state-badge.js";function L(){const e=u.c(5),{data:o}=g(f);let s;e[0]===Symbol.for("react.memo_cache_sentinel")?(s={running:0,pending:1,stopping:2,"shutting-down":3,stopped:4,terminated:5},e[0]=s):s=e[0];const c=s;let n;if(e[1]!==o.Reservations){let a;e[3]===Symbol.for("react.memo_cache_sentinel")?(a=(m,l)=>{const d=c[m.State?.Name??""]??6,p=c[l.State?.Name??""]??6;return d!==p?d-p:(m.InstanceId??"").localeCompare(l.InstanceId??"")},e[3]=a):a=e[3];const i=(o.Reservations?.flatMap(_)||[]).toSorted(a);let r;e[4]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(j,{actions:t.jsx(I,{to:"/ec2/run-instances",children:t.jsx(x,{children:"Run Instances"})}),title:"EC2 Instances"}),e[4]=r):r=e[4],n=t.jsxs(t.Fragment,{children:[r,i.length>0?t.jsx("div",{className:"space-y-4",children:i.map(y)}):t.jsx("p",{className:"text-muted-foreground",children:"No instances found."})]}),e[1]=o.Reservations,e[2]=n}else n=e[2];return n}function y(e){return e.InstanceId?t.jsx(h,{badge:t.jsx(S,{state:e.State?.Name}),params:{id:e.InstanceId},subtitle:`${e.InstanceType} • ${e.ImageId}`,title:e.InstanceId,to:"/ec2/describe-instances/$id"},e.InstanceId):null}function _(e){return e.Instances||[]}export{L as component};
|
||||
import{c as i,C as c,j as e,L as m,B as p}from"./index.js";import{u as l}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as u}from"./page-heading.js";import{S}from"./state-badge.js";function b(){const t=i.c(5),{data:r}=l(c);let s;t[0]!==r.Snapshots?(s=r.Snapshots||[],t[0]=r.Snapshots,t[1]=s):s=t[1];const o=s;let a;t[2]===Symbol.for("react.memo_cache_sentinel")?(a=e.jsx(u,{actions:e.jsx(m,{to:"/ec2/create-snapshot",children:e.jsx(p,{children:"Create Snapshot"})}),title:"EC2 Snapshots"}),t[2]=a):a=t[2];let n;return t[3]!==o?(n=e.jsxs(e.Fragment,{children:[a,o.length>0?e.jsx("div",{className:"space-y-4",children:o.map(h)}):e.jsx("p",{className:"text-muted-foreground",children:"No snapshots found."})]}),t[3]=o,t[4]=n):n=t[4],n}function h(t){return t.SnapshotId?e.jsx(d,{badge:e.jsx(S,{state:t.State}),params:{id:t.SnapshotId},subtitle:`${t.VolumeSize} GiB • ${t.VolumeId}`,title:t.SnapshotId,to:"/ec2/describe-snapshots/$id"},t.SnapshotId):null}export{b as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as n,l as i,j as t}from"./index.js";import{u as c}from"./useSuspenseQuery.js";import{L as l}from"./list-card.js";import{P as d}from"./page-heading.js";import{S as p}from"./state-badge.js";function h(){const e=n.c(5),{data:o}=c(i);let s;e[0]!==o.Images?(s=o.Images||[],e[0]=o.Images,e[1]=s):s=e[1];const a=s;let m;e[2]===Symbol.for("react.memo_cache_sentinel")?(m=t.jsx(d,{title:"EC2 Images"}),e[2]=m):m=e[2];let r;return e[3]!==a?(r=t.jsxs(t.Fragment,{children:[m,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(u)}):t.jsx("p",{className:"text-muted-foreground",children:"No images found."})]}),e[3]=a,e[4]=r):r=e[4],r}function u(e){return e.ImageId?t.jsx(l,{badge:t.jsx(p,{state:e.State}),params:{id:e.ImageId},subtitle:e.Name,title:e.ImageId,to:"/ec2/describe-images/$id"},e.ImageId):null}export{h as component};
|
||||
import{c as m,q as l,j as t,L as n,B as c}from"./index.js";import{u as p}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as y}from"./page-heading.js";function P(){const e=m.c(4),{data:s}=p(l);let r;if(e[0]!==s.KeyPairs){const a=(s.KeyPairs||[]).toSorted(f);let i;e[2]===Symbol.for("react.memo_cache_sentinel")?(i=t.jsx(n,{to:"/ec2/import-key-pair",children:t.jsx(c,{variant:"outline",children:"Import Key Pair"})}),e[2]=i):i=e[2];let o;e[3]===Symbol.for("react.memo_cache_sentinel")?(o=t.jsx(y,{actions:t.jsxs("div",{className:"flex gap-2",children:[i,t.jsx(n,{to:"/ec2/create-key-pair",children:t.jsx(c,{children:"Create Key Pair"})})]}),title:"EC2 Key Pairs"}),e[3]=o):o=e[3],r=t.jsxs(t.Fragment,{children:[o,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(u)}):t.jsx("p",{className:"text-muted-foreground",children:"No key pairs found."})]}),e[0]=s.KeyPairs,e[1]=r}else r=e[1];return r}function u(e){return e.KeyPairId?t.jsx(d,{params:{id:e.KeyPairId},subtitle:`${e.KeyPairId} • ${e.KeyType}`,title:e.KeyName??"",to:"/ec2/describe-key-pairs/$id"},e.KeyPairId):null}function f(e,s){const r=e.KeyName?.toLowerCase()??"",a=s.KeyName?.toLowerCase()??"";return r.localeCompare(a)}export{P as component};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import{c as u,E as f,j as t,L as I,B as x}from"./index.js";import{u as g}from"./useSuspenseQuery.js";import{L as h}from"./list-card.js";import{P as j}from"./page-heading.js";import{S}from"./state-badge.js";function L(){const e=u.c(5),{data:o}=g(f);let s;e[0]===Symbol.for("react.memo_cache_sentinel")?(s={running:0,pending:1,stopping:2,"shutting-down":3,stopped:4,terminated:5},e[0]=s):s=e[0];const c=s;let n;if(e[1]!==o.Reservations){let a;e[3]===Symbol.for("react.memo_cache_sentinel")?(a=(m,l)=>{const d=c[m.State?.Name??""]??6,p=c[l.State?.Name??""]??6;return d!==p?d-p:(m.InstanceId??"").localeCompare(l.InstanceId??"")},e[3]=a):a=e[3];const i=(o.Reservations?.flatMap(_)||[]).toSorted(a);let r;e[4]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(j,{actions:t.jsx(I,{to:"/ec2/run-instances",children:t.jsx(x,{children:"Run Instances"})}),title:"EC2 Instances"}),e[4]=r):r=e[4],n=t.jsxs(t.Fragment,{children:[r,i.length>0?t.jsx("div",{className:"space-y-4",children:i.map(y)}):t.jsx("p",{className:"text-muted-foreground",children:"No instances found."})]}),e[1]=o.Reservations,e[2]=n}else n=e[2];return n}function y(e){return e.InstanceId?t.jsx(h,{badge:t.jsx(S,{state:e.State?.Name}),params:{id:e.InstanceId},subtitle:`${e.InstanceType} • ${e.ImageId}`,title:e.InstanceId,to:"/ec2/describe-instances/$id"},e.InstanceId):null}function _(e){return e.Instances||[]}export{L as component};
|
||||
@@ -0,0 +1 @@
|
||||
import{c as n,p as i,j as t}from"./index.js";import{u as c}from"./useSuspenseQuery.js";import{L as l}from"./list-card.js";import{P as d}from"./page-heading.js";import{S as p}from"./state-badge.js";function h(){const e=n.c(5),{data:o}=c(i);let s;e[0]!==o.Images?(s=o.Images||[],e[0]=o.Images,e[1]=s):s=e[1];const a=s;let m;e[2]===Symbol.for("react.memo_cache_sentinel")?(m=t.jsx(d,{title:"EC2 Images"}),e[2]=m):m=e[2];let r;return e[3]!==a?(r=t.jsxs(t.Fragment,{children:[m,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(u)}):t.jsx("p",{className:"text-muted-foreground",children:"No images found."})]}),e[3]=a,e[4]=r):r=e[4],r}function u(e){return e.ImageId?t.jsx(l,{badge:t.jsx(p,{state:e.State}),params:{id:e.ImageId},subtitle:e.Name,title:e.ImageId,to:"/ec2/describe-images/$id"},e.ImageId):null}export{h as component};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{cK as z,cL as x}from"./index.js";class T{marshaller;serializer;deserializer;serdeContext;defaultContentType;constructor({marshaller:i,serializer:n,deserializer:o,serdeContext:c,defaultContentType:y}){this.marshaller=i,this.serializer=n,this.deserializer=o,this.serdeContext=c,this.defaultContentType=y}async serializeEventStream({eventStream:i,requestSchema:n,initialRequest:o}){const c=this.marshaller,y=n.getEventStreamMember(),p=n.getMemberSchema(y),d=this.serializer,f=this.defaultContentType,h=Symbol("initialRequestMarker"),S={async*[Symbol.asyncIterator](){if(o){const r={":event-type":{type:"string",value:"initial-request"},":message-type":{type:"string",value:"event"},":content-type":{type:"string",value:f}};d.write(n,o);const t=d.flush();yield{[h]:!0,headers:r,body:t}}for await(const r of i)yield r}};return c.serialize(S,r=>{if(r[h])return{headers:r.headers,body:r.body};const t=Object.keys(r).find(s=>s!=="__type")??"",{additionalHeaders:e,body:a,eventType:l,explicitPayloadContentType:m}=this.writeEventBody(t,p,r);return{headers:{":event-type":{type:"string",value:l},":message-type":{type:"string",value:"event"},":content-type":{type:"string",value:m??f},...e},body:a}})}async deserializeEventStream({response:i,responseSchema:n,initialResponseContainer:o}){const c=this.marshaller,y=n.getEventStreamMember(),d=n.getMemberSchema(y).getMemberSchemas(),f=Symbol("initialResponseMarker"),h=c.deserialize(i.body,async t=>{const e=Object.keys(t).find(l=>l!=="__type")??"",a=t[e].body;if(e==="initial-response"){const l=await this.deserializer.read(n,a);return delete l[y],{[f]:!0,...l}}else if(e in d){const l=d[e];if(l.isStructSchema()){const m={};let u=!1;for(const[s,g]of l.structIterator()){const{eventHeader:v,eventPayload:w}=g.getMergedTraits();if(u=u||!!(v||w),w)g.isBlobSchema()?m[s]=a:g.isStringSchema()?m[s]=(this.serdeContext?.utf8Encoder??z)(a):g.isStructSchema()&&(m[s]=await this.deserializer.read(g,a));else if(v){const b=t[e].headers[s]?.value;b!=null&&(g.isNumericSchema()?b&&typeof b=="object"&&"bytes"in b?m[s]=BigInt(b.toString()):m[s]=Number(b):m[s]=b)}}if(u)return{[e]:m};if(a.byteLength===0)return{[e]:{}}}return{[e]:await this.deserializer.read(l,a)}}else return{$unknown:t}}),S=h[Symbol.asyncIterator](),r=await S.next();if(r.done)return h;if(r.value?.[f]){if(!n)throw new Error("@smithy::core/protocols - initial-response event encountered in event stream but no response schema given.");for(const[t,e]of Object.entries(r.value))o[t]=e}return{async*[Symbol.asyncIterator](){for(r?.value?.[f]||(yield r.value);;){const{done:t,value:e}=await S.next();if(t)break;yield e}}}}writeEventBody(i,n,o){const c=this.serializer;let y=i,p=null,d;const f=n.getSchema()[4].includes(i),h={};if(f){const t=n.getMemberSchema(i);if(t.isStructSchema()){for(const[e,a]of t.structIterator()){const{eventHeader:l,eventPayload:m}=a.getMergedTraits();if(m)p=e;else if(l){const u=o[i][e];let s="binary";a.isNumericSchema()?(-2)**31<=u&&u<=2**31-1?s="integer":s="long":a.isTimestampSchema()?s="timestamp":a.isStringSchema()?s="string":a.isBooleanSchema()&&(s="boolean"),u!=null&&(h[e]={type:s,value:u},delete o[i][e])}}if(p!==null){const e=t.getMemberSchema(p);e.isBlobSchema()?d="application/octet-stream":e.isStringSchema()&&(d="text/plain"),c.write(e,o[i][p])}else c.write(t,o[i])}else if(t.isUnitSchema())c.write(t,{});else throw new Error("@smithy/core/event-streams - non-struct member not supported in event stream union.")}else{const[t,e]=o[i];y=t,c.write(15,e)}const S=c.flush();return{body:typeof S=="string"?(this.serdeContext?.utf8Decoder??x)(S):S,eventType:y,explicitPayloadContentType:d,additionalHeaders:h}}}export{T as EventStreamSerde};
|
||||
import{d5 as z,d6 as x}from"./index.js";class T{marshaller;serializer;deserializer;serdeContext;defaultContentType;constructor({marshaller:i,serializer:n,deserializer:o,serdeContext:c,defaultContentType:d}){this.marshaller=i,this.serializer=n,this.deserializer=o,this.serdeContext=c,this.defaultContentType=d}async serializeEventStream({eventStream:i,requestSchema:n,initialRequest:o}){const c=this.marshaller,d=n.getEventStreamMember(),p=n.getMemberSchema(d),y=this.serializer,f=this.defaultContentType,h=Symbol("initialRequestMarker"),S={async*[Symbol.asyncIterator](){if(o){const r={":event-type":{type:"string",value:"initial-request"},":message-type":{type:"string",value:"event"},":content-type":{type:"string",value:f}};y.write(n,o);const t=y.flush();yield{[h]:!0,headers:r,body:t}}for await(const r of i)yield r}};return c.serialize(S,r=>{if(r[h])return{headers:r.headers,body:r.body};const t=Object.keys(r).find(s=>s!=="__type")??"",{additionalHeaders:e,body:a,eventType:l,explicitPayloadContentType:m}=this.writeEventBody(t,p,r);return{headers:{":event-type":{type:"string",value:l},":message-type":{type:"string",value:"event"},":content-type":{type:"string",value:m??f},...e},body:a}})}async deserializeEventStream({response:i,responseSchema:n,initialResponseContainer:o}){const c=this.marshaller,d=n.getEventStreamMember(),y=n.getMemberSchema(d).getMemberSchemas(),f=Symbol("initialResponseMarker"),h=c.deserialize(i.body,async t=>{const e=Object.keys(t).find(l=>l!=="__type")??"",a=t[e].body;if(e==="initial-response"){const l=await this.deserializer.read(n,a);return delete l[d],{[f]:!0,...l}}else if(e in y){const l=y[e];if(l.isStructSchema()){const m={};let u=!1;for(const[s,g]of l.structIterator()){const{eventHeader:v,eventPayload:w}=g.getMergedTraits();if(u=u||!!(v||w),w)g.isBlobSchema()?m[s]=a:g.isStringSchema()?m[s]=(this.serdeContext?.utf8Encoder??z)(a):g.isStructSchema()&&(m[s]=await this.deserializer.read(g,a));else if(v){const b=t[e].headers[s]?.value;b!=null&&(g.isNumericSchema()?b&&typeof b=="object"&&"bytes"in b?m[s]=BigInt(b.toString()):m[s]=Number(b):m[s]=b)}}if(u)return{[e]:m};if(a.byteLength===0)return{[e]:{}}}return{[e]:await this.deserializer.read(l,a)}}else return{$unknown:t}}),S=h[Symbol.asyncIterator](),r=await S.next();if(r.done)return h;if(r.value?.[f]){if(!n)throw new Error("@smithy::core/protocols - initial-response event encountered in event stream but no response schema given.");for(const[t,e]of Object.entries(r.value))o[t]=e}return{async*[Symbol.asyncIterator](){for(r?.value?.[f]||(yield r.value);;){const{done:t,value:e}=await S.next();if(t)break;yield e}}}}writeEventBody(i,n,o){const c=this.serializer;let d=i,p=null,y;const f=n.getSchema()[4].includes(i),h={};if(f){const t=n.getMemberSchema(i);if(t.isStructSchema()){for(const[e,a]of t.structIterator()){const{eventHeader:l,eventPayload:m}=a.getMergedTraits();if(m)p=e;else if(l){const u=o[i][e];let s="binary";a.isNumericSchema()?(-2)**31<=u&&u<=2**31-1?s="integer":s="long":a.isTimestampSchema()?s="timestamp":a.isStringSchema()?s="string":a.isBooleanSchema()&&(s="boolean"),u!=null&&(h[e]={type:s,value:u},delete o[i][e])}}if(p!==null){const e=t.getMemberSchema(p);e.isBlobSchema()?y="application/octet-stream":e.isStringSchema()&&(y="text/plain"),c.write(e,o[i][p])}else c.write(t,o[i])}else if(t.isUnitSchema())c.write(t,{});else throw new Error("@smithy/core/event-streams - non-struct member not supported in event stream union.")}else{const[t,e]=o[i];d=t,c.write(15,e)}const S=c.flush();return{body:typeof S=="string"?(this.serdeContext?.utf8Decoder??x)(S):S,eventType:d,explicitPayloadContentType:y,additionalHeaders:h}}}export{T as EventStreamSerde};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as n,e as r,j as i}from"./index.js";import{u as a}from"./useSuspenseQuery.js";import{P as p}from"./page-heading.js";function g(){const e=n.c(2),{data:t}=a(r),s=`${t?.Regions?.[0]?.RegionName} Dashboard`;let o;return e[0]!==s?(o=i.jsx(p,{title:s}),e[0]=s,e[1]=o):o=e[1],o}export{g as component};
|
||||
import{c as n,f as r,j as i}from"./index.js";import{u as a}from"./useSuspenseQuery.js";import{P as p}from"./page-heading.js";function f(){const e=n.c(2),{data:t}=a(r),s=`${t?.Regions?.[0]?.RegionName} Dashboard`;let o;return e[0]!==s?(o=i.jsx(p,{title:s}),e[0]=s,e[1]=o):o=e[1],o}export{f as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as i,d as c,j as t,L as m,B as u}from"./index.js";import{u as l}from"./useSuspenseQuery.js";import{L as p}from"./list-card.js";import{P as d}from"./page-heading.js";function h(){const e=i.c(5),{data:n}=l(c);let s;e[0]!==n.Buckets?(s=n.Buckets||[],e[0]=n.Buckets,e[1]=s):s=e[1];const a=s;let r;e[2]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(d,{actions:t.jsx(m,{to:"/s3/create-bucket",children:t.jsx(u,{children:"Create Bucket"})}),title:"S3 Buckets"}),e[2]=r):r=e[2];let o;return e[3]!==a?(o=t.jsxs(t.Fragment,{children:[r,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(f)}):t.jsx("p",{className:"text-muted-foreground",children:"No buckets found."})]}),e[3]=a,e[4]=o):o=e[4],o}function f(e){return e.Name?t.jsx(p,{params:{bucket:e.Name},subtitle:e.CreationDate?`Created: ${e.CreationDate.toLocaleString()}`:void 0,title:e.Name,to:"/s3/ls/$bucket"},e.Name):null}export{h as component};
|
||||
import{c as i,h as c,j as t,i as m,L as u,B as l}from"./index.js";import{u as p}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as f}from"./page-heading.js";function N(){const e=i.c(5),{data:n}=p(c);let s;e[0]!==n.Buckets?(s=n.Buckets||[],e[0]=n.Buckets,e[1]=s):s=e[1];const a=s;let r;e[2]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(f,{actions:t.jsx(u,{to:"/s3/create-bucket",children:t.jsx(l,{children:"Create Bucket"})}),title:"S3 Buckets"}),e[2]=r):r=e[2];let o;return e[3]!==a?(o=t.jsxs(t.Fragment,{children:[r,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(x)}):t.jsx("p",{className:"text-muted-foreground",children:"No buckets found."})]}),e[3]=a,e[4]=o):o=e[4],o}function x(e){return e.Name?t.jsx(d,{params:{bucket:e.Name},subtitle:e.CreationDate?`Created: ${m(e.CreationDate)}`:void 0,title:e.Name,to:"/s3/ls/$bucket"},e.Name):null}export{N as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as b,q as y,j as s,t as k,v as N}from"./index.js";import{u as B}from"./useSuspenseQuery.js";import{B as O}from"./back-link.js";import{E as C}from"./error-banner.js";import{P as v}from"./page-heading.js";import{a as F}from"./s3.js";import{U as K,F as E,O as U}from"./upload-button.js";import"./alert-dialog.js";import"./download.js";import"./trash-2.js";function T(){const e=b.c(31),{bucket:t}=y.useParams();let x;e[0]!==t?(x=N(t),e[0]=t,e[1]=x):x=e[1];const{data:i}=B(x),r=F();let p;e[2]!==i.Contents?(p=i.Contents||[],e[2]=i.Contents,e[3]=p):p=e[3];const o=p;let h;e[4]!==i.CommonPrefixes?(h=i.CommonPrefixes||[],e[4]=i.CommonPrefixes,e[5]=h):h=e[5];const l=h;let g;e[6]===Symbol.for("react.memo_cache_sentinel")?(g=s.jsx(O,{to:"/s3/ls",children:"Back to buckets"}),e[6]=g):g=e[6];let a;e[7]!==t||e[8]!==r.isPending||e[9]!==r.mutateAsync?(a=s.jsx(K,{bucket:t,isPending:r.isPending,onUpload:r.mutateAsync}),e[7]=t,e[8]=r.isPending,e[9]=r.mutateAsync,e[10]=a):a=e[10];let m;e[11]!==t||e[12]!==a?(m=s.jsx(v,{actions:a,title:t}),e[11]=t,e[12]=a,e[13]=m):m=e[13];let c;e[14]!==r.error?(c=r.error&&s.jsx(C,{error:r.error,msg:"Failed to upload object"}),e[14]=r.error,e[15]=c):c=e[15];let u;e[16]!==t||e[17]!==l?(u=l.length>0&&s.jsxs("div",{className:"mb-6",children:[s.jsx("h2",{className:"mb-3 font-semibold text-lg",children:"Folders"}),s.jsx("div",{className:"space-y-2",children:l.map(n=>{if(!n.Prefix)return null;const P=k(n.Prefix);return s.jsx(E,{bucketName:t,folderName:P,fullPrefix:n.Prefix},n.Prefix)})})]}),e[16]=t,e[17]=l,e[18]=u):u=e[18];let f;e[19]!==t||e[20]!==o?(f=o.length>0&&s.jsxs("div",{className:"space-y-2",children:[s.jsx("h2",{className:"mb-3 font-semibold text-lg",children:"Objects"}),o.map(n=>n.Key?s.jsx(U,{bucketName:t,displayName:n.Key,fullKey:n.Key,object:n},n.Key):null)]}),e[19]=t,e[20]=o,e[21]=f):f=e[21];let d;e[22]!==l.length||e[23]!==o.length?(d=o.length===0&&l.length===0&&s.jsx("p",{className:"text-muted-foreground",children:"No objects found in this bucket."}),e[22]=l.length,e[23]=o.length,e[24]=d):d=e[24];let j;return e[25]!==m||e[26]!==c||e[27]!==u||e[28]!==f||e[29]!==d?(j=s.jsxs(s.Fragment,{children:[g,m,c,u,f,d]}),e[25]=m,e[26]=c,e[27]=u,e[28]=f,e[29]=d,e[30]=j):j=e[30],j}export{T as component};
|
||||
import{c as b,w as y,j as s,x as k,y as N}from"./index.js";import{u as B}from"./useSuspenseQuery.js";import{B as O}from"./back-link.js";import{E as C}from"./error-banner.js";import{P as F}from"./page-heading.js";import{a as K}from"./s3.js";import{U as v,F as E,O as U}from"./upload-button.js";import"./delete-confirmation-dialog.js";import"./alert-dialog.js";import"./download.js";import"./trash-2.js";function $(){const e=b.c(31),{bucket:t}=y.useParams();let x;e[0]!==t?(x=N(t),e[0]=t,e[1]=x):x=e[1];const{data:i}=B(x),r=K();let p;e[2]!==i.Contents?(p=i.Contents||[],e[2]=i.Contents,e[3]=p):p=e[3];const n=p;let h;e[4]!==i.CommonPrefixes?(h=i.CommonPrefixes||[],e[4]=i.CommonPrefixes,e[5]=h):h=e[5];const l=h;let g;e[6]===Symbol.for("react.memo_cache_sentinel")?(g=s.jsx(O,{to:"/s3/ls",children:"Back to buckets"}),e[6]=g):g=e[6];let a;e[7]!==t||e[8]!==r.isPending||e[9]!==r.mutateAsync?(a=s.jsx(v,{bucket:t,isPending:r.isPending,onUpload:r.mutateAsync}),e[7]=t,e[8]=r.isPending,e[9]=r.mutateAsync,e[10]=a):a=e[10];let m;e[11]!==t||e[12]!==a?(m=s.jsx(F,{actions:a,title:t}),e[11]=t,e[12]=a,e[13]=m):m=e[13];let c;e[14]!==r.error?(c=r.error&&s.jsx(C,{error:r.error,msg:"Failed to upload object"}),e[14]=r.error,e[15]=c):c=e[15];let u;e[16]!==t||e[17]!==l?(u=l.length>0&&s.jsxs("div",{className:"mb-6",children:[s.jsx("h2",{className:"mb-3 font-semibold text-lg",children:"Folders"}),s.jsx("div",{className:"space-y-2",children:l.map(o=>{if(!o.Prefix)return null;const P=k(o.Prefix);return s.jsx(E,{bucketName:t,folderName:P,fullPrefix:o.Prefix},o.Prefix)})})]}),e[16]=t,e[17]=l,e[18]=u):u=e[18];let f;e[19]!==t||e[20]!==n?(f=n.length>0&&s.jsxs("div",{className:"space-y-2",children:[s.jsx("h2",{className:"mb-3 font-semibold text-lg",children:"Objects"}),n.map(o=>o.Key?s.jsx(U,{bucketName:t,displayName:o.Key,fullKey:o.Key,object:o},o.Key):null)]}),e[19]=t,e[20]=n,e[21]=f):f=e[21];let d;e[22]!==l.length||e[23]!==n.length?(d=n.length===0&&l.length===0&&s.jsx("p",{className:"text-muted-foreground",children:"No objects found in this bucket."}),e[22]=l.length,e[23]=n.length,e[24]=d):d=e[24];let j;return e[25]!==m||e[26]!==c||e[27]!==u||e[28]!==f||e[29]!==d?(j=s.jsxs(s.Fragment,{children:[g,m,c,u,f,d]}),e[25]=m,e[26]=c,e[27]=u,e[28]=f,e[29]=d,e[30]=j):j=e[30],j}export{$ as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c,h as i,j as t,L as m,B as d}from"./index.js";import{u as l}from"./useSuspenseQuery.js";import{L as p}from"./list-card.js";import{P as u}from"./page-heading.js";import{S as f}from"./state-badge.js";function I(){const e=c.c(5),{data:s}=l(i);let r;e[0]!==s.Vpcs?(r=s.Vpcs||[],e[0]=s.Vpcs,e[1]=r):r=e[1];const a=r;let n;e[2]===Symbol.for("react.memo_cache_sentinel")?(n=t.jsx(u,{actions:t.jsx(m,{to:"/ec2/create-vpc",children:t.jsx(d,{children:"Create VPC"})}),title:"VPCs"}),e[2]=n):n=e[2];let o;return e[3]!==a?(o=t.jsxs(t.Fragment,{children:[n,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(V)}):t.jsx("p",{className:"text-muted-foreground",children:"No VPCs found."})]}),e[3]=a,e[4]=o):o=e[4],o}function V(e){if(!e.VpcId)return null;const s=e.Tags?.find(x)?.Value;return t.jsx(p,{badge:t.jsx(f,{state:e.State}),params:{id:e.VpcId},subtitle:e.CidrBlock??"",title:s?`${e.VpcId} (${s})`:e.VpcId,to:"/ec2/describe-vpcs/$id"},e.VpcId)}function x(e){return e.Key==="Name"}export{I as component};
|
||||
import{c as m,z as n,j as s,i,L as c,B as l}from"./index.js";import{u}from"./useSuspenseQuery.js";import{L as p}from"./list-card.js";import{P as d}from"./page-heading.js";function C(){const e=m.c(3),{data:r}=u(n);let t;if(e[0]!==r.Users){const a=(r.Users||[]).toSorted(U);let o;e[2]===Symbol.for("react.memo_cache_sentinel")?(o=s.jsx(d,{actions:s.jsx(c,{to:"/iam/create-user",children:s.jsx(l,{children:"Create User"})}),title:"IAM Users"}),e[2]=o):o=e[2],t=s.jsxs(s.Fragment,{children:[o,a.length>0?s.jsx("div",{className:"space-y-4",children:a.map(f)}):s.jsx("p",{className:"text-muted-foreground",children:"No IAM users found."})]}),e[0]=r.Users,e[1]=t}else t=e[1];return t}function f(e){return e.UserName?s.jsx(p,{params:{userName:e.UserName},subtitle:`${e.UserId} • ${e.Path??"/"} • Created ${i(e.CreateDate)}`,title:e.UserName,to:"/iam/list-users/$userName"},e.UserName):null}function U(e,r){const t=e.UserName?.toLowerCase()??"",a=r.UserName?.toLowerCase()??"";return t.localeCompare(a)}export{C as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as m,i as l,j as t,L as n,B as u}from"./index.js";import{u as c}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as p}from"./page-heading.js";import{S as f}from"./state-badge.js";function y(){const e=m.c(5),{data:a}=c(l);let s;e[0]!==a.Volumes?(s=a.Volumes||[],e[0]=a.Volumes,e[1]=s):s=e[1];const o=s;let r;e[2]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(p,{actions:t.jsx(n,{to:"/ec2/create-volume",children:t.jsx(u,{children:"Create Volume"})}),title:"EC2 Volumes"}),e[2]=r):r=e[2];let i;return e[3]!==o?(i=t.jsxs(t.Fragment,{children:[r,o.length>0?t.jsx("div",{className:"space-y-4",children:o.map(x)}):t.jsx("p",{className:"text-muted-foreground",children:"No volumes found."})]}),e[3]=o,e[4]=i):i=e[4],i}function x(e){return e.VolumeId?t.jsx(d,{badge:t.jsx(f,{state:e.State}),params:{id:e.VolumeId},subtitle:`${e.Size} GiB • ${e.VolumeType}`,title:e.VolumeId,to:"/ec2/describe-volumes/$id"},e.VolumeId):null}export{y as component};
|
||||
import{c as n,A as a,j as t,i as c,L as m,B as l}from"./index.js";import{u as p}from"./useSuspenseQuery.js";import{L as u}from"./list-card.js";import{P as d}from"./page-heading.js";function h(){const e=n.c(3),{data:i}=p(a);let o;if(e[0]!==i.Policies){const s=(i.Policies||[]).toSorted(x);let r;e[2]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(d,{actions:t.jsx(m,{to:"/iam/create-policy",children:t.jsx(l,{children:"Create Policy"})}),title:"IAM Policies"}),e[2]=r):r=e[2],o=t.jsxs(t.Fragment,{children:[r,s.length>0?t.jsx("div",{className:"space-y-4",children:s.map(f)}):t.jsx("p",{className:"text-muted-foreground",children:"No IAM policies found."})]}),e[0]=i.Policies,e[1]=o}else o=e[1];return o}function f(e){return e.Arn?t.jsx(u,{params:{policyArn:encodeURIComponent(e.Arn)},subtitle:`${e.Arn} • Created ${c(e.CreateDate)}`,title:e.PolicyName??"",to:"/iam/list-policies/$policyArn"},e.Arn):null}function x(e,i){const o=e.PolicyName?.toLowerCase()??"",s=i.PolicyName?.toLowerCase()??"";return o.localeCompare(s)}export{h as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as o,p as c,j as t,L as u,B as m}from"./index.js";import{u as l}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as p}from"./page-heading.js";import{S}from"./state-badge.js";function $(){const e=o.c(5),{data:s}=l(c);let n;e[0]!==s.Subnets?(n=s.Subnets||[],e[0]=s.Subnets,e[1]=n):n=e[1];const a=n;let r;e[2]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(p,{actions:t.jsx(u,{to:"/ec2/create-subnet",children:t.jsx(m,{children:"Create Subnet"})}),title:"Subnets"}),e[2]=r):r=e[2];let i;return e[3]!==a?(i=t.jsxs(t.Fragment,{children:[r,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(f)}):t.jsx("p",{className:"text-muted-foreground",children:"No subnets found."})]}),e[3]=a,e[4]=i):i=e[4],i}function f(e){if(!e.SubnetId)return null;const s=e.Tags?.find(b)?.Value;return t.jsx(d,{badge:t.jsx(S,{state:e.State}),params:{id:e.SubnetId},subtitle:`${e.CidrBlock??""} • ${e.AvailabilityZone??""}`,title:s?`${e.SubnetId} (${s})`:e.SubnetId,to:"/ec2/describe-subnets/$id"},e.SubnetId)}function b(e){return e.Key==="Name"}export{$ as component};
|
||||
import{c,m as i,j as t,L as m,B as d}from"./index.js";import{u as l}from"./useSuspenseQuery.js";import{L as p}from"./list-card.js";import{P as u}from"./page-heading.js";import{S as f}from"./state-badge.js";function I(){const e=c.c(5),{data:s}=l(i);let r;e[0]!==s.Vpcs?(r=s.Vpcs||[],e[0]=s.Vpcs,e[1]=r):r=e[1];const a=r;let n;e[2]===Symbol.for("react.memo_cache_sentinel")?(n=t.jsx(u,{actions:t.jsx(m,{to:"/ec2/create-vpc",children:t.jsx(d,{children:"Create VPC"})}),title:"VPCs"}),e[2]=n):n=e[2];let o;return e[3]!==a?(o=t.jsxs(t.Fragment,{children:[n,a.length>0?t.jsx("div",{className:"space-y-4",children:a.map(V)}):t.jsx("p",{className:"text-muted-foreground",children:"No VPCs found."})]}),e[3]=a,e[4]=o):o=e[4],o}function V(e){if(!e.VpcId)return null;const s=e.Tags?.find(x)?.Value;return t.jsx(p,{badge:t.jsx(f,{state:e.State}),params:{id:e.VpcId},subtitle:e.CidrBlock??"",title:s?`${e.VpcId} (${s})`:e.VpcId,to:"/ec2/describe-vpcs/$id"},e.VpcId)}function x(e){return e.Key==="Name"}export{I as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as i,w as c,j as e,L as m,B as p}from"./index.js";import{u as l}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as u}from"./page-heading.js";import{S}from"./state-badge.js";function b(){const t=i.c(5),{data:r}=l(c);let s;t[0]!==r.Snapshots?(s=r.Snapshots||[],t[0]=r.Snapshots,t[1]=s):s=t[1];const o=s;let a;t[2]===Symbol.for("react.memo_cache_sentinel")?(a=e.jsx(u,{actions:e.jsx(m,{to:"/ec2/create-snapshot",children:e.jsx(p,{children:"Create Snapshot"})}),title:"EC2 Snapshots"}),t[2]=a):a=t[2];let n;return t[3]!==o?(n=e.jsxs(e.Fragment,{children:[a,o.length>0?e.jsx("div",{className:"space-y-4",children:o.map(h)}):e.jsx("p",{className:"text-muted-foreground",children:"No snapshots found."})]}),t[3]=o,t[4]=n):n=t[4],n}function h(t){return t.SnapshotId?e.jsx(d,{badge:e.jsx(S,{state:t.State}),params:{id:t.SnapshotId},subtitle:`${t.VolumeSize} GiB • ${t.VolumeId}`,title:t.SnapshotId,to:"/ec2/describe-snapshots/$id"},t.SnapshotId):null}export{b as component};
|
||||
import{c as m,n,j as t,L as l,B as u}from"./index.js";import{u as c}from"./useSuspenseQuery.js";import{L as d}from"./list-card.js";import{P as p}from"./page-heading.js";import{S as f}from"./state-badge.js";function y(){const e=m.c(5),{data:i}=c(n);let s;e[0]!==i.Volumes?(s=i.Volumes||[],e[0]=i.Volumes,e[1]=s):s=e[1];const o=s;let r;e[2]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx(p,{actions:t.jsx(l,{to:"/ec2/create-volume",children:t.jsx(u,{children:"Create Volume"})}),title:"EC2 Volumes"}),e[2]=r):r=e[2];let a;return e[3]!==o?(a=t.jsxs(t.Fragment,{children:[r,o.length>0?t.jsx("div",{className:"space-y-4",children:o.map(x)}):t.jsx("p",{className:"text-muted-foreground",children:"No volumes found."})]}),e[3]=o,e[4]=a):a=e[4],a}function x(e){return e.VolumeId?t.jsx(d,{badge:t.jsx(f,{state:e.State}),params:{id:e.VolumeId},subtitle:`${e.Size} GiB • ${e.VolumeType}`,title:e.VolumeId,to:"/ec2/describe-volumes/$id"},e.VolumeId):null}export{y as component};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{r as b,a_ as h,b2 as U,b3 as $,bv as A,aT as K,bt as S,j as F,c as _,a as q}from"./index.js";import{d as z,h as G,e as H,f as J,g as O,i as Q}from"./field.js";const W=b.forwardRef(function(e,a){const{render:r,className:l,id:i,name:o,value:s,disabled:I=!1,onValueChange:w,defaultValue:v,autoFocus:f=!1,...V}=e,{state:T,name:D,disabled:N,setTouched:g,setDirty:P,validityData:j,setFocused:d,setFilled:c,validationMode:k,validation:n}=z(),x=N||I,R=D??o,L={...T,disabled:x},{labelId:M}=G(),y=H({id:i});h(()=>{const t=s!=null;n.inputRef.current?.value||t&&s!==""?c(!0):t&&s===""&&c(!1)},[n.inputRef,c,s]);const p=b.useRef(null);h(()=>{f&&p.current===U($(p.current))&&d(!0)},[f,d]);const[B]=J({controlled:s,default:v,name:"FieldControl",state:"value"}),C=s!==void 0,E=C?B:void 0;return O({id:y,name:R,commit:n.commit,value:E,getValue:()=>n.inputRef.current?.value,controlRef:n.inputRef}),A("input",e,{ref:[a,p],state:L,props:[{id:y,disabled:x,name:R,ref:n.inputRef,"aria-labelledby":M,autoFocus:f,...C?{value:E}:{defaultValue:v},onChange(t){const m=t.currentTarget.value;w?.(m,K(S,t.nativeEvent)),P(m!==j.initialValue),c(m!=="")},onFocus(){d(!0)},onBlur(t){g(!0),d(!1),k==="onBlur"&&n.commit(t.currentTarget.value)},onKeyDown(t){t.currentTarget.tagName==="INPUT"&&t.key==="Enter"&&(g(!0),n.commit(t.currentTarget.value))}},n.getInputValidationProps(),V],stateAttributesMapping:Q})}),X=b.forwardRef(function(e,a){return F.jsx(W,{ref:a,...e})});function te(u){const e=_.c(10);let a,r,l;e[0]!==u?({className:a,type:l,...r}=u,e[0]=u,e[1]=a,e[2]=r,e[3]=l):(a=e[1],r=e[2],l=e[3]);let i;e[4]!==a?(i=q("h-7 w-full min-w-0 rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-xs/relaxed placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",a),e[4]=a,e[5]=i):i=e[5];let o;return e[6]!==r||e[7]!==i||e[8]!==l?(o=F.jsx(X,{className:i,"data-slot":"input",type:l,...r}),e[6]=r,e[7]=i,e[8]=l,e[9]=o):o=e[9],o}export{te as I};
|
||||
import{r as b,b1 as h,b5 as U,b6 as $,b7 as A,b8 as K,b9 as S,j as F,c as q,a as z}from"./index.js";import{u as G,d as H,e as J,f as O,g as Q,h as W}from"./field.js";const X=b.forwardRef(function(e,a){const{render:r,className:l,id:i,name:o,value:s,disabled:I=!1,onValueChange:w,defaultValue:v,autoFocus:f=!1,...V}=e,{state:D,name:N,disabled:P,setTouched:g,setDirty:T,validityData:j,setFocused:d,setFilled:c,validationMode:k,validation:n}=G(),x=P||I,R=N??o,L={...D,disabled:x},{labelId:M}=H(),y=J({id:i});h(()=>{const t=s!=null;n.inputRef.current?.value||t&&s!==""?c(!0):t&&s===""&&c(!1)},[n.inputRef,c,s]);const p=b.useRef(null);h(()=>{f&&p.current===U($(p.current))&&d(!0)},[f,d]);const[B]=O({controlled:s,default:v,name:"FieldControl",state:"value"}),C=s!==void 0,E=C?B:void 0;return Q({id:y,name:R,commit:n.commit,value:E,getValue:()=>n.inputRef.current?.value,controlRef:n.inputRef}),A("input",e,{ref:[a,p],state:L,props:[{id:y,disabled:x,name:R,ref:n.inputRef,"aria-labelledby":M,autoFocus:f,...C?{value:E}:{defaultValue:v},onChange(t){const m=t.currentTarget.value;w?.(m,K(S,t.nativeEvent)),T(m!==j.initialValue),c(m!=="")},onFocus(){d(!0)},onBlur(t){g(!0),d(!1),k==="onBlur"&&n.commit(t.currentTarget.value)},onKeyDown(t){t.currentTarget.tagName==="INPUT"&&t.key==="Enter"&&(g(!0),n.commit(t.currentTarget.value))}},n.getInputValidationProps(),V],stateAttributesMapping:W})}),Y=b.forwardRef(function(e,a){return F.jsx(X,{ref:a,...e})});function te(u){const e=q.c(10);let a,r,l;e[0]!==u?({className:a,type:l,...r}=u,e[0]=u,e[1]=a,e[2]=r,e[3]=l):(a=e[1],r=e[2],l=e[3]);let i;e[4]!==a?(i=z("h-7 w-full min-w-0 rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-xs/relaxed placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",a),e[4]=a,e[5]=i):i=e[5];let o;return e[6]!==r||e[7]!==i||e[8]!==l?(o=F.jsx(Y,{className:i,"data-slot":"input",type:l,...r}),e[6]=r,e[7]=i,e[8]=l,e[9]=o):o=e[9],o}export{te as I};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{c as b,a as v,j as a,u as P,b as D,s as L,B as R}from"./index.js";import{a as X,u as B}from"./zod.js";import{F,a as _,b as z,c as G}from"./field.js";import{I as $}from"./input.js";function O(e){const s=b.c(10);let l,t,r;s[0]!==e?({className:l,size:r,...t}=e,s[0]=e,s[1]=l,s[2]=t,s[3]=r):(l=s[1],t=s[2],r=s[3]);const c=r===void 0?"default":r;let i;s[4]!==l?(i=v("group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-card-foreground text-xs/relaxed ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",l),s[4]=l,s[5]=i):i=s[5];let o;return s[6]!==t||s[7]!==c||s[8]!==i?(o=a.jsx("div",{className:i,"data-size":c,"data-slot":"card",...t}),s[6]=t,s[7]=c,s[8]=i,s[9]=o):o=s[9],o}function T(e){const s=b.c(8);let l,t;s[0]!==e?({className:l,...t}=e,s[0]=e,s[1]=l,s[2]=t):(l=s[1],t=s[2]);let r;s[3]!==l?(r=v("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",l),s[3]=l,s[4]=r):r=s[4];let c;return s[5]!==t||s[6]!==r?(c=a.jsx("div",{className:r,"data-slot":"card-header",...t}),s[5]=t,s[6]=r,s[7]=c):c=s[7],c}function Y(e){const s=b.c(8);let l,t;s[0]!==e?({className:l,...t}=e,s[0]=e,s[1]=l,s[2]=t):(l=s[1],t=s[2]);let r;s[3]!==l?(r=v("font-medium text-sm",l),s[3]=l,s[4]=r):r=s[4];let c;return s[5]!==t||s[6]!==r?(c=a.jsx("div",{className:r,"data-slot":"card-title",...t}),s[5]=t,s[6]=r,s[7]=c):c=s[7],c}function H(e){const s=b.c(8);let l,t;s[0]!==e?({className:l,...t}=e,s[0]=e,s[1]=l,s[2]=t):(l=s[1],t=s[2]);let r;s[3]!==l?(r=v("px-4 group-data-[size=sm]/card:px-3",l),s[3]=l,s[4]=r):r=s[4];let c;return s[5]!==t||s[6]!==r?(c=a.jsx("div",{className:r,"data-slot":"card-content",...t}),s[5]=t,s[6]=r,s[7]=c):c=s[7],c}function Q(){const e=b.c(38),s=P();let l;e[0]===Symbol.for("react.memo_cache_sentinel")?(l={resolver:X(D)},e[0]=l):l=e[0];const{register:t,handleSubmit:r,formState:c}=B(l),{errors:i,isSubmitting:o}=c;let K;e[1]!==s?(K=function(M){L(M),s({to:"/"})},e[1]=s,e[2]=K):K=e[2];const C=K;let N;e[3]===Symbol.for("react.memo_cache_sentinel")?(N=a.jsx(T,{children:a.jsx(Y,{children:"AWS Credentials"})}),e[3]=N):N=e[3];let d;e[4]!==r||e[5]!==C?(d=r(C),e[4]=r,e[5]=C,e[6]=d):d=e[6];let S;e[7]===Symbol.for("react.memo_cache_sentinel")?(S=a.jsx(_,{children:a.jsx("label",{htmlFor:"accessKeyId",children:"Access Key ID"})}),e[7]=S):S=e[7];const w=!!i.accessKeyId;let n;e[8]!==t?(n=t("accessKeyId"),e[8]=t,e[9]=n):n=e[9];let m;e[10]!==w||e[11]!==n?(m=a.jsx($,{"aria-invalid":w,autoComplete:"username",id:"accessKeyId",placeholder:"AKIAIOSFODNN7EXAMPLE",...n}),e[10]=w,e[11]=n,e[12]=m):m=e[12];let f;e[13]!==i.accessKeyId?(f=a.jsx(F,{errors:[i.accessKeyId]}),e[13]=i.accessKeyId,e[14]=f):f=e[14];let u;e[15]!==m||e[16]!==f?(u=a.jsxs(z,{children:[S,m,f]}),e[15]=m,e[16]=f,e[17]=u):u=e[17];let A;e[18]===Symbol.for("react.memo_cache_sentinel")?(A=a.jsx(_,{children:a.jsx("label",{htmlFor:"secretAccessKey",children:"Secret Access Key"})}),e[18]=A):A=e[18];const E=!!i.secretAccessKey;let p;e[19]!==t?(p=t("secretAccessKey"),e[19]=t,e[20]=p):p=e[20];let x;e[21]!==E||e[22]!==p?(x=a.jsx($,{"aria-invalid":E,autoComplete:"current-password",id:"secretAccessKey",placeholder:"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",type:"password",...p}),e[21]=E,e[22]=p,e[23]=x):x=e[23];let h;e[24]!==i.secretAccessKey?(h=a.jsx(F,{errors:[i.secretAccessKey]}),e[24]=i.secretAccessKey,e[25]=h):h=e[25];let g;e[26]!==x||e[27]!==h?(g=a.jsxs(z,{children:[A,x,h]}),e[26]=x,e[27]=h,e[28]=g):g=e[28];let j;e[29]!==o?(j=a.jsx(R,{className:"w-full",disabled:o,type:"submit",children:"Sign In"}),e[29]=o,e[30]=j):j=e[30];let y;e[31]!==u||e[32]!==g||e[33]!==j?(y=a.jsxs(G,{children:[u,g,j]}),e[31]=u,e[32]=g,e[33]=j,e[34]=y):y=e[34];let I;return e[35]!==y||e[36]!==d?(I=a.jsx("div",{className:"flex flex-1 items-center justify-center",children:a.jsxs(O,{className:"w-full max-w-sm",children:[N,a.jsx(H,{children:a.jsx("form",{onSubmit:d,children:y})})]})}),e[35]=y,e[36]=d,e[37]=I):I=e[37],I}export{Q as component};
|
||||
import{c as A,a as F,j as c,u as G,r as O,b as T,d as $,s as Y,g as k,D as H,e as J,B as U}from"./index.js";import{a as W,u as q}from"./zod.js";import{F as M,a as L,b as R,c as Q}from"./field.js";import{I as X}from"./input.js";function V(e){const s=A.c(10);let t,a,l;s[0]!==e?({className:t,size:l,...a}=e,s[0]=e,s[1]=t,s[2]=a,s[3]=l):(t=s[1],a=s[2],l=s[3]);const r=l===void 0?"default":l;let i;s[4]!==t?(i=F("group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-card-foreground text-xs/relaxed ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",t),s[4]=t,s[5]=i):i=s[5];let n;return s[6]!==a||s[7]!==r||s[8]!==i?(n=c.jsx("div",{className:i,"data-size":r,"data-slot":"card",...a}),s[6]=a,s[7]=r,s[8]=i,s[9]=n):n=s[9],n}function Z(e){const s=A.c(8);let t,a;s[0]!==e?({className:t,...a}=e,s[0]=e,s[1]=t,s[2]=a):(t=s[1],a=s[2]);let l;s[3]!==t?(l=F("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",t),s[3]=t,s[4]=l):l=s[4];let r;return s[5]!==a||s[6]!==l?(r=c.jsx("div",{className:l,"data-slot":"card-header",...a}),s[5]=a,s[6]=l,s[7]=r):r=s[7],r}function ee(e){const s=A.c(8);let t,a;s[0]!==e?({className:t,...a}=e,s[0]=e,s[1]=t,s[2]=a):(t=s[1],a=s[2]);let l;s[3]!==t?(l=F("font-medium text-sm",t),s[3]=t,s[4]=l):l=s[4];let r;return s[5]!==a||s[6]!==l?(r=c.jsx("div",{className:l,"data-slot":"card-title",...a}),s[5]=a,s[6]=l,s[7]=r):r=s[7],r}function se(e){const s=A.c(8);let t,a;s[0]!==e?({className:t,...a}=e,s[0]=e,s[1]=t,s[2]=a):(t=s[1],a=s[2]);let l;s[3]!==t?(l=F("px-4 group-data-[size=sm]/card:px-3",t),s[3]=t,s[4]=l):l=s[4];let r;return s[5]!==a||s[6]!==l?(r=c.jsx("div",{className:l,"data-slot":"card-content",...a}),s[5]=a,s[6]=l,s[7]=r):r=s[7],r}function ie(){const e=A.c(44),s=G(),[t,a]=O.useState(null);let l;e[0]===Symbol.for("react.memo_cache_sentinel")?(l={resolver:W(T)},e[0]=l):l=e[0];const{register:r,handleSubmit:i,formState:n}=q(l),{errors:o,isSubmitting:I}=n;let N;e[1]!==s?(N=async function(B){a(null),$(),Y(B);try{await k().send(new H({})),s({to:"/"})}catch{J(),$(),a("Invalid credentials. Please check your Access Key ID and Secret Access Key.")}},e[1]=s,e[2]=N):N=e[2];const _=N;let v;e[3]===Symbol.for("react.memo_cache_sentinel")?(v=c.jsx(Z,{children:c.jsx(ee,{children:"AWS Credentials"})}),e[3]=v):v=e[3];let d;e[4]!==t?(d=t&&c.jsx("p",{className:"mb-4 rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:t}),e[4]=t,e[5]=d):d=e[5];let m;e[6]!==i||e[7]!==_?(m=i(_),e[6]=i,e[7]=_,e[8]=m):m=e[8];let C;e[9]===Symbol.for("react.memo_cache_sentinel")?(C=c.jsx(L,{children:c.jsx("label",{htmlFor:"accessKeyId",children:"Access Key ID"})}),e[9]=C):C=e[9];const z=!!o.accessKeyId;let f;e[10]!==r?(f=r("accessKeyId"),e[10]=r,e[11]=f):f=e[11];let u;e[12]!==z||e[13]!==f?(u=c.jsx(X,{"aria-invalid":z,autoComplete:"username",id:"accessKeyId",placeholder:"AKIAIOSFODNN7EXAMPLE",...f}),e[12]=z,e[13]=f,e[14]=u):u=e[14];let p;e[15]!==o.accessKeyId?(p=c.jsx(M,{errors:[o.accessKeyId]}),e[15]=o.accessKeyId,e[16]=p):p=e[16];let x;e[17]!==p||e[18]!==u?(x=c.jsxs(R,{children:[C,u,p]}),e[17]=p,e[18]=u,e[19]=x):x=e[19];let E;e[20]===Symbol.for("react.memo_cache_sentinel")?(E=c.jsx(L,{children:c.jsx("label",{htmlFor:"secretAccessKey",children:"Secret Access Key"})}),e[20]=E):E=e[20];const D=!!o.secretAccessKey;let h;e[21]!==r?(h=r("secretAccessKey"),e[21]=r,e[22]=h):h=e[22];let g;e[23]!==D||e[24]!==h?(g=c.jsx(X,{"aria-invalid":D,autoComplete:"current-password",id:"secretAccessKey",placeholder:"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",type:"password",...h}),e[23]=D,e[24]=h,e[25]=g):g=e[25];let y;e[26]!==o.secretAccessKey?(y=c.jsx(M,{errors:[o.secretAccessKey]}),e[26]=o.secretAccessKey,e[27]=y):y=e[27];let b;e[28]!==g||e[29]!==y?(b=c.jsxs(R,{children:[E,g,y]}),e[28]=g,e[29]=y,e[30]=b):b=e[30];const P=I?"Signing in...":"Sign In";let j;e[31]!==I||e[32]!==P?(j=c.jsx(U,{className:"w-full",disabled:I,type:"submit",children:P}),e[31]=I,e[32]=P,e[33]=j):j=e[33];let K;e[34]!==x||e[35]!==b||e[36]!==j?(K=c.jsxs(Q,{children:[x,b,j]}),e[34]=x,e[35]=b,e[36]=j,e[37]=K):K=e[37];let S;e[38]!==K||e[39]!==m?(S=c.jsx("form",{onSubmit:m,children:K}),e[38]=K,e[39]=m,e[40]=S):S=e[40];let w;return e[41]!==S||e[42]!==d?(w=c.jsx("div",{className:"flex flex-1 items-center justify-center",children:c.jsxs(V,{className:"w-full max-w-sm",children:[v,c.jsxs(se,{children:[d,S]})]})}),e[41]=S,e[42]=d,e[43]=w):w=e[43],w}export{ie as component};
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{u as $,l as R,m as U,n as q,p as H,j as e,B as C}from"./index.js";import{u as L,a as M,C as x}from"./zod.js";import{u as h}from"./useSuspenseQuery.js";import{B as W}from"./back-link.js";import{E as S}from"./error-banner.js";import{P as z}from"./page-heading.js";import{b as c,a as l,F as j}from"./field.js";import{I as G}from"./input.js";import{S as p,a as y,b as I,c as g,d as o}from"./select.js";import{f as J}from"./ec2.js";import{f as X}from"./ec22.js";import"./check.js";function oe(){const f=$(),{data:T}=h(R),{data:F}=h(U),{data:k}=h(q),{data:P}=h(H),r=J(),b=T.Images??[],N=F.KeyPairs??[],w=P.Subnets??[],d=k.InstanceTypes?.reduce((n,a)=>{const s=a.InstanceType;return s&&(n[s]=(n[s]||0)+1),n},{})??{},i=Object.keys(d).sort(),K=b[0]?.ImageId,B=N[0]?.KeyName,V=i.find(n=>n.endsWith(".nano"))??i[0],{control:m,handleSubmit:A,register:E,watch:O,formState:{errors:t,isSubmitting:v}}=L({resolver:M(X.refine(n=>n.count<=(d[n.instanceType]??1),{message:"Cannot exceed available capacity",path:["count"]})),defaultValues:{count:1,imageId:K??"",keyName:B??"",instanceType:V??""}}),u=O("instanceType"),Q=u?d[u]??1:1,D=async n=>{await r.mutateAsync(n),f({to:"/ec2/describe-instances"})};return e.jsxs(e.Fragment,{children:[e.jsx(W,{to:"/ec2/describe-instances",children:"Back to instances"}),e.jsx(z,{title:"Run EC2 Instances"}),i.length===0&&e.jsx(S,{msg:"No compute available. No new instances can be created until compute is available."}),r.error&&e.jsx(S,{error:r.error,msg:"Failed to create instance"}),e.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:A(D),children:[e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"imageId",children:"Image"})}),e.jsx(x,{control:m,name:"imageId",render:({field:n})=>{const a=b.find(s=>s.ImageId===n.value);return e.jsxs(p,{onValueChange:s=>n.onChange(s),value:n.value??"",children:[e.jsx(y,{"aria-invalid":!!t.imageId,className:"w-full",id:"imageId",children:e.jsx(I,{children:a?`${a.Name||"Unnamed"} (${a.Architecture})`:""})}),e.jsx(g,{children:b.map(s=>e.jsxs(o,{value:s.ImageId??"",children:[s.Name||"Unnamed"," (",s.Architecture,")"]},s.ImageId))})]})}}),e.jsx(j,{errors:[t.imageId]})]}),e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"instanceType",children:"Instance Type"})}),e.jsx(x,{control:m,name:"instanceType",render:({field:n})=>e.jsxs(p,{onValueChange:a=>n.onChange(a),value:n.value?n.value:"",children:[e.jsx(y,{"aria-invalid":!!t.instanceType,className:"w-full",id:"instanceType",children:e.jsx(I,{})}),e.jsx(g,{children:i.map(a=>e.jsxs(o,{value:a,children:[a," (",d[a]," available)"]},a))})]})}),e.jsx(j,{errors:[t.instanceType]})]}),e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"keyName",children:"Key Pair"})}),e.jsx(x,{control:m,name:"keyName",render:({field:n})=>e.jsxs(p,{onValueChange:a=>n.onChange(a),value:n.value?n.value:"",children:[e.jsx(y,{"aria-invalid":!!t.keyName,className:"w-full",id:"keyName",children:e.jsx(I,{})}),e.jsx(g,{children:N.map(a=>e.jsx(o,{value:a.KeyName??"",children:a.KeyName},a.KeyPairId))})]})}),e.jsx(j,{errors:[t.keyName]})]}),e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"subnetId",children:"Subnet"})}),e.jsx(x,{control:m,name:"subnetId",render:({field:n})=>e.jsxs(p,{onValueChange:a=>n.onChange(a==="none"?void 0:a),value:n.value??"none",children:[e.jsx(y,{className:"w-full",id:"subnetId",children:e.jsx(I,{})}),e.jsxs(g,{children:[e.jsx(o,{value:"none",children:"none"}),w.map(a=>e.jsx(o,{value:a.SubnetId??"",children:a.SubnetId},a.SubnetId))]})]})})]}),e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"count",children:"Number of Instances"})}),e.jsx(G,{"aria-describedby":"count-description","aria-invalid":!!t.count,id:"count",type:"number",...E("count",{valueAsNumber:!0})}),e.jsx("p",{className:"text-muted-foreground text-xs",id:"count-description",children:u&&`Available capacity for ${u}: ${Q}`}),e.jsx(j,{errors:[t.count]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(C,{disabled:v||r.isPending,onClick:()=>f({to:"/ec2/describe-instances"}),type:"button",variant:"outline",children:"Cancel"}),e.jsx(C,{disabled:v||r.isPending||i.length===0,type:"submit",children:v||r.isPending?"Creating…":"Create Instance"})]})]})]})}export{oe as component};
|
||||
import{u as $,p as q,q as R,t as U,v as H,j as e,B as C}from"./index.js";import{u as L,a as M,C as x}from"./zod.js";import{u as h}from"./useSuspenseQuery.js";import{B as W}from"./back-link.js";import{E as S}from"./error-banner.js";import{P as z}from"./page-heading.js";import{b as c,a as l,F as j}from"./field.js";import{I as G}from"./input.js";import{S as p,a as y,b as I,c as g,d as o}from"./select.js";import{f as J}from"./ec2.js";import{f as X}from"./ec22.js";import"./check.js";function oe(){const f=$(),{data:T}=h(q),{data:F}=h(R),{data:k}=h(U),{data:P}=h(H),r=J(),b=T.Images??[],N=F.KeyPairs??[],w=P.Subnets??[],d=k.InstanceTypes?.reduce((n,a)=>{const s=a.InstanceType;return s&&(n[s]=(n[s]||0)+1),n},{})??{},i=Object.keys(d).sort(),K=b[0]?.ImageId,B=N[0]?.KeyName,V=i.find(n=>n.endsWith(".nano"))??i[0],{control:m,handleSubmit:A,register:E,watch:O,formState:{errors:t,isSubmitting:v}}=L({resolver:M(X.refine(n=>n.count<=(d[n.instanceType]??1),{message:"Cannot exceed available capacity",path:["count"]})),defaultValues:{count:1,imageId:K??"",keyName:B??"",instanceType:V??""}}),u=O("instanceType"),Q=u?d[u]??1:1,D=async n=>{await r.mutateAsync(n),f({to:"/ec2/describe-instances"})};return e.jsxs(e.Fragment,{children:[e.jsx(W,{to:"/ec2/describe-instances",children:"Back to instances"}),e.jsx(z,{title:"Run EC2 Instances"}),i.length===0&&e.jsx(S,{msg:"No compute available. No new instances can be created until compute is available."}),r.error&&e.jsx(S,{error:r.error,msg:"Failed to create instance"}),e.jsxs("form",{className:"max-w-4xl space-y-6",onSubmit:A(D),children:[e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"imageId",children:"Image"})}),e.jsx(x,{control:m,name:"imageId",render:({field:n})=>{const a=b.find(s=>s.ImageId===n.value);return e.jsxs(p,{onValueChange:s=>n.onChange(s),value:n.value??"",children:[e.jsx(y,{"aria-invalid":!!t.imageId,className:"w-full",id:"imageId",children:e.jsx(I,{children:a?`${a.Name||"Unnamed"} (${a.Architecture})`:""})}),e.jsx(g,{children:b.map(s=>e.jsxs(o,{value:s.ImageId??"",children:[s.Name||"Unnamed"," (",s.Architecture,")"]},s.ImageId))})]})}}),e.jsx(j,{errors:[t.imageId]})]}),e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"instanceType",children:"Instance Type"})}),e.jsx(x,{control:m,name:"instanceType",render:({field:n})=>e.jsxs(p,{onValueChange:a=>n.onChange(a),value:n.value?n.value:"",children:[e.jsx(y,{"aria-invalid":!!t.instanceType,className:"w-full",id:"instanceType",children:e.jsx(I,{})}),e.jsx(g,{children:i.map(a=>e.jsxs(o,{value:a,children:[a," (",d[a]," available)"]},a))})]})}),e.jsx(j,{errors:[t.instanceType]})]}),e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"keyName",children:"Key Pair"})}),e.jsx(x,{control:m,name:"keyName",render:({field:n})=>e.jsxs(p,{onValueChange:a=>n.onChange(a),value:n.value?n.value:"",children:[e.jsx(y,{"aria-invalid":!!t.keyName,className:"w-full",id:"keyName",children:e.jsx(I,{})}),e.jsx(g,{children:N.map(a=>e.jsx(o,{value:a.KeyName??"",children:a.KeyName},a.KeyPairId))})]})}),e.jsx(j,{errors:[t.keyName]})]}),e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"subnetId",children:"Subnet"})}),e.jsx(x,{control:m,name:"subnetId",render:({field:n})=>e.jsxs(p,{onValueChange:a=>n.onChange(a==="none"?void 0:a),value:n.value??"none",children:[e.jsx(y,{className:"w-full",id:"subnetId",children:e.jsx(I,{})}),e.jsxs(g,{children:[e.jsx(o,{value:"none",children:"none"}),w.map(a=>e.jsx(o,{value:a.SubnetId??"",children:a.SubnetId},a.SubnetId))]})]})})]}),e.jsxs(c,{children:[e.jsx(l,{children:e.jsx("label",{htmlFor:"count",children:"Number of Instances"})}),e.jsx(G,{"aria-describedby":"count-description","aria-invalid":!!t.count,id:"count",type:"number",...E("count",{valueAsNumber:!0})}),e.jsx("p",{className:"text-muted-foreground text-xs",id:"count-description",children:u&&`Available capacity for ${u}: ${Q}`}),e.jsx(j,{errors:[t.count]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(C,{disabled:v||r.isPending,onClick:()=>f({to:"/ec2/describe-instances"}),type:"button",variant:"outline",children:"Cancel"}),e.jsx(C,{disabled:v||r.isPending||i.length===0,type:"submit",children:v||r.isPending?"Creating…":"Create Instance"})]})]})]})}export{oe as component};
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{c as g,j as l}from"./index.js";function i(d){const t=g.c(3),{state:e}=d;let r="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100";e==="running"||e==="available"||e==="completed"?r="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100":e==="stopped"||e==="error"?r="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100":e==="pending"||e==="shutting-down"||e==="stopping"?r="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100":e==="terminated"&&(r="bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400");const n=`rounded-full px-2 py-1 text-xs ${r}`;let a;return t[0]!==e||t[1]!==n?(a=l.jsx("div",{className:n,children:e}),t[0]=e,t[1]=n,t[2]=a):a=t[2],a}export{i as S};
|
||||
import{c as g,j as i}from"./index.js";function s(d){const t=g.c(3),{state:e}=d;let r="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100";e==="running"||e==="available"||e==="completed"||e==="Active"?r="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100":e==="stopped"||e==="error"?r="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100":e==="pending"||e==="shutting-down"||e==="stopping"?r="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100":e==="terminated"&&(r="bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400");const n=`rounded-full px-2 py-1 text-xs ${r}`;let a;return t[0]!==e||t[1]!==n?(a=i.jsx("div",{className:n,children:e}),t[0]=e,t[1]=n,t[2]=a):a=t[2],a}export{s as S};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{k as a}from"./index.js";const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],o=a("trash-2",t);export{o as T};
|
||||
import{J as a}from"./index.js";const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],o=a("trash-2",t);export{o as T};
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{k as u,H as g,D as j,E as b,F as y,G as k,I as C,c as w,j as e,L as v,r as f,J as D,K as N,B as A}from"./index.js";import{E as M}from"./error-banner.js";import{g as S,f as E,b as R,a as L,c as O,d as z,e as B,h as F}from"./alert-dialog.js";import{g as $,b as P,c as U}from"./s3.js";import{D as _}from"./download.js";import{T}from"./trash-2.js";const H=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}]],I=u("file",H);const G=[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",key:"1kt360"}]],K=u("folder",G);const q=[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]],V=u("upload",q),W=l=>(t,a)=>async r=>{const n=await t(r),{response:s}=n;if(g.isInstance(s)&&s.headers.expires){s.headers.expiresstring=s.headers.expires;try{j(s.headers.expires)}catch(o){a.logger?.warn(`AWS SDK Warning for ${a.clientName}::${a.commandName} response parsing (${s.headers.expires}): ${o}`),delete s.headers.expires}}return n},J={tags:["S3"],name:"s3ExpiresMiddleware",override:!0,relation:"after",toMiddleware:"deserializerMiddleware"},Z=l=>({applyToStack:t=>{t.addRelativeTo(W(),J)}});class Q extends b.classBuilder().ep({...y,Bucket:{type:"contextParams",name:"Bucket"},Key:{type:"contextParams",name:"Key"}}).m(function(t,a,r,n){return[k(r,t.getEndpointParameterInstructions()),$(r,{requestChecksumRequired:!1,requestValidationModeMember:"ChecksumMode",responseAlgorithms:["CRC64NVME","CRC32","CRC32C","SHA256","SHA1"]}),P(r),Z()]}).s("AmazonS3","GetObject",{}).n("S3Client","GetObjectCommand").sc(C).build(){}function re(l){const t=w.c(9),{folderName:a,fullPrefix:r,bucketName:n}=l;let s;t[0]!==n||t[1]!==r?(s={bucket:n,_splat:r},t[0]=n,t[1]=r,t[2]=s):s=t[2];let o;t[3]===Symbol.for("react.memo_cache_sentinel")?(o=e.jsx(K,{className:"size-5 text-muted-foreground"}),t[3]=o):o=t[3];let i;t[4]!==a?(i=e.jsx("span",{className:"font-medium",children:a}),t[4]=a,t[5]=i):i=t[5];let c;return t[6]!==s||t[7]!==i?(c=e.jsxs(v,{className:"flex items-center gap-3 rounded-lg border bg-card p-4 transition-colors hover:bg-accent",params:s,to:"/s3/ls/$bucket/$",children:[o,i]}),t[6]=s,t[7]=i,t[8]=c):c=t[8],c}function ne({object:l,bucketName:t,displayName:a,fullKey:r}){const[n,s]=f.useState(!1),o=U();async function i(){try{const m=new Q({Bucket:t,Key:r}),p=await N().send(m);if(p.Body){const x=await p.Body.transformToByteArray(),h=URL.createObjectURL(new Blob([x])),d=document.createElement("a");d.href=h,d.download=a,document.body.appendChild(d),d.click(),document.body.removeChild(d),URL.revokeObjectURL(h)}}catch{throw new Error("Failed to download object")}}async function c(){try{await o.mutateAsync({bucket:t,key:r})}finally{s(!1)}}return e.jsxs("div",{children:[o.error&&e.jsx(M,{error:o.error,msg:"Failed to delete object"}),e.jsxs("div",{className:"flex items-center justify-between rounded-lg border bg-card p-4",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(I,{className:"size-5 text-muted-foreground"}),e.jsxs("div",{children:[e.jsx("h3",{className:"font-medium",children:a}),l.LastModified&&e.jsxs("p",{className:"text-muted-foreground text-sm",children:["Last Modified: ",l.LastModified.toLocaleString()]})]})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"text-muted-foreground text-sm",children:D(l.Size||0)}),e.jsx("button",{className:"rounded-md p-2 transition-colors hover:bg-accent",onClick:i,type:"button",children:e.jsx(_,{className:"size-4"})}),e.jsx("button",{className:"rounded-md p-2 text-destructive transition-colors hover:bg-accent",onClick:()=>s(!0),type:"button",children:e.jsx(T,{className:"size-4"})})]}),e.jsx(S,{onOpenChange:s,open:n,children:e.jsxs(E,{children:[e.jsxs(R,{children:[e.jsx(L,{children:"Delete Object"}),e.jsxs(O,{children:["Are you sure you want to delete ",e.jsx("strong",{children:a}),"? This action cannot be undone."]})]}),e.jsxs(z,{children:[e.jsx(B,{children:"Cancel"}),e.jsx(F,{className:"bg-destructive hover:bg-destructive/90",onClick:c,children:o.isPending?"Deleting…":"Delete"})]})]})})]})]},l.Key)}function oe({bucket:l,prefix:t="",isPending:a,onUpload:r}){const n=f.useRef(null);async function s(o){const i=o.target.files;if(!(!i||i.length===0))try{await Promise.all(Array.from(i).map(c=>{const m=`${t}${c.name}`;return r({bucket:l,key:m,file:c})}))}finally{n.current&&(n.current.value="")}}return e.jsxs(e.Fragment,{children:[e.jsx("input",{accept:"*/*","aria-label":"Choose files to upload",className:"hidden",multiple:!0,onChange:s,ref:n,type:"file"}),e.jsxs(A,{disabled:a,onClick:()=>n.current?.click(),size:"sm",children:[e.jsx(V,{className:"mr-2 size-4"}),a?"Uploading…":"Upload Files"]})]})}export{re as F,ne as O,oe as U};
|
||||
import{J as u,K as g,M as j,N as b,P as y,Q as k,S as C,c as w,j as t,L as N,r as h,i as v,T as M,U as S,B as D}from"./index.js";import{D as R}from"./delete-confirmation-dialog.js";import{E}from"./error-banner.js";import{g as L,b as O,c as z}from"./s3.js";import{D as B}from"./download.js";import{T as P}from"./trash-2.js";const $=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}]],A=u("file",$);const F=[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",key:"1kt360"}]],U=u("folder",F);const T=[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]],_=u("upload",T),I=c=>(e,a)=>async n=>{const r=await e(n),{response:s}=r;if(g.isInstance(s)&&s.headers.expires){s.headers.expiresstring=s.headers.expires;try{j(s.headers.expires)}catch(o){a.logger?.warn(`AWS SDK Warning for ${a.clientName}::${a.commandName} response parsing (${s.headers.expires}): ${o}`),delete s.headers.expires}}return r},H={tags:["S3"],name:"s3ExpiresMiddleware",override:!0,relation:"after",toMiddleware:"deserializerMiddleware"},K=c=>({applyToStack:e=>{e.addRelativeTo(I(),H)}});class q extends b.classBuilder().ep({...y,Bucket:{type:"contextParams",name:"Bucket"},Key:{type:"contextParams",name:"Key"}}).m(function(e,a,n,r){return[k(n,e.getEndpointParameterInstructions()),L(n,{requestChecksumRequired:!1,requestValidationModeMember:"ChecksumMode",responseAlgorithms:["CRC64NVME","CRC32","CRC32C","SHA256","SHA1"]}),O(n),K()]}).s("AmazonS3","GetObject",{}).n("S3Client","GetObjectCommand").sc(C).build(){}function X(c){const e=w.c(9),{folderName:a,fullPrefix:n,bucketName:r}=c;let s;e[0]!==r||e[1]!==n?(s={bucket:r,_splat:n},e[0]=r,e[1]=n,e[2]=s):s=e[2];let o;e[3]===Symbol.for("react.memo_cache_sentinel")?(o=t.jsx(U,{className:"size-5 text-muted-foreground"}),e[3]=o):o=e[3];let i;e[4]!==a?(i=t.jsx("span",{className:"font-medium",children:a}),e[4]=a,e[5]=i):i=e[5];let l;return e[6]!==s||e[7]!==i?(l=t.jsxs(N,{className:"flex items-center gap-3 rounded-lg border bg-card p-4 transition-colors hover:bg-accent",params:s,to:"/s3/ls/$bucket/$",children:[o,i]}),e[6]=s,e[7]=i,e[8]=l):l=e[8],l}function Y({object:c,bucketName:e,displayName:a,fullKey:n}){const[r,s]=h.useState(!1),o=z();async function i(){try{const m=new q({Bucket:e,Key:n}),p=await S().send(m);if(p.Body){const x=await p.Body.transformToByteArray(),f=URL.createObjectURL(new Blob([x])),d=document.createElement("a");d.href=f,d.download=a,document.body.appendChild(d),d.click(),document.body.removeChild(d),URL.revokeObjectURL(f)}}catch{throw new Error("Failed to download object")}}async function l(){try{await o.mutateAsync({bucket:e,key:n})}finally{s(!1)}}return t.jsxs("div",{children:[o.error&&t.jsx(E,{error:o.error,msg:"Failed to delete object"}),t.jsxs("div",{className:"flex items-center justify-between rounded-lg border bg-card p-4",children:[t.jsxs("div",{className:"flex items-center gap-3",children:[t.jsx(A,{className:"size-5 text-muted-foreground"}),t.jsxs("div",{children:[t.jsx("h3",{className:"font-medium",children:a}),c.LastModified&&t.jsxs("p",{className:"text-muted-foreground text-sm",children:["Last Modified: ",v(c.LastModified)]})]})]}),t.jsxs("div",{className:"flex items-center gap-3",children:[t.jsx("div",{className:"text-muted-foreground text-sm",children:M(c.Size||0)}),t.jsx("button",{className:"rounded-md p-2 transition-colors hover:bg-accent",onClick:i,type:"button",children:t.jsx(B,{className:"size-4"})}),t.jsx("button",{className:"rounded-md p-2 text-destructive transition-colors hover:bg-accent",onClick:()=>s(!0),type:"button",children:t.jsx(P,{className:"size-4"})})]}),t.jsx(R,{description:t.jsxs(t.Fragment,{children:["Are you sure you want to delete ",t.jsx("strong",{children:a}),"? This action cannot be undone."]}),isPending:o.isPending,onConfirm:l,onOpenChange:s,open:r,title:"Delete Object"})]})]},c.Key)}function ee({bucket:c,prefix:e="",isPending:a,onUpload:n}){const r=h.useRef(null);async function s(o){const i=o.target.files;if(!(!i||i.length===0))try{await Promise.all(Array.from(i).map(l=>{const m=`${e}${l.name}`;return n({bucket:c,key:m,file:l})}))}finally{r.current&&(r.current.value="")}}return t.jsxs(t.Fragment,{children:[t.jsx("input",{accept:"*/*","aria-label":"Choose files to upload",className:"hidden",multiple:!0,onChange:s,ref:r,type:"file"}),t.jsxs(D,{disabled:a,onClick:()=>r.current?.click(),size:"sm",children:[t.jsx(_,{className:"mr-2 size-4"}),a?"Uploading…":"Upload Files"]})]})}export{X as F,Y as O,ee as U};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import{J as p,c as m,r as s}from"./index.js";const f=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],y=p("triangle-alert",f),d=2e3;function T(){const e=m.c(5),[n,i]=s.useState(!1),t=s.useRef(null);let r,o;e[0]===Symbol.for("react.memo_cache_sentinel")?(r=()=>()=>{t.current&&clearTimeout(t.current)},o=[],e[0]=r,e[1]=o):(r=e[0],o=e[1]),s.useEffect(r,o);let c;e[2]===Symbol.for("react.memo_cache_sentinel")?(c=async u=>{await navigator.clipboard.writeText(u),i(!0),t.current&&clearTimeout(t.current),t.current=setTimeout(()=>i(!1),d)},e[2]=c):c=e[2];const l=c;let a;return e[3]!==n?(a={copied:n,copy:l},e[3]=n,e[4]=a):a=e[4],a}export{y as T,T as u};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -4,13 +4,14 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 3000",
|
||||
"build": "vite build && tsc",
|
||||
"build": "vite build && tsgo",
|
||||
"preview": "vite preview",
|
||||
"check": "biome check",
|
||||
"fix": "biome check --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-ec2": "^3.1000.0",
|
||||
"@aws-sdk/client-iam": "^3.1000.0",
|
||||
"@aws-sdk/client-s3": "^3.995.0",
|
||||
"@aws-sdk/lib-storage": "^3.995.0",
|
||||
"@base-ui/react": "^1.2.0",
|
||||
@@ -36,6 +37,7 @@
|
||||
"@types/node": "^24.10.13",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260302.1",
|
||||
"@vitejs/plugin-basic-ssl": "^2.1.4",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
@@ -48,5 +50,5 @@
|
||||
"fast-xml-parser": ">=5.3.8"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.30.0+sha512.2b5753de015d480eeb88f5b5b61e0051f05b4301808a82ec8b840c9d2adf7748eb352c83f5c1593ca703ff1017295bc3fdd3119abb9686efc96b9fcb18200937"
|
||||
"packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017"
|
||||
}
|
||||
|
||||
+125
@@ -14,6 +14,9 @@ importers:
|
||||
'@aws-sdk/client-ec2':
|
||||
specifier: ^3.1000.0
|
||||
version: 3.1000.0
|
||||
'@aws-sdk/client-iam':
|
||||
specifier: ^3.1000.0
|
||||
version: 3.1000.0
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: ^3.995.0
|
||||
version: 3.1000.0
|
||||
@@ -84,6 +87,9 @@ importers:
|
||||
'@types/react-dom':
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3(@types/react@19.2.14)
|
||||
'@typescript/native-preview':
|
||||
specifier: 7.0.0-dev.20260302.1
|
||||
version: 7.0.0-dev.20260302.1
|
||||
'@vitejs/plugin-basic-ssl':
|
||||
specifier: ^2.1.4
|
||||
version: 2.1.4(vite@7.3.1(@types/node@24.10.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))
|
||||
@@ -132,6 +138,10 @@ packages:
|
||||
resolution: {integrity: sha512-SLdVSUScYXbUq2VRdKa3MloNRGnKqnmVdXkfQfJ4WyR5Lzrh1Gs6t9MXBxBMYPaFEdZhav/wMK92PSYdqLBKnA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/client-iam@3.1000.0':
|
||||
resolution: {integrity: sha512-RR4mq7t+4Fb2iaFDTcXF6Tcigl2nSWUyH61rwtiSbRrNp9A3xSexCSw79SIAkfwsuhk8Ig/ZVt5qpRHWRj2zgA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/client-s3@3.1000.0':
|
||||
resolution: {integrity: sha512-7kPy33qNGq3NfwHC0412T6LDK1bp4+eiPzetX0sVd9cpTSXuQDKpoOFnB0Njj6uZjJDcLS3n2OeyarwwgkQ0Ow==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -1208,6 +1218,45 @@ packages:
|
||||
'@types/react@19.2.14':
|
||||
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
||||
|
||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260302.1':
|
||||
resolution: {integrity: sha512-bSSHf2/3/pUa1FHdHkoD2SbACh4G8GQElQmkHksf42L30rgED+thloZPyQEvaIXE44IsfFDEvsa6reF/pob5Aw==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260302.1':
|
||||
resolution: {integrity: sha512-CfBxf/qEFgoe1zZ8U/DfgP2izrDEnERXj2uuX5Mi2m6NIdS6yo9fv+TiQS7UDINIOcRaN17NMUmIRrj8RLz3TA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260302.1':
|
||||
resolution: {integrity: sha512-Ojecn2PD9Pgs3UUkxzKNwesFmKiCrRHHc6DFlE7Tpsf53tLEOkVTS8SdYOxoPvbM+ee9lQ+WDWyHsOHfbUXFuQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260302.1':
|
||||
resolution: {integrity: sha512-TWdwsqactujpdAuu/pepoXg7DK2TQ6Q3kYeqLlTic+0d61jece12yqk4hlmXXzQrc5D2ht9Qr3ny4BHyBvKlZw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260302.1':
|
||||
resolution: {integrity: sha512-f+8wzy/4xcDMgAdkJy722BL0pau8O0S4+HUR2CyFJy7HbDIGbbl/Zm5ZAq2xjMGEdq83+klp+7y/JuDIb0herA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260302.1':
|
||||
resolution: {integrity: sha512-yN+UMH41G2UgXn5ER0FzgVQPQ6YLfpSoSaWV38l+hIkgKQSv7TsF778Svgh0dd5OpFKoeQ1aPOMalugxI3FnWw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260302.1':
|
||||
resolution: {integrity: sha512-Ni9nVkZRt3MofSUSilF51w1tkxlPegz3+zJLDPZZdc0IIaENTP37gfVkngio4tdsjIjsPFieJOkogej/wYT1AA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@typescript/native-preview@7.0.0-dev.20260302.1':
|
||||
resolution: {integrity: sha512-KR2mM+hGSF9CcYRFLe/8jAFuRr0Ao3vJDnfGtEoSSpgfw0kCZTMv8fyBc5Vicrg/ByZSwGhZf7Q8HDc5lPYLGg==}
|
||||
hasBin: true
|
||||
|
||||
'@vitejs/plugin-basic-ssl@2.1.4':
|
||||
resolution: {integrity: sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
@@ -1894,6 +1943,51 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-iam@3.1000.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.973.15
|
||||
'@aws-sdk/credential-provider-node': 3.972.14
|
||||
'@aws-sdk/middleware-host-header': 3.972.6
|
||||
'@aws-sdk/middleware-logger': 3.972.6
|
||||
'@aws-sdk/middleware-recursion-detection': 3.972.6
|
||||
'@aws-sdk/middleware-user-agent': 3.972.15
|
||||
'@aws-sdk/region-config-resolver': 3.972.6
|
||||
'@aws-sdk/types': 3.973.4
|
||||
'@aws-sdk/util-endpoints': 3.996.3
|
||||
'@aws-sdk/util-user-agent-browser': 3.972.6
|
||||
'@aws-sdk/util-user-agent-node': 3.973.0
|
||||
'@smithy/config-resolver': 4.4.9
|
||||
'@smithy/core': 3.23.6
|
||||
'@smithy/fetch-http-handler': 5.3.11
|
||||
'@smithy/hash-node': 4.2.10
|
||||
'@smithy/invalid-dependency': 4.2.10
|
||||
'@smithy/middleware-content-length': 4.2.10
|
||||
'@smithy/middleware-endpoint': 4.4.20
|
||||
'@smithy/middleware-retry': 4.4.37
|
||||
'@smithy/middleware-serde': 4.2.11
|
||||
'@smithy/middleware-stack': 4.2.10
|
||||
'@smithy/node-config-provider': 4.3.10
|
||||
'@smithy/node-http-handler': 4.4.12
|
||||
'@smithy/protocol-http': 5.3.10
|
||||
'@smithy/smithy-client': 4.12.0
|
||||
'@smithy/types': 4.13.0
|
||||
'@smithy/url-parser': 4.2.10
|
||||
'@smithy/util-base64': 4.3.1
|
||||
'@smithy/util-body-length-browser': 4.2.1
|
||||
'@smithy/util-body-length-node': 4.2.2
|
||||
'@smithy/util-defaults-mode-browser': 4.3.36
|
||||
'@smithy/util-defaults-mode-node': 4.2.39
|
||||
'@smithy/util-endpoints': 3.3.1
|
||||
'@smithy/util-middleware': 4.2.10
|
||||
'@smithy/util-retry': 4.2.10
|
||||
'@smithy/util-utf8': 4.2.1
|
||||
'@smithy/util-waiter': 4.2.10
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-s3@3.1000.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha1-browser': 5.2.0
|
||||
@@ -3244,6 +3338,37 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260302.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260302.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260302.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260302.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260302.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260302.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260302.1':
|
||||
optional: true
|
||||
|
||||
'@typescript/native-preview@7.0.0-dev.20260302.1':
|
||||
optionalDependencies:
|
||||
'@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260302.1
|
||||
'@typescript/native-preview-darwin-x64': 7.0.0-dev.20260302.1
|
||||
'@typescript/native-preview-linux-arm': 7.0.0-dev.20260302.1
|
||||
'@typescript/native-preview-linux-arm64': 7.0.0-dev.20260302.1
|
||||
'@typescript/native-preview-linux-x64': 7.0.0-dev.20260302.1
|
||||
'@typescript/native-preview-win32-arm64': 7.0.0-dev.20260302.1
|
||||
'@typescript/native-preview-win32-x64': 7.0.0-dev.20260302.1
|
||||
|
||||
'@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.1(@types/node@24.10.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))':
|
||||
dependencies:
|
||||
vite: 7.3.1(@types/node@24.10.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
|
||||
interface DeleteConfirmationDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
title: string
|
||||
description: ReactNode
|
||||
isPending: boolean
|
||||
onConfirm: () => void
|
||||
}
|
||||
|
||||
export function DeleteConfirmationDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
description,
|
||||
isPending,
|
||||
onConfirm,
|
||||
}: DeleteConfirmationDialogProps) {
|
||||
return (
|
||||
<AlertDialog onOpenChange={onOpenChange} open={open}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction disabled={isPending} onClick={onConfirm}>
|
||||
{isPending ? "Deleting\u2026" : "Delete"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user