mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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 ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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 @@
|
||||
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
+2
-2
File diff suppressed because one or more lines are too long
@@ -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
@@ -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:
|
||||
|
||||
+13
@@ -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>
|
||||
|
||||
|
||||
+35
@@ -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>
|
||||
|
||||
+130
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
+169
@@ -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"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
+66
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
+2
-4
@@ -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
Reference in New Issue
Block a user