diff --git a/app/src/api/cmd.ts b/app/src/api/cmd.ts index 5bb7aca9..9be352a5 100644 --- a/app/src/api/cmd.ts +++ b/app/src/api/cmd.ts @@ -104,13 +104,17 @@ export const userKey = "SPHINX_TOKEN"; export async function send_cmd(type: CmdType, data: CmdData, tag?: string) { const txt = JSON.stringify({ type, data }); + const encodedTxt = encodeURIComponent(txt); let ret = ""; try { - const r = await fetch(`${root}/cmd?txt=${txt}&tag=${tag || "SWARM"}`, { - headers: { - "x-jwt": localStorage.getItem(userKey), - }, - }); + const r = await fetch( + `${root}/cmd?txt=${encodedTxt}&tag=${tag || "SWARM"}`, + { + headers: { + "x-jwt": localStorage.getItem(userKey), + }, + } + ); ret = await r.text(); const jj = JSON.parse(ret); if (jj && jj["stack_error"]) { diff --git a/src/bin/super/superapp/src/Remotes.svelte b/src/bin/super/superapp/src/Remotes.svelte index 6b6fc1d3..1b4a468f 100644 --- a/src/bin/super/superapp/src/Remotes.svelte +++ b/src/bin/super/superapp/src/Remotes.svelte @@ -23,7 +23,11 @@ import { remotes, tribes } from "./store"; import { onMount } from "svelte"; import type { Remote } from "./types/types"; - import { getSwarmNumber, splitHost } from "./utils/index"; + import { + getSwarmNumber, + isValidVanityAddress, + splitHost, + } from "./utils/index"; import { selectedNode } from "./store"; import { create_new_swarm_ec2, @@ -60,6 +64,7 @@ let swarm_name_width = max_input_with; let aws_instance_types = []; let selected_instance = ""; + let vanity_address_error = ""; let selectedRowIds = []; @@ -494,7 +499,10 @@ } function updateVanityAddressWidth(event) { + vanity_address_error = ""; vanity_address = event.target.value.replace(/\s+/g, ""); + vanity_address_error = isValidVanityAddress(vanity_address); + const span = document.querySelector(".vanity_address_measure"); vanity_input_width = span.offsetWidth; if (!vanity_input_width) { @@ -671,7 +679,10 @@ 0} primaryButtonText={isSubmitting ? "Loading..." : "Create"} secondaryButtonText="Cancel" on:click:button--secondary={() => (open_create_ec2 = false)} @@ -711,17 +722,19 @@ {/if} - (selected_instance = e.target.value)} - helperText="Select Ec2 Instance Size" - labelText="Ec2 Instance Size" - selected={selected_instance} - > - - {#each aws_instance_types as option} - - {/each} - + + (selected_instance = e.target.value)} + helperText="Select Ec2 Instance Size" + labelText="Ec2 Instance Size" + selected={selected_instance} + > + + {#each aws_instance_types as option} + + {/each} + + Vanity Address @@ -740,6 +753,7 @@ {domain} {/if} + {vanity_address_error} @@ -774,7 +788,6 @@ overflow: hidden; border: solid 1px #494949; border-radius: 0.5rem; - margin-bottom: 1rem; } .suffix { @@ -818,4 +831,15 @@ border: none; margin: 0; } + + .error-message { + color: #d32f2f; + margin-top: 0; + font-size: 0.7rem; + } + + .select_instance_container { + margin-bottom: 1rem; + margin-top: 1rem; + } diff --git a/src/bin/super/superapp/src/utils/index.ts b/src/bin/super/superapp/src/utils/index.ts index d002cef9..d5cb5e48 100644 --- a/src/bin/super/superapp/src/utils/index.ts +++ b/src/bin/super/superapp/src/utils/index.ts @@ -19,3 +19,22 @@ export function getSwarmNumber(default_host: string) { return ""; } } + +export function isValidVanityAddress(vanity_address: string) { + const valid_chars = /^[a-zA-Z0-9-]+$/; // Only letters, numbers, and hyphens + const consecutive_hyphens = /--/; // Check for consecutive hyphens + + if (vanity_address.startsWith("-") || vanity_address.endsWith("-")) { + return "Hyphen cannot be the first or last character."; + } + + if (consecutive_hyphens.test(vanity_address)) { + return "Hyphens cannot appear consecutively."; + } + + if (!valid_chars.test(vanity_address) && vanity_address) { + return "Vanity address can only contain letters, numbers, and hyphens."; + } + + return ""; +} diff --git a/src/bin/super/util.rs b/src/bin/super/util.rs index f3ea62bb..6ef53544 100644 --- a/src/bin/super/util.rs +++ b/src/bin/super/util.rs @@ -519,6 +519,18 @@ async fn add_domain_name_to_route53(domain_name: &str, public_ip: &str) -> Resul //Sample execution function pub async fn create_swarm_ec2(info: &CreateEc2InstanceInfo) -> Result<(), Error> { + if let Some(vanity_address) = &info.vanity_address { + if !vanity_address.is_empty() { + if let Some(subdomain) = vanity_address.strip_suffix(".sphinx.chat") { + let domain_status = is_valid_domain(subdomain.to_string()); + if !domain_status.is_empty() { + return Err(anyhow!(domain_status)); + } + } else { + return Err(anyhow!("Vanity Address doesn't match the expected format.")); + } + } + } let ec2_intance_id = create_ec2_instance( info.name.clone(), info.vanity_address.clone(), @@ -542,3 +554,28 @@ pub async fn create_swarm_ec2(info: &CreateEc2InstanceInfo) -> Result<(), Error> log::info!("Public_IP: {}", ec2_ip_address); Ok(()) } + +fn is_valid_domain(domain: String) -> String { + let valid_chars = |c: char| c.is_ascii_alphanumeric() || c == '-'; + + if domain.starts_with('-') || domain.ends_with('-') { + return "Hyphen cannot be the first or last character.".to_string(); + } + + let mut previous_char: Option = None; + for c in domain.chars() { + if !valid_chars(c) { + return "Domain can only contain letters, numbers, and hyphens.".to_string(); + } + + if let Some(prev) = previous_char { + if prev == '-' && c == '-' { + return "Hyphens cannot appear consecutively.".to_string(); + } + } + + previous_char = Some(c); + } + + "".to_string() +}