Merge commit '820f620e34006699de1105859301146aaf491ca5' into vpc-public-subnet

# Conflicts:
#	cmd/spinifex/cmd/admin.go
#	docs/installing-spinifex/README.md
#	scripts/setup.sh
#	spinifex/admin/admin.go
#	spinifex/admin/network_detect.go
#	spinifex/handlers/iam/service_impl.go
This commit is contained in:
Ben Duncan
2026-03-25 14:16:47 +10:00
110 changed files with 2081 additions and 327 deletions
-2
View File
@@ -124,5 +124,3 @@ linters:
linters: [gosec] # subprocess execution — legitimate in VM orchestration
- text: "G304"
linters: [gosec] # file path from variable — necessary for config loading
- text: "G402"
linters: [gosec] # TLS config — we handle TLS ourselves
+2 -2
View File
@@ -50,7 +50,7 @@ AWS-Compatible Interfaces – Provision infrastructure with awscli, Terraform, o
- Designed for Control – Run on your own terms, whether that’s in a datacenter, remote site, or sensitive environment.
- Minimal Dependencies – Each service is standalone and avoids complex orchestration layers.
- Works Offline – No reliance on centralized cloud services or external networks.
- Open Source – Licensed under Apache 2.0. Fork it, extend it, or deploy it as-is.
- Open Source – Licensed under AGPL 3.0. Fork it, extend it, or deploy it as-is.
## Installation
@@ -121,4 +121,4 @@ This hybrid approach ensures Spinifex benefits from both proven engineering expe
## License
Spinifex is open source under the Apache 2.0 License. You're free to use, modify, and deploy it—anywhere you need reliable infrastructure without depending on centralized cloud platforms.
Spinifex is open source under the [GNU Affero General Public License v3.0](LICENSE). You're free to use, modify, and deploy it—anywhere you need reliable infrastructure without depending on centralized cloud platforms.
+88 -15
View File
@@ -1,23 +1,25 @@
/*
Copyright © 2025 Mulga Defense Corporation
Copyright © 2026 Mulga Defense Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmd
import (
"bytes"
"context"
"crypto/tls"
_ "embed"
"encoding/base64"
"encoding/json"
@@ -519,6 +521,12 @@ func runimagesListCmd(cmd *cobra.Command, args []string) {
// TODO: Move all logic to a module, use minimal application logic in viper commands
func runAdminInit(cmd *cobra.Command, args []string) {
if os.Getuid() != 0 {
fmt.Fprintln(os.Stderr, "⚠️ Not running as root — CA certificate will not be installed to system trust store.")
fmt.Fprintln(os.Stderr, " Run with sudo, or manually: sudo cp ~/spinifex/config/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt && sudo update-ca-certificates")
fmt.Fprintln(os.Stderr)
}
force, _ := cmd.Flags().GetBool("force")
configDir, _ := cmd.Flags().GetString("config-dir")
spxRoot, _ := cmd.Flags().GetString("spinifex-dir")
@@ -738,6 +746,9 @@ func runAdminInit(cmd *cobra.Command, args []string) {
// Generate SSL certificates (with bind IP in SANs for multi-node support)
certPath := admin.GenerateCertificatesIfNeeded(configDir, force, bindIP)
// Install CA certificate into system trust store
installCACertificate(filepath.Join(configDir, "ca.pem"))
// Generate NATS token
natsToken, err := admin.GenerateNATSToken()
if err != nil {
@@ -1007,9 +1018,11 @@ func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID,
os.Exit(1)
}
// Start formation server
// Start formation server (HTTPS using the CA-signed server cert)
formationAddr := fmt.Sprintf("%s:%d", bindIP, port)
if err := fs.Start(formationAddr); err != nil {
serverCert := filepath.Join(configDir, "server.pem")
serverKey := filepath.Join(configDir, "server.key")
if err := fs.Start(formationAddr, serverCert, serverKey); err != nil {
fmt.Fprintf(os.Stderr, "❌ Error starting formation server: %v\n", err)
os.Exit(1)
}
@@ -1146,6 +1159,11 @@ func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID,
}
func runAdminJoin(cmd *cobra.Command, args []string) {
if os.Getuid() != 0 {
fmt.Fprintln(os.Stderr, "⚠️ Not running as root — CA certificate will not be installed to system trust store.")
fmt.Fprintln(os.Stderr)
}
node, _ := cmd.Flags().GetString("node")
leaderHost, _ := cmd.Flags().GetString("host")
region, _ := cmd.Flags().GetString("region")
@@ -1222,9 +1240,14 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
os.Exit(1)
}
client := &http.Client{Timeout: 30 * time.Second}
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // joining node doesn't have CA yet
},
}
joinURL := fmt.Sprintf("http://%s/formation/join", leaderHost)
joinURL := fmt.Sprintf("https://%s/formation/join", leaderHost)
resp, err := client.Post(joinURL, "application/json", bytes.NewBuffer(reqBody))
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Error connecting to formation server: %v\n", err)
@@ -1239,6 +1262,11 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
os.Exit(1)
}
if resp.StatusCode != http.StatusOK {
fmt.Fprintf(os.Stderr, "❌ Formation server returned HTTP %d: %s\n", resp.StatusCode, strings.TrimSpace(string(body)))
os.Exit(1)
}
var joinResp formation.JoinResponse
if err := json.Unmarshal(body, &joinResp); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing join response: %v\n", err)
@@ -1253,7 +1281,7 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
fmt.Printf("✅ Registered with formation server (%d/%d nodes joined)\n", joinResp.Joined, joinResp.Expected)
// Poll status until formation is complete
statusURL := fmt.Sprintf("http://%s/formation/status", leaderHost)
statusURL := fmt.Sprintf("https://%s/formation/status", leaderHost)
var statusResp formation.StatusResponse
for {
@@ -1270,6 +1298,11 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
os.Exit(1)
}
if sResp.StatusCode != http.StatusOK {
fmt.Fprintf(os.Stderr, "❌ Formation server returned HTTP %d: %s\n", sResp.StatusCode, strings.TrimSpace(string(sBody)))
os.Exit(1)
}
if err := json.Unmarshal(sBody, &statusResp); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing status response: %v\n", err)
os.Exit(1)
@@ -1321,6 +1354,9 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
}
fmt.Printf("✅ CA certificate received from leader: %s\n", caCertPath)
// Install CA certificate into system trust store
installCACertificate(caCertPath)
// Extract and write master key from formation server
if statusResp.MasterKey == "" {
fmt.Fprintf(os.Stderr, "❌ Error: formation server did not return master key\n")
@@ -1810,3 +1846,40 @@ func parseDNSFromResolvectl(output string) []string {
}
return servers
}
// installCACertificate copies the Spinifex CA certificate into the system
// trust store and runs update-ca-certificates so TLS clients (AWS CLI, etc.)
// trust the self-signed gateway certificate without extra configuration.
func installCACertificate(caPemPath string) {
if os.Getuid() != 0 {
return
}
const systemCertPath = "/usr/local/share/ca-certificates/spinifex-ca.crt"
data, err := os.ReadFile(caPemPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not read CA certificate %s: %v\n", caPemPath, err)
return
}
if err := os.MkdirAll(filepath.Dir(systemCertPath), 0755); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not create certificate directory: %v\n", err)
return
}
if err := os.WriteFile(systemCertPath, data, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not install CA certificate: %v\n", err)
return
}
cmd := exec.Command("update-ca-certificates")
cmd.Stdout = io.Discard
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: update-ca-certificates failed: %v\n", err)
return
}
fmt.Printf("✅ CA certificate installed to system trust store\n")
}
+28 -11
View File
@@ -1,17 +1,18 @@
/*
Copyright © 2025 Mulga Defense Corporation
Copyright © 2026 Mulga Defense Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmd
@@ -138,6 +139,20 @@ func formatUptime(seconds int64) string {
return fmt.Sprintf("%dm", mins)
}
func formatRoles(resp types.NodeStatusResponse) string {
var roles []string
if resp.NATSRole != "" {
roles = append(roles, "nats:"+resp.NATSRole)
}
if resp.PredastoreRole != "" {
roles = append(roles, "predastore:"+resp.PredastoreRole)
}
if len(roles) == 0 {
return "-"
}
return strings.Join(roles, ",")
}
func formatMemGB(gb float64) string {
if gb >= 1.0 {
return fmt.Sprintf("%.1fGi", gb)
@@ -172,7 +187,7 @@ func runGetNodes(cmd *cobra.Command, args []string) {
// Build table: union of config-known nodes + NATS responders
tableData := pterm.TableData{
{"NAME", "STATUS", "IP", "REGION", "AZ", "UPTIME", "VMs", "SERVICES"},
{"NAME", "STATUS", "ROLES", "IP", "REGION", "AZ", "UPTIME", "VMs", "SERVICES"},
}
// Collect all node names: config + responded (union)
@@ -195,6 +210,7 @@ func runGetNodes(cmd *cobra.Command, args []string) {
tableData = append(tableData, []string{
resp.Node,
resp.Status,
formatRoles(resp),
resp.Host,
resp.Region,
resp.AZ,
@@ -206,6 +222,7 @@ func runGetNodes(cmd *cobra.Command, args []string) {
tableData = append(tableData, []string{
name,
"NotReady",
"-",
nodeCfg.Host,
nodeCfg.Region,
nodeCfg.AZ,
+49
View File
@@ -0,0 +1,49 @@
package cmd
import (
"testing"
"github.com/mulgadc/spinifex/spinifex/types"
"github.com/stretchr/testify/assert"
)
func TestFormatRoles(t *testing.T) {
tests := []struct {
name string
resp types.NodeStatusResponse
want string
}{
{
name: "no roles",
resp: types.NodeStatusResponse{},
want: "-",
},
{
name: "nats leader only",
resp: types.NodeStatusResponse{NATSRole: "leader"},
want: "nats:leader",
},
{
name: "predastore follower only",
resp: types.NodeStatusResponse{PredastoreRole: "follower"},
want: "predastore:follower",
},
{
name: "both roles",
resp: types.NodeStatusResponse{NATSRole: "leader", PredastoreRole: "follower"},
want: "nats:leader,predastore:follower",
},
{
name: "both leaders",
resp: types.NodeStatusResponse{NATSRole: "leader", PredastoreRole: "leader"},
want: "nats:leader,predastore:leader",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := formatRoles(tt.resp)
assert.Equal(t, tt.want, got)
})
}
}
+18 -5
View File
@@ -2,18 +2,18 @@ package cmd
import (
"os"
"os/user"
"path/filepath"
)
// DefaultConfigDir returns the default configuration directory.
// Production: /etc/spinifex (when running as root or /etc/spinifex exists)
// Production: /etc/spinifex (when running as root AND /etc/spinifex exists)
// Development: ~/spinifex/config
func DefaultConfigDir() string {
if isProductionLayout() {
return "/etc/spinifex"
}
homeDir, _ := os.UserHomeDir()
return filepath.Join(homeDir, "spinifex", "config")
return filepath.Join(realUserHomeDir(), "spinifex", "config")
}
// DefaultDataDir returns the default data directory.
@@ -23,8 +23,20 @@ func DefaultDataDir() string {
if isProductionLayout() {
return "/var/lib/spinifex"
}
return filepath.Join(realUserHomeDir(), "spinifex")
}
// realUserHomeDir returns the home directory of the real (non-sudo) user.
// When running under sudo, SUDO_USER is set to the invoking user — resolve
// their home directory so config/data land in the right place.
func realUserHomeDir() string {
if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" {
if u, err := user.Lookup(sudoUser); err == nil {
return u.HomeDir
}
}
homeDir, _ := os.UserHomeDir()
return filepath.Join(homeDir, "spinifex")
return homeDir
}
// DefaultConfigFile returns the default path to spinifex.toml.
@@ -33,7 +45,8 @@ func DefaultConfigFile() string {
}
// isProductionLayout returns true when running in a production install.
// Detected by: running as root, or /etc/spinifex directory exists.
// Detected by: running as root AND /etc/spinifex directory already exists
// (created by setup.sh during binary install).
func isProductionLayout() bool {
if os.Getuid() == 0 {
if info, err := os.Stat("/etc/spinifex"); err == nil && info.IsDir() {
+13 -13
View File
@@ -1,17 +1,18 @@
/*
Copyright © 2025 Mulga Defense Corporation
Copyright © 2026 Mulga Defense Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmd
@@ -25,9 +26,8 @@ import (
)
var (
cfgFile string
//lint:ignore U1000 fixing config loading later
appConfig *config.ClusterConfig
cfgFile string
appConfig *config.ClusterConfig //nolint:unused // TODO: wire into config loading
)
// rootCmd represents the base command when called without any subcommands
+11 -10
View File
@@ -1,17 +1,18 @@
/*
Copyright © 2025 Mulga Defense Corporation
Copyright © 2026 Mulga Defense Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmd
+11 -10
View File
@@ -1,17 +1,18 @@
/*
Copyright © 2025 Mulga Defense Corporation
Copyright © 2026 Mulga Defense Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmd
+11 -10
View File
@@ -1,17 +1,18 @@
/*
Copyright © 2025 Mulga Defense Corporation
Copyright © 2026 Mulga Defense Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
+547
View File
@@ -0,0 +1,547 @@
---
title: "IAM Users and Policies"
description: "Create IAM users, manage access keys, and control permissions with policies."
category: "Administration"
tags:
- iam
- users
- policies
- access keys
- security
badge: setup
resources:
- title: "Spinifex Repository"
url: "https://github.com/mulgadc/spinifex"
- title: "AWS IAM CLI Reference"
url: "https://docs.aws.amazon.com/cli/latest/reference/iam/"
- title: "IAM Policy Reference"
url: "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html"
---
# IAM Users and Policies
> Create IAM users, manage access keys, and control permissions with policies.
## Table of Contents
- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Users](#users)
- [Access Keys](#access-keys)
- [Policies](#policies)
- [Attaching Policies](#attaching-policies)
- [Putting It All Together](#putting-it-all-together)
- [Command Reference](#command-reference)
- [Troubleshooting](#troubleshooting)
---
## Overview
Spinifex implements AWS-compatible IAM with 16 operations covering user management, access key lifecycle, policy CRUD, and policy attachment. All IAM resources are scoped to the account that creates them — users in one account cannot see or modify resources in another.
When you create an account with `spx admin account create`, Spinifex bootstraps a root user with an `AdministratorAccess` policy and writes the credentials to `~/.aws/credentials`. From there, you use the standard AWS CLI to manage additional users and permissions.
**How authentication works:** Every AWS CLI request is signed with SigV4 using an access key pair. The gateway verifies the signature, resolves the caller's account, and evaluates attached policies before routing the request. The root user (account `000000000000`) bypasses policy evaluation entirely.
## Instructions
## Prerequisites
- A running Spinifex cluster with the AWS gateway started
- An account created via `spx admin account create` (see [Creating Accounts](/docs/creating-accounts))
- AWS CLI configured with the account's profile:
```bash
export AWS_PROFILE=spinifex-myteam
aws sts get-caller-identity
```
## Users
### Create a User
```bash
aws iam create-user --user-name alice
```
The response includes the user's ARN, unique ID, and creation date:
```json
{
"User": {
"UserName": "alice",
"UserId": "AIDA1A2B3C4D5E6F7890",
"Arn": "arn:aws:iam::000000000001:user/alice",
"Path": "/",
"CreateDate": "2026-03-24T10:00:00Z"
}
}
```
Use `--path` to organise users into hierarchical groups:
```bash
aws iam create-user --user-name bob --path /developers/
```
### Get a User
```bash
aws iam get-user --user-name alice
```
### List Users
```bash
aws iam list-users
```
Filter by path prefix:
```bash
aws iam list-users --path-prefix /developers/
```
### Delete a User
Before deleting a user, remove all access keys and detach all policies:
```bash
# Remove access keys
aws iam list-access-keys --user-name alice
aws iam delete-access-key --user-name alice --access-key-id AKIA...
# Detach policies
aws iam list-attached-user-policies --user-name alice
aws iam detach-user-policy --user-name alice \
--policy-arn arn:aws:iam::000000000001:policy/MyPolicy
# Now delete
aws iam delete-user --user-name alice
```
## Access Keys
Access keys are how users authenticate with the AWS CLI. Each user can have up to **2 access keys** at a time, allowing key rotation without downtime.
### Create an Access Key
```bash
aws iam create-access-key --user-name alice
```
```json
{
"AccessKey": {
"UserName": "alice",
"AccessKeyId": "AKIA1A2B3C4D5E6F7890ABCD",
"Status": "Active",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"CreateDate": "2026-03-24T10:05:00Z"
}
}
```
> **The secret access key is only shown once.** Save it immediately. If lost, delete the key and create a new one.
Add the credentials to `~/.aws/credentials` under a new profile and set `AWS_PROFILE` to use them.
### List Access Keys
```bash
aws iam list-access-keys --user-name alice
```
### Rotate Access Keys
Create a second key, update your configuration, then delete the old one:
```bash
# 1. Create new key (while old key still works)
aws iam create-access-key --user-name alice
# 2. Update ~/.aws/credentials with the new key
# 3. Verify the new key works
AWS_PROFILE=spinifex-alice aws sts get-caller-identity
# 4. Delete the old key
aws iam delete-access-key --user-name alice --access-key-id AKIA_OLD_KEY_ID
```
### Deactivate an Access Key
Temporarily disable a key without deleting it:
```bash
aws iam update-access-key --user-name alice \
--access-key-id AKIA1A2B3C4D5E6F7890ABCD \
--status Inactive
```
Reactivate it later:
```bash
aws iam update-access-key --user-name alice \
--access-key-id AKIA1A2B3C4D5E6F7890ABCD \
--status Active
```
## Policies
Policies are JSON documents that define what actions a user can perform on which resources.
### Policy Document Format
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ec2:RunInstances", "ec2:DescribeInstances"],
"Resource": "*"
}
]
}
```
Each statement has:
- **Effect** — `Allow` or `Deny`
- **Action** — Service actions (e.g. `ec2:RunInstances`, `s3:GetObject`). Supports wildcards: `ec2:*`, `s3:Get*`, or `*` for all actions.
- **Resource** — Target resources. Use `*` for all resources.
**Evaluation order:** An explicit `Deny` always wins. If no statement matches, access is denied by default.
### Create a Policy
Save the policy document to a file, then create the policy:
```bash
cat > /tmp/ec2-readonly.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeImages",
"ec2:DescribeKeyPairs",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVpcs"
],
"Resource": "*"
}
]
}
EOF
aws iam create-policy \
--policy-name EC2ReadOnly \
--policy-document file:///tmp/ec2-readonly.json
```
```json
{
"Policy": {
"PolicyName": "EC2ReadOnly",
"PolicyId": "ANPA1A2B3C4D5E6F7890",
"Arn": "arn:aws:iam::000000000001:policy/EC2ReadOnly",
"Path": "/",
"DefaultVersionId": "v1",
"CreateDate": "2026-03-24T10:10:00Z"
}
}
```
### Common Policy Examples
**Full administrator access:**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
```
**S3 read/write (for hybrid sync, backups):**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": "*"
}
]
}
```
**EC2 operator (launch and manage instances, no VPC changes):**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:RunInstances",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:TerminateInstances",
"ec2:DescribeInstances",
"ec2:DescribeImages"
],
"Resource": "*"
}
]
}
```
**Deny terminate (attach alongside a broader Allow to prevent accidental termination):**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "ec2:TerminateInstances",
"Resource": "*"
}
]
}
```
### Get a Policy
```bash
aws iam get-policy \
--policy-arn arn:aws:iam::000000000001:policy/EC2ReadOnly
```
### Get the Policy Document
Use `get-policy-version` with version `v1` to retrieve the actual JSON document:
```bash
aws iam get-policy-version \
--policy-arn arn:aws:iam::000000000001:policy/EC2ReadOnly \
--version-id v1
```
### List Policies
```bash
aws iam list-policies
```
### Delete a Policy
A policy must be detached from all users before it can be deleted:
```bash
aws iam delete-policy \
--policy-arn arn:aws:iam::000000000001:policy/EC2ReadOnly
```
## Attaching Policies
Policies have no effect until attached to a user.
### Attach a Policy to a User
```bash
aws iam attach-user-policy --user-name alice \
--policy-arn arn:aws:iam::000000000001:policy/EC2ReadOnly
```
### List a User's Policies
```bash
aws iam list-attached-user-policies --user-name alice
```
### Detach a Policy
```bash
aws iam detach-user-policy --user-name alice \
--policy-arn arn:aws:iam::000000000001:policy/EC2ReadOnly
```
## Putting It All Together
A complete workflow for onboarding a developer with scoped EC2 and S3 access:
```bash
# 1. Create the user
aws iam create-user --user-name dev-carol --path /developers/
# 2. Create an access key
aws iam create-access-key --user-name dev-carol
# Save the AccessKeyId and SecretAccessKey from the output
# 3. Create a scoped policy
cat > /tmp/dev-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EC2Access",
"Effect": "Allow",
"Action": ["ec2:*"],
"Resource": "*"
},
{
"Sid": "S3ReadOnly",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": "*"
},
{
"Sid": "NoTerminate",
"Effect": "Deny",
"Action": "ec2:TerminateInstances",
"Resource": "*"
}
]
}
EOF
aws iam create-policy \
--policy-name DeveloperAccess \
--policy-document file:///tmp/dev-policy.json
# 4. Attach the policy
aws iam attach-user-policy --user-name dev-carol \
--policy-arn arn:aws:iam::000000000001:policy/DeveloperAccess
# 5. Configure the developer's AWS CLI
aws configure --profile spinifex-carol
# Enter the access key and secret from step 2
# 6. Verify
AWS_PROFILE=spinifex-carol aws ec2 describe-instances
AWS_PROFILE=spinifex-carol aws ec2 terminate-instances --instance-ids i-123
# ^ This will be denied by the NoTerminate statement
```
## Command Reference
| Command | Key Parameters | Notes |
|---------|---------------|-------|
| `create-user` | `--user-name`, `--path` | Path default: `/` |
| `get-user` | `--user-name` | |
| `list-users` | `--path-prefix` | |
| `delete-user` | `--user-name` | Must have no keys or policies |
| `create-access-key` | `--user-name` | Max 2 per user; secret shown once |
| `list-access-keys` | `--user-name` | |
| `delete-access-key` | `--user-name`, `--access-key-id` | |
| `update-access-key` | `--user-name`, `--access-key-id`, `--status` | `Active` or `Inactive` |
| `create-policy` | `--policy-name`, `--policy-document` | Max 6144 bytes |
| `get-policy` | `--policy-arn` | Metadata only |
| `get-policy-version` | `--policy-arn`, `--version-id` | Use `v1`; includes document |
| `list-policies` | | |
| `delete-policy` | `--policy-arn` | Must not be attached |
| `attach-user-policy` | `--user-name`, `--policy-arn` | Idempotent |
| `detach-user-policy` | `--user-name`, `--policy-arn` | |
| `list-attached-user-policies` | `--user-name` | |
## Troubleshooting
### AccessDenied on IAM commands
The calling user must have IAM permissions. Attach a policy with `iam:*` actions, or use the root account profile:
```bash
export AWS_PROFILE=spinifex-myteam
aws iam list-users
```
### InvalidClientTokenId
The access key is either inactive or does not exist. Check the key status:
```bash
aws iam list-access-keys --user-name alice
```
If the key shows `Inactive`, reactivate it:
```bash
aws iam update-access-key --user-name alice \
--access-key-id AKIA... --status Active
```
If the key was deleted, create a new one.
### DeleteConflict when deleting a user
The user still has access keys or attached policies. Remove them first:
```bash
# Check for access keys
aws iam list-access-keys --user-name alice
# Check for attached policies
aws iam list-attached-user-policies --user-name alice
```
### DeleteConflict when deleting a policy
The policy is still attached to one or more users. Detach it from all users before deleting:
```bash
# Find who has it attached, then detach
aws iam detach-user-policy --user-name alice \
--policy-arn arn:aws:iam::000000000001:policy/MyPolicy
aws iam delete-policy \
--policy-arn arn:aws:iam::000000000001:policy/MyPolicy
```
### LimitExceeded when creating access keys
Each user can have at most 2 access keys. Delete an existing key before creating a new one:
```bash
aws iam list-access-keys --user-name alice
aws iam delete-access-key --user-name alice --access-key-id AKIA_OLD
aws iam create-access-key --user-name alice
```
### MalformedPolicyDocument
The policy JSON is invalid. Check that:
- `Version` is exactly `"2012-10-17"`
- At least one `Statement` exists
- Each statement has `Effect` (`Allow`/`Deny`), `Action`, and `Resource`
- The document is under 6144 bytes
- The JSON is well-formed (no trailing commas, correct quoting)
+11 -8
View File
@@ -79,26 +79,27 @@ sudo /usr/local/share/spinifex/setup-ovn.sh --management --macvlan --wan-iface=e
### Step 3. Initialize
```bash
spx admin init --region ap-southeast-2 --az ap-southeast-2a --node node1 --nodes 1
sudo spx admin init --region ap-southeast-2 --az ap-southeast-2a --node node1 --nodes 1
```
This auto-detects your network topology and configures external networking. Save the admin credentials printed during init — they will not be shown again.
This auto-detects your network topology, generates configuration and TLS certificates, installs the CA into the system trust store, and configures AWS CLI credentials. Save the admin credentials printed during init — they will not be shown again.
### Step 4. Trust the CA certificate
### Step 3. Setup OVN networking
```bash
sudo cp ~/spinifex/config/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt
sudo update-ca-certificates
sudo /usr/local/share/spinifex/setup-ovn.sh --management --external-bridge --external-iface=enp0s3 --dhcp
```
### Step 5. Start services
Replace `enp0s3` with your WAN interface.
### Step 4. Start services
```bash
sudo systemctl start spinifex.target
export AWS_PROFILE=spinifex
```
### Step 6. Verify
### Step 5. Verify
```bash
aws ec2 describe-instance-types
@@ -122,8 +123,10 @@ export PATH=$PATH:/usr/local/bin
### CA certificate not trusted
`sudo spx admin init` installs the CA automatically. If you need to re-install it manually:
```bash
sudo cp ~/spinifex/config/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt
sudo cp /etc/spinifex/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt
sudo update-ca-certificates
```
+29 -23
View File
@@ -12,12 +12,18 @@ tags:
# VPC Networking
> How Spinifex implements AWS-compatible VPC networking with public and private subnets, security groups, and Elastic IPs using OVN.
## Overview
Spinifex provides AWS-compatible VPC networking on bare-metal. Every EC2 instance
runs inside an isolated virtual network backed by OVN (Open Virtual Network).
Instances can operate in two modes: **private** (overlay-only, no WAN access) or
**public** (routable from the WAN with a unique public IP).
# How It Works
## Instructions
## How It Works
Spinifex maps AWS VPC concepts directly to OVN constructs:
@@ -40,36 +46,36 @@ Spinifex maps AWS VPC concepts directly to OVN constructs:
└──────┬──────┘
│ ovn-bridge-mappings: external:br-wan
│
┌──────────┴──────────────────────────┐
┌──────────┴───────────────────────────┐
│ VPC Logical Router │
│ │
│ Gateway port ──── ext switch ─────── localnet port
│ (public IP) (OVN) (maps to br-wan)
│ │
│ NAT rules: │
│ SNAT: VPC CIDR → gateway IP │
│ dnat_and_snat: public ↔ private │
│ SNAT: VPC CIDR → gateway IP │
│ dnat_and_snat: public ↔ private │
│ │
│ Routes: │
│ 0.0.0.0/0 → WAN gateway │
│ 0.0.0.0/0 → WAN gateway │
│ │
├────────────┬──────────────┤ │
│ │ │ │
┌─────┴────┐ ┌────┴─────┐ │ │
│ subnet-a │ │ subnet-b │ │ │
│ (public) │ │ (private)│ │ │
│10.0.1/24 │ │10.0.2/24 │ │ │
│ eni-1 │ │ eni-3 │ │ │
│ eni-2 │ │ eni-4 │ │ │
└──────────┘ └──────────┘ │ │
└──────────┘
┌─────┴────┐ ┌─────┴────┐ │ │
│ subnet-a │ │ subnet-b │ │ │
│ (public) │ │ (private)│ │ │
│10.0.1/24 │ │10.0.2/24 │ │ │
│ eni-1 │ │ eni-3 │ │ │
│ eni-2 │ │ eni-4 │ │ │
└──────────┘ └──────────┘ │ │
└──────────┘
```
Cross-host traffic uses **Geneve tunnels** (UDP 6081) over the management/overlay
NIC. Each host runs `ovn-controller` which programs OpenFlow rules on `br-int`
(the integration bridge where all VM TAP devices connect).
# Private vs Public Subnets
## Private vs Public Subnets
A subnet's behavior depends on two things: whether the VPC has an Internet
Gateway, and whether the subnet has `MapPublicIpOnLaunch` enabled.
@@ -115,7 +121,7 @@ Inbound: 203.0.113.10 ──→ DNAT ──→ arrives at 10.0.1.5
| Instance sees public IP? | N/A | No — only sees private IP |
| Elastic IP support | Only if explicitly associated | Yes |
# External Connectivity Modes
## External Connectivity Modes
The `[network]` section in `spinifex.toml` controls how VMs reach the outside
world. There are three modes, and pool mode has two IP sources (static or DHCP).
@@ -265,7 +271,7 @@ communicate within their VPC.
If you start with `nat` and later need public subnets, switch to `pool` and
define a range (or use `source = "dhcp"`) — no data migration needed.
# Bridge Setup — Physical Network Wiring
## Bridge Setup — Physical Network Wiring
OVN needs an OVS bridge for external connectivity. `setup-ovn.sh` auto-detects
the best approach:
@@ -462,7 +468,7 @@ ip link show spx-ext-eth0
Physical WAN
```
# Configuration Reference
## Configuration Reference
All network configuration lives in `spinifex.toml`. Settings are split into three
levels: cluster-wide mode, IP pool definitions, and per-node NIC settings.
@@ -590,7 +596,7 @@ name. Allocation uses CAS (Compare-And-Set) for lock-free concurrent access:
Pools are initialized from `spinifex.toml` on vpcd startup (idempotent).
# Deployment Examples
## Deployment Examples
## Homelab / Dev (Single Pool)
@@ -749,7 +755,7 @@ Spinifex allocates from the correct pool based on where the instance launches.
An instance in `us-east-1a` gets an IP from `us-east-1a` first; if exhausted,
falls back to `us-east-overflow`.
# Security Groups
## Security Groups
Security groups are stateful firewalls enforced at the OVS datapath level on
each hypervisor. Traffic is filtered before it reaches the wire — equivalent to
@@ -806,7 +812,7 @@ aws ec2 run-instances --image-id $AMI --instance-type t3.small \
Rule changes take effect immediately — no instance restart needed.
# Elastic IPs
## Elastic IPs
Elastic IPs are static public IPs that persist across instance stop/start cycles.
Unlike auto-assigned public IPs (which change on stop/start), an Elastic IP stays
@@ -831,7 +837,7 @@ aws ec2 release-address --allocation-id $EIP
When you associate an Elastic IP with an instance that already has an
auto-assigned public IP, the auto-assigned IP is released and replaced.
# OVN Reference
## OVN Reference
For operators debugging or verifying the OVN topology.
@@ -926,7 +932,7 @@ sudo ovn-nbctl acl-list sg-{groupId}
sudo ovn-nbctl pg-get-ports sg-{groupId}
```
# Quick Start
## Quick Start
## 1. Set Up OVN Bridges
@@ -973,7 +979,7 @@ aws ec2 describe-instances --instance-ids $INSTANCE \
--query 'Reservations[0].Instances[0].[PrivateIpAddress,PublicIpAddress]'
```
# Troubleshooting
## Troubleshooting
## Debugging Toolkit
+10 -10
View File
@@ -8,9 +8,9 @@ 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.1.1
github.com/mulgadc/viperblock v1.1.2
github.com/nats-io/nats-server/v2 v2.12.4
github.com/mulgadc/predastore v1.1.2
github.com/mulgadc/viperblock v1.1.3
github.com/nats-io/nats-server/v2 v2.12.6
github.com/nats-io/nats.go v1.49.0
github.com/ovn-kubernetes/libovsdb v0.8.1
github.com/pelletier/go-toml/v2 v2.2.4
@@ -72,7 +72,7 @@ require (
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nats-io/jwt/v2 v2.8.0 // indirect
github.com/nats-io/jwt/v2 v2.8.1 // indirect
github.com/nats-io/nkeys v0.4.15 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -94,16 +94,16 @@ require (
go.opentelemetry.io/otel/trace v1.41.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/telemetry v0.0.0-20260304144227-18da59047661 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
google.golang.org/protobuf v1.36.11 // indirect
+20 -20
View File
@@ -255,18 +255,18 @@ 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.1.1 h1:rqpvA/qmewzJBHT2r2N9gNuUaLUKJrONFAyVu2zsRbY=
github.com/mulgadc/predastore v1.1.1/go.mod h1:WFls3cw+0g0wYBDyKNjEvTihJxuMjIWe2lVNWcYtY7A=
github.com/mulgadc/viperblock v1.1.2 h1:o6sxoLJmXgPuR4OI2VUefRVzDtAJ/ionkenPjt4KhYg=
github.com/mulgadc/viperblock v1.1.2/go.mod h1:/WeRil1DFnUMB/qJPST/iUPHPUl73gIVMw0xpt5Wscs=
github.com/mulgadc/predastore v1.1.2 h1:cFPLFo3A3SX2lQGsh+Du3H3xDn1pHVaS79BYGdC9HMc=
github.com/mulgadc/predastore v1.1.2/go.mod h1:WFls3cw+0g0wYBDyKNjEvTihJxuMjIWe2lVNWcYtY7A=
github.com/mulgadc/viperblock v1.1.3 h1:1PMjN2WJGdEHKpYoL4LqLNtyDkdjs20qfw9w4aUrfxc=
github.com/mulgadc/viperblock v1.1.3/go.mod h1:/WeRil1DFnUMB/qJPST/iUPHPUl73gIVMw0xpt5Wscs=
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=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
github.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts=
github.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg=
github.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU=
github.com/nats-io/jwt/v2 v2.8.1/go.mod h1:nWnOEEiVMiKHQpnAy4eXlizVEtSfzacZ1Q43LIRavZg=
github.com/nats-io/nats-server/v2 v2.12.6 h1:Egbx9Vl7Ch8wTtpXPGqbehkZ+IncKqShUxvrt1+Enc8=
github.com/nats-io/nats-server/v2 v2.12.6/go.mod h1:4HPlrvtmSO3yd7KcElDNMx9kv5EBJBnJJzQPptXlheo=
github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=
github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw=
github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
@@ -387,8 +387,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -415,8 +415,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -445,8 +445,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260304144227-18da59047661 h1:AjMsnhVxCTd9HssPcdXPhNTQ0YazDt5+52EqmcWQ0PA=
golang.org/x/telemetry v0.0.0-20260304144227-18da59047661/go.mod h1:NuITXsA9cTiqnXtVk+/wrBT2Ja4X5hsfGOYRJ6kgYjs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -454,18 +454,18 @@ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+1 -1
View File
@@ -5,7 +5,7 @@ set -euo pipefail
# Usage: scripts/check-coverage.sh <coverprofile> [quiet]
PROFILE="${1:?Usage: check-coverage.sh <coverprofile> [quiet]}"
MIN=65.0
MIN=60.0
QUIET="${2:-}"
if [[ ! -s "$PROFILE" ]]; then
+3 -7
View File
@@ -172,14 +172,10 @@ echo ""
echo " 1. Setup OVN networking:"
echo " ./scripts/setup-ovn.sh --management"
echo ""
echo " 2. Initialize Spinifex (creates ~/spinifex/ directories, config, and certs):"
echo " ./bin/spx admin init --region ap-southeast-2 --az ap-southeast-2a --node node1 --nodes 1"
echo " 2. Initialize Spinifex (creates ~/spinifex/ directories, config, certs, and installs CA):"
echo " sudo ./bin/spx admin init --region ap-southeast-2 --az ap-southeast-2a --node node1 --nodes 1"
echo ""
echo " 3. Trust the CA certificate:"
echo " sudo cp ~/spinifex/config/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt"
echo " sudo update-ca-certificates"
echo ""
echo " 4. Start services:"
echo " 3. Start services:"
echo " ./scripts/start-dev.sh"
echo ""
echo "🔧 Development endpoints:"
+8 -8
View File
@@ -26,10 +26,10 @@ variable "region" {
# Set these environment variables or use terraform.tfvars:
# export AWS_ACCESS_KEY_ID=your_access_key
# export AWS_SECRET_ACCESS_KEY=your_secret_key
# export TF_VAR_mulgaos_endpoint=http://localhost:8080
# export TF_VAR_mulgaos_endpoint=https://localhost:9999
variable "mulgaos_endpoint" {
type = string
default = "http://localhost:8080"
default = "https://localhost:9999"
description = "MulgaOS API endpoint (awsd gateway)"
}
@@ -57,15 +57,15 @@ data "aws_availability_zones" "available" {
state = "available"
}
# Debian 12 AMI (official Debian account)
# For MulgaOS, you may need to import a local AMI first
# Debian 12 AMI
# For MulgaOS, need to import a local AMI first
data "aws_ami" "debian12" {
most_recent = true
owners = ["136693071363"] # Debian official
owners = ["000000000000"] # Spinifex system images (on AWS use "136693071363" for Debian official)
filter {
name = "name"
values = ["debian-12-amd64-*"]
values = ["*debian-12*"]
}
filter {
@@ -96,9 +96,9 @@ resource "local_file" "hive_test_pem" {
file_permission = "0600"
}
# 1: VPC 10.1.1.0/16
# 1: VPC 10.1.0.0/16
resource "aws_vpc" "main" {
cidr_block = "10.1.1.0/16"
cidr_block = "10.1.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
+1 -1
View File
@@ -110,7 +110,7 @@ INIT_PID=$!
# Wait for formation server to be ready
echo " Waiting for formation server on $NODE1_IP..."
attempts=0
while ! curl -s --connect-timeout 3 "http://$NODE1_IP:4432/formation/health" >/dev/null 2>&1; do
while ! curl -sk --connect-timeout 3 "https://$NODE1_IP:4432/formation/health" >/dev/null 2>&1; do
attempts=$((attempts + 1))
if [ $attempts -ge 60 ]; then
echo "Error: Formation server did not start on $NODE1_IP after 5 minutes"
+4 -5
View File
@@ -349,14 +349,13 @@ print_summary() {
echo " 2. Initialize a region:"
echo " sudo spx admin init --region <region> --az <az> --node node1 --nodes 1"
echo ""
echo " 3. Trust the CA certificate:"
echo " sudo cp /etc/spinifex/ca.pem /usr/local/share/ca-certificates/spinifex-ca.crt"
echo " sudo update-ca-certificates"
echo " 2. Setup OVN networking:"
echo " sudo /usr/local/share/spinifex/setup-ovn.sh --management"
echo ""
echo " 4. Start services:"
echo " 3. Start services:"
echo " sudo systemctl start spinifex.target"
echo ""
echo " 5. Verify:"
echo " 4. Verify:"
echo " export AWS_PROFILE=spinifex"
echo " aws ec2 describe-instance-types"
echo ""
+8 -6
View File
@@ -170,11 +170,13 @@ func GenerateCertificatesIfNeeded(configDir string, force bool, bindIP string) (
fmt.Printf(" Certificate: %s\n", serverCertPath)
fmt.Printf(" Key: %s\n", serverKeyPath)
// Print instructions for adding CA to system trust store
fmt.Println("\n📋 To trust the Spinifex CA system-wide (recommended):")
fmt.Printf(" sudo cp %s /usr/local/share/ca-certificates/spinifex-ca.crt\n", caCertPath)
fmt.Println(" sudo update-ca-certificates")
fmt.Println("\n This allows AWS CLI and other tools to trust Spinifex services automatically.")
// Print manual instructions only when not root (root gets auto-install)
if os.Getuid() != 0 {
fmt.Println("\n📋 To trust the Spinifex CA system-wide (recommended):")
fmt.Printf(" sudo cp %s /usr/local/share/ca-certificates/spinifex-ca.crt\n", caCertPath)
fmt.Println(" sudo update-ca-certificates")
fmt.Println("\n This allows AWS CLI and other tools to trust Spinifex services automatically.")
}
} else {
fmt.Println("\n✅ CA and SSL certificates already exist")
}
@@ -249,7 +251,7 @@ func ChownRecursive(path, username string) {
root, rootErr := os.OpenRoot(path)
if rootErr != nil {
// Fallback: direct chown on the top-level path only
if chownErr := os.Chown(path, uid, gid); chownErr != nil { // #nosec G122
if chownErr := os.Chown(path, uid, gid); chownErr != nil {
slog.Debug("chown failed", "path", path, "err", chownErr)
}
return
+4 -2
View File
@@ -5,6 +5,8 @@ import (
"net"
"os/exec"
"strings"
"github.com/mulgadc/spinifex/spinifex/utils"
)
// DetectedInterface represents a network interface discovered on the host.
@@ -169,10 +171,10 @@ func SuggestPoolRange(wan *DetectedInterface) (start, end string) {
for i := 3; i >= 0 && carry > 0; i-- {
val := int(startIP[i]) - carry
if val < 0 {
startIP[i] = byte(256 + val) //nolint:gosec // val is in [-256,0), so 256+val is in [0,256)
startIP[i] = utils.SafeIntToUint8(256 + val)
carry = 1
} else {
startIP[i] = byte(val) //nolint:gosec // val is non-negative and <= 255
startIP[i] = utils.SafeIntToUint8(val)
carry = 0
}
}
+11
View File
@@ -5,6 +5,7 @@ import (
"log/slog"
"os"
"slices"
"strings"
"github.com/spf13/viper"
)
@@ -197,5 +198,15 @@ func LoadConfig(configPath string) (*ClusterConfig, error) {
return nil, fmt.Errorf("error unmarshaling config: %w", err)
}
// Normalize the local node's bind address: 0.0.0.0 means "listen on all
// interfaces" but is not a valid connect address. Only rewrite for the
// local node — remote nodes use real IPs that must not be changed.
if local, ok := config.Nodes[config.Node]; ok {
if strings.HasPrefix(local.Predastore.Host, "0.0.0.0") {
local.Predastore.Host = strings.Replace(local.Predastore.Host, "0.0.0.0", "127.0.0.1", 1)
config.Nodes[config.Node] = local
}
}
return &config, nil
}
+1 -1
View File
@@ -205,7 +205,7 @@ expected_nodes = 3
assert.Equal(t, "0.0.0.0:4222", n.NATS.Host)
assert.Equal(t, "secret-token", n.NATS.ACL.Token)
assert.Equal(t, "test-subject", n.NATS.Sub.Subject)
assert.Equal(t, "0.0.0.0:8443", n.Predastore.Host)
assert.Equal(t, "127.0.0.1:8443", n.Predastore.Host) // 0.0.0.0 normalized to loopback
assert.Equal(t, "mybucket", n.Predastore.Bucket)
assert.Equal(t, "AK", n.Predastore.AccessKey)
assert.Equal(t, "/data", n.Predastore.BaseDir)
+102
View File
@@ -5,6 +5,9 @@ import (
"fmt"
"log/slog"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/mulgadc/spinifex/spinifex/awserrors"
@@ -175,9 +178,108 @@ func (d *Daemon) handleNodeStatus(msg *nats.Msg) {
InstanceTypes: caps,
}
// Query service roles concurrently to halve worst-case latency (500ms vs 1s).
var wg sync.WaitGroup
wg.Add(2)
go func() { defer wg.Done(); resp.NATSRole = d.queryNATSRole() }()
go func() { defer wg.Done(); resp.PredastoreRole = d.queryPredastoreRole() }()
wg.Wait()
respondWithJSON(msg, resp)
}
const (
roleLeader = "leader"
roleFollower = "follower"
natsMonitorPort = 8222
predastoreDBPort = 6660
)
// queryNATSRole queries the local NATS monitoring endpoint to determine this
// node's JetStream meta-leader status. Returns "leader", "follower", or "".
func (d *Daemon) queryNATSRole() string {
if !d.config.HasService("nats") {
return ""
}
url := fmt.Sprintf("http://%s:%d/varz", d.daemonIP(), natsMonitorPort)
return fetchNATSRole(url, roleHTTPClient)
}
// queryPredastoreRole queries the local Predastore DB status endpoint to
// determine this node's Raft role. Returns "leader", "follower", or "".
func (d *Daemon) queryPredastoreRole() string {
if !d.config.HasService("predastore") {
return ""
}
url := fmt.Sprintf("https://%s:%d/status", d.daemonIP(), predastoreDBPort)
return fetchPredastoreRole(url, roleTLSHTTPClient)
}
var roleHTTPClient = &http.Client{Timeout: 500 * time.Millisecond}
var roleTLSHTTPClient = &http.Client{Timeout: 500 * time.Millisecond}
// fetchNATSRole queries a NATS /varz endpoint and returns "leader", "follower", or "".
func fetchNATSRole(url string, client *http.Client) string {
resp, err := client.Get(url) //nolint:noctx // internal monitoring call
if err != nil {
slog.Debug("Failed to query NATS monitoring", "err", err)
return ""
}
defer resp.Body.Close()
var varz struct {
ServerName string `json:"server_name"`
JetStream struct {
Meta struct {
Leader string `json:"leader"`
ClusterSize int `json:"cluster_size"`
} `json:"meta"`
} `json:"jetstream"`
}
if err := json.NewDecoder(resp.Body).Decode(&varz); err != nil {
slog.Debug("Failed to decode NATS varz", "err", err)
return ""
}
// Single node or no meta cluster — this node is the leader by default
if varz.JetStream.Meta.ClusterSize <= 1 {
return roleLeader
}
if varz.JetStream.Meta.Leader == varz.ServerName {
return roleLeader
}
return roleFollower
}
// fetchPredastoreRole queries a Predastore /status endpoint and returns "leader", "follower", or "".
func fetchPredastoreRole(url string, client *http.Client) string {
resp, err := client.Get(url) //nolint:noctx // internal monitoring call
if err != nil {
if strings.Contains(err.Error(), "x509") || strings.Contains(err.Error(), "certificate") || strings.Contains(err.Error(), "tls:") {
slog.Warn("Failed to query Predastore status (TLS misconfiguration — is the Spinifex CA installed in the system trust store?)", "err", err)
} else {
slog.Debug("Failed to query Predastore status", "err", err)
}
return ""
}
defer resp.Body.Close()
var status struct {
IsLeader bool `json:"is_leader"`
}
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
slog.Debug("Failed to decode Predastore status", "err", err)
return ""
}
if status.IsLeader {
return roleLeader
}
return roleFollower
}
// handleNodeVMs responds with the list of VMs running on this node.
// Used by the CLI: spx get vms.
func (d *Daemon) handleNodeVMs(msg *nats.Msg) {
+1 -1
View File
@@ -119,7 +119,7 @@ func OVSIfaceID(eniId string) string {
func generateDevMAC(instanceId string) string {
h := uint32(0)
for _, c := range instanceId {
h = h*31 + uint32(c) // #nosec G115 -- intentional overflow for hashing
h = h*31 + uint32(c) //nolint:gosec // G115: intentional overflow for hashing
}
return fmt.Sprintf("02:de:00:%02x:%02x:%02x", (h>>16)&0xff, (h>>8)&0xff, h&0xff)
}
+113
View File
@@ -0,0 +1,113 @@
package daemon
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFetchNATSRole_SingleNode(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{
"server_name": "spinifex-nats-node1",
"jetstream": {
"meta": {
"leader": "",
"cluster_size": 0
}
}
}`))
}))
defer srv.Close()
role := fetchNATSRole(srv.URL, srv.Client())
assert.Equal(t, "leader", role)
}
func TestFetchNATSRole_ClusterLeader(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{
"server_name": "spinifex-nats-node1",
"jetstream": {
"meta": {
"leader": "spinifex-nats-node1",
"cluster_size": 3
}
}
}`))
}))
defer srv.Close()
role := fetchNATSRole(srv.URL, srv.Client())
assert.Equal(t, "leader", role)
}
func TestFetchNATSRole_ClusterFollower(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{
"server_name": "spinifex-nats-node2",
"jetstream": {
"meta": {
"leader": "spinifex-nats-node1",
"cluster_size": 3
}
}
}`))
}))
defer srv.Close()
role := fetchNATSRole(srv.URL, srv.Client())
assert.Equal(t, "follower", role)
}
func TestFetchNATSRole_Unreachable(t *testing.T) {
role := fetchNATSRole("http://127.0.0.1:1/varz", &http.Client{})
assert.Equal(t, "", role)
}
func TestFetchNATSRole_BadJSON(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`not json`))
}))
defer srv.Close()
role := fetchNATSRole(srv.URL, srv.Client())
assert.Equal(t, "", role)
}
func TestFetchPredastoreRole_Leader(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"node_id":"1","state":"Leader","is_leader":true}`))
}))
defer srv.Close()
role := fetchPredastoreRole(srv.URL, srv.Client())
assert.Equal(t, "leader", role)
}
func TestFetchPredastoreRole_Follower(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"node_id":"2","state":"Follower","is_leader":false}`))
}))
defer srv.Close()
role := fetchPredastoreRole(srv.URL, srv.Client())
assert.Equal(t, "follower", role)
}
func TestFetchPredastoreRole_Unreachable(t *testing.T) {
role := fetchPredastoreRole("https://127.0.0.1:1/status", &http.Client{})
assert.Equal(t, "", role)
}
func TestFetchPredastoreRole_BadJSON(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`not json`))
}))
defer srv.Close()
role := fetchPredastoreRole(srv.URL, srv.Client())
assert.Equal(t, "", role)
}
+16 -4
View File
@@ -2,6 +2,7 @@ package formation
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"log/slog"
@@ -165,8 +166,9 @@ func (fs *FormationServer) SetMasterKey(key string) {
fs.masterKey = key
}
// Start launches the HTTP server on the given address (e.g. "10.0.0.1:4432").
func (fs *FormationServer) Start(bindAddr string) error {
// Start launches the HTTPS server on the given address (e.g. "10.0.0.1:4432").
// tlsCert and tlsKey are paths to the CA-signed server certificate and key.
func (fs *FormationServer) Start(bindAddr, tlsCert, tlsKey string) error {
mux := http.NewServeMux()
mux.HandleFunc("POST /formation/join", fs.handleJoin)
mux.HandleFunc("GET /formation/status", fs.handleStatus)
@@ -181,18 +183,28 @@ func (fs *FormationServer) Start(bindAddr string) error {
ReadHeaderTimeout: 5 * time.Second,
}
cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey)
if err != nil {
return fmt.Errorf("formation server TLS: %w", err)
}
ln, err := net.Listen("tcp", bindAddr)
if err != nil {
return fmt.Errorf("formation server listen: %w", err)
}
tlsLn := tls.NewListener(ln, &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
})
go func() {
if err := fs.server.Serve(ln); err != nil && err != http.ErrServerClosed {
if err := fs.server.Serve(tlsLn); err != nil && err != http.ErrServerClosed {
slog.Error("Formation server error", "error", err)
}
}()
slog.Info("Formation server started", "addr", bindAddr)
slog.Info("Formation server started", "addr", bindAddr, "tls", true)
return nil
}
+51
View File
@@ -2,12 +2,20 @@ package formation
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"github.com/mulgadc/spinifex/spinifex/admin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -339,3 +347,46 @@ func TestFullFormationFlow(t *testing.T) {
assert.Len(t, pnodes, 3)
assert.Equal(t, 1, pnodes[0].ID)
}
func TestStartTLS(t *testing.T) {
t.Parallel()
// Generate temp CA + server cert
tmpDir := t.TempDir()
caCertPath := filepath.Join(tmpDir, "ca.pem")
caKeyPath := filepath.Join(tmpDir, "ca.key")
serverCertPath := filepath.Join(tmpDir, "server.pem")
serverKeyPath := filepath.Join(tmpDir, "server.key")
require.NoError(t, admin.GenerateCACert(caCertPath, caKeyPath))
require.NoError(t, admin.GenerateSignedCert(serverCertPath, serverKeyPath, caCertPath, caKeyPath, "127.0.0.1"))
// Pick a free port
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
addr := ln.Addr().String()
ln.Close()
// Start the formation server with TLS
fs := NewFormationServer(1, testCreds(), "ca-cert", "ca-key")
require.NoError(t, fs.Start(addr, serverCertPath, serverKeyPath))
defer fs.Shutdown(context.Background())
// Build an HTTPS client that trusts the CA
caCert, err := os.ReadFile(caCertPath)
require.NoError(t, err)
pool := x509.NewCertPool()
require.True(t, pool.AppendCertsFromPEM(caCert))
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
},
}
// Hit /formation/health over HTTPS and verify 200 OK
resp, err := client.Get(fmt.Sprintf("https://%s/formation/health", addr))
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
+5 -1
View File
@@ -186,9 +186,13 @@ func (s *ImageServiceImpl) DescribeImages(input *ec2.DescribeImagesInput, accoun
found = true
}
default:
// Match by explicit account ID
// Match by explicit account ID. System AMIs are stored
// with a non-account owner (e.g. "spinifex") but report
// GlobalAccountID in the response, so match against both.
if amiOwner == *owner {
found = true
} else if isSystemAMI && *owner == utils.GlobalAccountID {
found = true
}
}
if found {
@@ -418,6 +418,23 @@ func TestDescribeImages_SystemAMIVisibleToAll(t *testing.T) {
require.Len(t, result2.Images, 1)
}
func TestDescribeImages_FilterSystemAMIByGlobalAccountID(t *testing.T) {
svc, store := setupTestImageService(t)
// Create a system AMI (non-account-ID owner like "spinifex")
createTestAMIConfigWithOwner(t, store, "ami-sysfilter", "system-debian", "spinifex")
// Filtering by GlobalAccountID ("000000000000") should match system AMIs
// because that's the OwnerId returned in the response
result, err := svc.DescribeImages(&ec2.DescribeImagesInput{
Owners: []*string{aws.String("000000000000")},
}, "000000000001")
require.NoError(t, err)
require.Len(t, result.Images, 1)
assert.Equal(t, "ami-sysfilter", *result.Images[0].ImageId)
assert.Equal(t, "000000000000", *result.Images[0].OwnerId)
}
func TestDescribeImages_NilInput(t *testing.T) {
svc, _ := setupTestImageService(t)
+2 -2
View File
@@ -2,7 +2,7 @@ package handlers_ec2_key
import (
"bytes"
"crypto/md5" //#nosec G501 - need md5 for AWS compatibility
"crypto/md5" //nolint:gosec // G501: need md5 for AWS compatibility
"crypto/sha256"
"encoding/base64"
"encoding/hex"
@@ -217,7 +217,7 @@ func (s *KeyServiceImpl) calculateFingerprint(publicKeyData []byte, keyType stri
} else {
// RSA uses SHA-1 or MD5 fingerprint
// AWS uses MD5 for RSA keys for backward compatibility
hash := md5.Sum(keyData) //#nosec G401 - need md5 for AWS compatibility
hash := md5.Sum(keyData) //nolint:gosec // G401: need md5 for AWS compatibility
return formatFingerprint(hash[:], "MD5"), nil
}
}
+6 -9
View File
@@ -266,8 +266,7 @@ func (s *VolumeServiceImpl) DescribeVolumes(input *ec2.DescribeVolumesInput, acc
}
// Skip volumes not owned by the caller's account.
// Pre-Phase4 volumes (empty TenantID) are visible to all accounts.
if accountID != "" && result.tenantID != "" && result.tenantID != accountID {
if result.tenantID != accountID {
continue
}
@@ -303,7 +302,7 @@ func (s *VolumeServiceImpl) DescribeVolumeStatus(input *ec2.DescribeVolumeStatus
return nil, errors.New(awserrors.ErrorInvalidVolumeNotFound)
}
// Skip volumes not owned by the caller's account
if accountID != "" && tenantID != "" && tenantID != accountID {
if tenantID != accountID {
return nil, errors.New(awserrors.ErrorInvalidVolumeNotFound)
}
statusItems = append(statusItems, item)
@@ -326,8 +325,7 @@ func (s *VolumeServiceImpl) DescribeVolumeStatus(input *ec2.DescribeVolumeStatus
}
// Skip volumes not owned by the caller's account.
// Pre-Phase4 volumes (empty TenantID) are visible to all accounts.
if accountID != "" && tenantID != "" && tenantID != accountID {
if tenantID != accountID {
continue
}
@@ -433,8 +431,7 @@ func (s *VolumeServiceImpl) fetchVolumesByIDs(volumeIDs []*string, accountID str
}
// Skip volumes not owned by the caller's account.
// Pre-Phase4 volumes (empty TenantID) are visible to all accounts.
if accountID != "" && result.tenantID != "" && result.tenantID != accountID {
if result.tenantID != accountID {
return
}
@@ -669,7 +666,7 @@ func (s *VolumeServiceImpl) ModifyVolume(input *ec2.ModifyVolumeInput, accountID
}
// Verify caller owns this volume
if accountID != "" && cfg.VolumeMetadata.TenantID != "" && cfg.VolumeMetadata.TenantID != accountID {
if cfg.VolumeMetadata.TenantID != accountID {
return nil, errors.New(awserrors.ErrorInvalidVolumeNotFound)
}
@@ -758,7 +755,7 @@ func (s *VolumeServiceImpl) DeleteVolume(input *ec2.DeleteVolumeInput, accountID
}
// Verify caller owns this volume
if accountID != "" && cfg.VolumeMetadata.TenantID != "" && cfg.VolumeMetadata.TenantID != accountID {
if cfg.VolumeMetadata.TenantID != accountID {
return nil, errors.New(awserrors.ErrorInvalidVolumeNotFound)
}
@@ -815,18 +815,15 @@ func TestDescribeVolumes_AccountScoping_SlowPath(t *testing.T) {
store := objectstore.NewMemoryObjectStore()
svc := newTestVolumeServiceWithStore("ap-southeast-2a", store)
// Create volumes for two different accounts and one pre-Phase4 (empty TenantID)
// Create volumes for two different accounts
createVolumeInStoreWithMeta(t, store, "vol-acctA", viperblock.VolumeMetadata{
VolumeID: "vol-acctA", SizeGiB: 10, State: "available", TenantID: "111111111111",
})
createVolumeInStoreWithMeta(t, store, "vol-acctB", viperblock.VolumeMetadata{
VolumeID: "vol-acctB", SizeGiB: 10, State: "available", TenantID: "222222222222",
})
createVolumeInStoreWithMeta(t, store, "vol-legacy", viperblock.VolumeMetadata{
VolumeID: "vol-legacy", SizeGiB: 10, State: "available", TenantID: "",
})
// Account A sees its own volume + legacy (empty TenantID)
// Account A sees only its own volume
output, err := svc.DescribeVolumes(&ec2.DescribeVolumesInput{}, "111111111111")
require.NoError(t, err)
ids := map[string]bool{}
@@ -835,9 +832,8 @@ func TestDescribeVolumes_AccountScoping_SlowPath(t *testing.T) {
}
assert.True(t, ids["vol-acctA"], "Account A should see its own volume")
assert.False(t, ids["vol-acctB"], "Account A should NOT see Account B's volume")
assert.True(t, ids["vol-legacy"], "Account A should see pre-Phase4 volumes")
// Account B sees its own volume + legacy
// Account B sees only its own volume
output, err = svc.DescribeVolumes(&ec2.DescribeVolumesInput{}, "222222222222")
require.NoError(t, err)
ids = map[string]bool{}
@@ -846,12 +842,6 @@ func TestDescribeVolumes_AccountScoping_SlowPath(t *testing.T) {
}
assert.True(t, ids["vol-acctB"], "Account B should see its own volume")
assert.False(t, ids["vol-acctA"], "Account B should NOT see Account A's volume")
assert.True(t, ids["vol-legacy"], "Account B should see pre-Phase4 volumes")
// Empty accountID sees all volumes (backward compat)
output, err = svc.DescribeVolumes(&ec2.DescribeVolumesInput{}, "")
require.NoError(t, err)
assert.Len(t, output.Volumes, 3)
}
func TestDescribeVolumes_AccountScoping_FastPath(t *testing.T) {
+1 -1
View File
@@ -407,7 +407,7 @@ func (s *VPCServiceImpl) eniRecordToEC2(record *ENIRecord, accountID string) *ec
func generateENIMac(eniId string) string {
h := uint32(0)
for _, c := range eniId {
h = h*31 + uint32(c) // #nosec G115 -- intentional overflow for hashing
h = h*31 + uint32(c) //nolint:gosec // G115: intentional overflow for hashing
}
return fmt.Sprintf("02:00:00:%02x:%02x:%02x", (h>>16)&0xff, (h>>8)&0xff, h&0xff)
}
+1 -1
View File
@@ -182,7 +182,7 @@ func (m *IPAM) nextAvailableIP(record *IPAMRecord) (string, error) {
ones, bits := ipNet.Mask.Size()
hostBits := bits - ones // always 0-32 for IPv4 CIDRs, safe for uint conversion
totalIPs := new(big.Int).Lsh(big.NewInt(1), uint(hostBits)) //#nosec G115 - hostBits is 0-32 for IPv4
totalIPs := new(big.Int).Lsh(big.NewInt(1), uint(hostBits)) //nolint:gosec // G115: hostBits is 0-32 for IPv4
// Start at offset 4 (.0=network, .1=gateway, .2=DNS, .3=reserved)
networkIP := ipToInt(ipNet.IP)
+2 -2
View File
@@ -446,7 +446,7 @@ func (s *VPCServiceImpl) CreateSubnet(input *ec2.CreateSubnetInput, accountID st
// Calculate available IPs (total hosts minus AWS reserved: network, router, DNS, future, broadcast)
// ones is validated to be 16-28 above, so (32-ones) is always 4-16 and safe for uint conversion
totalHosts := max((1<<(32-ones))-5, 0) //#nosec G115 - ones validated 16-28
totalHosts := max((1<<(32-ones))-5, 0)
record := SubnetRecord{
SubnetId: subnetID,
@@ -575,7 +575,7 @@ func (s *VPCServiceImpl) DescribeSubnets(input *ec2.DescribeSubnetsInput, accoun
availableIPs := 0
if err == nil {
ones, _ := subnetNet.Mask.Size()
availableIPs = max((1<<(32-ones))-5, 0) //#nosec G115 - ones from validated CIDR
availableIPs = max((1<<(32-ones))-5, 0)
}
subnets = append(subnets, s.subnetRecordToEC2(&record, availableIPs, accountID))
+3 -1
View File
@@ -737,7 +737,9 @@ func (s *IAMServiceImpl) seedAdminAccount(admin *AdminBootstrapData) error {
}{AccountID: admin.AccountID, AccountName: admin.AccountName})
if err != nil {
slog.Warn("Failed to marshal account creation event", "accountID", admin.AccountID, "error", err)
} else if err := s.natsConn.Publish("iam.account.created", evt); err != nil {
return nil
}
if err := s.natsConn.Publish("iam.account.created", evt); err != nil {
slog.Warn("Failed to publish account creation event for admin account", "accountID", admin.AccountID, "error", err)
}
}
-13
View File
@@ -5,10 +5,8 @@ package objectstore
import (
"bytes"
"crypto/tls"
"errors"
"io"
"net/http"
"strings"
"sync"
@@ -57,22 +55,11 @@ type ObjectStore interface {
// NewS3ObjectStoreFromConfig creates an S3ObjectStore from Predastore connection parameters,
// eliminating the duplicated TLS+HTTP/2+session boilerplate in each service.
func NewS3ObjectStoreFromConfig(host, region, accessKey, secretKey string) *S3ObjectStore {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // Skip TLS verification for self-signed certs
NextProtos: []string{"h2", "http/1.1"},
},
ForceAttemptHTTP2: true,
}
httpClient := &http.Client{Transport: tr}
sess := session.Must(session.NewSession(&aws.Config{
Endpoint: aws.String(host),
Region: aws.String(region),
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
S3ForcePathStyle: aws.Bool(true),
HTTPClient: httpClient,
}))
return NewS3ObjectStore(s3.New(sess))
@@ -1 +1 @@
import{Dt as e,Et as t,jt as n,nt as r}from"./awsClient.js";var i=class extends n.classBuilder().ep(t).m(function(t,n,r,i){return[e(r,t.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeInstances`,{}).n(`EC2Client`,`DescribeInstancesCommand`).sc(r).build(){};export{i as t};
import{At as e,Pt as t,it as n,kt as r}from"./awsClient.js";var i=class extends t.classBuilder().ep(r).m(function(t,n,r,i){return[e(r,t.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeInstances`,{}).n(`EC2Client`,`DescribeInstancesCommand`).sc(n).build(){};export{i as t};
+1 -1
View File
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as r}from"./error-banner.js";import{c as i,i as a,r as o,t as s}from"./utils.js";import"./useStore.js";import{t as c}from"./link.js";import"./button.js";import{t as l}from"./back-link.js";import{n as u,r as d,t as f}from"./upload-button.js";import"./awsClient.js";import{r as p}from"./s3.js";import{t as m}from"./s32.js";import{d as h}from"./index.js";import"./alert-dialog.js";import"./delete-confirmation-dialog.js";var g=t(),_=e();function v(){let e=(0,g.c)(24),{bucket:t,_splat:v}=h.useParams(),y=o(v??``),{data:b}=n(m(t,y)),x=p(),S;e[0]===b.Contents?S=e[1]:(S=b.Contents??[],e[0]=b.Contents,e[1]=S);let C=S,w;e[2]===b.CommonPrefixes?w=e[3]:(w=b.CommonPrefixes??[],e[2]=b.CommonPrefixes,e[3]=w);let T=w,E=y.split(`/`).filter(Boolean),D=[{name:t,path:`/s3/ls/${t}`},...E.map((e,n)=>({name:e,path:`/s3/ls/${t}/${`${E.slice(0,n+1).join(`/`)}/`}`}))],O=E.length>1?`/s3/ls/${t}/${E.slice(0,-1).join(`/`)}/`:`/s3/ls/${t}`,k;e[4]===O?k=e[5]:(k=(0,_.jsx)(l,{to:O,children:`Back`}),e[4]=O,e[5]=k);let A;e[6]!==t||e[7]!==y||e[8]!==x.isPending||e[9]!==x.mutateAsync?(A=(0,_.jsx)(f,{bucket:t,isPending:x.isPending,onUpload:x.mutateAsync,prefix:y}),e[6]=t,e[7]=y,e[8]=x.isPending,e[9]=x.mutateAsync,e[10]=A):A=e[10];let j;e[11]===x.error?j=e[12]:(j=x.error&&(0,_.jsx)(r,{error:x.error,msg:`Failed to upload object`}),e[11]=x.error,e[12]=j);let M;e[13]!==t||e[14]!==T||e[15]!==y?(M=T.length>0&&(0,_.jsxs)(`div`,{className:`mb-6`,children:[(0,_.jsx)(`h2`,{className:`mb-3 text-lg font-semibold`,children:`Folders`}),(0,_.jsx)(`div`,{className:`space-y-2`,children:T.map(e=>e.Prefix?(0,_.jsx)(d,{bucketName:t,folderName:i(a(e.Prefix,y)),fullPrefix:s(e.Prefix,y)},e.Prefix):null)})]}),e[13]=t,e[14]=T,e[15]=y,e[16]=M):M=e[16];let N;e[17]!==t||e[18]!==C||e[19]!==y?(N=C.length>0&&(0,_.jsxs)(`div`,{className:`space-y-2`,children:[(0,_.jsx)(`h2`,{className:`mb-3 text-lg font-semibold`,children:`Objects`}),C.map(e=>e.Key?(0,_.jsx)(u,{bucketName:t,displayName:a(e.Key,y),fullKey:s(e.Key,y),object:e},e.Key):null)]}),e[17]=t,e[18]=C,e[19]=y,e[20]=N):N=e[20];let P;return e[21]!==T.length||e[22]!==C.length?(P=C.length===0&&T.length===0&&(0,_.jsx)(`p`,{className:`text-muted-foreground`,children:`No objects found in this folder.`}),e[21]=T.length,e[22]=C.length,e[23]=P):P=e[23],(0,_.jsxs)(_.Fragment,{children:[k,(0,_.jsx)(`div`,{className:`mb-6`,children:(0,_.jsxs)(`div`,{className:`mb-2 flex items-center justify-between`,children:[(0,_.jsx)(`div`,{className:`flex items-center gap-2 text-sm text-muted-foreground`,children:D.map((e,t)=>(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[t>0&&(0,_.jsx)(`span`,{children:`/`}),t===D.length-1?(0,_.jsx)(`span`,{className:`text-foreground`,children:e.name}):(0,_.jsx)(c,{className:`hover:text-foreground`,to:e.path,children:e.name})]},e.path))}),A]})}),j,M,N,P]})}export{v as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as r}from"./error-banner.js";import{c as i,i as a,r as o,t as s}from"./utils.js";import"./useStore.js";import{t as c}from"./link.js";import"./button.js";import{t as l}from"./back-link.js";import{n as u,r as d,t as f}from"./upload-button.js";import"./awsClient.js";import{r as p}from"./s3.js";import{t as m}from"./s32.js";import{f as h}from"./index.js";import"./alert-dialog.js";import"./delete-confirmation-dialog.js";var g=t(),_=e();function v(){let e=(0,g.c)(24),{bucket:t,_splat:v}=h.useParams(),y=o(v??``),{data:b}=n(m(t,y)),x=p(),S;e[0]===b.Contents?S=e[1]:(S=b.Contents??[],e[0]=b.Contents,e[1]=S);let C=S,w;e[2]===b.CommonPrefixes?w=e[3]:(w=b.CommonPrefixes??[],e[2]=b.CommonPrefixes,e[3]=w);let T=w,E=y.split(`/`).filter(Boolean),D=[{name:t,path:`/s3/ls/${t}`},...E.map((e,n)=>({name:e,path:`/s3/ls/${t}/${`${E.slice(0,n+1).join(`/`)}/`}`}))],O=E.length>1?`/s3/ls/${t}/${E.slice(0,-1).join(`/`)}/`:`/s3/ls/${t}`,k;e[4]===O?k=e[5]:(k=(0,_.jsx)(l,{to:O,children:`Back`}),e[4]=O,e[5]=k);let A;e[6]!==t||e[7]!==y||e[8]!==x.isPending||e[9]!==x.mutateAsync?(A=(0,_.jsx)(f,{bucket:t,isPending:x.isPending,onUpload:x.mutateAsync,prefix:y}),e[6]=t,e[7]=y,e[8]=x.isPending,e[9]=x.mutateAsync,e[10]=A):A=e[10];let j;e[11]===x.error?j=e[12]:(j=x.error&&(0,_.jsx)(r,{error:x.error,msg:`Failed to upload object`}),e[11]=x.error,e[12]=j);let M;e[13]!==t||e[14]!==T||e[15]!==y?(M=T.length>0&&(0,_.jsxs)(`div`,{className:`mb-6`,children:[(0,_.jsx)(`h2`,{className:`mb-3 text-lg font-semibold`,children:`Folders`}),(0,_.jsx)(`div`,{className:`space-y-2`,children:T.map(e=>e.Prefix?(0,_.jsx)(d,{bucketName:t,folderName:i(a(e.Prefix,y)),fullPrefix:s(e.Prefix,y)},e.Prefix):null)})]}),e[13]=t,e[14]=T,e[15]=y,e[16]=M):M=e[16];let N;e[17]!==t||e[18]!==C||e[19]!==y?(N=C.length>0&&(0,_.jsxs)(`div`,{className:`space-y-2`,children:[(0,_.jsx)(`h2`,{className:`mb-3 text-lg font-semibold`,children:`Objects`}),C.map(e=>e.Key?(0,_.jsx)(u,{bucketName:t,displayName:a(e.Key,y),fullKey:s(e.Key,y),object:e},e.Key):null)]}),e[17]=t,e[18]=C,e[19]=y,e[20]=N):N=e[20];let P;return e[21]!==T.length||e[22]!==C.length?(P=C.length===0&&T.length===0&&(0,_.jsx)(`p`,{className:`text-muted-foreground`,children:`No objects found in this folder.`}),e[21]=T.length,e[22]=C.length,e[23]=P):P=e[23],(0,_.jsxs)(_.Fragment,{children:[k,(0,_.jsx)(`div`,{className:`mb-6`,children:(0,_.jsxs)(`div`,{className:`mb-2 flex items-center justify-between`,children:[(0,_.jsx)(`div`,{className:`flex items-center gap-2 text-sm text-muted-foreground`,children:D.map((e,t)=>(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[t>0&&(0,_.jsx)(`span`,{children:`/`}),t===D.length-1?(0,_.jsx)(`span`,{className:`text-foreground`,children:e.name}):(0,_.jsx)(c,{className:`hover:text-foreground`,to:e.path,children:e.name})]},e.path))}),A]})}),j,M,N,P]})}export{v as component};
+1 -1
View File
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import"./awsClient.js";import{l as r}from"./ec22.js";import{t as i}from"./page-heading.js";var a=t(),o=e();function s(){let e=(0,a.c)(2),{data:t}=n(r),s=`${t?.Regions?.[0]?.RegionName} Dashboard`,c;return e[0]===s?c=e[1]:(c=(0,o.jsx)(i,{title:s}),e[0]=s,e[1]=c),c}export{s as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import"./awsClient.js";import{d as r}from"./ec22.js";import{t as i}from"./page-heading.js";var a=t(),o=e();function s(){let e=(0,a.c)(2),{data:t}=n(r),s=`${t?.Regions?.[0]?.RegionName} Dashboard`,c;return e[0]===s?c=e[1]:(c=(0,o.jsx)(i,{title:s}),e[0]=s,e[1]=c),c}export{s as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as r}from"./error-banner.js";import{c as i}from"./utils.js";import"./useStore.js";import"./button.js";import{t as a}from"./back-link.js";import{n as o,r as s,t as c}from"./upload-button.js";import"./awsClient.js";import{r as l}from"./s3.js";import{t as u}from"./s32.js";import{f as d}from"./index.js";import{t as f}from"./page-heading.js";import"./alert-dialog.js";import"./delete-confirmation-dialog.js";var p=t(),m=e();function h(){let e=(0,p.c)(31),{bucket:t}=d.useParams(),h;e[0]===t?h=e[1]:(h=u(t),e[0]=t,e[1]=h);let{data:g}=n(h),_=l(),v;e[2]===g.Contents?v=e[3]:(v=g.Contents??[],e[2]=g.Contents,e[3]=v);let y=v,b;e[4]===g.CommonPrefixes?b=e[5]:(b=g.CommonPrefixes??[],e[4]=g.CommonPrefixes,e[5]=b);let x=b,S;e[6]===Symbol.for(`react.memo_cache_sentinel`)?(S=(0,m.jsx)(a,{to:`/s3/ls`,children:`Back to buckets`}),e[6]=S):S=e[6];let C;e[7]!==t||e[8]!==_.isPending||e[9]!==_.mutateAsync?(C=(0,m.jsx)(c,{bucket:t,isPending:_.isPending,onUpload:_.mutateAsync}),e[7]=t,e[8]=_.isPending,e[9]=_.mutateAsync,e[10]=C):C=e[10];let w;e[11]!==t||e[12]!==C?(w=(0,m.jsx)(f,{actions:C,title:t}),e[11]=t,e[12]=C,e[13]=w):w=e[13];let T;e[14]===_.error?T=e[15]:(T=_.error&&(0,m.jsx)(r,{error:_.error,msg:`Failed to upload object`}),e[14]=_.error,e[15]=T);let E;e[16]!==t||e[17]!==x?(E=x.length>0&&(0,m.jsxs)(`div`,{className:`mb-6`,children:[(0,m.jsx)(`h2`,{className:`mb-3 text-lg font-semibold`,children:`Folders`}),(0,m.jsx)(`div`,{className:`space-y-2`,children:x.map(e=>e.Prefix?(0,m.jsx)(s,{bucketName:t,folderName:i(e.Prefix),fullPrefix:e.Prefix},e.Prefix):null)})]}),e[16]=t,e[17]=x,e[18]=E):E=e[18];let D;e[19]!==t||e[20]!==y?(D=y.length>0&&(0,m.jsxs)(`div`,{className:`space-y-2`,children:[(0,m.jsx)(`h2`,{className:`mb-3 text-lg font-semibold`,children:`Objects`}),y.map(e=>e.Key?(0,m.jsx)(o,{bucketName:t,displayName:e.Key,fullKey:e.Key,object:e},e.Key):null)]}),e[19]=t,e[20]=y,e[21]=D):D=e[21];let O;e[22]!==x.length||e[23]!==y.length?(O=y.length===0&&x.length===0&&(0,m.jsx)(`p`,{className:`text-muted-foreground`,children:`No objects found in this bucket.`}),e[22]=x.length,e[23]=y.length,e[24]=O):O=e[24];let k;return e[25]!==w||e[26]!==T||e[27]!==E||e[28]!==D||e[29]!==O?(k=(0,m.jsxs)(m.Fragment,{children:[S,w,T,E,D,O]}),e[25]=w,e[26]=T,e[27]=E,e[28]=D,e[29]=O,e[30]=k):k=e[30],k}export{h as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as r}from"./error-banner.js";import{c as i}from"./utils.js";import"./useStore.js";import"./button.js";import{t as a}from"./back-link.js";import{n as o,r as s,t as c}from"./upload-button.js";import"./awsClient.js";import{r as l}from"./s3.js";import{t as u}from"./s32.js";import{p as d}from"./index.js";import{t as f}from"./page-heading.js";import"./alert-dialog.js";import"./delete-confirmation-dialog.js";var p=t(),m=e();function h(){let e=(0,p.c)(31),{bucket:t}=d.useParams(),h;e[0]===t?h=e[1]:(h=u(t),e[0]=t,e[1]=h);let{data:g}=n(h),_=l(),v;e[2]===g.Contents?v=e[3]:(v=g.Contents??[],e[2]=g.Contents,e[3]=v);let y=v,b;e[4]===g.CommonPrefixes?b=e[5]:(b=g.CommonPrefixes??[],e[4]=g.CommonPrefixes,e[5]=b);let x=b,S;e[6]===Symbol.for(`react.memo_cache_sentinel`)?(S=(0,m.jsx)(a,{to:`/s3/ls`,children:`Back to buckets`}),e[6]=S):S=e[6];let C;e[7]!==t||e[8]!==_.isPending||e[9]!==_.mutateAsync?(C=(0,m.jsx)(c,{bucket:t,isPending:_.isPending,onUpload:_.mutateAsync}),e[7]=t,e[8]=_.isPending,e[9]=_.mutateAsync,e[10]=C):C=e[10];let w;e[11]!==t||e[12]!==C?(w=(0,m.jsx)(f,{actions:C,title:t}),e[11]=t,e[12]=C,e[13]=w):w=e[13];let T;e[14]===_.error?T=e[15]:(T=_.error&&(0,m.jsx)(r,{error:_.error,msg:`Failed to upload object`}),e[14]=_.error,e[15]=T);let E;e[16]!==t||e[17]!==x?(E=x.length>0&&(0,m.jsxs)(`div`,{className:`mb-6`,children:[(0,m.jsx)(`h2`,{className:`mb-3 text-lg font-semibold`,children:`Folders`}),(0,m.jsx)(`div`,{className:`space-y-2`,children:x.map(e=>e.Prefix?(0,m.jsx)(s,{bucketName:t,folderName:i(e.Prefix),fullPrefix:e.Prefix},e.Prefix):null)})]}),e[16]=t,e[17]=x,e[18]=E):E=e[18];let D;e[19]!==t||e[20]!==y?(D=y.length>0&&(0,m.jsxs)(`div`,{className:`space-y-2`,children:[(0,m.jsx)(`h2`,{className:`mb-3 text-lg font-semibold`,children:`Objects`}),y.map(e=>e.Key?(0,m.jsx)(o,{bucketName:t,displayName:e.Key,fullKey:e.Key,object:e},e.Key):null)]}),e[19]=t,e[20]=y,e[21]=D):D=e[21];let O;e[22]!==x.length||e[23]!==y.length?(O=y.length===0&&x.length===0&&(0,m.jsx)(`p`,{className:`text-muted-foreground`,children:`No objects found in this bucket.`}),e[22]=x.length,e[23]=y.length,e[24]=O):O=e[24];let k;return e[25]!==w||e[26]!==T||e[27]!==E||e[28]!==D||e[29]!==O?(k=(0,m.jsxs)(m.Fragment,{children:[S,w,T,E,D,O]}),e[25]=w,e[26]=T,e[27]=E,e[28]=D,e[29]=O,e[30]=k):k=e[30],k}export{h as component};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
import{a as e,n as t,r as n}from"./compiler-runtime.js";import{t as r}from"./useSuspenseQuery.js";import{t as i}from"./error-banner.js";import{a}from"./utils.js";import"./useStore.js";import{t as o}from"./useNavigate.js";import{t as s}from"./button.js";import{t as c}from"./back-link.js";import{t as l}from"./trash-2.js";import"./awsClient.js";import{u}from"./ec2.js";import{s as d}from"./ec22.js";import{r as f}from"./index.js";import{t as p}from"./page-heading.js";import{n as m,t as h}from"./detail-row.js";import"./alert-dialog.js";import{t as g}from"./delete-confirmation-dialog.js";var _=e(n()),v=t();function y(){let{id:e}=f.useParams(),t=o(),{data:n}=r(d(e)),y=n.KeyPairs?.[0],b=u(),[x,S]=(0,_.useState)(!1),C=async()=>{try{await b.mutateAsync(e),t({to:`/ec2/describe-key-pairs`})}finally{S(!1)}};if(!y?.KeyPairId)return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(c,{to:`/ec2/describe-key-pairs`,children:`Back to key pairs`}),(0,v.jsx)(`p`,{className:`text-muted-foreground`,children:`Key pair not found.`})]});let w=a(y.CreateTime);return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(c,{to:`/ec2/describe-key-pairs`,children:`Back to key pairs`}),b.error&&(0,v.jsx)(i,{error:b.error,msg:`Failed to delete key pair`}),(0,v.jsxs)(`div`,{className:`space-y-6`,children:[(0,v.jsx)(p,{actions:(0,v.jsxs)(s,{onClick:()=>S(!0),size:`sm`,variant:`destructive`,children:[(0,v.jsx)(l,{className:`size-4`}),`Delete`]}),subtitle:`EC2 Key Pair Details`,title:y.KeyName??``}),(0,v.jsxs)(m,{children:[(0,v.jsx)(m.Header,{children:`Key Pair Information`}),(0,v.jsxs)(m.Content,{children:[(0,v.jsx)(h,{label:`Key Name`,value:y.KeyName}),(0,v.jsx)(h,{label:`Key Pair ID`,value:y.KeyPairId}),(0,v.jsx)(h,{label:`Key Type`,value:y.KeyType}),(0,v.jsx)(h,{label:`Key Fingerprint`,value:y.KeyFingerprint}),(0,v.jsx)(h,{label:`Creation Date`,value:w})]})]}),y.Tags&&y.Tags.length>0&&(0,v.jsxs)(m,{children:[(0,v.jsx)(m.Header,{children:`Tags`}),(0,v.jsx)(m.Content,{children:y.Tags.map(e=>(0,v.jsx)(h,{label:e.Key??``,value:e.Value},e.Key))})]})]}),(0,v.jsx)(g,{description:`Are you sure you want to delete the key pair "${y.KeyName}"? This action cannot be undone. Any instances using this key pair will no longer be able to be accessed with it.`,isPending:b.isPending,onConfirm:C,onOpenChange:S,open:x,title:`Delete Key Pair`})]})}export{y as component};
import{a as e,n as t,r as n}from"./compiler-runtime.js";import{t as r}from"./useSuspenseQuery.js";import{t as i}from"./error-banner.js";import{a}from"./utils.js";import"./useStore.js";import{t as o}from"./useNavigate.js";import{t as s}from"./button.js";import{t as c}from"./back-link.js";import{t as l}from"./trash-2.js";import"./awsClient.js";import{d as u}from"./ec2.js";import{s as d}from"./ec22.js";import{r as f}from"./index.js";import{t as p}from"./page-heading.js";import{n as m,t as h}from"./detail-row.js";import"./alert-dialog.js";import{t as g}from"./delete-confirmation-dialog.js";var _=e(n()),v=t();function y(){let{id:e}=f.useParams(),t=o(),{data:n}=r(d(e)),y=n.KeyPairs?.[0],b=u(),[x,S]=(0,_.useState)(!1),C=async()=>{try{await b.mutateAsync(e),t({to:`/ec2/describe-key-pairs`})}finally{S(!1)}};if(!y?.KeyPairId)return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(c,{to:`/ec2/describe-key-pairs`,children:`Back to key pairs`}),(0,v.jsx)(`p`,{className:`text-muted-foreground`,children:`Key pair not found.`})]});let w=a(y.CreateTime);return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(c,{to:`/ec2/describe-key-pairs`,children:`Back to key pairs`}),b.error&&(0,v.jsx)(i,{error:b.error,msg:`Failed to delete key pair`}),(0,v.jsxs)(`div`,{className:`space-y-6`,children:[(0,v.jsx)(p,{actions:(0,v.jsxs)(s,{onClick:()=>S(!0),size:`sm`,variant:`destructive`,children:[(0,v.jsx)(l,{className:`size-4`}),`Delete`]}),subtitle:`EC2 Key Pair Details`,title:y.KeyName??``}),(0,v.jsxs)(m,{children:[(0,v.jsx)(m.Header,{children:`Key Pair Information`}),(0,v.jsxs)(m.Content,{children:[(0,v.jsx)(h,{label:`Key Name`,value:y.KeyName}),(0,v.jsx)(h,{label:`Key Pair ID`,value:y.KeyPairId}),(0,v.jsx)(h,{label:`Key Type`,value:y.KeyType}),(0,v.jsx)(h,{label:`Key Fingerprint`,value:y.KeyFingerprint}),(0,v.jsx)(h,{label:`Creation Date`,value:w})]})]}),y.Tags&&y.Tags.length>0&&(0,v.jsxs)(m,{children:[(0,v.jsx)(m.Header,{children:`Tags`}),(0,v.jsx)(m.Content,{children:y.Tags.map(e=>(0,v.jsx)(h,{label:e.Key??``,value:e.Value},e.Key))})]})]}),(0,v.jsx)(g,{description:`Are you sure you want to delete the key pair "${y.KeyName}"? This action cannot be undone. Any instances using this key pair will no longer be able to be accessed with it.`,isPending:b.isPending,onConfirm:C,onOpenChange:S,open:x,title:`Delete Key Pair`})]})}export{y as component};
+1 -1
View File
@@ -1 +1 @@
import{a as e,n as t,r as n}from"./compiler-runtime.js";import{t as r}from"./useSuspenseQuery.js";import{t as i}from"./error-banner.js";import{a}from"./utils.js";import"./useStore.js";import{t as o}from"./link.js";import{t as s}from"./useNavigate.js";import{t as c}from"./button.js";import{t as l}from"./createLucideIcon.js";import{t as u}from"./back-link.js";import{t as d}from"./trash-2.js";import"./separator.js";import"./awsClient.js";import{d as f,n as p}from"./ec2.js";import{l as m,u as h}from"./ec22.js";import{i as g,k as _}from"./index.js";import{t as v}from"./page-heading.js";import{t as y}from"./state-badge.js";import{n as b,t as x}from"./detail-row.js";import{a as S,i as C,l as w,n as T,o as E,r as D,s as O,t as k}from"./alert-dialog.js";import{i as A,t as j}from"./field.js";import{t as M}from"./delete-confirmation-dialog.js";var N=l(`copy`,[[`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`}]]),P=e(n()),F=t();function I(){let{id:e}=g.useParams(),t=s(),{data:n}=r(h(e)),{data:l}=r(m),I=n.Snapshots?.[0],L=f(),R=p(),[z,B]=(0,P.useState)(!1),[V,H]=(0,P.useState)(!1),[U,W]=(0,P.useState)(``),G=l.Regions?.[0]?.RegionName??`ap-southeast-2`,K=I?.State===`completed`,q=async()=>{try{await L.mutateAsync(e),t({to:`/ec2/describe-snapshots`})}finally{B(!1)}},J=async()=>{try{await R.mutateAsync({sourceSnapshotId:e,sourceRegion:G,description:U||void 0}),H(!1),W(``)}catch{}};if(!I?.SnapshotId)return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(u,{to:`/ec2/describe-snapshots`,children:`Back to snapshots`}),(0,F.jsx)(`p`,{className:`text-muted-foreground`,children:`Snapshot not found.`})]});let Y=a(I.StartTime);return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(u,{to:`/ec2/describe-snapshots`,children:`Back to snapshots`}),L.error&&(0,F.jsx)(i,{error:L.error,msg:`Failed to delete snapshot`}),R.error&&(0,F.jsx)(i,{error:R.error,msg:`Failed to copy snapshot`}),(0,F.jsxs)(`div`,{className:`space-y-6`,children:[(0,F.jsx)(v,{actions:(0,F.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,F.jsxs)(c,{disabled:!K,onClick:()=>B(!0),size:`sm`,variant:`destructive`,children:[(0,F.jsx)(d,{className:`size-4`}),`Delete`]}),(0,F.jsxs)(c,{disabled:!K,onClick:()=>H(!0),size:`sm`,variant:`outline`,children:[(0,F.jsx)(N,{className:`size-4`}),`Copy Snapshot`]}),(0,F.jsx)(y,{state:I.State})]}),subtitle:`EC2 Snapshot Details`,title:I.SnapshotId}),(0,F.jsxs)(b,{children:[(0,F.jsx)(b.Header,{children:`Snapshot Information`}),(0,F.jsxs)(b.Content,{children:[(0,F.jsx)(x,{label:`Snapshot ID`,value:I.SnapshotId}),(0,F.jsx)(x,{label:`Volume ID`,value:I.VolumeId?(0,F.jsx)(o,{className:`text-primary hover:underline`,params:{id:I.VolumeId},to:`/ec2/describe-volumes/$id`,children:I.VolumeId}):void 0}),(0,F.jsx)(x,{label:`Volume Size`,value:`${I.VolumeSize} GiB`}),(0,F.jsx)(x,{label:`State`,value:I.State}),(0,F.jsx)(x,{label:`Description`,value:I.Description}),(0,F.jsx)(x,{label:`Progress`,value:I.Progress}),(0,F.jsx)(x,{label:`Start Time`,value:Y}),(0,F.jsx)(x,{label:`Owner ID`,value:I.OwnerId}),(0,F.jsx)(x,{label:`Encrypted`,value:I.Encrypted?`Yes`:`No`})]})]})]}),(0,F.jsx)(M,{description:`Are you sure you want to delete the snapshot "${I.SnapshotId}"? This action cannot be undone.`,isPending:L.isPending,onConfirm:q,onOpenChange:B,open:z,title:`Delete Snapshot`}),(0,F.jsx)(k,{onOpenChange:H,open:V,children:(0,F.jsxs)(C,{children:[(0,F.jsxs)(O,{children:[(0,F.jsx)(w,{children:`Copy Snapshot`}),(0,F.jsx)(S,{children:`Create a copy of this snapshot in the current region.`})]}),(0,F.jsxs)(j,{children:[(0,F.jsx)(A,{children:(0,F.jsx)(`label`,{htmlFor:`copyDescription`,children:`Description (optional)`})}),(0,F.jsx)(_,{id:`copyDescription`,onChange:e=>W(e.target.value),placeholder:`Description for the snapshot copy`,value:U})]}),(0,F.jsxs)(E,{children:[(0,F.jsx)(D,{children:`Cancel`}),(0,F.jsx)(T,{disabled:R.isPending,onClick:J,children:R.isPending?`Copying…`:`Copy Snapshot`})]})]})})]})}export{I as component};
import{a as e,n as t,r as n}from"./compiler-runtime.js";import{t as r}from"./useSuspenseQuery.js";import{t as i}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as a}from"./link.js";import{t as o}from"./useNavigate.js";import{t as s}from"./button.js";import{t as c}from"./back-link.js";import{t as l}from"./trash-2.js";import"./awsClient.js";import{f as u}from"./ec2.js";import{l as d,o as f}from"./ec22.js";import{i as p}from"./index.js";import{t as m}from"./page-heading.js";import{t as h}from"./state-badge.js";import{n as g,t as _}from"./detail-row.js";import"./alert-dialog.js";import{t as v}from"./delete-confirmation-dialog.js";var y=e(n()),b=t();function x(){let{id:e}=p.useParams(),t=o(),{data:n}=r(d(e)),{data:x}=r(f),S=u(),[C,w]=(0,y.useState)(!1),T=n.PlacementGroups?.[0],E=x.Reservations?.flatMap(e=>e.Instances??[]).filter(e=>e.Placement?.GroupName===T?.GroupName)??[];return T?.GroupId?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(c,{to:`/ec2/describe-placement-groups`,children:`Back to placement groups`}),S.error&&(0,b.jsx)(i,{error:S.error,msg:`Failed to delete placement group`}),(0,b.jsxs)(`div`,{className:`space-y-6`,children:[(0,b.jsx)(m,{actions:(0,b.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,b.jsxs)(s,{onClick:()=>w(!0),size:`sm`,variant:`destructive`,children:[(0,b.jsx)(l,{className:`size-4`}),`Delete`]}),(0,b.jsx)(h,{state:T.State})]}),subtitle:`EC2 Placement Group Details`,title:T.GroupName??T.GroupId}),(0,b.jsxs)(g,{children:[(0,b.jsx)(g.Header,{children:`Placement Group Information`}),(0,b.jsxs)(g.Content,{children:[(0,b.jsx)(_,{label:`Group ID`,value:T.GroupId}),(0,b.jsx)(_,{label:`Group Name`,value:T.GroupName}),(0,b.jsx)(_,{label:`Strategy`,value:T.Strategy}),(0,b.jsx)(_,{label:`State`,value:T.State}),(0,b.jsx)(_,{label:`Spread Level`,value:T.SpreadLevel})]})]}),(0,b.jsxs)(g,{children:[(0,b.jsx)(g.Header,{children:`Instances`}),E.length>0?E.map(e=>(0,b.jsxs)(g.Content,{children:[(0,b.jsx)(_,{label:`Instance ID`,value:e.InstanceId?(0,b.jsx)(a,{className:`text-primary hover:underline`,params:{id:e.InstanceId},to:`/ec2/describe-instances/$id`,children:e.InstanceId}):void 0}),(0,b.jsx)(_,{label:`State`,value:e.State?.Name}),(0,b.jsx)(_,{label:`Instance Type`,value:e.InstanceType}),(0,b.jsx)(_,{label:`Availability Zone`,value:e.Placement?.AvailabilityZone})]},e.InstanceId)):(0,b.jsx)(g.Content,{children:(0,b.jsx)(`p`,{className:`text-muted-foreground`,children:`No instances in this placement group.`})})]})]}),(0,b.jsx)(v,{description:`Are you sure you want to delete the placement group "${T.GroupName}"? The group must have no running instances.`,isPending:S.isPending,onConfirm:async()=>{if(T?.GroupName)try{await S.mutateAsync(T.GroupName),t({to:`/ec2/describe-placement-groups`})}finally{w(!1)}},onOpenChange:w,open:C,title:`Delete Placement Group`})]}):(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(c,{to:`/ec2/describe-placement-groups`,children:`Back to placement groups`}),(0,b.jsx)(`p`,{className:`text-muted-foreground`,children:`Placement group not found.`})]})}export{x as component};
+1 -1
View File
@@ -1 +1 @@
import{a as e,n as t,r as n}from"./compiler-runtime.js";import{t as r}from"./useSuspenseQuery.js";import{t as i}from"./error-banner.js";import{s as a}from"./utils.js";import"./useStore.js";import{t as o}from"./link.js";import{t as s}from"./useNavigate.js";import{t as c}from"./button.js";import{t as l}from"./back-link.js";import{t as u}from"./trash-2.js";import"./awsClient.js";import{f as d}from"./ec2.js";import{f}from"./ec22.js";import{a as p}from"./index.js";import{t as m}from"./page-heading.js";import{t as h}from"./state-badge.js";import{n as g,t as _}from"./detail-row.js";import"./alert-dialog.js";import{t as v}from"./delete-confirmation-dialog.js";var y=e(n()),b=t();function x(){let{id:e}=p.useParams(),t=s(),{data:n}=r(f(e)),x=n.Subnets?.[0],S=d(),[C,w]=(0,y.useState)(!1),T=async()=>{try{await S.mutateAsync(e),t({to:`/ec2/describe-subnets`})}finally{w(!1)}};if(!x?.SubnetId)return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(l,{to:`/ec2/describe-subnets`,children:`Back to Subnets`}),(0,b.jsx)(`p`,{className:`text-muted-foreground`,children:`Subnet not found.`})]});let E=a(x.Tags);return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsx)(l,{to:`/ec2/describe-subnets`,children:`Back to Subnets`}),S.error&&(0,b.jsx)(i,{error:S.error,msg:`Failed to delete subnet`}),(0,b.jsxs)(`div`,{className:`space-y-6`,children:[(0,b.jsx)(m,{actions:(0,b.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,b.jsxs)(c,{onClick:()=>w(!0),size:`sm`,variant:`destructive`,children:[(0,b.jsx)(u,{className:`size-4`}),`Delete`]}),(0,b.jsx)(h,{state:x.State})]}),subtitle:`Subnet Details`,title:E?`${x.SubnetId} (${E})`:x.SubnetId}),(0,b.jsxs)(g,{children:[(0,b.jsx)(g.Header,{children:`Subnet Information`}),(0,b.jsxs)(g.Content,{children:[(0,b.jsx)(_,{label:`Subnet ID`,value:x.SubnetId}),(0,b.jsx)(_,{label:`VPC ID`,value:x.VpcId?(0,b.jsx)(o,{className:`text-primary hover:underline`,params:{id:x.VpcId},to:`/ec2/describe-vpcs/$id`,children:x.VpcId}):`—`}),(0,b.jsx)(_,{label:`State`,value:x.State}),(0,b.jsx)(_,{label:`CIDR Block`,value:x.CidrBlock}),(0,b.jsx)(_,{label:`Availability Zone`,value:x.AvailabilityZone}),(0,b.jsx)(_,{label:`Available IP Count`,value:x.AvailableIpAddressCount?.toString()}),(0,b.jsx)(_,{label:`Map Public IP on Launch`,value:x.MapPublicIpOnLaunch?`Yes`:`No`}),(0,b.jsx)(_,{label:`Default for AZ`,value:x.DefaultForAz?`Yes`:`No`})]})]})]}),(0,b.jsx)(v,{description:`Are you sure you want to delete the subnet "${x.SubnetId}"? This action cannot be undone.`,isPending:S.isPending,onConfirm:T,onOpenChange:w,open:C,title:`Delete Subnet`})]})}export{x as component};
import{a as e,n as t,r as n}from"./compiler-runtime.js";import{t as r}from"./useSuspenseQuery.js";import{t as i}from"./error-banner.js";import{a}from"./utils.js";import"./useStore.js";import{t as o}from"./link.js";import{t as s}from"./useNavigate.js";import{t as c}from"./button.js";import{t as l}from"./createLucideIcon.js";import{t as u}from"./back-link.js";import{t as d}from"./trash-2.js";import"./separator.js";import"./awsClient.js";import{n as f,p}from"./ec2.js";import{d as m,f as h}from"./ec22.js";import{A as g,a as _}from"./index.js";import{t as v}from"./page-heading.js";import{t as y}from"./state-badge.js";import{n as b,t as x}from"./detail-row.js";import{a as S,i as C,l as w,n as T,o as E,r as D,s as O,t as k}from"./alert-dialog.js";import{i as A,t as j}from"./field.js";import{t as M}from"./delete-confirmation-dialog.js";var N=l(`copy`,[[`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`}]]),P=e(n()),F=t();function I(){let{id:e}=_.useParams(),t=s(),{data:n}=r(h(e)),{data:l}=r(m),I=n.Snapshots?.[0],L=p(),R=f(),[z,B]=(0,P.useState)(!1),[V,H]=(0,P.useState)(!1),[U,W]=(0,P.useState)(``),G=l.Regions?.[0]?.RegionName??`ap-southeast-2`,K=I?.State===`completed`,q=async()=>{try{await L.mutateAsync(e),t({to:`/ec2/describe-snapshots`})}finally{B(!1)}},J=async()=>{try{await R.mutateAsync({sourceSnapshotId:e,sourceRegion:G,description:U||void 0}),H(!1),W(``)}catch{}};if(!I?.SnapshotId)return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(u,{to:`/ec2/describe-snapshots`,children:`Back to snapshots`}),(0,F.jsx)(`p`,{className:`text-muted-foreground`,children:`Snapshot not found.`})]});let Y=a(I.StartTime);return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(u,{to:`/ec2/describe-snapshots`,children:`Back to snapshots`}),L.error&&(0,F.jsx)(i,{error:L.error,msg:`Failed to delete snapshot`}),R.error&&(0,F.jsx)(i,{error:R.error,msg:`Failed to copy snapshot`}),(0,F.jsxs)(`div`,{className:`space-y-6`,children:[(0,F.jsx)(v,{actions:(0,F.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,F.jsxs)(c,{disabled:!K,onClick:()=>B(!0),size:`sm`,variant:`destructive`,children:[(0,F.jsx)(d,{className:`size-4`}),`Delete`]}),(0,F.jsxs)(c,{disabled:!K,onClick:()=>H(!0),size:`sm`,variant:`outline`,children:[(0,F.jsx)(N,{className:`size-4`}),`Copy Snapshot`]}),(0,F.jsx)(y,{state:I.State})]}),subtitle:`EC2 Snapshot Details`,title:I.SnapshotId}),(0,F.jsxs)(b,{children:[(0,F.jsx)(b.Header,{children:`Snapshot Information`}),(0,F.jsxs)(b.Content,{children:[(0,F.jsx)(x,{label:`Snapshot ID`,value:I.SnapshotId}),(0,F.jsx)(x,{label:`Volume ID`,value:I.VolumeId?(0,F.jsx)(o,{className:`text-primary hover:underline`,params:{id:I.VolumeId},to:`/ec2/describe-volumes/$id`,children:I.VolumeId}):void 0}),(0,F.jsx)(x,{label:`Volume Size`,value:`${I.VolumeSize} GiB`}),(0,F.jsx)(x,{label:`State`,value:I.State}),(0,F.jsx)(x,{label:`Description`,value:I.Description}),(0,F.jsx)(x,{label:`Progress`,value:I.Progress}),(0,F.jsx)(x,{label:`Start Time`,value:Y}),(0,F.jsx)(x,{label:`Owner ID`,value:I.OwnerId}),(0,F.jsx)(x,{label:`Encrypted`,value:I.Encrypted?`Yes`:`No`})]})]})]}),(0,F.jsx)(M,{description:`Are you sure you want to delete the snapshot "${I.SnapshotId}"? This action cannot be undone.`,isPending:L.isPending,onConfirm:q,onOpenChange:B,open:z,title:`Delete Snapshot`}),(0,F.jsx)(k,{onOpenChange:H,open:V,children:(0,F.jsxs)(C,{children:[(0,F.jsxs)(O,{children:[(0,F.jsx)(w,{children:`Copy Snapshot`}),(0,F.jsx)(S,{children:`Create a copy of this snapshot in the current region.`})]}),(0,F.jsxs)(j,{children:[(0,F.jsx)(A,{children:(0,F.jsx)(`label`,{htmlFor:`copyDescription`,children:`Description (optional)`})}),(0,F.jsx)(g,{id:`copyDescription`,onChange:e=>W(e.target.value),placeholder:`Description for the snapshot copy`,value:U})]}),(0,F.jsxs)(E,{children:[(0,F.jsx)(D,{children:`Cancel`}),(0,F.jsx)(T,{disabled:R.isPending,onClick:J,children:R.isPending?`Copying…`:`Copy Snapshot`})]})]})})]})}export{I 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{a as e,n as t,r as n}from"./compiler-runtime.js";import{t as r}from"./useSuspenseQuery.js";import{t as i}from"./error-banner.js";import{s as a}from"./utils.js";import"./useStore.js";import{t as o}from"./link.js";import{t as s}from"./useNavigate.js";import{t as c}from"./button.js";import{t as l}from"./back-link.js";import{t as u}from"./trash-2.js";import"./awsClient.js";import{m as d}from"./ec2.js";import{g as f,p}from"./ec22.js";import{c as m}from"./index.js";import{t as h}from"./page-heading.js";import{t as g}from"./state-badge.js";import{n as _,t as v}from"./detail-row.js";import"./alert-dialog.js";import{t as y}from"./delete-confirmation-dialog.js";var b=e(n()),x=t();function S(){let{id:e}=m.useParams(),t=s(),{data:n}=r(f(e)),{data:S}=r(p),C=n.Vpcs?.[0],w=d(),[T,E]=(0,b.useState)(!1),D=S.Subnets?.filter(t=>t.VpcId===e)??[],O=async()=>{try{await w.mutateAsync(e),t({to:`/ec2/describe-vpcs`})}finally{E(!1)}};if(!C?.VpcId)return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(l,{to:`/ec2/describe-vpcs`,children:`Back to VPCs`}),(0,x.jsx)(`p`,{className:`text-muted-foreground`,children:`VPC not found.`})]});let k=a(C.Tags);return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(l,{to:`/ec2/describe-vpcs`,children:`Back to VPCs`}),w.error&&(0,x.jsx)(i,{error:w.error,msg:`Failed to delete VPC`}),(0,x.jsxs)(`div`,{className:`space-y-6`,children:[(0,x.jsx)(h,{actions:(0,x.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,x.jsxs)(c,{onClick:()=>E(!0),size:`sm`,variant:`destructive`,children:[(0,x.jsx)(u,{className:`size-4`}),`Delete`]}),(0,x.jsx)(g,{state:C.State})]}),subtitle:`VPC Details`,title:k?`${C.VpcId} (${k})`:C.VpcId}),(0,x.jsxs)(_,{children:[(0,x.jsx)(_.Header,{children:`VPC Information`}),(0,x.jsxs)(_.Content,{children:[(0,x.jsx)(v,{label:`VPC ID`,value:C.VpcId}),(0,x.jsx)(v,{label:`State`,value:C.State}),(0,x.jsx)(v,{label:`CIDR Block`,value:C.CidrBlock}),(0,x.jsx)(v,{label:`Is Default`,value:C.IsDefault?`Yes`:`No`}),(0,x.jsx)(v,{label:`DHCP Options ID`,value:C.DhcpOptionsId}),(0,x.jsx)(v,{label:`Instance Tenancy`,value:C.InstanceTenancy}),(0,x.jsx)(v,{label:`Owner ID`,value:C.OwnerId})]})]}),D.length>0&&(0,x.jsxs)(_,{children:[(0,x.jsxs)(_.Header,{children:[`Subnets (`,D.length,`)`]}),D.map(e=>e.SubnetId?(0,x.jsxs)(_.Content,{children:[(0,x.jsx)(v,{label:`Subnet ID`,value:(0,x.jsx)(o,{className:`text-primary hover:underline`,params:{id:e.SubnetId},to:`/ec2/describe-subnets/$id`,children:e.SubnetId})}),(0,x.jsx)(v,{label:`CIDR Block`,value:e.CidrBlock}),(0,x.jsx)(v,{label:`Availability Zone`,value:e.AvailabilityZone}),(0,x.jsx)(v,{label:`State`,value:e.State})]},e.SubnetId):null)]})]}),(0,x.jsx)(y,{description:`Are you sure you want to delete the VPC "${C.VpcId}"? This action cannot be undone. The VPC must have no associated subnets or internet gateways.`,isPending:w.isPending,onConfirm:O,onOpenChange:E,open:T,title:`Delete VPC`})]})}export{S as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as r}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as i}from"./useNavigate.js";import"./button.js";import{t as a}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{x as o}from"./ec2.js";import{g as s}from"./ec22.js";import{A as c,c as l}from"./index.js";import{t as u}from"./page-heading.js";import{i as d,n as f,t as p}from"./field.js";import{r as m,t as h}from"./zod.js";import{l as g}from"./ec23.js";import{t as _}from"./form-actions.js";var v=t(),y=e();function b(){let e=(0,v.c)(53),{id:t}=l.useParams(),b=i(),x;e[0]===t?x=e[1]:(x=s(t),e[0]=t,e[1]=x);let{data:S}=n(x),C=S.Volumes?.[0],w=C?.VolumeId,T=o(),E=C?.Size??1,D;e[2]===E?D=e[3]:(D=h(g.refine(e=>e.size>E,{message:`New size must be greater than current size (${E} GiB)`,path:[`size`]})),e[2]=E,e[3]=D);let O;e[4]===E?O=e[5]:(O={size:E},e[4]=E,e[5]=O);let k;e[6]!==D||e[7]!==O?(k={resolver:D,defaultValues:O},e[6]=D,e[7]=O,e[8]=k):k=e[8];let{handleSubmit:A,register:j,formState:M}=m(k),{errors:N,isSubmitting:P}=M;if(!w){let t;return e[9]===Symbol.for(`react.memo_cache_sentinel`)?(t=(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(a,{to:`/ec2/describe-volumes`,children:`Back to volumes`}),(0,y.jsx)(`p`,{className:`text-muted-foreground`,children:`Volume not found.`})]}),e[9]=t):t=e[9],t}let F;e[10]!==T||e[11]!==b||e[12]!==w?(F=async e=>{await T.mutateAsync({...e,volumeId:w}),b({to:`/ec2/describe-volumes/$id`,params:{id:w}})},e[10]=T,e[11]=b,e[12]=w,e[13]=F):F=e[13];let I=F,L,R;e[14]===w?(L=e[15],R=e[16]):(L=(0,y.jsx)(a,{params:{id:w},to:`/ec2/describe-volumes/$id`,children:`Back to volume`}),R=(0,y.jsx)(u,{subtitle:w,title:`Resize Volume`}),e[14]=w,e[15]=L,e[16]=R);let z;e[17]===T.error?z=e[18]:(z=T.error&&(0,y.jsx)(r,{error:T.error,msg:`Failed to resize volume`}),e[17]=T.error,e[18]=z);let B;e[19]!==A||e[20]!==I?(B=A(I),e[19]=A,e[20]=I,e[21]=B):B=e[21];let V;e[22]===E?V=e[23]:(V=(0,y.jsx)(`div`,{className:`space-y-2`,children:(0,y.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`Current size: `,E,` GiB`]})}),e[22]=E,e[23]=V);let H;e[24]===Symbol.for(`react.memo_cache_sentinel`)?(H=(0,y.jsx)(d,{children:(0,y.jsx)(`label`,{htmlFor:`size`,children:`New size (GiB)`})}),e[24]=H):H=e[24];let U=!!N.size,W=E+1,G;e[25]===j?G=e[26]:(G=j(`size`,{valueAsNumber:!0}),e[25]=j,e[26]=G);let K;e[27]!==U||e[28]!==W||e[29]!==G?(K=(0,y.jsx)(c,{"aria-invalid":U,id:`size`,min:W,type:`number`,...G}),e[27]=U,e[28]=W,e[29]=G,e[30]=K):K=e[30];let q;e[31]===N.size?q=e[32]:(q=(0,y.jsx)(f,{errors:[N.size]}),e[31]=N.size,e[32]=q);let J;e[33]!==K||e[34]!==q?(J=(0,y.jsxs)(p,{children:[H,K,q]}),e[33]=K,e[34]=q,e[35]=J):J=e[35];let Y;e[36]!==b||e[37]!==w?(Y=()=>b({to:`/ec2/describe-volumes/$id`,params:{id:w}}),e[36]=b,e[37]=w,e[38]=Y):Y=e[38];let X;e[39]!==P||e[40]!==T.isPending||e[41]!==Y?(X=(0,y.jsx)(_,{isPending:T.isPending,isSubmitting:P,onCancel:Y,pendingLabel:`Resizing\\u2026`,submitLabel:`Resize Volume`}),e[39]=P,e[40]=T.isPending,e[41]=Y,e[42]=X):X=e[42];let Z;e[43]!==V||e[44]!==J||e[45]!==X||e[46]!==B?(Z=(0,y.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:B,children:[V,J,X]}),e[43]=V,e[44]=J,e[45]=X,e[46]=B,e[47]=Z):Z=e[47];let Q;return e[48]!==Z||e[49]!==L||e[50]!==R||e[51]!==z?(Q=(0,y.jsxs)(y.Fragment,{children:[L,R,z,Z]}),e[48]=Z,e[49]=L,e[50]=R,e[51]=z,e[52]=Q):Q=e[52],Q}export{b as component};
@@ -1 +1 @@
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{t as i}from"./useSuspenseQuery.js";import{t as a}from"./error-banner.js";import{a as o}from"./utils.js";import"./useStore.js";import{t as s}from"./useNavigate.js";import{t as c}from"./button.js";import{t as l}from"./back-link.js";import{t as u}from"./trash-2.js";import"./awsClient.js";import{o as d}from"./iam.js";import{a as f,i as p}from"./iam2.js";import{l as m}from"./index.js";import{t as h}from"./page-heading.js";import{n as g,t as _}from"./detail-row.js";import"./alert-dialog.js";import{t as v}from"./delete-confirmation-dialog.js";var y=e(n()),b=r(),x=t();function S(e){let t=(0,b.c)(4),{document:n}=e,r;try{let e;t[0]===n?e=t[1]:(e=JSON.stringify(JSON.parse(n),null,2),t[0]=n,t[1]=e),r=e}catch{r=n}let i;return t[2]===r?i=t[3]:(i=(0,x.jsx)(`pre`,{className:`overflow-auto rounded-md border bg-muted p-4 font-mono text-sm`,children:r}),t[2]=r,t[3]=i),i}function C(){let{policyArn:e}=m.useParams(),t=decodeURIComponent(e),n=s(),{data:r}=i(p(t)),b=r.Policy,C=d(),[w,T]=(0,y.useState)(!1),{data:E}=i(f(t,b?.DefaultVersionId??`v1`)),D=E?.PolicyVersion?.Document?decodeURIComponent(E.PolicyVersion.Document):null;return b?(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(l,{to:`/iam/list-policies`,children:`Back to policies`}),C.error&&(0,x.jsx)(a,{error:C.error,msg:`Failed to delete policy`}),(0,x.jsxs)(`div`,{className:`space-y-6`,children:[(0,x.jsx)(h,{actions:(0,x.jsxs)(c,{onClick:()=>T(!0),size:`sm`,variant:`destructive`,children:[(0,x.jsx)(u,{className:`size-4`}),`Delete`]}),subtitle:`IAM Policy Details`,title:b.PolicyName??``}),(0,x.jsxs)(g,{children:[(0,x.jsx)(g.Header,{children:`Policy Information`}),(0,x.jsxs)(g.Content,{children:[(0,x.jsx)(_,{label:`Policy Name`,value:b.PolicyName}),(0,x.jsx)(_,{label:`Policy ID`,value:b.PolicyId}),(0,x.jsx)(_,{label:`ARN`,value:b.Arn}),(0,x.jsx)(_,{label:`Path`,value:b.Path}),(0,x.jsx)(_,{label:`Description`,value:b.Description??`-`}),(0,x.jsx)(_,{label:`Created`,value:o(b.CreateDate)})]})]}),D&&(0,x.jsxs)(g,{children:[(0,x.jsx)(g.Header,{children:`Policy Document`}),(0,x.jsx)(`div`,{className:`p-4`,children:(0,x.jsx)(S,{document:D})})]})]}),(0,x.jsx)(v,{description:`Are you sure you want to delete the policy "${b.PolicyName}"? This action cannot be undone.`,isPending:C.isPending,onConfirm:async()=>{try{await C.mutateAsync(t),n({to:`/iam/list-policies`})}finally{T(!1)}},onOpenChange:T,open:w,title:`Delete Policy`})]}):(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(l,{to:`/iam/list-policies`,children:`Back to policies`}),(0,x.jsx)(`p`,{className:`text-muted-foreground`,children:`Policy not found.`})]})}export{C as component};
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{t as i}from"./useSuspenseQuery.js";import{t as a}from"./error-banner.js";import{a as o}from"./utils.js";import"./useStore.js";import{t as s}from"./useNavigate.js";import{t as c}from"./button.js";import{t as l}from"./back-link.js";import{t as u}from"./trash-2.js";import"./awsClient.js";import{o as d}from"./iam.js";import{a as f,i as p}from"./iam2.js";import{u as m}from"./index.js";import{t as h}from"./page-heading.js";import{n as g,t as _}from"./detail-row.js";import"./alert-dialog.js";import{t as v}from"./delete-confirmation-dialog.js";var y=e(n()),b=r(),x=t();function S(e){let t=(0,b.c)(4),{document:n}=e,r;try{let e;t[0]===n?e=t[1]:(e=JSON.stringify(JSON.parse(n),null,2),t[0]=n,t[1]=e),r=e}catch{r=n}let i;return t[2]===r?i=t[3]:(i=(0,x.jsx)(`pre`,{className:`overflow-auto rounded-md border bg-muted p-4 font-mono text-sm`,children:r}),t[2]=r,t[3]=i),i}function C(){let{policyArn:e}=m.useParams(),t=decodeURIComponent(e),n=s(),{data:r}=i(p(t)),b=r.Policy,C=d(),[w,T]=(0,y.useState)(!1),{data:E}=i(f(t,b?.DefaultVersionId??`v1`)),D=E?.PolicyVersion?.Document?decodeURIComponent(E.PolicyVersion.Document):null;return b?(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(l,{to:`/iam/list-policies`,children:`Back to policies`}),C.error&&(0,x.jsx)(a,{error:C.error,msg:`Failed to delete policy`}),(0,x.jsxs)(`div`,{className:`space-y-6`,children:[(0,x.jsx)(h,{actions:(0,x.jsxs)(c,{onClick:()=>T(!0),size:`sm`,variant:`destructive`,children:[(0,x.jsx)(u,{className:`size-4`}),`Delete`]}),subtitle:`IAM Policy Details`,title:b.PolicyName??``}),(0,x.jsxs)(g,{children:[(0,x.jsx)(g.Header,{children:`Policy Information`}),(0,x.jsxs)(g.Content,{children:[(0,x.jsx)(_,{label:`Policy Name`,value:b.PolicyName}),(0,x.jsx)(_,{label:`Policy ID`,value:b.PolicyId}),(0,x.jsx)(_,{label:`ARN`,value:b.Arn}),(0,x.jsx)(_,{label:`Path`,value:b.Path}),(0,x.jsx)(_,{label:`Description`,value:b.Description??`-`}),(0,x.jsx)(_,{label:`Created`,value:o(b.CreateDate)})]})]}),D&&(0,x.jsxs)(g,{children:[(0,x.jsx)(g.Header,{children:`Policy Document`}),(0,x.jsx)(`div`,{className:`p-4`,children:(0,x.jsx)(S,{document:D})})]})]}),(0,x.jsx)(v,{description:`Are you sure you want to delete the policy "${b.PolicyName}"? This action cannot be undone.`,isPending:C.isPending,onConfirm:async()=>{try{await C.mutateAsync(t),n({to:`/iam/list-policies`})}finally{T(!1)}},onOpenChange:T,open:w,title:`Delete Policy`})]}):(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(l,{to:`/iam/list-policies`,children:`Back to policies`}),(0,x.jsx)(`p`,{className:`text-muted-foreground`,children:`Policy not found.`})]})}export{C as component};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import{Qt as a,Zt as o}from"./awsClient.js";import{t as s}from"./s3.js";import{k as c}from"./index.js";import{t as l}from"./page-heading.js";import{i as u,n as d,t as f}from"./field.js";import{r as p,t as m}from"./zod.js";import{t as h}from"./form-actions.js";var g=t(),_=o({bucketName:a().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`)}),v=e();function y(){let e=(0,g.c)(35),t=r(),a=s(),o;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(o={resolver:m(_)},e[0]=o):o=e[0];let{handleSubmit:y,register:b,formState:x}=p(o),{errors:S,isSubmitting:C}=x,w;e[1]!==a||e[2]!==t?(w=async e=>{await a.mutateAsync(e),t({to:`/s3/ls`})},e[1]=a,e[2]=t,e[3]=w):w=e[3];let T=w,E,D;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(E=(0,v.jsx)(i,{to:`/s3/ls`,children:`Back to buckets`}),D=(0,v.jsx)(l,{title:`Create Bucket`}),e[4]=E,e[5]=D):(E=e[4],D=e[5]);let O;e[6]===a.error?O=e[7]:(O=a.error&&(0,v.jsx)(n,{error:a.error,msg:`Failed to create bucket`}),e[6]=a.error,e[7]=O);let k;e[8]!==y||e[9]!==T?(k=y(T),e[8]=y,e[9]=T,e[10]=k):k=e[10];let A;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(A=(0,v.jsx)(u,{children:(0,v.jsx)(`label`,{htmlFor:`bucketName`,children:`Bucket Name`})}),e[11]=A):A=e[11];let j=!!S.bucketName,M;e[12]===b?M=e[13]:(M=b(`bucketName`),e[12]=b,e[13]=M);let N;e[14]!==j||e[15]!==M?(N=(0,v.jsx)(c,{"aria-invalid":j,id:`bucketName`,placeholder:`my-bucket`,type:`text`,...M}),e[14]=j,e[15]=M,e[16]=N):N=e[16];let P;e[17]===S.bucketName?P=e[18]:(P=(0,v.jsx)(d,{errors:[S.bucketName]}),e[17]=S.bucketName,e[18]=P);let F;e[19]!==N||e[20]!==P?(F=(0,v.jsxs)(f,{children:[A,N,P]}),e[19]=N,e[20]=P,e[21]=F):F=e[21];let I;e[22]===t?I=e[23]:(I=()=>t({to:`/s3/ls`}),e[22]=t,e[23]=I);let L;e[24]!==a.isPending||e[25]!==C||e[26]!==I?(L=(0,v.jsx)(h,{isPending:a.isPending,isSubmitting:C,onCancel:I,pendingLabel:`Creating\\u2026`,submitLabel:`Create Bucket`}),e[24]=a.isPending,e[25]=C,e[26]=I,e[27]=L):L=e[27];let R;e[28]!==F||e[29]!==L||e[30]!==k?(R=(0,v.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:k,children:[F,L]}),e[28]=F,e[29]=L,e[30]=k,e[31]=R):R=e[31];let z;return e[32]!==R||e[33]!==O?(z=(0,v.jsxs)(v.Fragment,{children:[E,D,O,R]}),e[32]=R,e[33]=O,e[34]=z):z=e[34],z}export{y as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import{en as a,tn as o}from"./awsClient.js";import{t as s}from"./s3.js";import{A as c}from"./index.js";import{t as l}from"./page-heading.js";import{i as u,n as d,t as f}from"./field.js";import{r as p,t as m}from"./zod.js";import{t as h}from"./form-actions.js";var g=t(),_=a({bucketName:o().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`)}),v=e();function y(){let e=(0,g.c)(35),t=r(),a=s(),o;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(o={resolver:m(_)},e[0]=o):o=e[0];let{handleSubmit:y,register:b,formState:x}=p(o),{errors:S,isSubmitting:C}=x,w;e[1]!==a||e[2]!==t?(w=async e=>{await a.mutateAsync(e),t({to:`/s3/ls`})},e[1]=a,e[2]=t,e[3]=w):w=e[3];let T=w,E,D;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(E=(0,v.jsx)(i,{to:`/s3/ls`,children:`Back to buckets`}),D=(0,v.jsx)(l,{title:`Create Bucket`}),e[4]=E,e[5]=D):(E=e[4],D=e[5]);let O;e[6]===a.error?O=e[7]:(O=a.error&&(0,v.jsx)(n,{error:a.error,msg:`Failed to create bucket`}),e[6]=a.error,e[7]=O);let k;e[8]!==y||e[9]!==T?(k=y(T),e[8]=y,e[9]=T,e[10]=k):k=e[10];let A;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(A=(0,v.jsx)(u,{children:(0,v.jsx)(`label`,{htmlFor:`bucketName`,children:`Bucket Name`})}),e[11]=A):A=e[11];let j=!!S.bucketName,M;e[12]===b?M=e[13]:(M=b(`bucketName`),e[12]=b,e[13]=M);let N;e[14]!==j||e[15]!==M?(N=(0,v.jsx)(c,{"aria-invalid":j,id:`bucketName`,placeholder:`my-bucket`,type:`text`,...M}),e[14]=j,e[15]=M,e[16]=N):N=e[16];let P;e[17]===S.bucketName?P=e[18]:(P=(0,v.jsx)(d,{errors:[S.bucketName]}),e[17]=S.bucketName,e[18]=P);let F;e[19]!==N||e[20]!==P?(F=(0,v.jsxs)(f,{children:[A,N,P]}),e[19]=N,e[20]=P,e[21]=F):F=e[21];let I;e[22]===t?I=e[23]:(I=()=>t({to:`/s3/ls`}),e[22]=t,e[23]=I);let L;e[24]!==a.isPending||e[25]!==C||e[26]!==I?(L=(0,v.jsx)(h,{isPending:a.isPending,isSubmitting:C,onCancel:I,pendingLabel:`Creating\\u2026`,submitLabel:`Create Bucket`}),e[24]=a.isPending,e[25]=C,e[26]=I,e[27]=L):L=e[27];let R;e[28]!==F||e[29]!==L||e[30]!==k?(R=(0,v.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:k,children:[F,L]}),e[28]=F,e[29]=L,e[30]=k,e[31]=R):R=e[31];let z;return e[32]!==R||e[33]!==O?(z=(0,v.jsxs)(v.Fragment,{children:[E,D,O,R]}),e[32]=R,e[33]=O,e[34]=z):z=e[34],z}export{y as component};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{r as a}from"./iam.js";import{k as o}from"./index.js";import{t as s}from"./page-heading.js";import{i as c,n as l,t as u}from"./field.js";import{t as d}from"./textarea.js";import{r as f,t as p}from"./zod.js";import{t as m}from"./form-actions.js";import{t as h}from"./iam3.js";var g=t(),_=e(),v=JSON.stringify({Version:`2012-10-17`,Statement:[{Effect:`Allow`,Action:`*`,Resource:`*`}]},null,2);function y(){let e=(0,g.c)(58),t=r(),y=a(),b;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(b={resolver:p(h),defaultValues:{policyDocument:v}},e[0]=b):b=e[0];let{handleSubmit:x,register:S,formState:C}=f(b),{errors:w,isSubmitting:T}=C,E;e[1]!==y||e[2]!==t?(E=async e=>{await y.mutateAsync(e),t({to:`/iam/list-policies`})},e[1]=y,e[2]=t,e[3]=E):E=e[3];let D=E,O,k;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(O=(0,_.jsx)(i,{to:`/iam/list-policies`,children:`Back to policies`}),k=(0,_.jsx)(s,{title:`Create Policy`}),e[4]=O,e[5]=k):(O=e[4],k=e[5]);let A;e[6]===y.error?A=e[7]:(A=y.error&&(0,_.jsx)(n,{error:y.error,msg:`Failed to create policy`}),e[6]=y.error,e[7]=A);let j;e[8]!==x||e[9]!==D?(j=x(D),e[8]=x,e[9]=D,e[10]=j):j=e[10];let M;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(M=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`policyName`,children:`Policy Name`})}),e[11]=M):M=e[11];let N=!!w.policyName,P;e[12]===S?P=e[13]:(P=S(`policyName`),e[12]=S,e[13]=P);let F;e[14]!==N||e[15]!==P?(F=(0,_.jsx)(o,{"aria-invalid":N,id:`policyName`,placeholder:`my-policy...`,...P}),e[14]=N,e[15]=P,e[16]=F):F=e[16];let I;e[17]===w.policyName?I=e[18]:(I=(0,_.jsx)(l,{errors:[w.policyName]}),e[17]=w.policyName,e[18]=I);let L;e[19]!==F||e[20]!==I?(L=(0,_.jsxs)(u,{children:[M,F,I]}),e[19]=F,e[20]=I,e[21]=L):L=e[21];let R;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(R=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`description`,children:`Description (optional)`})}),e[22]=R):R=e[22];let z;e[23]===S?z=e[24]:(z=S(`description`),e[23]=S,e[24]=z);let B;e[25]===z?B=e[26]:(B=(0,_.jsx)(o,{id:`description`,placeholder:`A brief description of this policy...`,...z}),e[25]=z,e[26]=B);let V;e[27]===w.description?V=e[28]:(V=(0,_.jsx)(l,{errors:[w.description]}),e[27]=w.description,e[28]=V);let H;e[29]!==B||e[30]!==V?(H=(0,_.jsxs)(u,{children:[R,B,V]}),e[29]=B,e[30]=V,e[31]=H):H=e[31];let U;e[32]===Symbol.for(`react.memo_cache_sentinel`)?(U=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`policyDocument`,children:`Policy Document (JSON)`})}),e[32]=U):U=e[32];let W=!!w.policyDocument,G;e[33]===S?G=e[34]:(G=S(`policyDocument`),e[33]=S,e[34]=G);let K;e[35]!==W||e[36]!==G?(K=(0,_.jsx)(d,{"aria-invalid":W,className:`font-mono text-sm`,id:`policyDocument`,rows:15,...G}),e[35]=W,e[36]=G,e[37]=K):K=e[37];let q;e[38]===w.policyDocument?q=e[39]:(q=(0,_.jsx)(l,{errors:[w.policyDocument]}),e[38]=w.policyDocument,e[39]=q);let J;e[40]!==K||e[41]!==q?(J=(0,_.jsxs)(u,{children:[U,K,q]}),e[40]=K,e[41]=q,e[42]=J):J=e[42];let Y;e[43]===t?Y=e[44]:(Y=()=>t({to:`/iam/list-policies`}),e[43]=t,e[44]=Y);let X;e[45]!==y.isPending||e[46]!==T||e[47]!==Y?(X=(0,_.jsx)(m,{isPending:y.isPending,isSubmitting:T,onCancel:Y,pendingLabel:`Creating...`,submitLabel:`Create Policy`}),e[45]=y.isPending,e[46]=T,e[47]=Y,e[48]=X):X=e[48];let Z;e[49]!==L||e[50]!==H||e[51]!==J||e[52]!==X||e[53]!==j?(Z=(0,_.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:j,children:[L,H,J,X]}),e[49]=L,e[50]=H,e[51]=J,e[52]=X,e[53]=j,e[54]=Z):Z=e[54];let Q;return e[55]!==Z||e[56]!==A?(Q=(0,_.jsxs)(_.Fragment,{children:[O,k,A,Z]}),e[55]=Z,e[56]=A,e[57]=Q):Q=e[57],Q}export{y as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{r as a}from"./iam.js";import{A as o}from"./index.js";import{t as s}from"./page-heading.js";import{i as c,n as l,t as u}from"./field.js";import{t as d}from"./textarea.js";import{r as f,t as p}from"./zod.js";import{t as m}from"./form-actions.js";import{t as h}from"./iam3.js";var g=t(),_=e(),v=JSON.stringify({Version:`2012-10-17`,Statement:[{Effect:`Allow`,Action:`*`,Resource:`*`}]},null,2);function y(){let e=(0,g.c)(58),t=r(),y=a(),b;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(b={resolver:p(h),defaultValues:{policyDocument:v}},e[0]=b):b=e[0];let{handleSubmit:x,register:S,formState:C}=f(b),{errors:w,isSubmitting:T}=C,E;e[1]!==y||e[2]!==t?(E=async e=>{await y.mutateAsync(e),t({to:`/iam/list-policies`})},e[1]=y,e[2]=t,e[3]=E):E=e[3];let D=E,O,k;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(O=(0,_.jsx)(i,{to:`/iam/list-policies`,children:`Back to policies`}),k=(0,_.jsx)(s,{title:`Create Policy`}),e[4]=O,e[5]=k):(O=e[4],k=e[5]);let A;e[6]===y.error?A=e[7]:(A=y.error&&(0,_.jsx)(n,{error:y.error,msg:`Failed to create policy`}),e[6]=y.error,e[7]=A);let j;e[8]!==x||e[9]!==D?(j=x(D),e[8]=x,e[9]=D,e[10]=j):j=e[10];let M;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(M=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`policyName`,children:`Policy Name`})}),e[11]=M):M=e[11];let N=!!w.policyName,P;e[12]===S?P=e[13]:(P=S(`policyName`),e[12]=S,e[13]=P);let F;e[14]!==N||e[15]!==P?(F=(0,_.jsx)(o,{"aria-invalid":N,id:`policyName`,placeholder:`my-policy...`,...P}),e[14]=N,e[15]=P,e[16]=F):F=e[16];let I;e[17]===w.policyName?I=e[18]:(I=(0,_.jsx)(l,{errors:[w.policyName]}),e[17]=w.policyName,e[18]=I);let L;e[19]!==F||e[20]!==I?(L=(0,_.jsxs)(u,{children:[M,F,I]}),e[19]=F,e[20]=I,e[21]=L):L=e[21];let R;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(R=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`description`,children:`Description (optional)`})}),e[22]=R):R=e[22];let z;e[23]===S?z=e[24]:(z=S(`description`),e[23]=S,e[24]=z);let B;e[25]===z?B=e[26]:(B=(0,_.jsx)(o,{id:`description`,placeholder:`A brief description of this policy...`,...z}),e[25]=z,e[26]=B);let V;e[27]===w.description?V=e[28]:(V=(0,_.jsx)(l,{errors:[w.description]}),e[27]=w.description,e[28]=V);let H;e[29]!==B||e[30]!==V?(H=(0,_.jsxs)(u,{children:[R,B,V]}),e[29]=B,e[30]=V,e[31]=H):H=e[31];let U;e[32]===Symbol.for(`react.memo_cache_sentinel`)?(U=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`policyDocument`,children:`Policy Document (JSON)`})}),e[32]=U):U=e[32];let W=!!w.policyDocument,G;e[33]===S?G=e[34]:(G=S(`policyDocument`),e[33]=S,e[34]=G);let K;e[35]!==W||e[36]!==G?(K=(0,_.jsx)(d,{"aria-invalid":W,className:`font-mono text-sm`,id:`policyDocument`,rows:15,...G}),e[35]=W,e[36]=G,e[37]=K):K=e[37];let q;e[38]===w.policyDocument?q=e[39]:(q=(0,_.jsx)(l,{errors:[w.policyDocument]}),e[38]=w.policyDocument,e[39]=q);let J;e[40]!==K||e[41]!==q?(J=(0,_.jsxs)(u,{children:[U,K,q]}),e[40]=K,e[41]=q,e[42]=J):J=e[42];let Y;e[43]===t?Y=e[44]:(Y=()=>t({to:`/iam/list-policies`}),e[43]=t,e[44]=Y);let X;e[45]!==y.isPending||e[46]!==T||e[47]!==Y?(X=(0,_.jsx)(m,{isPending:y.isPending,isSubmitting:T,onCancel:Y,pendingLabel:`Creating...`,submitLabel:`Create Policy`}),e[45]=y.isPending,e[46]=T,e[47]=Y,e[48]=X):X=e[48];let Z;e[49]!==L||e[50]!==H||e[51]!==J||e[52]!==X||e[53]!==j?(Z=(0,_.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:j,children:[L,H,J,X]}),e[49]=L,e[50]=H,e[51]=J,e[52]=X,e[53]=j,e[54]=Z):Z=e[54];let Q;return e[55]!==Z||e[56]!==A?(Q=(0,_.jsxs)(_.Fragment,{children:[O,k,A,Z]}),e[55]=Z,e[56]=A,e[57]=Q):Q=e[57],Q}export{y as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as ee}from"./useSuspenseQuery.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import{a,i as o,n as s,r as c,t as l}from"./select.js";import"./separator.js";import"./awsClient.js";import{o as u}from"./ec2.js";import{h as d}from"./ec22.js";import{p as f}from"./index.js";import{t as p}from"./page-heading.js";import{i as m,n as te,t as h}from"./field.js";import{t as g}from"./textarea.js";import{n as _,r as v,t as y}from"./zod.js";import{r as b}from"./ec23.js";import{t as ne}from"./form-actions.js";var re=t(),x=e();function S(){let e=(0,re.c)(46),t=r(),{data:c}=ee(d),S=u(),w;e[0]===c.Volumes?w=e[1]:(w=c.Volumes??[],e[0]=c.Volumes,e[1]=w);let T=w,{volumeId:E}=f.useSearch(),D=E??T[0]?.VolumeId??``,O;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(O=y(b),e[2]=O):O=e[2];let k;e[3]===D?k=e[4]:(k={resolver:O,defaultValues:{volumeId:D,description:``}},e[3]=D,e[4]=k);let{control:A,handleSubmit:j,register:M,formState:N}=v(k),{errors:P,isSubmitting:F}=N,I;e[5]!==S||e[6]!==t?(I=async e=>{await S.mutateAsync(e),t({to:`/ec2/describe-snapshots`})},e[5]=S,e[6]=t,e[7]=I):I=e[7];let L=I,R,z;e[8]===Symbol.for(`react.memo_cache_sentinel`)?(R=(0,x.jsx)(i,{to:`/ec2/describe-snapshots`,children:`Back to snapshots`}),z=(0,x.jsx)(p,{title:`Create Snapshot`}),e[8]=R,e[9]=z):(R=e[8],z=e[9]);let B;e[10]===S.error?B=e[11]:(B=S.error&&(0,x.jsx)(n,{error:S.error,msg:`Failed to create snapshot`}),e[10]=S.error,e[11]=B);let V;e[12]!==j||e[13]!==L?(V=j(L),e[12]=j,e[13]=L,e[14]=V):V=e[14];let H;e[15]===Symbol.for(`react.memo_cache_sentinel`)?(H=(0,x.jsx)(m,{children:(0,x.jsx)(`label`,{htmlFor:`volumeId`,children:`Volume`})}),e[15]=H):H=e[15];let U;e[16]!==P.volumeId||e[17]!==T?(U=e=>{let{field:t}=e;return(0,x.jsxs)(l,{onValueChange:e=>t.onChange(e),value:t.value??``,children:[(0,x.jsx)(o,{"aria-invalid":!!P.volumeId,className:`w-full`,id:`volumeId`,children:(0,x.jsx)(a,{})}),(0,x.jsx)(s,{children:T.map(C)})]})},e[16]=P.volumeId,e[17]=T,e[18]=U):U=e[18];let W;e[19]!==A||e[20]!==U?(W=(0,x.jsx)(_,{control:A,name:`volumeId`,render:U}),e[19]=A,e[20]=U,e[21]=W):W=e[21];let G;e[22]===P.volumeId?G=e[23]:(G=(0,x.jsx)(te,{errors:[P.volumeId]}),e[22]=P.volumeId,e[23]=G);let K;e[24]!==W||e[25]!==G?(K=(0,x.jsxs)(h,{children:[H,W,G]}),e[24]=W,e[25]=G,e[26]=K):K=e[26];let q;e[27]===Symbol.for(`react.memo_cache_sentinel`)?(q=(0,x.jsx)(m,{children:(0,x.jsx)(`label`,{htmlFor:`description`,children:`Description (optional)`})}),e[27]=q):q=e[27];let J;e[28]===M?J=e[29]:(J=M(`description`),e[28]=M,e[29]=J);let Y;e[30]===J?Y=e[31]:(Y=(0,x.jsxs)(h,{children:[q,(0,x.jsx)(g,{id:`description`,placeholder:`Snapshot description`,...J})]}),e[30]=J,e[31]=Y);let X;e[32]===t?X=e[33]:(X=()=>t({to:`/ec2/describe-snapshots`}),e[32]=t,e[33]=X);let Z;e[34]!==S.isPending||e[35]!==F||e[36]!==X?(Z=(0,x.jsx)(ne,{isPending:S.isPending,isSubmitting:F,onCancel:X,pendingLabel:`Creating\\u2026`,submitLabel:`Create Snapshot`}),e[34]=S.isPending,e[35]=F,e[36]=X,e[37]=Z):Z=e[37];let Q;e[38]!==K||e[39]!==Y||e[40]!==Z||e[41]!==V?(Q=(0,x.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:V,children:[K,Y,Z]}),e[38]=K,e[39]=Y,e[40]=Z,e[41]=V,e[42]=Q):Q=e[42];let $;return e[43]!==Q||e[44]!==B?($=(0,x.jsxs)(x.Fragment,{children:[R,z,B,Q]}),e[43]=Q,e[44]=B,e[45]=$):$=e[45],$}function C(e){return(0,x.jsxs)(c,{value:e.VolumeId??``,children:[e.VolumeId,` (`,e.Size,` GiB)`]},e.VolumeId)}export{S as component};
import{n as e,t}from"./compiler-runtime.js";import{t as ee}from"./useSuspenseQuery.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import{a,i as o,n as s,r as c,t as l}from"./select.js";import"./separator.js";import"./awsClient.js";import{s as u}from"./ec2.js";import{_ as d}from"./ec22.js";import{m as f}from"./index.js";import{t as p}from"./page-heading.js";import{i as m,n as te,t as h}from"./field.js";import{t as g}from"./textarea.js";import{n as _,r as v,t as y}from"./zod.js";import{i as b}from"./ec23.js";import{t as ne}from"./form-actions.js";var re=t(),x=e();function S(){let e=(0,re.c)(46),t=r(),{data:c}=ee(d),S=u(),w;e[0]===c.Volumes?w=e[1]:(w=c.Volumes??[],e[0]=c.Volumes,e[1]=w);let T=w,{volumeId:E}=f.useSearch(),D=E??T[0]?.VolumeId??``,O;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(O=y(b),e[2]=O):O=e[2];let k;e[3]===D?k=e[4]:(k={resolver:O,defaultValues:{volumeId:D,description:``}},e[3]=D,e[4]=k);let{control:A,handleSubmit:j,register:M,formState:N}=v(k),{errors:P,isSubmitting:F}=N,I;e[5]!==S||e[6]!==t?(I=async e=>{await S.mutateAsync(e),t({to:`/ec2/describe-snapshots`})},e[5]=S,e[6]=t,e[7]=I):I=e[7];let L=I,R,z;e[8]===Symbol.for(`react.memo_cache_sentinel`)?(R=(0,x.jsx)(i,{to:`/ec2/describe-snapshots`,children:`Back to snapshots`}),z=(0,x.jsx)(p,{title:`Create Snapshot`}),e[8]=R,e[9]=z):(R=e[8],z=e[9]);let B;e[10]===S.error?B=e[11]:(B=S.error&&(0,x.jsx)(n,{error:S.error,msg:`Failed to create snapshot`}),e[10]=S.error,e[11]=B);let V;e[12]!==j||e[13]!==L?(V=j(L),e[12]=j,e[13]=L,e[14]=V):V=e[14];let H;e[15]===Symbol.for(`react.memo_cache_sentinel`)?(H=(0,x.jsx)(m,{children:(0,x.jsx)(`label`,{htmlFor:`volumeId`,children:`Volume`})}),e[15]=H):H=e[15];let U;e[16]!==P.volumeId||e[17]!==T?(U=e=>{let{field:t}=e;return(0,x.jsxs)(l,{onValueChange:e=>t.onChange(e),value:t.value??``,children:[(0,x.jsx)(o,{"aria-invalid":!!P.volumeId,className:`w-full`,id:`volumeId`,children:(0,x.jsx)(a,{})}),(0,x.jsx)(s,{children:T.map(C)})]})},e[16]=P.volumeId,e[17]=T,e[18]=U):U=e[18];let W;e[19]!==A||e[20]!==U?(W=(0,x.jsx)(_,{control:A,name:`volumeId`,render:U}),e[19]=A,e[20]=U,e[21]=W):W=e[21];let G;e[22]===P.volumeId?G=e[23]:(G=(0,x.jsx)(te,{errors:[P.volumeId]}),e[22]=P.volumeId,e[23]=G);let K;e[24]!==W||e[25]!==G?(K=(0,x.jsxs)(h,{children:[H,W,G]}),e[24]=W,e[25]=G,e[26]=K):K=e[26];let q;e[27]===Symbol.for(`react.memo_cache_sentinel`)?(q=(0,x.jsx)(m,{children:(0,x.jsx)(`label`,{htmlFor:`description`,children:`Description (optional)`})}),e[27]=q):q=e[27];let J;e[28]===M?J=e[29]:(J=M(`description`),e[28]=M,e[29]=J);let Y;e[30]===J?Y=e[31]:(Y=(0,x.jsxs)(h,{children:[q,(0,x.jsx)(g,{id:`description`,placeholder:`Snapshot description`,...J})]}),e[30]=J,e[31]=Y);let X;e[32]===t?X=e[33]:(X=()=>t({to:`/ec2/describe-snapshots`}),e[32]=t,e[33]=X);let Z;e[34]!==S.isPending||e[35]!==F||e[36]!==X?(Z=(0,x.jsx)(ne,{isPending:S.isPending,isSubmitting:F,onCancel:X,pendingLabel:`Creating\\u2026`,submitLabel:`Create Snapshot`}),e[34]=S.isPending,e[35]=F,e[36]=X,e[37]=Z):Z=e[37];let Q;e[38]!==K||e[39]!==Y||e[40]!==Z||e[41]!==V?(Q=(0,x.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:V,children:[K,Y,Z]}),e[38]=K,e[39]=Y,e[40]=Z,e[41]=V,e[42]=Q):Q=e[42];let $;return e[43]!==Q||e[44]!==B?($=(0,x.jsxs)(x.Fragment,{children:[R,z,B,Q]}),e[43]=Q,e[44]=B,e[45]=$):$=e[45],$}function C(e){return(0,x.jsxs)(c,{value:e.VolumeId??``,children:[e.VolumeId,` (`,e.Size,` GiB)`]},e.VolumeId)}export{S as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as ee}from"./error-banner.js";import{s as r}from"./utils.js";import"./useStore.js";import{t as te}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import{a,i as o,n as s,r as c,t as l}from"./select.js";import"./separator.js";import"./awsClient.js";import{s as ne}from"./ec2.js";import{_ as re,t as ie}from"./ec22.js";import{k as ae}from"./index.js";import{t as oe}from"./page-heading.js";import{i as u,n as d,t as f}from"./field.js";import{n as p,r as se,t as ce}from"./zod.js";import{i as m}from"./ec23.js";import{t as le}from"./form-actions.js";var ue=t(),h=e();function g(){let e=(0,ue.c)(61),t=te(),{data:r}=n(re),{data:g}=n(ie),_=ne(),v;e[0]===r.Vpcs?v=e[1]:(v=r.Vpcs??[],e[0]=r.Vpcs,e[1]=v);let y=v,b;e[2]===g.AvailabilityZones?b=e[3]:(b=g.AvailabilityZones??[],e[2]=g.AvailabilityZones,e[3]=b);let x=b,S;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(S=ce(m),e[4]=S):S=e[4];let C=y[0]?.VpcId??``,w;e[5]===C?w=e[6]:(w={resolver:S,defaultValues:{vpcId:C,cidrBlock:`10.0.1.0/24`,availabilityZone:``}},e[5]=C,e[6]=w);let{control:T,handleSubmit:E,register:D,formState:pe}=se(w),{errors:O,isSubmitting:k}=pe,A;e[7]!==_||e[8]!==t?(A=async e=>{let n=(await _.mutateAsync(e)).Subnet?.SubnetId;t(n?{to:`/ec2/describe-subnets/$id`,params:{id:n}}:{to:`/ec2/describe-subnets`})},e[7]=_,e[8]=t,e[9]=A):A=e[9];let j=A,M,N;e[10]===Symbol.for(`react.memo_cache_sentinel`)?(M=(0,h.jsx)(i,{to:`/ec2/describe-subnets`,children:`Back to Subnets`}),N=(0,h.jsx)(oe,{title:`Create Subnet`}),e[10]=M,e[11]=N):(M=e[10],N=e[11]);let P;e[12]===_.error?P=e[13]:(P=_.error&&(0,h.jsx)(ee,{error:_.error,msg:`Failed to create subnet`}),e[12]=_.error,e[13]=P);let F;e[14]!==E||e[15]!==j?(F=E(j),e[14]=E,e[15]=j,e[16]=F):F=e[16];let I;e[17]===Symbol.for(`react.memo_cache_sentinel`)?(I=(0,h.jsx)(u,{children:(0,h.jsx)(`label`,{htmlFor:`vpcId`,children:`VPC`})}),e[17]=I):I=e[17];let L;e[18]!==O.vpcId||e[19]!==y?(L=e=>{let{field:t}=e;return(0,h.jsxs)(l,{onValueChange:e=>t.onChange(e),value:t.value??``,children:[(0,h.jsx)(o,{"aria-invalid":!!O.vpcId,className:`w-full`,id:`vpcId`,children:(0,h.jsx)(a,{})}),(0,h.jsx)(s,{children:y.map(fe)})]})},e[18]=O.vpcId,e[19]=y,e[20]=L):L=e[20];let R;e[21]!==T||e[22]!==L?(R=(0,h.jsx)(p,{control:T,name:`vpcId`,render:L}),e[21]=T,e[22]=L,e[23]=R):R=e[23];let z;e[24]===O.vpcId?z=e[25]:(z=(0,h.jsx)(d,{errors:[O.vpcId]}),e[24]=O.vpcId,e[25]=z);let B;e[26]!==R||e[27]!==z?(B=(0,h.jsxs)(f,{children:[I,R,z]}),e[26]=R,e[27]=z,e[28]=B):B=e[28];let V;e[29]===Symbol.for(`react.memo_cache_sentinel`)?(V=(0,h.jsx)(u,{children:(0,h.jsx)(`label`,{htmlFor:`cidrBlock`,children:`CIDR Block`})}),e[29]=V):V=e[29];let H=!!O.cidrBlock,U;e[30]===D?U=e[31]:(U=D(`cidrBlock`),e[30]=D,e[31]=U);let W;e[32]!==H||e[33]!==U?(W=(0,h.jsx)(ae,{"aria-invalid":H,id:`cidrBlock`,placeholder:`10.0.1.0/24`,...U}),e[32]=H,e[33]=U,e[34]=W):W=e[34];let G;e[35]===O.cidrBlock?G=e[36]:(G=(0,h.jsx)(d,{errors:[O.cidrBlock]}),e[35]=O.cidrBlock,e[36]=G);let K;e[37]!==W||e[38]!==G?(K=(0,h.jsxs)(f,{children:[V,W,G]}),e[37]=W,e[38]=G,e[39]=K):K=e[39];let q;e[40]===Symbol.for(`react.memo_cache_sentinel`)?(q=(0,h.jsx)(u,{children:(0,h.jsx)(`label`,{htmlFor:`availabilityZone`,children:`Availability Zone (optional)`})}),e[40]=q):q=e[40];let J;e[41]===x?J=e[42]:(J=e=>{let{field:t}=e;return(0,h.jsxs)(l,{onValueChange:e=>t.onChange(e===`none`?``:e),value:t.value??`none`,children:[(0,h.jsx)(o,{className:`w-full`,id:`availabilityZone`,children:(0,h.jsx)(a,{})}),(0,h.jsxs)(s,{children:[(0,h.jsx)(c,{value:`none`,children:`none`}),x.map(de)]})]})},e[41]=x,e[42]=J);let Y;e[43]!==T||e[44]!==J?(Y=(0,h.jsxs)(f,{children:[q,(0,h.jsx)(p,{control:T,name:`availabilityZone`,render:J})]}),e[43]=T,e[44]=J,e[45]=Y):Y=e[45];let X;e[46]===t?X=e[47]:(X=()=>t({to:`/ec2/describe-subnets`}),e[46]=t,e[47]=X);let Z;e[48]!==_.isPending||e[49]!==k||e[50]!==X?(Z=(0,h.jsx)(le,{isPending:_.isPending,isSubmitting:k,onCancel:X,pendingLabel:`Creating\\u2026`,submitLabel:`Create Subnet`}),e[48]=_.isPending,e[49]=k,e[50]=X,e[51]=Z):Z=e[51];let Q;e[52]!==F||e[53]!==B||e[54]!==K||e[55]!==Y||e[56]!==Z?(Q=(0,h.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:F,children:[B,K,Y,Z]}),e[52]=F,e[53]=B,e[54]=K,e[55]=Y,e[56]=Z,e[57]=Q):Q=e[57];let $;return e[58]!==Q||e[59]!==P?($=(0,h.jsxs)(h.Fragment,{children:[M,N,P,Q]}),e[58]=Q,e[59]=P,e[60]=$):$=e[60],$}function de(e){return(0,h.jsx)(c,{value:e.ZoneName??``,children:e.ZoneName},e.ZoneName)}function fe(e){let t=r(e.Tags);return(0,h.jsx)(c,{value:e.VpcId??``,children:t?`${e.VpcId} (${t})`:`${e.VpcId} (${e.CidrBlock})`},e.VpcId)}export{g as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as ee}from"./error-banner.js";import{s as r}from"./utils.js";import"./useStore.js";import{t as te}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import{a,i as o,n as s,r as c,t as l}from"./select.js";import"./separator.js";import"./awsClient.js";import{c as ne}from"./ec2.js";import{t as re,y as ie}from"./ec22.js";import{A as ae}from"./index.js";import{t as oe}from"./page-heading.js";import{i as u,n as d,t as f}from"./field.js";import{n as p,r as se,t as ce}from"./zod.js";import{a as m}from"./ec23.js";import{t as le}from"./form-actions.js";var ue=t(),h=e();function g(){let e=(0,ue.c)(61),t=te(),{data:r}=n(ie),{data:g}=n(re),_=ne(),v;e[0]===r.Vpcs?v=e[1]:(v=r.Vpcs??[],e[0]=r.Vpcs,e[1]=v);let y=v,b;e[2]===g.AvailabilityZones?b=e[3]:(b=g.AvailabilityZones??[],e[2]=g.AvailabilityZones,e[3]=b);let x=b,S;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(S=ce(m),e[4]=S):S=e[4];let C=y[0]?.VpcId??``,w;e[5]===C?w=e[6]:(w={resolver:S,defaultValues:{vpcId:C,cidrBlock:`10.0.1.0/24`,availabilityZone:``}},e[5]=C,e[6]=w);let{control:T,handleSubmit:E,register:D,formState:pe}=se(w),{errors:O,isSubmitting:k}=pe,A;e[7]!==_||e[8]!==t?(A=async e=>{let n=(await _.mutateAsync(e)).Subnet?.SubnetId;t(n?{to:`/ec2/describe-subnets/$id`,params:{id:n}}:{to:`/ec2/describe-subnets`})},e[7]=_,e[8]=t,e[9]=A):A=e[9];let j=A,M,N;e[10]===Symbol.for(`react.memo_cache_sentinel`)?(M=(0,h.jsx)(i,{to:`/ec2/describe-subnets`,children:`Back to Subnets`}),N=(0,h.jsx)(oe,{title:`Create Subnet`}),e[10]=M,e[11]=N):(M=e[10],N=e[11]);let P;e[12]===_.error?P=e[13]:(P=_.error&&(0,h.jsx)(ee,{error:_.error,msg:`Failed to create subnet`}),e[12]=_.error,e[13]=P);let F;e[14]!==E||e[15]!==j?(F=E(j),e[14]=E,e[15]=j,e[16]=F):F=e[16];let I;e[17]===Symbol.for(`react.memo_cache_sentinel`)?(I=(0,h.jsx)(u,{children:(0,h.jsx)(`label`,{htmlFor:`vpcId`,children:`VPC`})}),e[17]=I):I=e[17];let L;e[18]!==O.vpcId||e[19]!==y?(L=e=>{let{field:t}=e;return(0,h.jsxs)(l,{onValueChange:e=>t.onChange(e),value:t.value??``,children:[(0,h.jsx)(o,{"aria-invalid":!!O.vpcId,className:`w-full`,id:`vpcId`,children:(0,h.jsx)(a,{})}),(0,h.jsx)(s,{children:y.map(fe)})]})},e[18]=O.vpcId,e[19]=y,e[20]=L):L=e[20];let R;e[21]!==T||e[22]!==L?(R=(0,h.jsx)(p,{control:T,name:`vpcId`,render:L}),e[21]=T,e[22]=L,e[23]=R):R=e[23];let z;e[24]===O.vpcId?z=e[25]:(z=(0,h.jsx)(d,{errors:[O.vpcId]}),e[24]=O.vpcId,e[25]=z);let B;e[26]!==R||e[27]!==z?(B=(0,h.jsxs)(f,{children:[I,R,z]}),e[26]=R,e[27]=z,e[28]=B):B=e[28];let V;e[29]===Symbol.for(`react.memo_cache_sentinel`)?(V=(0,h.jsx)(u,{children:(0,h.jsx)(`label`,{htmlFor:`cidrBlock`,children:`CIDR Block`})}),e[29]=V):V=e[29];let H=!!O.cidrBlock,U;e[30]===D?U=e[31]:(U=D(`cidrBlock`),e[30]=D,e[31]=U);let W;e[32]!==H||e[33]!==U?(W=(0,h.jsx)(ae,{"aria-invalid":H,id:`cidrBlock`,placeholder:`10.0.1.0/24`,...U}),e[32]=H,e[33]=U,e[34]=W):W=e[34];let G;e[35]===O.cidrBlock?G=e[36]:(G=(0,h.jsx)(d,{errors:[O.cidrBlock]}),e[35]=O.cidrBlock,e[36]=G);let K;e[37]!==W||e[38]!==G?(K=(0,h.jsxs)(f,{children:[V,W,G]}),e[37]=W,e[38]=G,e[39]=K):K=e[39];let q;e[40]===Symbol.for(`react.memo_cache_sentinel`)?(q=(0,h.jsx)(u,{children:(0,h.jsx)(`label`,{htmlFor:`availabilityZone`,children:`Availability Zone (optional)`})}),e[40]=q):q=e[40];let J;e[41]===x?J=e[42]:(J=e=>{let{field:t}=e;return(0,h.jsxs)(l,{onValueChange:e=>t.onChange(e===`none`?``:e),value:t.value??`none`,children:[(0,h.jsx)(o,{className:`w-full`,id:`availabilityZone`,children:(0,h.jsx)(a,{})}),(0,h.jsxs)(s,{children:[(0,h.jsx)(c,{value:`none`,children:`none`}),x.map(de)]})]})},e[41]=x,e[42]=J);let Y;e[43]!==T||e[44]!==J?(Y=(0,h.jsxs)(f,{children:[q,(0,h.jsx)(p,{control:T,name:`availabilityZone`,render:J})]}),e[43]=T,e[44]=J,e[45]=Y):Y=e[45];let X;e[46]===t?X=e[47]:(X=()=>t({to:`/ec2/describe-subnets`}),e[46]=t,e[47]=X);let Z;e[48]!==_.isPending||e[49]!==k||e[50]!==X?(Z=(0,h.jsx)(le,{isPending:_.isPending,isSubmitting:k,onCancel:X,pendingLabel:`Creating\\u2026`,submitLabel:`Create Subnet`}),e[48]=_.isPending,e[49]=k,e[50]=X,e[51]=Z):Z=e[51];let Q;e[52]!==F||e[53]!==B||e[54]!==K||e[55]!==Y||e[56]!==Z?(Q=(0,h.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:F,children:[B,K,Y,Z]}),e[52]=F,e[53]=B,e[54]=K,e[55]=Y,e[56]=Z,e[57]=Q):Q=e[57];let $;return e[58]!==Q||e[59]!==P?($=(0,h.jsxs)(h.Fragment,{children:[M,N,P,Q]}),e[58]=Q,e[59]=P,e[60]=$):$=e[60],$}function de(e){return(0,h.jsx)(c,{value:e.ZoneName??``,children:e.ZoneName},e.ZoneName)}function fe(e){let t=r(e.Tags);return(0,h.jsx)(c,{value:e.VpcId??``,children:t?`${e.VpcId} (${t})`:`${e.VpcId} (${e.CidrBlock})`},e.VpcId)}export{g as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{i as a}from"./iam.js";import{k as o}from"./index.js";import{t as s}from"./page-heading.js";import{i as c,n as l,t as u}from"./field.js";import{r as d,t as f}from"./zod.js";import{t as p}from"./form-actions.js";import{n as m}from"./iam3.js";var h=t(),g=e();function _(){let e=(0,h.c)(46),t=r(),_=a(),v;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(v={resolver:f(m)},e[0]=v):v=e[0];let{handleSubmit:y,register:b,formState:x}=d(v),{errors:S,isSubmitting:C}=x,w;e[1]!==_||e[2]!==t?(w=async e=>{await _.mutateAsync(e),t({to:`/iam/list-users`})},e[1]=_,e[2]=t,e[3]=w):w=e[3];let T=w,E,D;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(E=(0,g.jsx)(i,{to:`/iam/list-users`,children:`Back to users`}),D=(0,g.jsx)(s,{title:`Create User`}),e[4]=E,e[5]=D):(E=e[4],D=e[5]);let O;e[6]===_.error?O=e[7]:(O=_.error&&(0,g.jsx)(n,{error:_.error,msg:`Failed to create user`}),e[6]=_.error,e[7]=O);let k;e[8]!==y||e[9]!==T?(k=y(T),e[8]=y,e[9]=T,e[10]=k):k=e[10];let A;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(A=(0,g.jsx)(c,{children:(0,g.jsx)(`label`,{htmlFor:`userName`,children:`User Name`})}),e[11]=A):A=e[11];let j=!!S.userName,M;e[12]===b?M=e[13]:(M=b(`userName`),e[12]=b,e[13]=M);let N;e[14]!==j||e[15]!==M?(N=(0,g.jsx)(o,{"aria-invalid":j,id:`userName`,placeholder:`my-user...`,...M}),e[14]=j,e[15]=M,e[16]=N):N=e[16];let P;e[17]===S.userName?P=e[18]:(P=(0,g.jsx)(l,{errors:[S.userName]}),e[17]=S.userName,e[18]=P);let F;e[19]!==N||e[20]!==P?(F=(0,g.jsxs)(u,{children:[A,N,P]}),e[19]=N,e[20]=P,e[21]=F):F=e[21];let I;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(I=(0,g.jsx)(c,{children:(0,g.jsx)(`label`,{htmlFor:`path`,children:`Path (optional)`})}),e[22]=I):I=e[22];let L;e[23]===b?L=e[24]:(L=b(`path`),e[23]=b,e[24]=L);let R;e[25]===L?R=e[26]:(R=(0,g.jsx)(o,{id:`path`,placeholder:`/`,...L}),e[25]=L,e[26]=R);let z;e[27]===S.path?z=e[28]:(z=(0,g.jsx)(l,{errors:[S.path]}),e[27]=S.path,e[28]=z);let B;e[29]!==R||e[30]!==z?(B=(0,g.jsxs)(u,{children:[I,R,z]}),e[29]=R,e[30]=z,e[31]=B):B=e[31];let V;e[32]===t?V=e[33]:(V=()=>t({to:`/iam/list-users`}),e[32]=t,e[33]=V);let H;e[34]!==_.isPending||e[35]!==C||e[36]!==V?(H=(0,g.jsx)(p,{isPending:_.isPending,isSubmitting:C,onCancel:V,pendingLabel:`Creating...`,submitLabel:`Create User`}),e[34]=_.isPending,e[35]=C,e[36]=V,e[37]=H):H=e[37];let U;e[38]!==F||e[39]!==B||e[40]!==H||e[41]!==k?(U=(0,g.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:k,children:[F,B,H]}),e[38]=F,e[39]=B,e[40]=H,e[41]=k,e[42]=U):U=e[42];let W;return e[43]!==U||e[44]!==O?(W=(0,g.jsxs)(g.Fragment,{children:[E,D,O,U]}),e[43]=U,e[44]=O,e[45]=W):W=e[45],W}export{_ as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{i as a}from"./iam.js";import{A as o}from"./index.js";import{t as s}from"./page-heading.js";import{i as c,n as l,t as u}from"./field.js";import{r as d,t as f}from"./zod.js";import{t as p}from"./form-actions.js";import{n as m}from"./iam3.js";var h=t(),g=e();function _(){let e=(0,h.c)(46),t=r(),_=a(),v;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(v={resolver:f(m)},e[0]=v):v=e[0];let{handleSubmit:y,register:b,formState:x}=d(v),{errors:S,isSubmitting:C}=x,w;e[1]!==_||e[2]!==t?(w=async e=>{await _.mutateAsync(e),t({to:`/iam/list-users`})},e[1]=_,e[2]=t,e[3]=w):w=e[3];let T=w,E,D;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(E=(0,g.jsx)(i,{to:`/iam/list-users`,children:`Back to users`}),D=(0,g.jsx)(s,{title:`Create User`}),e[4]=E,e[5]=D):(E=e[4],D=e[5]);let O;e[6]===_.error?O=e[7]:(O=_.error&&(0,g.jsx)(n,{error:_.error,msg:`Failed to create user`}),e[6]=_.error,e[7]=O);let k;e[8]!==y||e[9]!==T?(k=y(T),e[8]=y,e[9]=T,e[10]=k):k=e[10];let A;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(A=(0,g.jsx)(c,{children:(0,g.jsx)(`label`,{htmlFor:`userName`,children:`User Name`})}),e[11]=A):A=e[11];let j=!!S.userName,M;e[12]===b?M=e[13]:(M=b(`userName`),e[12]=b,e[13]=M);let N;e[14]!==j||e[15]!==M?(N=(0,g.jsx)(o,{"aria-invalid":j,id:`userName`,placeholder:`my-user...`,...M}),e[14]=j,e[15]=M,e[16]=N):N=e[16];let P;e[17]===S.userName?P=e[18]:(P=(0,g.jsx)(l,{errors:[S.userName]}),e[17]=S.userName,e[18]=P);let F;e[19]!==N||e[20]!==P?(F=(0,g.jsxs)(u,{children:[A,N,P]}),e[19]=N,e[20]=P,e[21]=F):F=e[21];let I;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(I=(0,g.jsx)(c,{children:(0,g.jsx)(`label`,{htmlFor:`path`,children:`Path (optional)`})}),e[22]=I):I=e[22];let L;e[23]===b?L=e[24]:(L=b(`path`),e[23]=b,e[24]=L);let R;e[25]===L?R=e[26]:(R=(0,g.jsx)(o,{id:`path`,placeholder:`/`,...L}),e[25]=L,e[26]=R);let z;e[27]===S.path?z=e[28]:(z=(0,g.jsx)(l,{errors:[S.path]}),e[27]=S.path,e[28]=z);let B;e[29]!==R||e[30]!==z?(B=(0,g.jsxs)(u,{children:[I,R,z]}),e[29]=R,e[30]=z,e[31]=B):B=e[31];let V;e[32]===t?V=e[33]:(V=()=>t({to:`/iam/list-users`}),e[32]=t,e[33]=V);let H;e[34]!==_.isPending||e[35]!==C||e[36]!==V?(H=(0,g.jsx)(p,{isPending:_.isPending,isSubmitting:C,onCancel:V,pendingLabel:`Creating...`,submitLabel:`Create User`}),e[34]=_.isPending,e[35]=C,e[36]=V,e[37]=H):H=e[37];let U;e[38]!==F||e[39]!==B||e[40]!==H||e[41]!==k?(U=(0,g.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:k,children:[F,B,H]}),e[38]=F,e[39]=B,e[40]=H,e[41]=k,e[42]=U):U=e[42];let W;return e[43]!==U||e[44]!==O?(W=(0,g.jsxs)(g.Fragment,{children:[E,D,O,U]}),e[43]=U,e[44]=O,e[45]=W):W=e[45],W}export{_ as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as r}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as i}from"./useNavigate.js";import"./button.js";import{t as a}from"./back-link.js";import{a as o,i as ee,n as s,r as c,t as l}from"./select.js";import"./separator.js";import"./awsClient.js";import{c as u}from"./ec2.js";import{t as d}from"./ec22.js";import{k as f}from"./index.js";import{t as te}from"./page-heading.js";import{i as p,n as m,t as h}from"./field.js";import{n as ne,r as re,t as g}from"./zod.js";import{a as _}from"./ec23.js";import{t as ie}from"./form-actions.js";var v=t(),y=e();function b(){let e=(0,v.c)(52),t=i(),{data:c}=n(d),b=u(),S;e[0]===c.AvailabilityZones?S=e[1]:(S=c.AvailabilityZones??[],e[0]=c.AvailabilityZones,e[1]=S);let C=S,w=C[0]?.ZoneName??``,T;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(T=g(_),e[2]=T):T=e[2];let E;e[3]===w?E=e[4]:(E={resolver:T,defaultValues:{size:1,availabilityZone:w}},e[3]=w,e[4]=E);let{control:D,handleSubmit:O,register:k,formState:A}=re(E),{errors:j,isSubmitting:M}=A,N;e[5]!==b||e[6]!==t?(N=async e=>{await b.mutateAsync(e),t({to:`/ec2/describe-volumes`})},e[5]=b,e[6]=t,e[7]=N):N=e[7];let P=N,F,I;e[8]===Symbol.for(`react.memo_cache_sentinel`)?(F=(0,y.jsx)(a,{to:`/ec2/describe-volumes`,children:`Back to volumes`}),I=(0,y.jsx)(te,{title:`Create Volume`}),e[8]=F,e[9]=I):(F=e[8],I=e[9]);let L;e[10]===b.error?L=e[11]:(L=b.error&&(0,y.jsx)(r,{error:b.error,msg:`Failed to create volume`}),e[10]=b.error,e[11]=L);let R;e[12]!==O||e[13]!==P?(R=O(P),e[12]=O,e[13]=P,e[14]=R):R=e[14];let z;e[15]===Symbol.for(`react.memo_cache_sentinel`)?(z=(0,y.jsx)(p,{children:(0,y.jsx)(`label`,{htmlFor:`size`,children:`Size (GiB)`})}),e[15]=z):z=e[15];let B=!!j.size,V;e[16]===k?V=e[17]:(V=k(`size`,{valueAsNumber:!0}),e[16]=k,e[17]=V);let H;e[18]!==B||e[19]!==V?(H=(0,y.jsx)(f,{"aria-invalid":B,id:`size`,max:16384,min:1,type:`number`,...V}),e[18]=B,e[19]=V,e[20]=H):H=e[20];let U;e[21]===j.size?U=e[22]:(U=(0,y.jsx)(m,{errors:[j.size]}),e[21]=j.size,e[22]=U);let W;e[23]!==H||e[24]!==U?(W=(0,y.jsxs)(h,{children:[z,H,U]}),e[23]=H,e[24]=U,e[25]=W):W=e[25];let G;e[26]===Symbol.for(`react.memo_cache_sentinel`)?(G=(0,y.jsx)(p,{children:(0,y.jsx)(`label`,{htmlFor:`availabilityZone`,children:`Availability Zone`})}),e[26]=G):G=e[26];let K;e[27]!==C||e[28]!==j.availabilityZone?(K=e=>{let{field:t}=e;return(0,y.jsxs)(l,{onValueChange:e=>t.onChange(e),value:t.value??``,children:[(0,y.jsx)(ee,{"aria-invalid":!!j.availabilityZone,className:`w-full`,id:`availabilityZone`,children:(0,y.jsx)(o,{})}),(0,y.jsx)(s,{children:C.map(x)})]})},e[27]=C,e[28]=j.availabilityZone,e[29]=K):K=e[29];let q;e[30]!==D||e[31]!==K?(q=(0,y.jsx)(ne,{control:D,name:`availabilityZone`,render:K}),e[30]=D,e[31]=K,e[32]=q):q=e[32];let J;e[33]===j.availabilityZone?J=e[34]:(J=(0,y.jsx)(m,{errors:[j.availabilityZone]}),e[33]=j.availabilityZone,e[34]=J);let Y;e[35]!==q||e[36]!==J?(Y=(0,y.jsxs)(h,{children:[G,q,J]}),e[35]=q,e[36]=J,e[37]=Y):Y=e[37];let X;e[38]===t?X=e[39]:(X=()=>t({to:`/ec2/describe-volumes`}),e[38]=t,e[39]=X);let Z;e[40]!==b.isPending||e[41]!==M||e[42]!==X?(Z=(0,y.jsx)(ie,{isPending:b.isPending,isSubmitting:M,onCancel:X,pendingLabel:`Creating\\u2026`,submitLabel:`Create Volume`}),e[40]=b.isPending,e[41]=M,e[42]=X,e[43]=Z):Z=e[43];let Q;e[44]!==W||e[45]!==Y||e[46]!==Z||e[47]!==R?(Q=(0,y.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:R,children:[W,Y,Z]}),e[44]=W,e[45]=Y,e[46]=Z,e[47]=R,e[48]=Q):Q=e[48];let $;return e[49]!==Q||e[50]!==L?($=(0,y.jsxs)(y.Fragment,{children:[F,I,L,Q]}),e[49]=Q,e[50]=L,e[51]=$):$=e[51],$}function x(e){return(0,y.jsx)(c,{value:e.ZoneName??``,children:e.ZoneName},e.ZoneName)}export{b as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{t as r}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as i}from"./useNavigate.js";import"./button.js";import{t as a}from"./back-link.js";import{a as o,i as ee,n as s,r as c,t as l}from"./select.js";import"./separator.js";import"./awsClient.js";import{l as u}from"./ec2.js";import{t as d}from"./ec22.js";import{A as f}from"./index.js";import{t as te}from"./page-heading.js";import{i as p,n as m,t as h}from"./field.js";import{n as ne,r as re,t as g}from"./zod.js";import{o as _}from"./ec23.js";import{t as ie}from"./form-actions.js";var v=t(),y=e();function b(){let e=(0,v.c)(52),t=i(),{data:c}=n(d),b=u(),S;e[0]===c.AvailabilityZones?S=e[1]:(S=c.AvailabilityZones??[],e[0]=c.AvailabilityZones,e[1]=S);let C=S,w=C[0]?.ZoneName??``,T;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(T=g(_),e[2]=T):T=e[2];let E;e[3]===w?E=e[4]:(E={resolver:T,defaultValues:{size:1,availabilityZone:w}},e[3]=w,e[4]=E);let{control:D,handleSubmit:O,register:k,formState:A}=re(E),{errors:j,isSubmitting:M}=A,N;e[5]!==b||e[6]!==t?(N=async e=>{await b.mutateAsync(e),t({to:`/ec2/describe-volumes`})},e[5]=b,e[6]=t,e[7]=N):N=e[7];let P=N,F,I;e[8]===Symbol.for(`react.memo_cache_sentinel`)?(F=(0,y.jsx)(a,{to:`/ec2/describe-volumes`,children:`Back to volumes`}),I=(0,y.jsx)(te,{title:`Create Volume`}),e[8]=F,e[9]=I):(F=e[8],I=e[9]);let L;e[10]===b.error?L=e[11]:(L=b.error&&(0,y.jsx)(r,{error:b.error,msg:`Failed to create volume`}),e[10]=b.error,e[11]=L);let R;e[12]!==O||e[13]!==P?(R=O(P),e[12]=O,e[13]=P,e[14]=R):R=e[14];let z;e[15]===Symbol.for(`react.memo_cache_sentinel`)?(z=(0,y.jsx)(p,{children:(0,y.jsx)(`label`,{htmlFor:`size`,children:`Size (GiB)`})}),e[15]=z):z=e[15];let B=!!j.size,V;e[16]===k?V=e[17]:(V=k(`size`,{valueAsNumber:!0}),e[16]=k,e[17]=V);let H;e[18]!==B||e[19]!==V?(H=(0,y.jsx)(f,{"aria-invalid":B,id:`size`,max:16384,min:1,type:`number`,...V}),e[18]=B,e[19]=V,e[20]=H):H=e[20];let U;e[21]===j.size?U=e[22]:(U=(0,y.jsx)(m,{errors:[j.size]}),e[21]=j.size,e[22]=U);let W;e[23]!==H||e[24]!==U?(W=(0,y.jsxs)(h,{children:[z,H,U]}),e[23]=H,e[24]=U,e[25]=W):W=e[25];let G;e[26]===Symbol.for(`react.memo_cache_sentinel`)?(G=(0,y.jsx)(p,{children:(0,y.jsx)(`label`,{htmlFor:`availabilityZone`,children:`Availability Zone`})}),e[26]=G):G=e[26];let K;e[27]!==C||e[28]!==j.availabilityZone?(K=e=>{let{field:t}=e;return(0,y.jsxs)(l,{onValueChange:e=>t.onChange(e),value:t.value??``,children:[(0,y.jsx)(ee,{"aria-invalid":!!j.availabilityZone,className:`w-full`,id:`availabilityZone`,children:(0,y.jsx)(o,{})}),(0,y.jsx)(s,{children:C.map(x)})]})},e[27]=C,e[28]=j.availabilityZone,e[29]=K):K=e[29];let q;e[30]!==D||e[31]!==K?(q=(0,y.jsx)(ne,{control:D,name:`availabilityZone`,render:K}),e[30]=D,e[31]=K,e[32]=q):q=e[32];let J;e[33]===j.availabilityZone?J=e[34]:(J=(0,y.jsx)(m,{errors:[j.availabilityZone]}),e[33]=j.availabilityZone,e[34]=J);let Y;e[35]!==q||e[36]!==J?(Y=(0,y.jsxs)(h,{children:[G,q,J]}),e[35]=q,e[36]=J,e[37]=Y):Y=e[37];let X;e[38]===t?X=e[39]:(X=()=>t({to:`/ec2/describe-volumes`}),e[38]=t,e[39]=X);let Z;e[40]!==b.isPending||e[41]!==M||e[42]!==X?(Z=(0,y.jsx)(ie,{isPending:b.isPending,isSubmitting:M,onCancel:X,pendingLabel:`Creating\\u2026`,submitLabel:`Create Volume`}),e[40]=b.isPending,e[41]=M,e[42]=X,e[43]=Z):Z=e[43];let Q;e[44]!==W||e[45]!==Y||e[46]!==Z||e[47]!==R?(Q=(0,y.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:R,children:[W,Y,Z]}),e[44]=W,e[45]=Y,e[46]=Z,e[47]=R,e[48]=Q):Q=e[48];let $;return e[49]!==Q||e[50]!==L?($=(0,y.jsxs)(y.Fragment,{children:[F,I,L,Q]}),e[49]=Q,e[50]=L,e[51]=$):$=e[51],$}function x(e){return(0,y.jsx)(c,{value:e.ZoneName??``,children:e.ZoneName},e.ZoneName)}export{b as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{l as a}from"./ec2.js";import"./ec22.js";import{k as o}from"./index.js";import{t as s}from"./page-heading.js";import{i as c,n as l,t as u}from"./field.js";import{r as d,t as f}from"./zod.js";import{o as p}from"./ec23.js";import{t as m}from"./form-actions.js";var h=t(),g=e();function _(){let e=(0,h.c)(41),t=r(),_=a(),v;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(v={resolver:f(p),defaultValues:{cidrBlock:`10.0.0.0/16`,name:``}},e[0]=v):v=e[0];let{handleSubmit:y,register:b,formState:x}=d(v),{errors:S,isSubmitting:C}=x,w;e[1]!==_||e[2]!==t?(w=async e=>{let n=(await _.mutateAsync(e)).Vpc?.VpcId;t(n?{to:`/ec2/describe-vpcs/$id`,params:{id:n}}:{to:`/ec2/describe-vpcs`})},e[1]=_,e[2]=t,e[3]=w):w=e[3];let T=w,E,D;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(E=(0,g.jsx)(i,{to:`/ec2/describe-vpcs`,children:`Back to VPCs`}),D=(0,g.jsx)(s,{title:`Create VPC`}),e[4]=E,e[5]=D):(E=e[4],D=e[5]);let O;e[6]===_.error?O=e[7]:(O=_.error&&(0,g.jsx)(n,{error:_.error,msg:`Failed to create VPC`}),e[6]=_.error,e[7]=O);let k;e[8]!==y||e[9]!==T?(k=y(T),e[8]=y,e[9]=T,e[10]=k):k=e[10];let A;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(A=(0,g.jsx)(c,{children:(0,g.jsx)(`label`,{htmlFor:`cidrBlock`,children:`CIDR Block`})}),e[11]=A):A=e[11];let j=!!S.cidrBlock,M;e[12]===b?M=e[13]:(M=b(`cidrBlock`),e[12]=b,e[13]=M);let N;e[14]!==j||e[15]!==M?(N=(0,g.jsx)(o,{"aria-invalid":j,id:`cidrBlock`,placeholder:`10.0.0.0/16`,...M}),e[14]=j,e[15]=M,e[16]=N):N=e[16];let P;e[17]===S.cidrBlock?P=e[18]:(P=(0,g.jsx)(l,{errors:[S.cidrBlock]}),e[17]=S.cidrBlock,e[18]=P);let F;e[19]!==N||e[20]!==P?(F=(0,g.jsxs)(u,{children:[A,N,P]}),e[19]=N,e[20]=P,e[21]=F):F=e[21];let I;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(I=(0,g.jsx)(c,{children:(0,g.jsx)(`label`,{htmlFor:`name`,children:`Name (optional)`})}),e[22]=I):I=e[22];let L;e[23]===b?L=e[24]:(L=b(`name`),e[23]=b,e[24]=L);let R;e[25]===L?R=e[26]:(R=(0,g.jsxs)(u,{children:[I,(0,g.jsx)(o,{id:`name`,placeholder:`my-vpc`,...L})]}),e[25]=L,e[26]=R);let z;e[27]===t?z=e[28]:(z=()=>t({to:`/ec2/describe-vpcs`}),e[27]=t,e[28]=z);let B;e[29]!==_.isPending||e[30]!==C||e[31]!==z?(B=(0,g.jsx)(m,{isPending:_.isPending,isSubmitting:C,onCancel:z,pendingLabel:`Creating\\u2026`,submitLabel:`Create VPC`}),e[29]=_.isPending,e[30]=C,e[31]=z,e[32]=B):B=e[32];let V;e[33]!==F||e[34]!==R||e[35]!==B||e[36]!==k?(V=(0,g.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:k,children:[F,R,B]}),e[33]=F,e[34]=R,e[35]=B,e[36]=k,e[37]=V):V=e[37];let H;return e[38]!==V||e[39]!==O?(H=(0,g.jsxs)(g.Fragment,{children:[E,D,O,V]}),e[38]=V,e[39]=O,e[40]=H):H=e[40],H}export{_ as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{u as a}from"./ec2.js";import"./ec22.js";import{A as o}from"./index.js";import{t as s}from"./page-heading.js";import{i as c,n as l,t as u}from"./field.js";import{r as d,t as f}from"./zod.js";import{s as p}from"./ec23.js";import{t as m}from"./form-actions.js";var h=t(),g=e();function _(){let e=(0,h.c)(41),t=r(),_=a(),v;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(v={resolver:f(p),defaultValues:{cidrBlock:`10.0.0.0/16`,name:``}},e[0]=v):v=e[0];let{handleSubmit:y,register:b,formState:x}=d(v),{errors:S,isSubmitting:C}=x,w;e[1]!==_||e[2]!==t?(w=async e=>{let n=(await _.mutateAsync(e)).Vpc?.VpcId;t(n?{to:`/ec2/describe-vpcs/$id`,params:{id:n}}:{to:`/ec2/describe-vpcs`})},e[1]=_,e[2]=t,e[3]=w):w=e[3];let T=w,E,D;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(E=(0,g.jsx)(i,{to:`/ec2/describe-vpcs`,children:`Back to VPCs`}),D=(0,g.jsx)(s,{title:`Create VPC`}),e[4]=E,e[5]=D):(E=e[4],D=e[5]);let O;e[6]===_.error?O=e[7]:(O=_.error&&(0,g.jsx)(n,{error:_.error,msg:`Failed to create VPC`}),e[6]=_.error,e[7]=O);let k;e[8]!==y||e[9]!==T?(k=y(T),e[8]=y,e[9]=T,e[10]=k):k=e[10];let A;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(A=(0,g.jsx)(c,{children:(0,g.jsx)(`label`,{htmlFor:`cidrBlock`,children:`CIDR Block`})}),e[11]=A):A=e[11];let j=!!S.cidrBlock,M;e[12]===b?M=e[13]:(M=b(`cidrBlock`),e[12]=b,e[13]=M);let N;e[14]!==j||e[15]!==M?(N=(0,g.jsx)(o,{"aria-invalid":j,id:`cidrBlock`,placeholder:`10.0.0.0/16`,...M}),e[14]=j,e[15]=M,e[16]=N):N=e[16];let P;e[17]===S.cidrBlock?P=e[18]:(P=(0,g.jsx)(l,{errors:[S.cidrBlock]}),e[17]=S.cidrBlock,e[18]=P);let F;e[19]!==N||e[20]!==P?(F=(0,g.jsxs)(u,{children:[A,N,P]}),e[19]=N,e[20]=P,e[21]=F):F=e[21];let I;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(I=(0,g.jsx)(c,{children:(0,g.jsx)(`label`,{htmlFor:`name`,children:`Name (optional)`})}),e[22]=I):I=e[22];let L;e[23]===b?L=e[24]:(L=b(`name`),e[23]=b,e[24]=L);let R;e[25]===L?R=e[26]:(R=(0,g.jsxs)(u,{children:[I,(0,g.jsx)(o,{id:`name`,placeholder:`my-vpc`,...L})]}),e[25]=L,e[26]=R);let z;e[27]===t?z=e[28]:(z=()=>t({to:`/ec2/describe-vpcs`}),e[27]=t,e[28]=z);let B;e[29]!==_.isPending||e[30]!==C||e[31]!==z?(B=(0,g.jsx)(m,{isPending:_.isPending,isSubmitting:C,onCancel:z,pendingLabel:`Creating\\u2026`,submitLabel:`Create VPC`}),e[29]=_.isPending,e[30]=C,e[31]=z,e[32]=B):B=e[32];let V;e[33]!==F||e[34]!==R||e[35]!==B||e[36]!==k?(V=(0,g.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:k,children:[F,R,B]}),e[33]=F,e[34]=R,e[35]=B,e[36]=k,e[37]=V):V=e[37];let H;return e[38]!==V||e[39]!==O?(H=(0,g.jsxs)(g.Fragment,{children:[E,D,O,V]}),e[38]=V,e[39]=O,e[40]=H):H=e[40],H}export{_ as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import"./utils.js";import"./useStore.js";import{t as r}from"./link.js";import{t as i}from"./button.js";import"./awsClient.js";import{d as a}from"./ec22.js";import{t as o}from"./page-heading.js";import{t as s}from"./state-badge.js";import{t as c}from"./list-card.js";var l=t(),u=e();function d(){let e=(0,l.c)(5),{data:t}=n(a),s;e[0]===t.Snapshots?s=e[1]:(s=t.Snapshots??[],e[0]=t.Snapshots,e[1]=s);let c=s,d;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(d=(0,u.jsx)(o,{actions:(0,u.jsx)(r,{to:`/ec2/create-snapshot`,children:(0,u.jsx)(i,{children:`Create Snapshot`})}),title:`EC2 Snapshots`}),e[2]=d):d=e[2];let p;return e[3]===c?p=e[4]:(p=(0,u.jsxs)(u.Fragment,{children:[d,c.length>0?(0,u.jsx)(`div`,{className:`space-y-4`,children:c.map(f)}):(0,u.jsx)(`p`,{className:`text-muted-foreground`,children:`No snapshots found.`})]}),e[3]=c,e[4]=p),p}function f(e){return e.SnapshotId?(0,u.jsx)(c,{badge:(0,u.jsx)(s,{state:e.State}),params:{id:e.SnapshotId},subtitle:`${e.VolumeSize} GiB \u2022 ${e.VolumeId}`,title:e.SnapshotId,to:`/ec2/describe-snapshots/$id`},e.SnapshotId):null}export{d as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import"./utils.js";import"./useStore.js";import{t as r}from"./link.js";import{t as i}from"./button.js";import"./awsClient.js";import{p as a}from"./ec22.js";import{t as o}from"./page-heading.js";import{t as s}from"./state-badge.js";import{t as c}from"./list-card.js";var l=t(),u=e();function d(){let e=(0,l.c)(5),{data:t}=n(a),s;e[0]===t.Snapshots?s=e[1]:(s=t.Snapshots??[],e[0]=t.Snapshots,e[1]=s);let c=s,d;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(d=(0,u.jsx)(o,{actions:(0,u.jsx)(r,{to:`/ec2/create-snapshot`,children:(0,u.jsx)(i,{children:`Create Snapshot`})}),title:`EC2 Snapshots`}),e[2]=d):d=e[2];let p;return e[3]===c?p=e[4]:(p=(0,u.jsxs)(u.Fragment,{children:[d,c.length>0?(0,u.jsx)(`div`,{className:`space-y-4`,children:c.map(f)}):(0,u.jsx)(`p`,{className:`text-muted-foreground`,children:`No snapshots found.`})]}),e[3]=c,e[4]=p),p}function f(e){return e.SnapshotId?(0,u.jsx)(c,{badge:(0,u.jsx)(s,{state:e.State}),params:{id:e.SnapshotId},subtitle:`${e.VolumeSize} GiB \u2022 ${e.VolumeId}`,title:e.SnapshotId,to:`/ec2/describe-snapshots/$id`},e.SnapshotId):null}export{d as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{s as r}from"./utils.js";import"./useStore.js";import{t as i}from"./link.js";import{t as a}from"./button.js";import"./awsClient.js";import{p as o}from"./ec22.js";import{t as s}from"./page-heading.js";import{t as c}from"./state-badge.js";import{t as l}from"./list-card.js";var u=t(),d=e();function f(){let e=(0,u.c)(5),{data:t}=n(o),r;e[0]===t.Subnets?r=e[1]:(r=t.Subnets??[],e[0]=t.Subnets,e[1]=r);let c=r,l;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(l=(0,d.jsx)(s,{actions:(0,d.jsx)(i,{to:`/ec2/create-subnet`,children:(0,d.jsx)(a,{children:`Create Subnet`})}),title:`Subnets`}),e[2]=l):l=e[2];let f;return e[3]===c?f=e[4]:(f=(0,d.jsxs)(d.Fragment,{children:[l,c.length>0?(0,d.jsx)(`div`,{className:`space-y-4`,children:c.map(p)}):(0,d.jsx)(`p`,{className:`text-muted-foreground`,children:`No subnets found.`})]}),e[3]=c,e[4]=f),f}function p(e){if(!e.SubnetId)return null;let t=r(e.Tags);return(0,d.jsx)(l,{badge:(0,d.jsx)(c,{state:e.State}),params:{id:e.SubnetId},subtitle:`${e.CidrBlock??``} \u2022 ${e.AvailabilityZone??``}`,title:t?`${e.SubnetId} (${t})`:e.SubnetId,to:`/ec2/describe-subnets/$id`},e.SubnetId)}export{f as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{s as r}from"./utils.js";import"./useStore.js";import{t as i}from"./link.js";import{t as a}from"./button.js";import"./awsClient.js";import{h as o}from"./ec22.js";import{t as s}from"./page-heading.js";import{t as c}from"./state-badge.js";import{t as l}from"./list-card.js";var u=t(),d=e();function f(){let e=(0,u.c)(5),{data:t}=n(o),r;e[0]===t.Subnets?r=e[1]:(r=t.Subnets??[],e[0]=t.Subnets,e[1]=r);let c=r,l;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(l=(0,d.jsx)(s,{actions:(0,d.jsx)(i,{to:`/ec2/create-subnet`,children:(0,d.jsx)(a,{children:`Create Subnet`})}),title:`Subnets`}),e[2]=l):l=e[2];let f;return e[3]===c?f=e[4]:(f=(0,d.jsxs)(d.Fragment,{children:[l,c.length>0?(0,d.jsx)(`div`,{className:`space-y-4`,children:c.map(p)}):(0,d.jsx)(`p`,{className:`text-muted-foreground`,children:`No subnets found.`})]}),e[3]=c,e[4]=f),f}function p(e){if(!e.SubnetId)return null;let t=r(e.Tags);return(0,d.jsx)(l,{badge:(0,d.jsx)(c,{state:e.State}),params:{id:e.SubnetId},subtitle:`${e.CidrBlock??``} \u2022 ${e.AvailabilityZone??``}`,title:t?`${e.SubnetId} (${t})`:e.SubnetId,to:`/ec2/describe-subnets/$id`},e.SubnetId)}export{f as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import"./utils.js";import"./useStore.js";import{t as r}from"./link.js";import{t as i}from"./button.js";import"./awsClient.js";import{h as a}from"./ec22.js";import{t as o}from"./page-heading.js";import{t as s}from"./state-badge.js";import{t as c}from"./list-card.js";var l=t(),u=e();function d(){let e=(0,l.c)(5),{data:t}=n(a),s;e[0]===t.Volumes?s=e[1]:(s=t.Volumes??[],e[0]=t.Volumes,e[1]=s);let c=s,d;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(d=(0,u.jsx)(o,{actions:(0,u.jsx)(r,{to:`/ec2/create-volume`,children:(0,u.jsx)(i,{children:`Create Volume`})}),title:`EC2 Volumes`}),e[2]=d):d=e[2];let p;return e[3]===c?p=e[4]:(p=(0,u.jsxs)(u.Fragment,{children:[d,c.length>0?(0,u.jsx)(`div`,{className:`space-y-4`,children:c.map(f)}):(0,u.jsx)(`p`,{className:`text-muted-foreground`,children:`No volumes found.`})]}),e[3]=c,e[4]=p),p}function f(e){return e.VolumeId?(0,u.jsx)(c,{badge:(0,u.jsx)(s,{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{d as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import"./utils.js";import"./useStore.js";import{t as r}from"./link.js";import{t as i}from"./button.js";import"./awsClient.js";import{_ as a}from"./ec22.js";import{t as o}from"./page-heading.js";import{t as s}from"./state-badge.js";import{t as c}from"./list-card.js";var l=t(),u=e();function d(){let e=(0,l.c)(5),{data:t}=n(a),s;e[0]===t.Volumes?s=e[1]:(s=t.Volumes??[],e[0]=t.Volumes,e[1]=s);let c=s,d;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(d=(0,u.jsx)(o,{actions:(0,u.jsx)(r,{to:`/ec2/create-volume`,children:(0,u.jsx)(i,{children:`Create Volume`})}),title:`EC2 Volumes`}),e[2]=d):d=e[2];let p;return e[3]===c?p=e[4]:(p=(0,u.jsxs)(u.Fragment,{children:[d,c.length>0?(0,u.jsx)(`div`,{className:`space-y-4`,children:c.map(f)}):(0,u.jsx)(`p`,{className:`text-muted-foreground`,children:`No volumes found.`})]}),e[3]=c,e[4]=p),p}function f(e){return e.VolumeId?(0,u.jsx)(c,{badge:(0,u.jsx)(s,{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{d as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{s as r}from"./utils.js";import"./useStore.js";import{t as i}from"./link.js";import{t as a}from"./button.js";import"./awsClient.js";import{_ as o}from"./ec22.js";import{t as s}from"./page-heading.js";import{t as c}from"./state-badge.js";import{t as l}from"./list-card.js";var u=t(),d=e();function f(){let e=(0,u.c)(5),{data:t}=n(o),r;e[0]===t.Vpcs?r=e[1]:(r=t.Vpcs??[],e[0]=t.Vpcs,e[1]=r);let c=r,l;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(l=(0,d.jsx)(s,{actions:(0,d.jsx)(i,{to:`/ec2/create-vpc`,children:(0,d.jsx)(a,{children:`Create VPC`})}),title:`VPCs`}),e[2]=l):l=e[2];let f;return e[3]===c?f=e[4]:(f=(0,d.jsxs)(d.Fragment,{children:[l,c.length>0?(0,d.jsx)(`div`,{className:`space-y-4`,children:c.map(p)}):(0,d.jsx)(`p`,{className:`text-muted-foreground`,children:`No VPCs found.`})]}),e[3]=c,e[4]=f),f}function p(e){if(!e.VpcId)return null;let t=r(e.Tags);return(0,d.jsx)(l,{badge:(0,d.jsx)(c,{state:e.State}),params:{id:e.VpcId},subtitle:e.CidrBlock??``,title:t?`${e.VpcId} (${t})`:e.VpcId,to:`/ec2/describe-vpcs/$id`},e.VpcId)}export{f as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./useSuspenseQuery.js";import{s as r}from"./utils.js";import"./useStore.js";import{t as i}from"./link.js";import{t as a}from"./button.js";import"./awsClient.js";import{y as o}from"./ec22.js";import{t as s}from"./page-heading.js";import{t as c}from"./state-badge.js";import{t as l}from"./list-card.js";var u=t(),d=e();function f(){let e=(0,u.c)(5),{data:t}=n(o),r;e[0]===t.Vpcs?r=e[1]:(r=t.Vpcs??[],e[0]=t.Vpcs,e[1]=r);let c=r,l;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(l=(0,d.jsx)(s,{actions:(0,d.jsx)(i,{to:`/ec2/create-vpc`,children:(0,d.jsx)(a,{children:`Create VPC`})}),title:`VPCs`}),e[2]=l):l=e[2];let f;return e[3]===c?f=e[4]:(f=(0,d.jsxs)(d.Fragment,{children:[l,c.length>0?(0,d.jsx)(`div`,{className:`space-y-4`,children:c.map(p)}):(0,d.jsx)(`p`,{className:`text-muted-foreground`,children:`No VPCs found.`})]}),e[3]=c,e[4]=f),f}function p(e){if(!e.VpcId)return null;let t=r(e.Tags);return(0,d.jsx)(l,{badge:(0,d.jsx)(c,{state:e.State}),params:{id:e.VpcId},subtitle:e.CidrBlock??``,title:t?`${e.VpcId} (${t})`:e.VpcId,to:`/ec2/describe-vpcs/$id`},e.VpcId)}export{f as component};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
import{t as e}from"./queryOptions.js";import{$ as t,Dt as n,Et as r,at as i,ct as a,et as o,it as s,jt as c,n as l,ot as u,rt as d,st as f,tt as p}from"./awsClient.js";import{t as m}from"./DescribeInstancesCommand.js";var h=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeAvailabilityZones`,{}).n(`EC2Client`,`DescribeAvailabilityZonesCommand`).sc(t).build(){},g=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeImages`,{}).n(`EC2Client`,`DescribeImagesCommand`).sc(o).build(){},_=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeInstanceTypes`,{}).n(`EC2Client`,`DescribeInstanceTypesCommand`).sc(p).build(){},v=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeKeyPairs`,{}).n(`EC2Client`,`DescribeKeyPairsCommand`).sc(d).build(){},y=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeRegions`,{}).n(`EC2Client`,`DescribeRegionsCommand`).sc(s).build(){},b=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeSnapshots`,{}).n(`EC2Client`,`DescribeSnapshotsCommand`).sc(i).build(){},x=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeSubnets`,{}).n(`EC2Client`,`DescribeSubnetsCommand`).sc(u).build(){},S=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeVolumes`,{}).n(`EC2Client`,`DescribeVolumesCommand`).sc(f).build(){},C=class extends c.classBuilder().ep(r).m(function(e,t,r,i){return[n(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeVpcs`,{}).n(`EC2Client`,`DescribeVpcsCommand`).sc(a).build(){},w=e({queryKey:[`ec2`,`availabilityZones`],queryFn:()=>{let e=new h({});return l().send(e)},staleTime:3e5}),T=e({queryKey:[`ec2`,`instances`],queryFn:()=>{let e=new m({});return l().send(e)},refetchInterval:5e3}),E=t=>e({queryKey:[`ec2`,`instances`,t],queryFn:()=>{let e=new m({InstanceIds:[t]});return l().send(e)},refetchInterval:5e3}),D=e({queryKey:[`ec2`,`instances`,`types`],queryFn:()=>{let e=new _({Filters:[{Name:`capacity`,Values:[`true`]}]});return l().send(e)},refetchInterval:5e3}),O=e({queryKey:[`ec2`,`images`],queryFn:()=>{let e=new g({});return l().send(e)}}),k=t=>e({queryKey:[`ec2`,`images`,t??`none`],queryFn:()=>{if(!t)return{Images:[],$metadata:{}};let e=new g({ImageIds:[t]});return l().send(e)}}),A=e({queryKey:[`ec2`,`keypairs`],queryFn:()=>{let e=new v({});return l().send(e)}}),j=t=>e({queryKey:[`ec2`,`keypairs`,t],queryFn:()=>{let e=new v({KeyPairIds:[t]});return l().send(e)}}),M=e({queryKey:[`ec2`,`regions`],queryFn:()=>{let e=new y({});return l().send(e)},staleTime:3e5}),N=e({queryKey:[`ec2`,`subnets`],queryFn:()=>{let e=new x({});return l().send(e)}}),P=t=>e({queryKey:[`ec2`,`subnets`,t],queryFn:()=>{let e=new x({SubnetIds:[t]});return l().send(e)}}),F=e({queryKey:[`ec2`,`snapshots`],queryFn:()=>{let e=new b({});return l().send(e)},refetchInterval:5e3}),I=t=>e({queryKey:[`ec2`,`snapshots`,t],queryFn:()=>{let e=new b({SnapshotIds:[t]});return l().send(e)},refetchInterval:5e3}),L=e({queryKey:[`ec2`,`vpcs`],queryFn:()=>{let e=new C({});return l().send(e)}}),R=t=>e({queryKey:[`ec2`,`vpcs`,t],queryFn:()=>{let e=new C({VpcIds:[t]});return l().send(e)}}),z=e({queryKey:[`ec2`,`volumes`],queryFn:()=>{let e=new S({});return l().send(e)},refetchInterval:5e3}),B=t=>e({queryKey:[`ec2`,`volumes`,t],queryFn:()=>{let e=new S({VolumeIds:[t]});return l().send(e)},refetchInterval:5e3});export{L as _,D as a,A as c,F as d,P as f,R as g,z as h,E as i,M as l,B as m,k as n,T as o,N as p,O as r,j as s,w as t,I as u};
import{t as e}from"./queryOptions.js";import{At as t,Pt as n,at as r,ct as i,dt as a,kt as o,lt as s,n as c,nt as l,ot as u,rt as d,st as f,tt as p,ut as m}from"./awsClient.js";import{t as h}from"./DescribeInstancesCommand.js";var g=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeAvailabilityZones`,{}).n(`EC2Client`,`DescribeAvailabilityZonesCommand`).sc(p).build(){},_=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeImages`,{}).n(`EC2Client`,`DescribeImagesCommand`).sc(l).build(){},v=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeInstanceTypes`,{}).n(`EC2Client`,`DescribeInstanceTypesCommand`).sc(d).build(){},y=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeKeyPairs`,{}).n(`EC2Client`,`DescribeKeyPairsCommand`).sc(r).build(){},b=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribePlacementGroups`,{}).n(`EC2Client`,`DescribePlacementGroupsCommand`).sc(u).build(){},x=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeRegions`,{}).n(`EC2Client`,`DescribeRegionsCommand`).sc(f).build(){},S=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeSnapshots`,{}).n(`EC2Client`,`DescribeSnapshotsCommand`).sc(i).build(){},C=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeSubnets`,{}).n(`EC2Client`,`DescribeSubnetsCommand`).sc(s).build(){},w=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeVolumes`,{}).n(`EC2Client`,`DescribeVolumesCommand`).sc(m).build(){},T=class extends n.classBuilder().ep(o).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AmazonEC2`,`DescribeVpcs`,{}).n(`EC2Client`,`DescribeVpcsCommand`).sc(a).build(){},E=e({queryKey:[`ec2`,`availabilityZones`],queryFn:()=>{let e=new g({});return c().send(e)},staleTime:3e5}),D=e({queryKey:[`ec2`,`instances`],queryFn:()=>{let e=new h({});return c().send(e)},refetchInterval:5e3}),O=t=>e({queryKey:[`ec2`,`instances`,t],queryFn:()=>{let e=new h({InstanceIds:[t]});return c().send(e)},refetchInterval:5e3}),k=e({queryKey:[`ec2`,`instances`,`types`],queryFn:()=>{let e=new v({Filters:[{Name:`capacity`,Values:[`true`]}]});return c().send(e)},refetchInterval:5e3}),A=e({queryKey:[`ec2`,`images`],queryFn:()=>{let e=new _({});return c().send(e)}}),j=t=>e({queryKey:[`ec2`,`images`,t??`none`],queryFn:()=>{if(!t)return{Images:[],$metadata:{}};let e=new _({ImageIds:[t]});return c().send(e)}}),M=e({queryKey:[`ec2`,`keypairs`],queryFn:()=>{let e=new y({});return c().send(e)}}),N=t=>e({queryKey:[`ec2`,`keypairs`,t],queryFn:()=>{let e=new y({KeyPairIds:[t]});return c().send(e)}}),P=e({queryKey:[`ec2`,`regions`],queryFn:()=>{let e=new x({});return c().send(e)},staleTime:3e5}),F=e({queryKey:[`ec2`,`subnets`],queryFn:()=>{let e=new C({});return c().send(e)}}),I=t=>e({queryKey:[`ec2`,`subnets`,t],queryFn:()=>{let e=new C({SubnetIds:[t]});return c().send(e)}}),L=e({queryKey:[`ec2`,`snapshots`],queryFn:()=>{let e=new S({});return c().send(e)},refetchInterval:5e3}),R=t=>e({queryKey:[`ec2`,`snapshots`,t],queryFn:()=>{let e=new S({SnapshotIds:[t]});return c().send(e)},refetchInterval:5e3}),z=e({queryKey:[`ec2`,`vpcs`],queryFn:()=>{let e=new T({});return c().send(e)}}),B=t=>e({queryKey:[`ec2`,`vpcs`,t],queryFn:()=>{let e=new T({VpcIds:[t]});return c().send(e)}}),V=e({queryKey:[`ec2`,`placementGroups`],queryFn:()=>{let e=new b({});return c().send(e)}}),H=t=>e({queryKey:[`ec2`,`placementGroups`,t],queryFn:()=>{let e=new b({GroupIds:[t]});return c().send(e)}}),U=e({queryKey:[`ec2`,`volumes`],queryFn:()=>{let e=new w({});return c().send(e)},refetchInterval:5e3}),W=t=>e({queryKey:[`ec2`,`volumes`,t],queryFn:()=>{let e=new w({VolumeIds:[t]});return c().send(e)},refetchInterval:5e3});export{U as _,k as a,M as c,P as d,R as f,W as g,F as h,O as i,H as l,I as m,j as n,D as o,L as p,A as r,N as s,E as t,V as u,B as v,z as y};
+1 -1
View File
@@ -1 +1 @@
import{Jt as e,Qt as t,Xt as n,Yt as r,Zt as i}from"./awsClient.js";var a=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/,o=t().min(1,`Key name is required`).max(255,`Key name must be 255 characters or less`).regex(/^[\w\s._\-:/()#,@[\]+=&;{}!$*]+$/,`Key name contains invalid characters`),s=i({imageId:t(`Please select an Image`),instanceType:t(`Please select an instance type`),keyName:t(`Please select a key pair`),subnetId:t().optional(),count:r(`Instance count must be a whole number`).min(1,`Instance count must be at least 1`)}),c=i({keyName:o}),l=i({keyName:o,publicKeyMaterial:t().min(1,`Public key is required`).refine(e=>e.trim().length>0,`Public key cannot be empty`)}),u=i({size:n().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:t().min(1,`Availability zone is required`)}),d=i({size:n().int(`Size must be a whole number`).min(1,`Size must be at least 1 GiB`)}),f=i({volumeId:t().min(1,`Volume is required`),description:t().optional()});i({sourceSnapshotId:t().min(1,`Source snapshot is required`),sourceRegion:t().min(1,`Source region is required`),description:t().optional()}),i({volumeId:t().min(1,`Volume is required`),instanceId:t().min(1,`Instance is required`),device:t().optional()}),i({volumeId:t().min(1,`Volume is required`),instanceId:t().optional(),force:e().optional()}),i({instanceType:t().min(1,`Instance type is required`)}),i({name:t().min(1,`Name is required`),description:t().optional()});var p=i({vpcId:t().min(1,`VPC is required`),cidrBlock:t().min(1,`CIDR block is required`).regex(a,`Must be a valid CIDR block (e.g. 10.0.1.0/24)`),availabilityZone:t().optional()}),m=i({cidrBlock:t().min(1,`CIDR block is required`).regex(a,`Must be a valid CIDR block (e.g. 10.0.0.0/16)`),name:t().optional()});export{u as a,d as c,p as i,c as n,m as o,f as r,l as s,s as t};
import{$t as e,Qt as t,Zt as n,en as r,tn as i}from"./awsClient.js";var a=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/,o=i().min(1,`Key name is required`).max(255,`Key name must be 255 characters or less`).regex(/^[\w\s._\-:/()#,@[\]+=&;{}!$*]+$/,`Key name contains invalid characters`),s=r({imageId:i(`Please select an Image`),instanceType:i(`Please select an instance type`),keyName:i(`Please select a key pair`),subnetId:i().optional(),placementGroupName:i().optional(),count:t(`Instance count must be a whole number`).min(1,`Instance count must be at least 1`)}),c=r({keyName:o}),l=r({keyName:o,publicKeyMaterial:i().min(1,`Public key is required`).refine(e=>e.trim().length>0,`Public key cannot be empty`)}),u=r({size:e().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:i().min(1,`Availability zone is required`)}),d=r({size:e().int(`Size must be a whole number`).min(1,`Size must be at least 1 GiB`)}),f=r({volumeId:i().min(1,`Volume is required`),description:i().optional()});r({sourceSnapshotId:i().min(1,`Source snapshot is required`),sourceRegion:i().min(1,`Source region is required`),description:i().optional()}),r({volumeId:i().min(1,`Volume is required`),instanceId:i().min(1,`Instance is required`),device:i().optional()}),r({volumeId:i().min(1,`Volume is required`),instanceId:i().optional(),force:n().optional()}),r({instanceType:i().min(1,`Instance type is required`)}),r({name:i().min(1,`Name is required`),description:i().optional()});var p=r({vpcId:i().min(1,`VPC is required`),cidrBlock:i().min(1,`CIDR block is required`).regex(a,`Must be a valid CIDR block (e.g. 10.0.1.0/24)`),availabilityZone:i().optional()}),m=r({cidrBlock:i().min(1,`CIDR block is required`).regex(a,`Must be a valid CIDR block (e.g. 10.0.0.0/16)`),name:i().optional()}),h=r({groupName:i().min(1,`Group name is required`).max(255,`Group name must be 255 characters or less`),strategy:i().min(1,`Strategy is required`)});export{p as a,l as c,f as i,d as l,c as n,u as o,h as r,m as s,s as t};
@@ -1 +1 @@
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{D as i,E as a,M as o,c as s,g as c,n as l,x as u}from"./QueryClientProvider.js";import{M as d}from"./index.js";var f=class extends o{#e;#t=void 0;#n;#r;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#i()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),a(this.options,t)||this.#e.getMutationCache().notify({type:`observerOptionsUpdated`,mutation:this.#n,observer:this}),t?.mutationKey&&this.options.mutationKey&&c(t.mutationKey)!==c(this.options.mutationKey)?this.reset():this.#n?.state.status===`pending`&&this.#n.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#n?.removeObserver(this)}onMutationUpdate(e){this.#i(),this.#a(e)}getCurrentResult(){return this.#t}reset(){this.#n?.removeObserver(this),this.#n=void 0,this.#i(),this.#a()}mutate(e,t){return this.#r=t,this.#n?.removeObserver(this),this.#n=this.#e.getMutationCache().build(this.#e,this.options),this.#n.addObserver(this),this.#n.execute(e)}#i(){let e=this.#n?.state??d();this.#t={...e,isPending:e.status===`pending`,isSuccess:e.status===`success`,isError:e.status===`error`,isIdle:e.status===`idle`,mutate:this.mutate,reset:this.reset}}#a(e){s.batch(()=>{if(this.#r&&this.hasListeners()){let t=this.#t.variables,n=this.#t.context,r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type===`success`){try{this.#r.onSuccess?.(e.data,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(e.data,null,t,n,r)}catch(e){Promise.reject(e)}}else if(e?.type===`error`){try{this.#r.onError?.(e.error,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(void 0,e.error,t,n,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},p=e(n(),1);function m(e,t){let n=l(t),[r]=p.useState(()=>new f(n,e));p.useEffect(()=>{r.setOptions(e)},[r,e]);let a=p.useSyncExternalStore(p.useCallback(e=>r.subscribe(s.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),o=p.useCallback((e,t)=>{r.mutate(e,t).catch(u)},[r]);if(a.error&&i(r.options.throwOnError,[a.error]))throw a.error;return{...a,mutate:o,mutateAsync:a.mutate}}var h=r(),g=t();function _(e){let t=(0,h.c)(7),{msg:n,error:r}=e,i;r&&(i=r.name===`Error`?r.message:r.name);let a;t[0]===n?a=t[1]:(a=n&&(0,g.jsx)(`h2`,{className:`text-sm text-destructive`,children:n}),t[0]=n,t[1]=a);let o;t[2]===i?o=t[3]:(o=i&&(0,g.jsx)(`p`,{className:`text-sm text-destructive`,children:i}),t[2]=i,t[3]=o);let s;return t[4]!==a||t[5]!==o?(s=(0,g.jsxs)(`div`,{className:`mb-6 max-w-4xl rounded-md border border-destructive bg-destructive/10 p-4`,children:[a,o]}),t[4]=a,t[5]=o,t[6]=s):s=t[6],s}export{m as n,_ as t};
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{D as i,E as a,M as o,c as s,g as c,n as l,x as u}from"./QueryClientProvider.js";import{N as d}from"./index.js";var f=class extends o{#e;#t=void 0;#n;#r;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#i()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),a(this.options,t)||this.#e.getMutationCache().notify({type:`observerOptionsUpdated`,mutation:this.#n,observer:this}),t?.mutationKey&&this.options.mutationKey&&c(t.mutationKey)!==c(this.options.mutationKey)?this.reset():this.#n?.state.status===`pending`&&this.#n.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#n?.removeObserver(this)}onMutationUpdate(e){this.#i(),this.#a(e)}getCurrentResult(){return this.#t}reset(){this.#n?.removeObserver(this),this.#n=void 0,this.#i(),this.#a()}mutate(e,t){return this.#r=t,this.#n?.removeObserver(this),this.#n=this.#e.getMutationCache().build(this.#e,this.options),this.#n.addObserver(this),this.#n.execute(e)}#i(){let e=this.#n?.state??d();this.#t={...e,isPending:e.status===`pending`,isSuccess:e.status===`success`,isError:e.status===`error`,isIdle:e.status===`idle`,mutate:this.mutate,reset:this.reset}}#a(e){s.batch(()=>{if(this.#r&&this.hasListeners()){let t=this.#t.variables,n=this.#t.context,r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type===`success`){try{this.#r.onSuccess?.(e.data,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(e.data,null,t,n,r)}catch(e){Promise.reject(e)}}else if(e?.type===`error`){try{this.#r.onError?.(e.error,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(void 0,e.error,t,n,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},p=e(n(),1);function m(e,t){let n=l(t),[r]=p.useState(()=>new f(n,e));p.useEffect(()=>{r.setOptions(e)},[r,e]);let a=p.useSyncExternalStore(p.useCallback(e=>r.subscribe(s.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),o=p.useCallback((e,t)=>{r.mutate(e,t).catch(u)},[r]);if(a.error&&i(r.options.throwOnError,[a.error]))throw a.error;return{...a,mutate:o,mutateAsync:a.mutate}}var h=r(),g=t();function _(e){let t=(0,h.c)(7),{msg:n,error:r}=e,i;r&&(i=r.name===`Error`?r.message:r.name);let a;t[0]===n?a=t[1]:(a=n&&(0,g.jsx)(`h2`,{className:`text-sm text-destructive`,children:n}),t[0]=n,t[1]=a);let o;t[2]===i?o=t[3]:(o=i&&(0,g.jsx)(`p`,{className:`text-sm text-destructive`,children:i}),t[2]=i,t[3]=o);let s;return t[4]!==a||t[5]!==o?(s=(0,g.jsxs)(`div`,{className:`mb-6 max-w-4xl rounded-md border border-destructive bg-destructive/10 p-4`,children:[a,o]}),t[4]=a,t[5]=o,t[6]=s):s=t[6],s}export{m as n,_ as t};
+1 -1
View File
@@ -1 +1 @@
import{t as e}from"./compiler-runtime.js";import{n as t}from"./QueryClientProvider.js";import{n}from"./error-banner.js";import{A as r,C as i,D as a,Dt as o,E as s,O as c,R as l,T as u,jt as d,k as f,r as p,w as m,z as h}from"./awsClient.js";var g=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`AttachUserPolicy`,{}).n(`IAMClient`,`AttachUserPolicyCommand`).sc(i).build(){},_=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`CreateAccessKey`,{}).n(`IAMClient`,`CreateAccessKeyCommand`).sc(m).build(){},v=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`CreatePolicy`,{}).n(`IAMClient`,`CreatePolicyCommand`).sc(u).build(){},y=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`CreateUser`,{}).n(`IAMClient`,`CreateUserCommand`).sc(s).build(){},b=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`DeleteAccessKey`,{}).n(`IAMClient`,`DeleteAccessKeyCommand`).sc(a).build(){},x=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`DeletePolicy`,{}).n(`IAMClient`,`DeletePolicyCommand`).sc(c).build(){},S=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`DeleteUser`,{}).n(`IAMClient`,`DeleteUserCommand`).sc(f).build(){},C=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`DetachUserPolicy`,{}).n(`IAMClient`,`DetachUserPolicyCommand`).sc(r).build(){},w=class extends d.classBuilder().ep(h).m(function(e,t,n,r){return[o(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`UpdateAccessKey`,{}).n(`IAMClient`,`UpdateAccessKeyCommand`).sc(l).build(){},T=e();function E(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:D,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`users`]})}},e[0]=r,e[1]=i),n(i)}function D(e){let t=new y({UserName:e.userName,Path:e.path||void 0});return p().send(t)}function O(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:k,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`users`]})}},e[0]=r,e[1]=i),n(i)}function k(e){let t=new S({UserName:e});return p().send(t)}function A(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:j,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`access-keys`]})}},e[0]=r,e[1]=i),n(i)}function j(e){let t=new _({UserName:e});return p().send(t)}function M(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:N,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`access-keys`]})}},e[0]=r,e[1]=i),n(i)}function N(e){let{userName:t,accessKeyId:n}=e,r=new b({UserName:t,AccessKeyId:n});return p().send(r)}function P(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:F,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`access-keys`]})}},e[0]=r,e[1]=i),n(i)}function F(e){let{userName:t,accessKeyId:n,status:r}=e,i=new w({UserName:t,AccessKeyId:n,Status:r});return p().send(i)}function I(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:L,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`policies`]})}},e[0]=r,e[1]=i),n(i)}function L(e){let t=new v({PolicyName:e.policyName,Description:e.description||void 0,PolicyDocument:e.policyDocument});return p().send(t)}function R(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:z,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`policies`]})}},e[0]=r,e[1]=i),n(i)}function z(e){let t=new x({PolicyArn:e});return p().send(t)}function B(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:V,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`attached-user-policies`]})}},e[0]=r,e[1]=i),n(i)}function V(e){let{userName:t,policyArn:n}=e,r=new g({UserName:t,PolicyArn:n});return p().send(r)}function H(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:U,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`attached-user-policies`]})}},e[0]=r,e[1]=i),n(i)}function U(e){let{userName:t,policyArn:n}=e,r=new C({UserName:t,PolicyArn:n});return p().send(r)}export{M as a,H as c,E as i,P as l,A as n,R as o,I as r,O as s,B as t};
import{t as e}from"./compiler-runtime.js";import{n as t}from"./QueryClientProvider.js";import{n}from"./error-banner.js";import{A as r,At as i,C as a,D as o,E as s,O as c,Pt as l,R as u,T as d,k as f,r as p,w as m,z as h}from"./awsClient.js";var g=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`AttachUserPolicy`,{}).n(`IAMClient`,`AttachUserPolicyCommand`).sc(a).build(){},_=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`CreateAccessKey`,{}).n(`IAMClient`,`CreateAccessKeyCommand`).sc(m).build(){},v=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`CreatePolicy`,{}).n(`IAMClient`,`CreatePolicyCommand`).sc(d).build(){},y=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`CreateUser`,{}).n(`IAMClient`,`CreateUserCommand`).sc(s).build(){},b=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`DeleteAccessKey`,{}).n(`IAMClient`,`DeleteAccessKeyCommand`).sc(o).build(){},x=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`DeletePolicy`,{}).n(`IAMClient`,`DeletePolicyCommand`).sc(c).build(){},S=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`DeleteUser`,{}).n(`IAMClient`,`DeleteUserCommand`).sc(f).build(){},C=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`DetachUserPolicy`,{}).n(`IAMClient`,`DetachUserPolicyCommand`).sc(r).build(){},w=class extends l.classBuilder().ep(h).m(function(e,t,n,r){return[i(n,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`UpdateAccessKey`,{}).n(`IAMClient`,`UpdateAccessKeyCommand`).sc(u).build(){},T=e();function E(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:D,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`users`]})}},e[0]=r,e[1]=i),n(i)}function D(e){let t=new y({UserName:e.userName,Path:e.path||void 0});return p().send(t)}function O(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:k,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`users`]})}},e[0]=r,e[1]=i),n(i)}function k(e){let t=new S({UserName:e});return p().send(t)}function A(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:j,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`access-keys`]})}},e[0]=r,e[1]=i),n(i)}function j(e){let t=new _({UserName:e});return p().send(t)}function M(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:N,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`access-keys`]})}},e[0]=r,e[1]=i),n(i)}function N(e){let{userName:t,accessKeyId:n}=e,r=new b({UserName:t,AccessKeyId:n});return p().send(r)}function P(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:F,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`access-keys`]})}},e[0]=r,e[1]=i),n(i)}function F(e){let{userName:t,accessKeyId:n,status:r}=e,i=new w({UserName:t,AccessKeyId:n,Status:r});return p().send(i)}function I(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:L,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`policies`]})}},e[0]=r,e[1]=i),n(i)}function L(e){let t=new v({PolicyName:e.policyName,Description:e.description||void 0,PolicyDocument:e.policyDocument});return p().send(t)}function R(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:z,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`policies`]})}},e[0]=r,e[1]=i),n(i)}function z(e){let t=new x({PolicyArn:e});return p().send(t)}function B(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:V,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`attached-user-policies`]})}},e[0]=r,e[1]=i),n(i)}function V(e){let{userName:t,policyArn:n}=e,r=new g({UserName:t,PolicyArn:n});return p().send(r)}function H(){let e=(0,T.c)(2),r=t(),i;return e[0]===r?i=e[1]:(i={mutationFn:U,onSuccess:()=>{r.invalidateQueries({queryKey:[`iam`,`attached-user-policies`]})}},e[0]=r,e[1]=i),n(i)}function U(e){let{userName:t,policyArn:n}=e,r=new C({UserName:t,PolicyArn:n});return p().send(r)}export{M as a,H as c,E as i,P as l,A as n,R as o,I as r,O as s,B as t};
+1 -1
View File
@@ -1 +1 @@
import{t as e}from"./queryOptions.js";import{Dt as t,F as n,I as r,L as i,M as a,N as o,P as s,j as c,jt as l,r as u,z as d}from"./awsClient.js";var f=class extends l.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`GetPolicy`,{}).n(`IAMClient`,`GetPolicyCommand`).sc(c).build(){},p=class extends l.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`GetPolicyVersion`,{}).n(`IAMClient`,`GetPolicyVersionCommand`).sc(a).build(){},m=class extends l.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`GetUser`,{}).n(`IAMClient`,`GetUserCommand`).sc(o).build(){},h=class extends l.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`ListAccessKeys`,{}).n(`IAMClient`,`ListAccessKeysCommand`).sc(s).build(){},g=class extends l.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`ListAttachedUserPolicies`,{}).n(`IAMClient`,`ListAttachedUserPoliciesCommand`).sc(n).build(){},_=class extends l.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`ListPolicies`,{}).n(`IAMClient`,`ListPoliciesCommand`).sc(r).build(){},v=class extends l.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`ListUsers`,{}).n(`IAMClient`,`ListUsersCommand`).sc(i).build(){},y=e({queryKey:[`iam`,`users`],queryFn:()=>{let e=new v({});return u().send(e)},staleTime:3e5}),b=t=>e({queryKey:[`iam`,`users`,t],queryFn:()=>{let e=new m({UserName:t});return u().send(e)},staleTime:3e5}),x=t=>e({queryKey:[`iam`,`access-keys`,t],queryFn:()=>{let e=new h({UserName:t});return u().send(e)},staleTime:3e5}),S=e({queryKey:[`iam`,`policies`],queryFn:()=>{let e=new _({Scope:`Local`});return u().send(e)},staleTime:3e5}),C=t=>e({queryKey:[`iam`,`policies`,t],queryFn:()=>{let e=new f({PolicyArn:t});return u().send(e)},staleTime:3e5}),w=(t,n)=>e({queryKey:[`iam`,`policy-versions`,t,n],queryFn:()=>{let e=new p({PolicyArn:t,VersionId:n});return u().send(e)},staleTime:3e5}),T=t=>e({queryKey:[`iam`,`attached-user-policies`,t],queryFn:()=>{let e=new g({UserName:t});return u().send(e)},staleTime:3e5});export{w as a,C as i,T as n,b as o,S as r,y as s,x as t};
import{t as e}from"./queryOptions.js";import{At as t,F as n,I as r,L as i,M as a,N as o,P as s,Pt as c,j as l,r as u,z as d}from"./awsClient.js";var f=class extends c.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`GetPolicy`,{}).n(`IAMClient`,`GetPolicyCommand`).sc(l).build(){},p=class extends c.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`GetPolicyVersion`,{}).n(`IAMClient`,`GetPolicyVersionCommand`).sc(a).build(){},m=class extends c.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`GetUser`,{}).n(`IAMClient`,`GetUserCommand`).sc(o).build(){},h=class extends c.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`ListAccessKeys`,{}).n(`IAMClient`,`ListAccessKeysCommand`).sc(s).build(){},g=class extends c.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`ListAttachedUserPolicies`,{}).n(`IAMClient`,`ListAttachedUserPoliciesCommand`).sc(n).build(){},_=class extends c.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`ListPolicies`,{}).n(`IAMClient`,`ListPoliciesCommand`).sc(r).build(){},v=class extends c.classBuilder().ep(d).m(function(e,n,r,i){return[t(r,e.getEndpointParameterInstructions())]}).s(`AWSIdentityManagementV20100508`,`ListUsers`,{}).n(`IAMClient`,`ListUsersCommand`).sc(i).build(){},y=e({queryKey:[`iam`,`users`],queryFn:()=>{let e=new v({});return u().send(e)},staleTime:3e5}),b=t=>e({queryKey:[`iam`,`users`,t],queryFn:()=>{let e=new m({UserName:t});return u().send(e)},staleTime:3e5}),x=t=>e({queryKey:[`iam`,`access-keys`,t],queryFn:()=>{let e=new h({UserName:t});return u().send(e)},staleTime:3e5}),S=e({queryKey:[`iam`,`policies`],queryFn:()=>{let e=new _({Scope:`Local`});return u().send(e)},staleTime:3e5}),C=t=>e({queryKey:[`iam`,`policies`,t],queryFn:()=>{let e=new f({PolicyArn:t});return u().send(e)},staleTime:3e5}),w=(t,n)=>e({queryKey:[`iam`,`policy-versions`,t,n],queryFn:()=>{let e=new p({PolicyArn:t,VersionId:n});return u().send(e)},staleTime:3e5}),T=t=>e({queryKey:[`iam`,`attached-user-policies`,t],queryFn:()=>{let e=new g({UserName:t});return u().send(e)},staleTime:3e5});export{w as a,C as i,T as n,b as o,S as r,y as s,x as t};
+1 -1
View File
@@ -1 +1 @@
import{Qt as e,Zt as t}from"./awsClient.js";var n=t({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()}),r=t({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(e=>{try{return JSON.parse(e),!0}catch{return!1}},{message:`Policy document must be valid JSON`})});export{n,r as t};
import{en as e,tn as t}from"./awsClient.js";var n=e({userName:t().min(1,`User name is required`).max(64,`User name must be 64 characters or less`).regex(/^[\w+=,.@-]+$/,`User name contains invalid characters`),path:t().optional()}),r=e({policyName:t().min(1,`Policy name is required`).max(128,`Policy name must be 128 characters or less`),description:t().optional(),policyDocument:t().min(1,`Policy document is required`).refine(e=>{try{return JSON.parse(e),!0}catch{return!1}},{message:`Policy document must be valid JSON`})});export{n,r as t};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{_ as a}from"./ec2.js";import"./ec22.js";import{k as o}from"./index.js";import{t as s}from"./page-heading.js";import{i as c,n as l,t as u}from"./field.js";import{t as d}from"./textarea.js";import{r as f,t as p}from"./zod.js";import{s as m}from"./ec23.js";import{t as h}from"./form-actions.js";var g=t(),_=e();function v(){let e=(0,g.c)(48),t=r(),v=a(),y;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(y={resolver:p(m)},e[0]=y):y=e[0];let{handleSubmit:b,register:x,formState:S}=f(y),{errors:C,isSubmitting:w}=S,T;e[1]!==v||e[2]!==t?(T=async e=>{await v.mutateAsync(e),t({to:`/ec2/describe-key-pairs`})},e[1]=v,e[2]=t,e[3]=T):T=e[3];let E=T,D,O;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(D=(0,_.jsx)(i,{to:`/ec2/describe-key-pairs`,children:`Back to key pairs`}),O=(0,_.jsx)(s,{title:`Import Key Pair`}),e[4]=D,e[5]=O):(D=e[4],O=e[5]);let k;e[6]===v.error?k=e[7]:(k=v.error&&(0,_.jsx)(n,{error:v.error,msg:`Failed to import key pair`}),e[6]=v.error,e[7]=k);let A;e[8]!==b||e[9]!==E?(A=b(E),e[8]=b,e[9]=E,e[10]=A):A=e[10];let j;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(j=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`keyName`,children:`Key Pair Name`})}),e[11]=j):j=e[11];let M=!!C.keyName,N;e[12]===x?N=e[13]:(N=x(`keyName`),e[12]=x,e[13]=N);let P;e[14]!==M||e[15]!==N?(P=(0,_.jsx)(o,{"aria-invalid":M,id:`keyName`,placeholder:`my-key-pair…`,...N}),e[14]=M,e[15]=N,e[16]=P):P=e[16];let F;e[17]===C.keyName?F=e[18]:(F=(0,_.jsx)(l,{errors:[C.keyName]}),e[17]=C.keyName,e[18]=F);let I;e[19]!==P||e[20]!==F?(I=(0,_.jsxs)(u,{children:[j,P,F]}),e[19]=P,e[20]=F,e[21]=I):I=e[21];let L,R;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(L=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`publicKeyMaterial`,children:`Public Key`})}),R=(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground`,id:`publicKey-description`,children:`Paste your OpenSSH public key (e.g. the contents of id_rsa.pub)`}),e[22]=L,e[23]=R):(L=e[22],R=e[23]);let z=!!C.publicKeyMaterial,B;e[24]===x?B=e[25]:(B=x(`publicKeyMaterial`),e[24]=x,e[25]=B);let V;e[26]!==z||e[27]!==B?(V=(0,_.jsx)(d,{"aria-invalid":z,id:`publicKeyMaterial`,placeholder:`ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC…`,rows:8,...B}),e[26]=z,e[27]=B,e[28]=V):V=e[28];let H;e[29]===C.publicKeyMaterial?H=e[30]:(H=(0,_.jsx)(l,{errors:[C.publicKeyMaterial]}),e[29]=C.publicKeyMaterial,e[30]=H);let U;e[31]!==V||e[32]!==H?(U=(0,_.jsxs)(u,{children:[L,R,V,H]}),e[31]=V,e[32]=H,e[33]=U):U=e[33];let W;e[34]===t?W=e[35]:(W=()=>t({to:`/ec2/describe-key-pairs`}),e[34]=t,e[35]=W);let G;e[36]!==v.isPending||e[37]!==w||e[38]!==W?(G=(0,_.jsx)(h,{isPending:v.isPending,isSubmitting:w,onCancel:W,pendingLabel:`Importing…`,submitLabel:`Import Key Pair`}),e[36]=v.isPending,e[37]=w,e[38]=W,e[39]=G):G=e[39];let K;e[40]!==I||e[41]!==U||e[42]!==G||e[43]!==A?(K=(0,_.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:A,children:[I,U,G]}),e[40]=I,e[41]=U,e[42]=G,e[43]=A,e[44]=K):K=e[44];let q;return e[45]!==K||e[46]!==k?(q=(0,_.jsxs)(_.Fragment,{children:[D,O,k,K]}),e[45]=K,e[46]=k,e[47]=q):q=e[47],q}export{v as component};
import{n as e,t}from"./compiler-runtime.js";import{t as n}from"./error-banner.js";import"./utils.js";import"./useStore.js";import{t as r}from"./useNavigate.js";import"./button.js";import{t as i}from"./back-link.js";import"./separator.js";import"./awsClient.js";import{y as a}from"./ec2.js";import"./ec22.js";import{A as o}from"./index.js";import{t as s}from"./page-heading.js";import{i as c,n as l,t as u}from"./field.js";import{t as d}from"./textarea.js";import{r as f,t as p}from"./zod.js";import{c as m}from"./ec23.js";import{t as h}from"./form-actions.js";var g=t(),_=e();function v(){let e=(0,g.c)(48),t=r(),v=a(),y;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(y={resolver:p(m)},e[0]=y):y=e[0];let{handleSubmit:b,register:x,formState:S}=f(y),{errors:C,isSubmitting:w}=S,T;e[1]!==v||e[2]!==t?(T=async e=>{await v.mutateAsync(e),t({to:`/ec2/describe-key-pairs`})},e[1]=v,e[2]=t,e[3]=T):T=e[3];let E=T,D,O;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(D=(0,_.jsx)(i,{to:`/ec2/describe-key-pairs`,children:`Back to key pairs`}),O=(0,_.jsx)(s,{title:`Import Key Pair`}),e[4]=D,e[5]=O):(D=e[4],O=e[5]);let k;e[6]===v.error?k=e[7]:(k=v.error&&(0,_.jsx)(n,{error:v.error,msg:`Failed to import key pair`}),e[6]=v.error,e[7]=k);let A;e[8]!==b||e[9]!==E?(A=b(E),e[8]=b,e[9]=E,e[10]=A):A=e[10];let j;e[11]===Symbol.for(`react.memo_cache_sentinel`)?(j=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`keyName`,children:`Key Pair Name`})}),e[11]=j):j=e[11];let M=!!C.keyName,N;e[12]===x?N=e[13]:(N=x(`keyName`),e[12]=x,e[13]=N);let P;e[14]!==M||e[15]!==N?(P=(0,_.jsx)(o,{"aria-invalid":M,id:`keyName`,placeholder:`my-key-pair…`,...N}),e[14]=M,e[15]=N,e[16]=P):P=e[16];let F;e[17]===C.keyName?F=e[18]:(F=(0,_.jsx)(l,{errors:[C.keyName]}),e[17]=C.keyName,e[18]=F);let I;e[19]!==P||e[20]!==F?(I=(0,_.jsxs)(u,{children:[j,P,F]}),e[19]=P,e[20]=F,e[21]=I):I=e[21];let L,R;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(L=(0,_.jsx)(c,{children:(0,_.jsx)(`label`,{htmlFor:`publicKeyMaterial`,children:`Public Key`})}),R=(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground`,id:`publicKey-description`,children:`Paste your OpenSSH public key (e.g. the contents of id_rsa.pub)`}),e[22]=L,e[23]=R):(L=e[22],R=e[23]);let z=!!C.publicKeyMaterial,B;e[24]===x?B=e[25]:(B=x(`publicKeyMaterial`),e[24]=x,e[25]=B);let V;e[26]!==z||e[27]!==B?(V=(0,_.jsx)(d,{"aria-invalid":z,id:`publicKeyMaterial`,placeholder:`ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC…`,rows:8,...B}),e[26]=z,e[27]=B,e[28]=V):V=e[28];let H;e[29]===C.publicKeyMaterial?H=e[30]:(H=(0,_.jsx)(l,{errors:[C.publicKeyMaterial]}),e[29]=C.publicKeyMaterial,e[30]=H);let U;e[31]!==V||e[32]!==H?(U=(0,_.jsxs)(u,{children:[L,R,V,H]}),e[31]=V,e[32]=H,e[33]=U):U=e[33];let W;e[34]===t?W=e[35]:(W=()=>t({to:`/ec2/describe-key-pairs`}),e[34]=t,e[35]=W);let G;e[36]!==v.isPending||e[37]!==w||e[38]!==W?(G=(0,_.jsx)(h,{isPending:v.isPending,isSubmitting:w,onCancel:W,pendingLabel:`Importing…`,submitLabel:`Import Key Pair`}),e[36]=v.isPending,e[37]=w,e[38]=W,e[39]=G):G=e[39];let K;e[40]!==I||e[41]!==U||e[42]!==G||e[43]!==A?(K=(0,_.jsxs)(`form`,{className:`max-w-4xl space-y-6`,onSubmit:A,children:[I,U,G]}),e[40]=I,e[41]=U,e[42]=G,e[43]=A,e[44]=K):K=e[44];let q;return e[45]!==K||e[46]!==k?(q=(0,_.jsxs)(_.Fragment,{children:[D,O,k,K]}),e[45]=K,e[46]=k,e[47]=q):q=e[47],q}export{v as component};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{n as i}from"./utils.js";import{t as a}from"./useNavigate.js";import{t as o}from"./button.js";import"./separator.js";import{Gt as s,Wt as c,n as l,qt as u,t as d}from"./awsClient.js";import{t as f}from"./DescribeInstancesCommand.js";import{k as p}from"./index.js";import{i as m,n as h,r as g,t as _}from"./field.js";import{r as v,t as y}from"./zod.js";var b=e(n()),x=r(),S=t();function C(e){let t=(0,x.c)(10),n,r,a;t[0]===e?(n=t[1],r=t[2],a=t[3]):({className:n,size:a,...r}=e,t[0]=e,t[1]=n,t[2]=r,t[3]=a);let o=a===void 0?`default`:a,s;t[4]===n?s=t[5]:(s=i(`group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-xs/relaxed text-card-foreground 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`,n),t[4]=n,t[5]=s);let c;return t[6]!==r||t[7]!==o||t[8]!==s?(c=(0,S.jsx)(`div`,{className:s,"data-size":o,"data-slot":`card`,...r}),t[6]=r,t[7]=o,t[8]=s,t[9]=c):c=t[9],c}function w(e){let t=(0,x.c)(8),n,r;t[0]===e?(n=t[1],r=t[2]):({className:n,...r}=e,t[0]=e,t[1]=n,t[2]=r);let a;t[3]===n?a=t[4]:(a=i(`group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3`,n),t[3]=n,t[4]=a);let o;return t[5]!==r||t[6]!==a?(o=(0,S.jsx)(`div`,{className:a,"data-slot":`card-header`,...r}),t[5]=r,t[6]=a,t[7]=o):o=t[7],o}function T(e){let t=(0,x.c)(8),n,r;t[0]===e?(n=t[1],r=t[2]):({className:n,...r}=e,t[0]=e,t[1]=n,t[2]=r);let a;t[3]===n?a=t[4]:(a=i(`text-sm font-medium`,n),t[3]=n,t[4]=a);let o;return t[5]!==r||t[6]!==a?(o=(0,S.jsx)(`div`,{className:a,"data-slot":`card-title`,...r}),t[5]=r,t[6]=a,t[7]=o):o=t[7],o}function E(e){let t=(0,x.c)(8),n,r;t[0]===e?(n=t[1],r=t[2]):({className:n,...r}=e,t[0]=e,t[1]=n,t[2]=r);let a;t[3]===n?a=t[4]:(a=i(`px-4 group-data-[size=sm]/card:px-3`,n),t[3]=n,t[4]=a);let o;return t[5]!==r||t[6]!==a?(o=(0,S.jsx)(`div`,{className:a,"data-slot":`card-content`,...r}),t[5]=r,t[6]=a,t[7]=o):o=t[7],o}function D(){let e=(0,x.c)(44),t=a(),[n,r]=(0,b.useState)(null),i;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(i={resolver:y(c)},e[0]=i):i=e[0];let{register:D,handleSubmit:O,formState:k}=v(i),{errors:A,isSubmitting:j}=k,M;e[1]===t?M=e[2]:(M=async function(e){r(null),d(),u(e);try{await l().send(new f({})),t({to:`/`})}catch{s(),d(),r(`Invalid credentials. Please check your Access Key ID and Secret Access Key.`)}},e[1]=t,e[2]=M);let N=M,P;e[3]===Symbol.for(`react.memo_cache_sentinel`)?(P=(0,S.jsx)(w,{children:(0,S.jsx)(T,{children:`AWS Credentials`})}),e[3]=P):P=e[3];let F;e[4]===n?F=e[5]:(F=n&&(0,S.jsx)(`p`,{className:`mb-4 rounded-md bg-destructive/10 p-3 text-sm text-destructive`,children:n}),e[4]=n,e[5]=F);let I;e[6]!==O||e[7]!==N?(I=O(N),e[6]=O,e[7]=N,e[8]=I):I=e[8];let L;e[9]===Symbol.for(`react.memo_cache_sentinel`)?(L=(0,S.jsx)(m,{children:(0,S.jsx)(`label`,{htmlFor:`accessKeyId`,children:`Access Key ID`})}),e[9]=L):L=e[9];let R=!!A.accessKeyId,z;e[10]===D?z=e[11]:(z=D(`accessKeyId`),e[10]=D,e[11]=z);let B;e[12]!==R||e[13]!==z?(B=(0,S.jsx)(p,{"aria-invalid":R,autoComplete:`username`,id:`accessKeyId`,placeholder:`AKIAIOSFODNN7EXAMPLE`,...z}),e[12]=R,e[13]=z,e[14]=B):B=e[14];let V;e[15]===A.accessKeyId?V=e[16]:(V=(0,S.jsx)(h,{errors:[A.accessKeyId]}),e[15]=A.accessKeyId,e[16]=V);let H;e[17]!==V||e[18]!==B?(H=(0,S.jsxs)(_,{children:[L,B,V]}),e[17]=V,e[18]=B,e[19]=H):H=e[19];let U;e[20]===Symbol.for(`react.memo_cache_sentinel`)?(U=(0,S.jsx)(m,{children:(0,S.jsx)(`label`,{htmlFor:`secretAccessKey`,children:`Secret Access Key`})}),e[20]=U):U=e[20];let W=!!A.secretAccessKey,G;e[21]===D?G=e[22]:(G=D(`secretAccessKey`),e[21]=D,e[22]=G);let K;e[23]!==W||e[24]!==G?(K=(0,S.jsx)(p,{"aria-invalid":W,autoComplete:`current-password`,id:`secretAccessKey`,placeholder:`wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`,type:`password`,...G}),e[23]=W,e[24]=G,e[25]=K):K=e[25];let q;e[26]===A.secretAccessKey?q=e[27]:(q=(0,S.jsx)(h,{errors:[A.secretAccessKey]}),e[26]=A.secretAccessKey,e[27]=q);let J;e[28]!==K||e[29]!==q?(J=(0,S.jsxs)(_,{children:[U,K,q]}),e[28]=K,e[29]=q,e[30]=J):J=e[30];let Y=j?`Signing in...`:`Sign In`,X;e[31]!==j||e[32]!==Y?(X=(0,S.jsx)(o,{className:`w-full`,disabled:j,type:`submit`,children:Y}),e[31]=j,e[32]=Y,e[33]=X):X=e[33];let Z;e[34]!==H||e[35]!==J||e[36]!==X?(Z=(0,S.jsxs)(g,{children:[H,J,X]}),e[34]=H,e[35]=J,e[36]=X,e[37]=Z):Z=e[37];let Q;e[38]!==Z||e[39]!==I?(Q=(0,S.jsx)(`form`,{onSubmit:I,children:Z}),e[38]=Z,e[39]=I,e[40]=Q):Q=e[40];let $;return e[41]!==Q||e[42]!==F?($=(0,S.jsx)(`div`,{className:`flex flex-1 items-center justify-center`,children:(0,S.jsxs)(C,{className:`w-full max-w-sm`,children:[P,(0,S.jsxs)(E,{children:[F,Q]})]})}),e[41]=Q,e[42]=F,e[43]=$):$=e[43],$}export{D as component};
import{a as e,n as t,r as n,t as r}from"./compiler-runtime.js";import{n as i}from"./utils.js";import{t as a}from"./useNavigate.js";import{t as o}from"./button.js";import"./separator.js";import{Jt as s,Xt as c,n as l,qt as u,t as d}from"./awsClient.js";import{t as f}from"./DescribeInstancesCommand.js";import{A as p}from"./index.js";import{i as m,n as h,r as g,t as _}from"./field.js";import{r as v,t as y}from"./zod.js";var b=e(n()),x=r(),S=t();function C(e){let t=(0,x.c)(10),n,r,a;t[0]===e?(n=t[1],r=t[2],a=t[3]):({className:n,size:a,...r}=e,t[0]=e,t[1]=n,t[2]=r,t[3]=a);let o=a===void 0?`default`:a,s;t[4]===n?s=t[5]:(s=i(`group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-xs/relaxed text-card-foreground 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`,n),t[4]=n,t[5]=s);let c;return t[6]!==r||t[7]!==o||t[8]!==s?(c=(0,S.jsx)(`div`,{className:s,"data-size":o,"data-slot":`card`,...r}),t[6]=r,t[7]=o,t[8]=s,t[9]=c):c=t[9],c}function w(e){let t=(0,x.c)(8),n,r;t[0]===e?(n=t[1],r=t[2]):({className:n,...r}=e,t[0]=e,t[1]=n,t[2]=r);let a;t[3]===n?a=t[4]:(a=i(`group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3`,n),t[3]=n,t[4]=a);let o;return t[5]!==r||t[6]!==a?(o=(0,S.jsx)(`div`,{className:a,"data-slot":`card-header`,...r}),t[5]=r,t[6]=a,t[7]=o):o=t[7],o}function T(e){let t=(0,x.c)(8),n,r;t[0]===e?(n=t[1],r=t[2]):({className:n,...r}=e,t[0]=e,t[1]=n,t[2]=r);let a;t[3]===n?a=t[4]:(a=i(`text-sm font-medium`,n),t[3]=n,t[4]=a);let o;return t[5]!==r||t[6]!==a?(o=(0,S.jsx)(`div`,{className:a,"data-slot":`card-title`,...r}),t[5]=r,t[6]=a,t[7]=o):o=t[7],o}function E(e){let t=(0,x.c)(8),n,r;t[0]===e?(n=t[1],r=t[2]):({className:n,...r}=e,t[0]=e,t[1]=n,t[2]=r);let a;t[3]===n?a=t[4]:(a=i(`px-4 group-data-[size=sm]/card:px-3`,n),t[3]=n,t[4]=a);let o;return t[5]!==r||t[6]!==a?(o=(0,S.jsx)(`div`,{className:a,"data-slot":`card-content`,...r}),t[5]=r,t[6]=a,t[7]=o):o=t[7],o}function D(){let e=(0,x.c)(44),t=a(),[n,r]=(0,b.useState)(null),i;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(i={resolver:y(u)},e[0]=i):i=e[0];let{register:D,handleSubmit:O,formState:k}=v(i),{errors:A,isSubmitting:j}=k,M;e[1]===t?M=e[2]:(M=async function(e){r(null),d(),c(e);try{await l().send(new f({})),t({to:`/`})}catch{s(),d(),r(`Invalid credentials. Please check your Access Key ID and Secret Access Key.`)}},e[1]=t,e[2]=M);let N=M,P;e[3]===Symbol.for(`react.memo_cache_sentinel`)?(P=(0,S.jsx)(w,{children:(0,S.jsx)(T,{children:`AWS Credentials`})}),e[3]=P):P=e[3];let F;e[4]===n?F=e[5]:(F=n&&(0,S.jsx)(`p`,{className:`mb-4 rounded-md bg-destructive/10 p-3 text-sm text-destructive`,children:n}),e[4]=n,e[5]=F);let I;e[6]!==O||e[7]!==N?(I=O(N),e[6]=O,e[7]=N,e[8]=I):I=e[8];let L;e[9]===Symbol.for(`react.memo_cache_sentinel`)?(L=(0,S.jsx)(m,{children:(0,S.jsx)(`label`,{htmlFor:`accessKeyId`,children:`Access Key ID`})}),e[9]=L):L=e[9];let R=!!A.accessKeyId,z;e[10]===D?z=e[11]:(z=D(`accessKeyId`),e[10]=D,e[11]=z);let B;e[12]!==R||e[13]!==z?(B=(0,S.jsx)(p,{"aria-invalid":R,autoComplete:`username`,id:`accessKeyId`,placeholder:`AKIAIOSFODNN7EXAMPLE`,...z}),e[12]=R,e[13]=z,e[14]=B):B=e[14];let V;e[15]===A.accessKeyId?V=e[16]:(V=(0,S.jsx)(h,{errors:[A.accessKeyId]}),e[15]=A.accessKeyId,e[16]=V);let H;e[17]!==V||e[18]!==B?(H=(0,S.jsxs)(_,{children:[L,B,V]}),e[17]=V,e[18]=B,e[19]=H):H=e[19];let U;e[20]===Symbol.for(`react.memo_cache_sentinel`)?(U=(0,S.jsx)(m,{children:(0,S.jsx)(`label`,{htmlFor:`secretAccessKey`,children:`Secret Access Key`})}),e[20]=U):U=e[20];let W=!!A.secretAccessKey,G;e[21]===D?G=e[22]:(G=D(`secretAccessKey`),e[21]=D,e[22]=G);let K;e[23]!==W||e[24]!==G?(K=(0,S.jsx)(p,{"aria-invalid":W,autoComplete:`current-password`,id:`secretAccessKey`,placeholder:`wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`,type:`password`,...G}),e[23]=W,e[24]=G,e[25]=K):K=e[25];let q;e[26]===A.secretAccessKey?q=e[27]:(q=(0,S.jsx)(h,{errors:[A.secretAccessKey]}),e[26]=A.secretAccessKey,e[27]=q);let J;e[28]!==K||e[29]!==q?(J=(0,S.jsxs)(_,{children:[U,K,q]}),e[28]=K,e[29]=q,e[30]=J):J=e[30];let Y=j?`Signing in...`:`Sign In`,X;e[31]!==j||e[32]!==Y?(X=(0,S.jsx)(o,{className:`w-full`,disabled:j,type:`submit`,children:Y}),e[31]=j,e[32]=Y,e[33]=X):X=e[33];let Z;e[34]!==H||e[35]!==J||e[36]!==X?(Z=(0,S.jsxs)(g,{children:[H,J,X]}),e[34]=H,e[35]=J,e[36]=X,e[37]=Z):Z=e[37];let Q;e[38]!==Z||e[39]!==I?(Q=(0,S.jsx)(`form`,{onSubmit:I,children:Z}),e[38]=Z,e[39]=I,e[40]=Q):Q=e[40];let $;return e[41]!==Q||e[42]!==F?($=(0,S.jsx)(`div`,{className:`flex flex-1 items-center justify-center`,children:(0,S.jsxs)(C,{className:`w-full max-w-sm`,children:[P,(0,S.jsxs)(E,{children:[F,Q]})]})}),e[41]=Q,e[42]=F,e[43]=$):$=e[43],$}export{D as component};
+1 -1
View File
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import"./useStore.js";import{j as n}from"./index.js";var r=t(),i=e(),a=()=>{let e=(0,r.c)(1),t;return e[0]===Symbol.for(`react.memo_cache_sentinel`)?(t=(0,i.jsx)(n,{}),e[0]=t):t=e[0],t};export{a as component};
import{n as e,t}from"./compiler-runtime.js";import"./useStore.js";import{M as n}from"./index.js";var r=t(),i=e(),a=()=>{let e=(0,r.c)(1),t;return e[0]===Symbol.for(`react.memo_cache_sentinel`)?(t=(0,i.jsx)(n,{}),e[0]=t):t=e[0],t};export{a as component};
@@ -1 +1 @@
import{n as e,t}from"./compiler-runtime.js";import"./useStore.js";import{j as n}from"./index.js";var r=t(),i=e(),a=()=>{let e=(0,r.c)(1),t;return e[0]===Symbol.for(`react.memo_cache_sentinel`)?(t=(0,i.jsx)(n,{}),e[0]=t):t=e[0],t};export{a as component};
import{n as e,t}from"./compiler-runtime.js";import"./useStore.js";import{M as n}from"./index.js";var r=t(),i=e(),a=()=>{let e=(0,r.c)(1),t;return e[0]===Symbol.for(`react.memo_cache_sentinel`)?(t=(0,i.jsx)(n,{}),e[0]=t):t=e[0],t};export{a 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{t as e}from"./queryOptions.js";import{Dt as t,_ as n,d as r,f as i,g as a,i as o,jt as s}from"./awsClient.js";var c=class extends s.classBuilder().ep(a).m(function(e,r,i,a){return[t(i,e.getEndpointParameterInstructions()),n(i)]}).s(`AmazonS3`,`ListBuckets`,{}).n(`S3Client`,`ListBucketsCommand`).sc(r).build(){},l=class extends s.classBuilder().ep({...a,Bucket:{type:`contextParams`,name:`Bucket`},Prefix:{type:`contextParams`,name:`Prefix`}}).m(function(e,r,i,a){return[t(i,e.getEndpointParameterInstructions()),n(i)]}).s(`AmazonS3`,`ListObjectsV2`,{}).n(`S3Client`,`ListObjectsV2Command`).sc(i).build(){},u=e({queryKey:[`s3`,`buckets`],queryFn:()=>{let e=new c({});return o().send(e)}}),d=(t,n)=>e({queryKey:[`s3`,`buckets`,t,`objects`,n??``],queryFn:()=>{let e=new l({Bucket:t,Prefix:n,Delimiter:`/`});return o().send(e)}});export{u as n,d as t};
import{t as e}from"./queryOptions.js";import{At as t,Pt as n,_ as r,d as i,f as a,g as o,i as s}from"./awsClient.js";var c=class extends n.classBuilder().ep(o).m(function(e,n,i,a){return[t(i,e.getEndpointParameterInstructions()),r(i)]}).s(`AmazonS3`,`ListBuckets`,{}).n(`S3Client`,`ListBucketsCommand`).sc(i).build(){},l=class extends n.classBuilder().ep({...o,Bucket:{type:`contextParams`,name:`Bucket`},Prefix:{type:`contextParams`,name:`Prefix`}}).m(function(e,n,i,a){return[t(i,e.getEndpointParameterInstructions()),r(i)]}).s(`AmazonS3`,`ListObjectsV2`,{}).n(`S3Client`,`ListObjectsV2Command`).sc(a).build(){},u=e({queryKey:[`s3`,`buckets`],queryFn:()=>{let e=new c({});return s().send(e)}}),d=(t,n)=>e({queryKey:[`s3`,`buckets`,t,`objects`,n??``],queryFn:()=>{let e=new l({Bucket:t,Prefix:n,Delimiter:`/`});return s().send(e)}});export{u as n,d as t};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -54,7 +54,7 @@
"oxfmt": "^0.41.0",
"oxlint": "^1.56.0",
"oxlint-tsgolint": "^0.17.1",
"typescript": "^5.9.3",
"typescript": "^6.0.0",
"ultracite": "7.3.2",
"vite": "^8.0.1",
"vitest": "^4.1.0"
+27 -27
View File
@@ -74,10 +74,10 @@ importers:
version: 4.2.2(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
'@tanstack/eslint-plugin-query':
specifier: ^5.95.0
version: 5.95.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
version: 5.95.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)
'@tanstack/eslint-plugin-router':
specifier: ^1.161.6
version: 1.161.6(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
version: 1.161.6(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)
'@tanstack/router-plugin':
specifier: ^1.167.3
version: 1.167.3(@tanstack/react-router@1.168.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
@@ -127,8 +127,8 @@ importers:
specifier: ^0.17.1
version: 0.17.1
typescript:
specifier: ^5.9.3
version: 5.9.3
specifier: ^6.0.0
version: 6.0.2
ultracite:
specifier: 7.3.2
version: 7.3.2(oxlint@1.56.0(oxlint-tsgolint@0.17.1))
@@ -2728,8 +2728,8 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
typescript@6.0.2:
resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==}
engines: {node: '>=14.17'}
hasBin: true
@@ -4504,18 +4504,18 @@ snapshots:
tailwindcss: 4.2.2
vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
'@tanstack/eslint-plugin-query@5.95.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
'@tanstack/eslint-plugin-query@5.95.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)':
dependencies:
'@typescript-eslint/utils': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/utils': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)
eslint: 9.39.4(jiti@2.6.1)
optionalDependencies:
typescript: 5.9.3
typescript: 6.0.2
transitivePeerDependencies:
- supports-color
'@tanstack/eslint-plugin-router@1.161.6(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
'@tanstack/eslint-plugin-router@1.161.6(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)':
dependencies:
'@typescript-eslint/utils': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/utils': 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)
eslint: 9.39.4(jiti@2.6.1)
transitivePeerDependencies:
- supports-color
@@ -4675,12 +4675,12 @@ snapshots:
dependencies:
'@types/node': 24.12.0
'@typescript-eslint/project-service@8.57.1(typescript@5.9.3)':
'@typescript-eslint/project-service@8.57.1(typescript@6.0.2)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.57.1(typescript@6.0.2)
'@typescript-eslint/types': 8.57.1
debug: 4.4.3
typescript: 5.9.3
typescript: 6.0.2
transitivePeerDependencies:
- supports-color
@@ -4689,35 +4689,35 @@ snapshots:
'@typescript-eslint/types': 8.57.1
'@typescript-eslint/visitor-keys': 8.57.1
'@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)':
'@typescript-eslint/tsconfig-utils@8.57.1(typescript@6.0.2)':
dependencies:
typescript: 5.9.3
typescript: 6.0.2
'@typescript-eslint/types@8.57.1': {}
'@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3)':
'@typescript-eslint/typescript-estree@8.57.1(typescript@6.0.2)':
dependencies:
'@typescript-eslint/project-service': 8.57.1(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
'@typescript-eslint/project-service': 8.57.1(typescript@6.0.2)
'@typescript-eslint/tsconfig-utils': 8.57.1(typescript@6.0.2)
'@typescript-eslint/types': 8.57.1
'@typescript-eslint/visitor-keys': 8.57.1
debug: 4.4.3
minimatch: 10.2.4
semver: 7.7.4
tinyglobby: 0.2.15
ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3
ts-api-utils: 2.4.0(typescript@6.0.2)
typescript: 6.0.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/utils@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
'@typescript-eslint/scope-manager': 8.57.1
'@typescript-eslint/types': 8.57.1
'@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.57.1(typescript@6.0.2)
eslint: 9.39.4(jiti@2.6.1)
typescript: 5.9.3
typescript: 6.0.2
transitivePeerDependencies:
- supports-color
@@ -5775,9 +5775,9 @@ snapshots:
punycode: 2.3.1
optional: true
ts-api-utils@2.4.0(typescript@5.9.3):
ts-api-utils@2.4.0(typescript@6.0.2):
dependencies:
typescript: 5.9.3
typescript: 6.0.2
tslib@2.8.1: {}
@@ -5792,7 +5792,7 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
typescript@5.9.3: {}
typescript@6.0.2: {}
ultracite@7.3.2(oxlint@1.56.0(oxlint-tsgolint@0.17.1)):
dependencies:
@@ -1 +1,3 @@
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
- typescript
@@ -6,6 +6,7 @@ import {
Home,
Image,
Key,
Layers,
LayoutGrid,
LogOut,
Network,
@@ -145,6 +146,21 @@ export function SidebarLayout() {
</SidebarMenuButton>
</Link>
</SidebarMenuItem>
<SidebarMenuItem>
<Link to="/ec2/describe-placement-groups">
<SidebarMenuButton
isActive={
pathname.startsWith("/ec2/describe-placement-groups") ||
pathname.startsWith("/ec2/create-placement-group")
}
tooltip="Placement Groups"
>
<Layers className="size-4" />
<span>Placement Groups</span>
</SidebarMenuButton>
</Link>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
@@ -15,11 +15,13 @@ import {
useCreateImage,
useCreateInstance,
useCreateKeyPair,
useCreatePlacementGroup,
useCreateSnapshot,
useCreateSubnet,
useCreateVolume,
useCreateVpc,
useDeleteKeyPair,
useDeletePlacementGroup,
useDeleteSnapshot,
useDeleteSubnet,
useDeleteVolume,
@@ -150,6 +152,40 @@ describe("useCreateInstance", () => {
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockSend.mock.calls[0]?.[0].input.SubnetId).toBeUndefined()
})
it("includes Placement when placementGroupName is provided", async () => {
createQueryClient()
const { result } = renderHook(() => useCreateInstance(), { wrapper })
result.current.mutate({
imageId: "ami-123",
instanceType: "t2.micro",
keyName: "my-key",
count: 1,
placementGroupName: "my-group",
})
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockSend.mock.calls[0]?.[0].input.Placement).toEqual({
GroupName: "my-group",
})
})
it("omits Placement when placementGroupName is empty", async () => {
createQueryClient()
const { result } = renderHook(() => useCreateInstance(), { wrapper })
result.current.mutate({
imageId: "ami-123",
instanceType: "t2.micro",
keyName: "my-key",
count: 1,
placementGroupName: "",
})
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockSend.mock.calls[0]?.[0].input.Placement).toBeUndefined()
})
})
describe("useImportKeyPair", () => {
@@ -522,3 +558,58 @@ describe("useDeleteSubnet", () => {
})
})
})
describe("useCreatePlacementGroup", () => {
it("sends CreatePlacementGroupCommand with group name and strategy", async () => {
createQueryClient()
const { result } = renderHook(() => useCreatePlacementGroup(), { wrapper })
result.current.mutate({ groupName: "my-group", strategy: "spread" })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockSend.mock.calls[0]?.[0].input).toEqual({
GroupName: "my-group",
Strategy: "spread",
})
})
it("invalidates placementGroups query on success", async () => {
createQueryClient()
const spy = vi.spyOn(queryClient, "invalidateQueries")
const { result } = renderHook(() => useCreatePlacementGroup(), { wrapper })
result.current.mutate({ groupName: "my-group", strategy: "cluster" })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(spy).toHaveBeenCalledWith({
queryKey: ["ec2", "placementGroups"],
})
})
})
describe("useDeletePlacementGroup", () => {
it("sends DeletePlacementGroupCommand with group name", async () => {
createQueryClient()
const { result } = renderHook(() => useDeletePlacementGroup(), { wrapper })
result.current.mutate("my-group")
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockSend.mock.calls[0]?.[0].input).toEqual({
GroupName: "my-group",
})
})
it("invalidates placementGroups query on success", async () => {
createQueryClient()
const spy = vi.spyOn(queryClient, "invalidateQueries")
const { result } = renderHook(() => useDeletePlacementGroup(), { wrapper })
result.current.mutate("my-group")
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(spy).toHaveBeenCalledWith({
queryKey: ["ec2", "placementGroups"],
})
})
})
@@ -1,14 +1,17 @@
import {
type _InstanceType,
type PlacementStrategy,
AttachVolumeCommand,
CopySnapshotCommand,
CreateImageCommand,
CreateKeyPairCommand,
CreatePlacementGroupCommand,
CreateSnapshotCommand,
CreateSubnetCommand,
CreateVolumeCommand,
CreateVpcCommand,
DeleteKeyPairCommand,
DeletePlacementGroupCommand,
DeleteSnapshotCommand,
DeleteSubnetCommand,
DeleteVolumeCommand,
@@ -34,6 +37,7 @@ import type {
CreateImageParams,
CreateInstanceParams,
CreateKeyPairData,
CreatePlacementGroupFormData,
CreateSnapshotFormData,
CreateSubnetFormData,
CreateVolumeFormData,
@@ -119,6 +123,9 @@ export function useCreateInstance() {
MaxCount: params.count,
// oxlint-disable-next-line typescript/prefer-nullish-coalescing
SubnetId: params.subnetId || undefined,
Placement: params.placementGroupName
? { GroupName: params.placementGroupName }
: undefined,
})
return getEc2Client().send(command)
},
@@ -433,3 +440,35 @@ export function useDeleteSubnet() {
},
})
}
export function useCreatePlacementGroup() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (params: CreatePlacementGroupFormData) => {
const command = new CreatePlacementGroupCommand({
GroupName: params.groupName,
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- AWS SDK expects PlacementStrategy enum
Strategy: params.strategy as PlacementStrategy,
})
return getEc2Client().send(command)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["ec2", "placementGroups"] })
},
})
}
export function useDeletePlacementGroup() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (groupName: string) => {
const command = new DeletePlacementGroupCommand({
GroupName: groupName,
})
return getEc2Client().send(command)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["ec2", "placementGroups"] })
},
})
}
@@ -15,6 +15,8 @@ import {
ec2InstanceTypesQueryOptions,
ec2KeyPairQueryOptions,
ec2KeyPairsQueryOptions,
ec2PlacementGroupQueryOptions,
ec2PlacementGroupsQueryOptions,
ec2RegionsQueryOptions,
ec2SnapshotQueryOptions,
ec2SnapshotsQueryOptions,
@@ -118,6 +120,21 @@ describe("query keys", () => {
"types",
])
})
it("ec2PlacementGroupsQueryOptions has correct key", () => {
expect(ec2PlacementGroupsQueryOptions.queryKey).toEqual([
"ec2",
"placementGroups",
])
})
it("ec2PlacementGroupQueryOptions includes groupId in key", () => {
expect(ec2PlacementGroupQueryOptions("pg-123").queryKey).toEqual([
"ec2",
"placementGroups",
"pg-123",
])
})
})
describe("staleTime and refetchInterval", () => {
@@ -313,4 +330,23 @@ describe("queryFn", () => {
Filters: [{ Name: "capacity", Values: ["true"] }],
})
})
it("ec2PlacementGroupsQueryOptions sends DescribePlacementGroupsCommand", async () => {
const queryFn = ec2PlacementGroupsQueryOptions.queryFn as (
ctx: never,
) => Promise<unknown>
await queryFn({} as never)
expect(mockSend).toHaveBeenCalledOnce()
expect(mockSend.mock.calls[0]?.[0].input).toEqual({})
})
it("ec2PlacementGroupQueryOptions sends DescribePlacementGroupsCommand with ID", async () => {
const queryFn = ec2PlacementGroupQueryOptions("pg-123").queryFn as (
ctx: never,
) => Promise<unknown>
await queryFn({} as never)
expect(mockSend.mock.calls[0]?.[0].input).toEqual({
GroupIds: ["pg-123"],
})
})
})
@@ -4,6 +4,7 @@ import {
DescribeInstancesCommand,
DescribeInstanceTypesCommand,
DescribeKeyPairsCommand,
DescribePlacementGroupsCommand,
DescribeRegionsCommand,
DescribeSnapshotsCommand,
DescribeSubnetsCommand,
@@ -169,6 +170,25 @@ export const ec2VpcQueryOptions = (vpcId: string) =>
},
})
export const ec2PlacementGroupsQueryOptions = queryOptions({
queryKey: ["ec2", "placementGroups"],
queryFn: () => {
const command = new DescribePlacementGroupsCommand({})
return getEc2Client().send(command)
},
})
export const ec2PlacementGroupQueryOptions = (groupId: string) =>
queryOptions({
queryKey: ["ec2", "placementGroups", groupId],
queryFn: () => {
const command = new DescribePlacementGroupsCommand({
GroupIds: [groupId],
})
return getEc2Client().send(command)
},
})
export const ec2VolumesQueryOptions = queryOptions({
queryKey: ["ec2", "volumes"],
queryFn: () => {
@@ -20,6 +20,7 @@ import { Route as AuthEc2vpcCreateVpcRouteImport } from './routes/_auth/ec2/(vpc
import { Route as AuthEc2volumesCreateVolumeRouteImport } from './routes/_auth/ec2/(volumes)/create-volume'
import { Route as AuthEc2subnetCreateSubnetRouteImport } from './routes/_auth/ec2/(subnet)/create-subnet'
import { Route as AuthEc2snapshotsCreateSnapshotRouteImport } from './routes/_auth/ec2/(snapshots)/create-snapshot'
import { Route as AuthEc2placementGroupsCreatePlacementGroupRouteImport } from './routes/_auth/ec2/(placement-groups)/create-placement-group'
import { Route as AuthEc2keyImportKeyPairRouteImport } from './routes/_auth/ec2/(key)/import-key-pair'
import { Route as AuthEc2keyCreateKeyPairRouteImport } from './routes/_auth/ec2/(key)/create-key-pair'
import { Route as AuthEc2instancesRunInstancesRouteImport } from './routes/_auth/ec2/(instances)/run-instances'
@@ -31,6 +32,7 @@ import { Route as AuthEc2vpcDescribeVpcsIndexRouteImport } from './routes/_auth/
import { Route as AuthEc2volumesDescribeVolumesIndexRouteImport } from './routes/_auth/ec2/(volumes)/describe-volumes/index'
import { Route as AuthEc2subnetDescribeSubnetsIndexRouteImport } from './routes/_auth/ec2/(subnet)/describe-subnets/index'
import { Route as AuthEc2snapshotsDescribeSnapshotsIndexRouteImport } from './routes/_auth/ec2/(snapshots)/describe-snapshots/index'
import { Route as AuthEc2placementGroupsDescribePlacementGroupsIndexRouteImport } from './routes/_auth/ec2/(placement-groups)/describe-placement-groups/index'
import { Route as AuthEc2keyDescribeKeyPairsIndexRouteImport } from './routes/_auth/ec2/(key)/describe-key-pairs/index'
import { Route as AuthEc2instancesDescribeInstancesIndexRouteImport } from './routes/_auth/ec2/(instances)/describe-instances/index'
import { Route as AuthEc2imagesDescribeImagesIndexRouteImport } from './routes/_auth/ec2/(images)/describe-images/index'
@@ -42,6 +44,7 @@ import { Route as AuthEc2volumesModifyVolumeIdRouteImport } from './routes/_auth
import { Route as AuthEc2volumesDescribeVolumesIdRouteImport } from './routes/_auth/ec2/(volumes)/describe-volumes/$id'
import { Route as AuthEc2subnetDescribeSubnetsIdRouteImport } from './routes/_auth/ec2/(subnet)/describe-subnets/$id'
import { Route as AuthEc2snapshotsDescribeSnapshotsIdRouteImport } from './routes/_auth/ec2/(snapshots)/describe-snapshots/$id'
import { Route as AuthEc2placementGroupsDescribePlacementGroupsIdRouteImport } from './routes/_auth/ec2/(placement-groups)/describe-placement-groups/$id'
import { Route as AuthEc2keyDescribeKeyPairsIdRouteImport } from './routes/_auth/ec2/(key)/describe-key-pairs/$id'
import { Route as AuthEc2instancesDescribeInstancesIdRouteImport } from './routes/_auth/ec2/(instances)/describe-instances/$id'
import { Route as AuthEc2imagesDescribeImagesIdRouteImport } from './routes/_auth/ec2/(images)/describe-images/$id'
@@ -105,6 +108,12 @@ const AuthEc2snapshotsCreateSnapshotRoute =
path: '/ec2/create-snapshot',
getParentRoute: () => AuthRouteRoute,
} as any)
const AuthEc2placementGroupsCreatePlacementGroupRoute =
AuthEc2placementGroupsCreatePlacementGroupRouteImport.update({
id: '/ec2/(placement-groups)/create-placement-group',
path: '/ec2/create-placement-group',
getParentRoute: () => AuthRouteRoute,
} as any)
const AuthEc2keyImportKeyPairRoute = AuthEc2keyImportKeyPairRouteImport.update({
id: '/ec2/(key)/import-key-pair',
path: '/ec2/import-key-pair',
@@ -167,6 +176,12 @@ const AuthEc2snapshotsDescribeSnapshotsIndexRoute =
path: '/ec2/describe-snapshots/',
getParentRoute: () => AuthRouteRoute,
} as any)
const AuthEc2placementGroupsDescribePlacementGroupsIndexRoute =
AuthEc2placementGroupsDescribePlacementGroupsIndexRouteImport.update({
id: '/ec2/(placement-groups)/describe-placement-groups/',
path: '/ec2/describe-placement-groups/',
getParentRoute: () => AuthRouteRoute,
} as any)
const AuthEc2keyDescribeKeyPairsIndexRoute =
AuthEc2keyDescribeKeyPairsIndexRouteImport.update({
id: '/ec2/(key)/describe-key-pairs/',
@@ -232,6 +247,12 @@ const AuthEc2snapshotsDescribeSnapshotsIdRoute =
path: '/ec2/describe-snapshots/$id',
getParentRoute: () => AuthRouteRoute,
} as any)
const AuthEc2placementGroupsDescribePlacementGroupsIdRoute =
AuthEc2placementGroupsDescribePlacementGroupsIdRouteImport.update({
id: '/ec2/(placement-groups)/describe-placement-groups/$id',
path: '/ec2/describe-placement-groups/$id',
getParentRoute: () => AuthRouteRoute,
} as any)
const AuthEc2keyDescribeKeyPairsIdRoute =
AuthEc2keyDescribeKeyPairsIdRouteImport.update({
id: '/ec2/(key)/describe-key-pairs/$id',
@@ -258,6 +279,7 @@ export interface FileRoutesByFullPath {
'/ec2/run-instances': typeof AuthEc2instancesRunInstancesRoute
'/ec2/create-key-pair': typeof AuthEc2keyCreateKeyPairRoute
'/ec2/import-key-pair': typeof AuthEc2keyImportKeyPairRoute
'/ec2/create-placement-group': typeof AuthEc2placementGroupsCreatePlacementGroupRoute
'/ec2/create-snapshot': typeof AuthEc2snapshotsCreateSnapshotRoute
'/ec2/create-subnet': typeof AuthEc2subnetCreateSubnetRoute
'/ec2/create-volume': typeof AuthEc2volumesCreateVolumeRoute
@@ -269,6 +291,7 @@ export interface FileRoutesByFullPath {
'/ec2/describe-images/$id': typeof AuthEc2imagesDescribeImagesIdRoute
'/ec2/describe-instances/$id': typeof AuthEc2instancesDescribeInstancesIdRoute
'/ec2/describe-key-pairs/$id': typeof AuthEc2keyDescribeKeyPairsIdRoute
'/ec2/describe-placement-groups/$id': typeof AuthEc2placementGroupsDescribePlacementGroupsIdRoute
'/ec2/describe-snapshots/$id': typeof AuthEc2snapshotsDescribeSnapshotsIdRoute
'/ec2/describe-subnets/$id': typeof AuthEc2subnetDescribeSubnetsIdRoute
'/ec2/describe-volumes/$id': typeof AuthEc2volumesDescribeVolumesIdRoute
@@ -280,6 +303,7 @@ export interface FileRoutesByFullPath {
'/ec2/describe-images/': typeof AuthEc2imagesDescribeImagesIndexRoute
'/ec2/describe-instances/': typeof AuthEc2instancesDescribeInstancesIndexRoute
'/ec2/describe-key-pairs/': typeof AuthEc2keyDescribeKeyPairsIndexRoute
'/ec2/describe-placement-groups/': typeof AuthEc2placementGroupsDescribePlacementGroupsIndexRoute
'/ec2/describe-snapshots/': typeof AuthEc2snapshotsDescribeSnapshotsIndexRoute
'/ec2/describe-subnets/': typeof AuthEc2subnetDescribeSubnetsIndexRoute
'/ec2/describe-volumes/': typeof AuthEc2volumesDescribeVolumesIndexRoute
@@ -294,6 +318,7 @@ export interface FileRoutesByTo {
'/ec2/run-instances': typeof AuthEc2instancesRunInstancesRoute
'/ec2/create-key-pair': typeof AuthEc2keyCreateKeyPairRoute
'/ec2/import-key-pair': typeof AuthEc2keyImportKeyPairRoute
'/ec2/create-placement-group': typeof AuthEc2placementGroupsCreatePlacementGroupRoute
'/ec2/create-snapshot': typeof AuthEc2snapshotsCreateSnapshotRoute
'/ec2/create-subnet': typeof AuthEc2subnetCreateSubnetRoute
'/ec2/create-volume': typeof AuthEc2volumesCreateVolumeRoute
@@ -305,6 +330,7 @@ export interface FileRoutesByTo {
'/ec2/describe-images/$id': typeof AuthEc2imagesDescribeImagesIdRoute
'/ec2/describe-instances/$id': typeof AuthEc2instancesDescribeInstancesIdRoute
'/ec2/describe-key-pairs/$id': typeof AuthEc2keyDescribeKeyPairsIdRoute
'/ec2/describe-placement-groups/$id': typeof AuthEc2placementGroupsDescribePlacementGroupsIdRoute
'/ec2/describe-snapshots/$id': typeof AuthEc2snapshotsDescribeSnapshotsIdRoute
'/ec2/describe-subnets/$id': typeof AuthEc2subnetDescribeSubnetsIdRoute
'/ec2/describe-volumes/$id': typeof AuthEc2volumesDescribeVolumesIdRoute
@@ -316,6 +342,7 @@ export interface FileRoutesByTo {
'/ec2/describe-images': typeof AuthEc2imagesDescribeImagesIndexRoute
'/ec2/describe-instances': typeof AuthEc2instancesDescribeInstancesIndexRoute
'/ec2/describe-key-pairs': typeof AuthEc2keyDescribeKeyPairsIndexRoute
'/ec2/describe-placement-groups': typeof AuthEc2placementGroupsDescribePlacementGroupsIndexRoute
'/ec2/describe-snapshots': typeof AuthEc2snapshotsDescribeSnapshotsIndexRoute
'/ec2/describe-subnets': typeof AuthEc2subnetDescribeSubnetsIndexRoute
'/ec2/describe-volumes': typeof AuthEc2volumesDescribeVolumesIndexRoute
@@ -333,6 +360,7 @@ export interface FileRoutesById {
'/_auth/ec2/(instances)/run-instances': typeof AuthEc2instancesRunInstancesRoute
'/_auth/ec2/(key)/create-key-pair': typeof AuthEc2keyCreateKeyPairRoute
'/_auth/ec2/(key)/import-key-pair': typeof AuthEc2keyImportKeyPairRoute
'/_auth/ec2/(placement-groups)/create-placement-group': typeof AuthEc2placementGroupsCreatePlacementGroupRoute
'/_auth/ec2/(snapshots)/create-snapshot': typeof AuthEc2snapshotsCreateSnapshotRoute
'/_auth/ec2/(subnet)/create-subnet': typeof AuthEc2subnetCreateSubnetRoute
'/_auth/ec2/(volumes)/create-volume': typeof AuthEc2volumesCreateVolumeRoute
@@ -344,6 +372,7 @@ export interface FileRoutesById {
'/_auth/ec2/(images)/describe-images/$id': typeof AuthEc2imagesDescribeImagesIdRoute
'/_auth/ec2/(instances)/describe-instances/$id': typeof AuthEc2instancesDescribeInstancesIdRoute
'/_auth/ec2/(key)/describe-key-pairs/$id': typeof AuthEc2keyDescribeKeyPairsIdRoute
'/_auth/ec2/(placement-groups)/describe-placement-groups/$id': typeof AuthEc2placementGroupsDescribePlacementGroupsIdRoute
'/_auth/ec2/(snapshots)/describe-snapshots/$id': typeof AuthEc2snapshotsDescribeSnapshotsIdRoute
'/_auth/ec2/(subnet)/describe-subnets/$id': typeof AuthEc2subnetDescribeSubnetsIdRoute
'/_auth/ec2/(volumes)/describe-volumes/$id': typeof AuthEc2volumesDescribeVolumesIdRoute
@@ -355,6 +384,7 @@ export interface FileRoutesById {
'/_auth/ec2/(images)/describe-images/': typeof AuthEc2imagesDescribeImagesIndexRoute
'/_auth/ec2/(instances)/describe-instances/': typeof AuthEc2instancesDescribeInstancesIndexRoute
'/_auth/ec2/(key)/describe-key-pairs/': typeof AuthEc2keyDescribeKeyPairsIndexRoute
'/_auth/ec2/(placement-groups)/describe-placement-groups/': typeof AuthEc2placementGroupsDescribePlacementGroupsIndexRoute
'/_auth/ec2/(snapshots)/describe-snapshots/': typeof AuthEc2snapshotsDescribeSnapshotsIndexRoute
'/_auth/ec2/(subnet)/describe-subnets/': typeof AuthEc2subnetDescribeSubnetsIndexRoute
'/_auth/ec2/(volumes)/describe-volumes/': typeof AuthEc2volumesDescribeVolumesIndexRoute
@@ -372,6 +402,7 @@ export interface FileRouteTypes {
| '/ec2/run-instances'
| '/ec2/create-key-pair'
| '/ec2/import-key-pair'
| '/ec2/create-placement-group'
| '/ec2/create-snapshot'
| '/ec2/create-subnet'
| '/ec2/create-volume'
@@ -383,6 +414,7 @@ export interface FileRouteTypes {
| '/ec2/describe-images/$id'
| '/ec2/describe-instances/$id'
| '/ec2/describe-key-pairs/$id'
| '/ec2/describe-placement-groups/$id'
| '/ec2/describe-snapshots/$id'
| '/ec2/describe-subnets/$id'
| '/ec2/describe-volumes/$id'
@@ -394,6 +426,7 @@ export interface FileRouteTypes {
| '/ec2/describe-images/'
| '/ec2/describe-instances/'
| '/ec2/describe-key-pairs/'
| '/ec2/describe-placement-groups/'
| '/ec2/describe-snapshots/'
| '/ec2/describe-subnets/'
| '/ec2/describe-volumes/'
@@ -408,6 +441,7 @@ export interface FileRouteTypes {
| '/ec2/run-instances'
| '/ec2/create-key-pair'
| '/ec2/import-key-pair'
| '/ec2/create-placement-group'
| '/ec2/create-snapshot'
| '/ec2/create-subnet'
| '/ec2/create-volume'
@@ -419,6 +453,7 @@ export interface FileRouteTypes {
| '/ec2/describe-images/$id'
| '/ec2/describe-instances/$id'
| '/ec2/describe-key-pairs/$id'
| '/ec2/describe-placement-groups/$id'
| '/ec2/describe-snapshots/$id'
| '/ec2/describe-subnets/$id'
| '/ec2/describe-volumes/$id'
@@ -430,6 +465,7 @@ export interface FileRouteTypes {
| '/ec2/describe-images'
| '/ec2/describe-instances'
| '/ec2/describe-key-pairs'
| '/ec2/describe-placement-groups'
| '/ec2/describe-snapshots'
| '/ec2/describe-subnets'
| '/ec2/describe-volumes'
@@ -446,6 +482,7 @@ export interface FileRouteTypes {
| '/_auth/ec2/(instances)/run-instances'
| '/_auth/ec2/(key)/create-key-pair'
| '/_auth/ec2/(key)/import-key-pair'
| '/_auth/ec2/(placement-groups)/create-placement-group'
| '/_auth/ec2/(snapshots)/create-snapshot'
| '/_auth/ec2/(subnet)/create-subnet'
| '/_auth/ec2/(volumes)/create-volume'
@@ -457,6 +494,7 @@ export interface FileRouteTypes {
| '/_auth/ec2/(images)/describe-images/$id'
| '/_auth/ec2/(instances)/describe-instances/$id'
| '/_auth/ec2/(key)/describe-key-pairs/$id'
| '/_auth/ec2/(placement-groups)/describe-placement-groups/$id'
| '/_auth/ec2/(snapshots)/describe-snapshots/$id'
| '/_auth/ec2/(subnet)/describe-subnets/$id'
| '/_auth/ec2/(volumes)/describe-volumes/$id'
@@ -468,6 +506,7 @@ export interface FileRouteTypes {
| '/_auth/ec2/(images)/describe-images/'
| '/_auth/ec2/(instances)/describe-instances/'
| '/_auth/ec2/(key)/describe-key-pairs/'
| '/_auth/ec2/(placement-groups)/describe-placement-groups/'
| '/_auth/ec2/(snapshots)/describe-snapshots/'
| '/_auth/ec2/(subnet)/describe-subnets/'
| '/_auth/ec2/(volumes)/describe-volumes/'
@@ -561,6 +600,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthEc2snapshotsCreateSnapshotRouteImport
parentRoute: typeof AuthRouteRoute
}
'/_auth/ec2/(placement-groups)/create-placement-group': {
id: '/_auth/ec2/(placement-groups)/create-placement-group'
path: '/ec2/create-placement-group'
fullPath: '/ec2/create-placement-group'
preLoaderRoute: typeof AuthEc2placementGroupsCreatePlacementGroupRouteImport
parentRoute: typeof AuthRouteRoute
}
'/_auth/ec2/(key)/import-key-pair': {
id: '/_auth/ec2/(key)/import-key-pair'
path: '/ec2/import-key-pair'
@@ -638,6 +684,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthEc2snapshotsDescribeSnapshotsIndexRouteImport
parentRoute: typeof AuthRouteRoute
}
'/_auth/ec2/(placement-groups)/describe-placement-groups/': {
id: '/_auth/ec2/(placement-groups)/describe-placement-groups/'
path: '/ec2/describe-placement-groups'
fullPath: '/ec2/describe-placement-groups/'
preLoaderRoute: typeof AuthEc2placementGroupsDescribePlacementGroupsIndexRouteImport
parentRoute: typeof AuthRouteRoute
}
'/_auth/ec2/(key)/describe-key-pairs/': {
id: '/_auth/ec2/(key)/describe-key-pairs/'
path: '/ec2/describe-key-pairs'
@@ -715,6 +768,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthEc2snapshotsDescribeSnapshotsIdRouteImport
parentRoute: typeof AuthRouteRoute
}
'/_auth/ec2/(placement-groups)/describe-placement-groups/$id': {
id: '/_auth/ec2/(placement-groups)/describe-placement-groups/$id'
path: '/ec2/describe-placement-groups/$id'
fullPath: '/ec2/describe-placement-groups/$id'
preLoaderRoute: typeof AuthEc2placementGroupsDescribePlacementGroupsIdRouteImport
parentRoute: typeof AuthRouteRoute
}
'/_auth/ec2/(key)/describe-key-pairs/$id': {
id: '/_auth/ec2/(key)/describe-key-pairs/$id'
path: '/ec2/describe-key-pairs/$id'
@@ -758,6 +818,7 @@ interface AuthRouteRouteChildren {
AuthEc2instancesRunInstancesRoute: typeof AuthEc2instancesRunInstancesRoute
AuthEc2keyCreateKeyPairRoute: typeof AuthEc2keyCreateKeyPairRoute
AuthEc2keyImportKeyPairRoute: typeof AuthEc2keyImportKeyPairRoute
AuthEc2placementGroupsCreatePlacementGroupRoute: typeof AuthEc2placementGroupsCreatePlacementGroupRoute
AuthEc2snapshotsCreateSnapshotRoute: typeof AuthEc2snapshotsCreateSnapshotRoute
AuthEc2subnetCreateSubnetRoute: typeof AuthEc2subnetCreateSubnetRoute
AuthEc2volumesCreateVolumeRoute: typeof AuthEc2volumesCreateVolumeRoute
@@ -769,6 +830,7 @@ interface AuthRouteRouteChildren {
AuthEc2imagesDescribeImagesIdRoute: typeof AuthEc2imagesDescribeImagesIdRoute
AuthEc2instancesDescribeInstancesIdRoute: typeof AuthEc2instancesDescribeInstancesIdRoute
AuthEc2keyDescribeKeyPairsIdRoute: typeof AuthEc2keyDescribeKeyPairsIdRoute
AuthEc2placementGroupsDescribePlacementGroupsIdRoute: typeof AuthEc2placementGroupsDescribePlacementGroupsIdRoute
AuthEc2snapshotsDescribeSnapshotsIdRoute: typeof AuthEc2snapshotsDescribeSnapshotsIdRoute
AuthEc2subnetDescribeSubnetsIdRoute: typeof AuthEc2subnetDescribeSubnetsIdRoute
AuthEc2volumesDescribeVolumesIdRoute: typeof AuthEc2volumesDescribeVolumesIdRoute
@@ -779,6 +841,7 @@ interface AuthRouteRouteChildren {
AuthEc2imagesDescribeImagesIndexRoute: typeof AuthEc2imagesDescribeImagesIndexRoute
AuthEc2instancesDescribeInstancesIndexRoute: typeof AuthEc2instancesDescribeInstancesIndexRoute
AuthEc2keyDescribeKeyPairsIndexRoute: typeof AuthEc2keyDescribeKeyPairsIndexRoute
AuthEc2placementGroupsDescribePlacementGroupsIndexRoute: typeof AuthEc2placementGroupsDescribePlacementGroupsIndexRoute
AuthEc2snapshotsDescribeSnapshotsIndexRoute: typeof AuthEc2snapshotsDescribeSnapshotsIndexRoute
AuthEc2subnetDescribeSubnetsIndexRoute: typeof AuthEc2subnetDescribeSubnetsIndexRoute
AuthEc2volumesDescribeVolumesIndexRoute: typeof AuthEc2volumesDescribeVolumesIndexRoute
@@ -793,6 +856,8 @@ const AuthRouteRouteChildren: AuthRouteRouteChildren = {
AuthEc2instancesRunInstancesRoute: AuthEc2instancesRunInstancesRoute,
AuthEc2keyCreateKeyPairRoute: AuthEc2keyCreateKeyPairRoute,
AuthEc2keyImportKeyPairRoute: AuthEc2keyImportKeyPairRoute,
AuthEc2placementGroupsCreatePlacementGroupRoute:
AuthEc2placementGroupsCreatePlacementGroupRoute,
AuthEc2snapshotsCreateSnapshotRoute: AuthEc2snapshotsCreateSnapshotRoute,
AuthEc2subnetCreateSubnetRoute: AuthEc2subnetCreateSubnetRoute,
AuthEc2volumesCreateVolumeRoute: AuthEc2volumesCreateVolumeRoute,
@@ -805,6 +870,8 @@ const AuthRouteRouteChildren: AuthRouteRouteChildren = {
AuthEc2instancesDescribeInstancesIdRoute:
AuthEc2instancesDescribeInstancesIdRoute,
AuthEc2keyDescribeKeyPairsIdRoute: AuthEc2keyDescribeKeyPairsIdRoute,
AuthEc2placementGroupsDescribePlacementGroupsIdRoute:
AuthEc2placementGroupsDescribePlacementGroupsIdRoute,
AuthEc2snapshotsDescribeSnapshotsIdRoute:
AuthEc2snapshotsDescribeSnapshotsIdRoute,
AuthEc2subnetDescribeSubnetsIdRoute: AuthEc2subnetDescribeSubnetsIdRoute,
@@ -818,6 +885,8 @@ const AuthRouteRouteChildren: AuthRouteRouteChildren = {
AuthEc2instancesDescribeInstancesIndexRoute:
AuthEc2instancesDescribeInstancesIndexRoute,
AuthEc2keyDescribeKeyPairsIndexRoute: AuthEc2keyDescribeKeyPairsIndexRoute,
AuthEc2placementGroupsDescribePlacementGroupsIndexRoute:
AuthEc2placementGroupsDescribePlacementGroupsIndexRoute,
AuthEc2snapshotsDescribeSnapshotsIndexRoute:
AuthEc2snapshotsDescribeSnapshotsIndexRoute,
AuthEc2subnetDescribeSubnetsIndexRoute:
@@ -243,6 +243,19 @@ function InstanceDetail() {
label="Availability Zone"
value={instance.Placement?.AvailabilityZone}
/>
<DetailRow
label="Placement Group"
value={
instance.Placement?.GroupName ? (
<Link
className="text-primary hover:underline"
to="/ec2/describe-placement-groups"
>
{instance.Placement.GroupName}
</Link>
) : undefined
}
/>
</DetailCard.Content>
</DetailCard>
@@ -21,6 +21,7 @@ import {
ec2ImagesQueryOptions,
ec2InstanceTypesQueryOptions,
ec2KeyPairsQueryOptions,
ec2PlacementGroupsQueryOptions,
ec2SubnetsQueryOptions,
} from "@/queries/ec2"
import { type CreateInstanceFormData, createInstanceSchema } from "@/types/ec2"
@@ -32,6 +33,7 @@ export const Route = createFileRoute("/_auth/ec2/(instances)/run-instances")({
context.queryClient.ensureQueryData(ec2KeyPairsQueryOptions),
context.queryClient.ensureQueryData(ec2InstanceTypesQueryOptions),
context.queryClient.ensureQueryData(ec2SubnetsQueryOptions),
context.queryClient.ensureQueryData(ec2PlacementGroupsQueryOptions),
])
},
head: () => ({
@@ -52,10 +54,12 @@ function CreateInstance() {
ec2InstanceTypesQueryOptions,
)
const { data: subnetsData } = useSuspenseQuery(ec2SubnetsQueryOptions)
const { data: pgData } = useSuspenseQuery(ec2PlacementGroupsQueryOptions)
const createMutation = useCreateInstance()
const images = imagesData.Images ?? []
const keyPairs = keyPairsData.KeyPairs ?? []
const subnets = subnetsData.Subnets ?? []
const placementGroups = pgData.PlacementGroups ?? []
const instanceTypeCounts: Record<string, number> = {}
for (const type of instanceTypesData.InstanceTypes ?? []) {
const typeName = type.InstanceType
@@ -275,6 +279,37 @@ function CreateInstance() {
/>
</Field>
{/* Placement Group */}
<Field>
<FieldTitle>
<label htmlFor="placementGroupName">Placement Group</label>
</FieldTitle>
<Controller
control={control}
name="placementGroupName"
render={({ field }) => (
<Select
onValueChange={(value) =>
field.onChange(value === "none" ? undefined : value)
}
value={field.value ?? "none"}
>
<SelectTrigger className="w-full" id="placementGroupName">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">none</SelectItem>
{placementGroups.map((pg) => (
<SelectItem key={pg.GroupId} value={pg.GroupName ?? ""}>
{pg.GroupName} ({pg.Strategy})
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</Field>
{/* Instance Count */}
<Field>
<FieldTitle>
@@ -0,0 +1,130 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { createFileRoute, useNavigate } from "@tanstack/react-router"
import { Controller, useForm } from "react-hook-form"
import { BackLink } from "@/components/back-link"
import { ErrorBanner } from "@/components/error-banner"
import { FormActions } from "@/components/form-actions"
import { PageHeading } from "@/components/page-heading"
import { Field, FieldError, FieldTitle } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { useCreatePlacementGroup } from "@/mutations/ec2"
import {
type CreatePlacementGroupFormData,
createPlacementGroupSchema,
} from "@/types/ec2"
export const Route = createFileRoute(
"/_auth/ec2/(placement-groups)/create-placement-group",
)({
head: () => ({
meta: [
{
title: "Create Placement Group | EC2 | Mulga",
},
],
}),
component: CreatePlacementGroup,
})
function CreatePlacementGroup() {
const navigate = useNavigate()
const createMutation = useCreatePlacementGroup()
const {
control,
handleSubmit,
register,
formState: { errors, isSubmitting },
} = useForm<CreatePlacementGroupFormData>({
resolver: zodResolver(createPlacementGroupSchema),
defaultValues: {
groupName: "",
strategy: "spread",
},
})
const onSubmit = async (data: CreatePlacementGroupFormData) => {
const result = await createMutation.mutateAsync(data)
navigate({
to: "/ec2/describe-placement-groups/$id",
params: { id: result.PlacementGroup?.GroupId ?? "" },
})
}
return (
<>
<BackLink to="/ec2/describe-placement-groups">
Back to placement groups
</BackLink>
<PageHeading title="Create Placement Group" />
{createMutation.error && (
<ErrorBanner
error={createMutation.error}
msg="Failed to create placement group"
/>
)}
<form className="max-w-4xl space-y-6" onSubmit={handleSubmit(onSubmit)}>
<Field>
<FieldTitle>
<label htmlFor="groupName">Group Name</label>
</FieldTitle>
<Input
aria-invalid={!!errors.groupName}
id="groupName"
placeholder="my-placement-group"
{...register("groupName")}
/>
<FieldError errors={[errors.groupName]} />
</Field>
<Field>
<FieldTitle>
<label htmlFor="strategy">Strategy</label>
</FieldTitle>
<Controller
control={control}
name="strategy"
render={({ field }) => (
<Select
onValueChange={(value) => field.onChange(value)}
value={field.value ?? ""}
>
<SelectTrigger
aria-invalid={!!errors.strategy}
className="w-full"
id="strategy"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="spread">spread</SelectItem>
<SelectItem value="cluster">cluster</SelectItem>
</SelectContent>
</Select>
)}
/>
<FieldError errors={[errors.strategy]} />
</Field>
<FormActions
isPending={createMutation.isPending}
isSubmitting={isSubmitting}
onCancel={() => navigate({ to: "/ec2/describe-placement-groups" })}
pendingLabel="Creating\u2026"
submitLabel="Create Placement Group"
/>
</form>
</>
)
}
@@ -0,0 +1,169 @@
import { useSuspenseQuery } from "@tanstack/react-query"
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"
import { Trash2 } from "lucide-react"
import { useState } from "react"
import { BackLink } from "@/components/back-link"
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog"
import { DetailCard } from "@/components/detail-card"
import { DetailRow } from "@/components/detail-row"
import { ErrorBanner } from "@/components/error-banner"
import { PageHeading } from "@/components/page-heading"
import { StateBadge } from "@/components/state-badge"
import { Button } from "@/components/ui/button"
import { useDeletePlacementGroup } from "@/mutations/ec2"
import {
ec2InstancesQueryOptions,
ec2PlacementGroupQueryOptions,
} from "@/queries/ec2"
export const Route = createFileRoute(
"/_auth/ec2/(placement-groups)/describe-placement-groups/$id",
)({
loader: async ({ context, params }) => {
await Promise.all([
context.queryClient.ensureQueryData(
ec2PlacementGroupQueryOptions(params.id),
),
context.queryClient.ensureQueryData(ec2InstancesQueryOptions),
])
},
head: ({ params }) => ({
meta: [
{
title: `${params.id} | EC2 | Mulga`,
},
],
}),
component: PlacementGroupDetail,
})
function PlacementGroupDetail() {
const { id } = Route.useParams()
const navigate = useNavigate()
const { data } = useSuspenseQuery(ec2PlacementGroupQueryOptions(id))
const { data: instancesData } = useSuspenseQuery(ec2InstancesQueryOptions)
const deleteMutation = useDeletePlacementGroup()
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const pg = data.PlacementGroups?.[0]
const instances =
instancesData.Reservations?.flatMap((r) => r.Instances ?? []).filter(
(inst) => inst.Placement?.GroupName === pg?.GroupName,
) ?? []
const handleDelete = async () => {
if (!pg?.GroupName) {
return
}
try {
await deleteMutation.mutateAsync(pg.GroupName)
navigate({ to: "/ec2/describe-placement-groups" })
} finally {
setShowDeleteDialog(false)
}
}
if (!pg?.GroupId) {
return (
<>
<BackLink to="/ec2/describe-placement-groups">
Back to placement groups
</BackLink>
<p className="text-muted-foreground">Placement group not found.</p>
</>
)
}
return (
<>
<BackLink to="/ec2/describe-placement-groups">
Back to placement groups
</BackLink>
{deleteMutation.error && (
<ErrorBanner
error={deleteMutation.error}
msg="Failed to delete placement group"
/>
)}
<div className="space-y-6">
<PageHeading
actions={
<div className="flex items-center gap-2">
<Button
onClick={() => setShowDeleteDialog(true)}
size="sm"
variant="destructive"
>
<Trash2 className="size-4" />
Delete
</Button>
<StateBadge state={pg.State} />
</div>
}
subtitle="EC2 Placement Group Details"
title={pg.GroupName ?? pg.GroupId}
/>
<DetailCard>
<DetailCard.Header>Placement Group Information</DetailCard.Header>
<DetailCard.Content>
<DetailRow label="Group ID" value={pg.GroupId} />
<DetailRow label="Group Name" value={pg.GroupName} />
<DetailRow label="Strategy" value={pg.Strategy} />
<DetailRow label="State" value={pg.State} />
<DetailRow label="Spread Level" value={pg.SpreadLevel} />
</DetailCard.Content>
</DetailCard>
<DetailCard>
<DetailCard.Header>Instances</DetailCard.Header>
{instances.length > 0 ? (
instances.map((inst) => (
<DetailCard.Content key={inst.InstanceId}>
<DetailRow
label="Instance ID"
value={
inst.InstanceId ? (
<Link
className="text-primary hover:underline"
params={{ id: inst.InstanceId }}
to="/ec2/describe-instances/$id"
>
{inst.InstanceId}
</Link>
) : undefined
}
/>
<DetailRow label="State" value={inst.State?.Name} />
<DetailRow label="Instance Type" value={inst.InstanceType} />
<DetailRow
label="Availability Zone"
value={inst.Placement?.AvailabilityZone}
/>
</DetailCard.Content>
))
) : (
<DetailCard.Content>
<p className="text-muted-foreground">
No instances in this placement group.
</p>
</DetailCard.Content>
)}
</DetailCard>
</div>
<DeleteConfirmationDialog
description={`Are you sure you want to delete the placement group "${pg.GroupName}"? The group must have no running instances.`}
isPending={deleteMutation.isPending}
onConfirm={handleDelete}
onOpenChange={setShowDeleteDialog}
open={showDeleteDialog}
title="Delete Placement Group"
/>
</>
)
}
@@ -0,0 +1,66 @@
import type { PlacementGroup } from "@aws-sdk/client-ec2"
import { useSuspenseQuery } from "@tanstack/react-query"
import { createFileRoute, Link } from "@tanstack/react-router"
import { ListCard } from "@/components/list-card"
import { PageHeading } from "@/components/page-heading"
import { StateBadge } from "@/components/state-badge"
import { Button } from "@/components/ui/button"
import { ec2PlacementGroupsQueryOptions } from "@/queries/ec2"
export const Route = createFileRoute(
"/_auth/ec2/(placement-groups)/describe-placement-groups/",
)({
loader: async ({ context }) => {
await context.queryClient.ensureQueryData(ec2PlacementGroupsQueryOptions)
},
head: () => ({
meta: [
{
title: "Placement Groups | EC2 | Mulga",
},
],
}),
component: PlacementGroups,
})
function PlacementGroups() {
const { data } = useSuspenseQuery(ec2PlacementGroupsQueryOptions)
const placementGroups = data.PlacementGroups ?? []
return (
<>
<PageHeading
actions={
<Link to="/ec2/create-placement-group">
<Button>Create Placement Group</Button>
</Link>
}
title="EC2 Placement Groups"
/>
{placementGroups.length > 0 ? (
<div className="space-y-4">
{placementGroups.map((pg: PlacementGroup) => {
if (!pg.GroupId) {
return null
}
return (
<ListCard
badge={<StateBadge state={pg.State} />}
key={pg.GroupId}
params={{ id: pg.GroupId }}
subtitle={`${pg.Strategy} \u2022 ${pg.GroupId}`}
title={pg.GroupName ?? pg.GroupId}
to="/ec2/describe-placement-groups/$id"
/>
)
})}
</div>
) : (
<p className="text-muted-foreground">No placement groups found.</p>
)}
</>
)
}
@@ -30,7 +30,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { formatDateTime } from "@/lib/utils"
import { formatDateTime, getNameTag } from "@/lib/utils"
import {
useAttachVolume,
useDeleteVolume,
@@ -319,9 +319,7 @@ function VolumeDetail() {
</SelectTrigger>
<SelectContent>
{runningInstances.map((instance) => {
const name = instance.Tags?.find(
(t) => t.Key === "Name",
)?.Value
const name = getNameTag(instance.Tags)
return (
<SelectItem
key={instance.InstanceId}

Some files were not shown because too many files have changed in this diff Show More