Compare commits

..

15 Commits

Author SHA1 Message Date
Julian Sommer 404b3d6246 release: update go.mod for v0.2.3 2026-03-10 09:36:58 +10:00
Julian Sommer 021b0982f4 fix: bump go 1.26.1 references and fix gosec G104 in hiveui 2026-03-10 08:33:04 +10:00
Julian Sommer 7b590957e1 fix: bump go 1.26.1 2026-03-10 08:19:38 +10:00
Julian Sommer c9c3cda651 fix: redirect HTTP to HTTPS on same port for hive-ui
Uses a TLS-detecting listener that peeks the first byte of each
connection. TLS handshakes (0x16) proceed normally; plain HTTP
requests get a 301 redirect to the HTTPS URL. This handles
http://localhost:3000 gracefully instead of showing a cryptic error.
2026-03-10 08:14:19 +10:00
Julian Sommer 1efb4c4a3b fix: config 2026-03-06 14:51:21 +10:00
Julian Sommer 05cb4ac108 feat: frontend refactors (#66) 2026-03-06 15:42:01 +11:00
Julian Sommer 6eac4d6598 feat: IAM Frontend (#65) 2026-03-06 15:14:50 +11:00
Julian Sommer f03a2314c6 feat: terminated instances expiry (#63) 2026-03-06 12:51:27 +11:00
Julian Sommer 2c444b1eba chore: install tsgo 2026-03-06 11:46:21 +10:00
Julian Sommer aea2e2c05a ci: setup go work after 2026-03-06 10:41:45 +10:00
Julian Sommer d90bf53eaf fix: log rotate ovn 2026-03-06 10:10:34 +10:00
Julian Sommer 14c52366da fix: decouple gateway from qmp 2026-03-06 10:06:59 +10:00
Julian Sommer 6737c42601 refactor: consolidate NATS/API types into types/ package (phase 1)
Move EBS, cluster, and node message types from config/ to types/.
Remove duplicate NodeDiscoverResponse from gateway and daemon.
Delete dead EC2StartInstances types and tests from config.
Update all imports across source and test files.
2026-03-06 09:56:04 +10:00
Julian Sommer 19988af768 fix: gofmt formatting 2026-03-06 09:48:16 +10:00
Julian Sommer d20fcd2c38 fix merge 2026-03-06 09:45:34 +10:00
151 changed files with 3628 additions and 1386 deletions
+9 -9
View File
@@ -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
View File
@@ -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.
+2 -2
View File
@@ -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
+3 -2
View File
@@ -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
View File
@@ -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
}
+8 -9
View File
@@ -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
View File
@@ -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
}
+3 -3
View File
@@ -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
+4 -4
View File
@@ -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
View File
@@ -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
-85
View File
@@ -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
View File
@@ -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
+10 -57
View File
@@ -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,
+112 -96
View File
@@ -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.
+179 -45
View File
@@ -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)
}
+7 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+246
View File
@@ -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)
}
+3 -3
View File
@@ -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
View File
@@ -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())
+40 -19
View File
@@ -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
}
+3 -9
View File
@@ -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)
+48 -10
View File
@@ -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)
+4 -4
View File
@@ -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,
},
+4 -4
View File
@@ -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,
+2 -6
View File
@@ -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
+3 -2
View File
@@ -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)
})
+1 -2
View File
@@ -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
}
+3 -2
View File
@@ -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)
+4 -3
View File
@@ -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,
+3 -2
View File
@@ -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)
+3 -2
View File
@@ -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)
})
-28
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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
View File
@@ -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};
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};
+1
View File
@@ -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};
+1
View File
@@ -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};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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};
+1
View File
@@ -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};
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -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
View File
@@ -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