diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 111b0222..fb0b516a 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -28,7 +28,7 @@ jobs: run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - name: Install Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v5 with: go-version: 1.24 diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index f2129300..c3db3738 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v5 - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v4 with: node-version: '22' diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 4a574d91..d0ca1685 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -14,7 +14,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@v9 with: days-before-stale: 14 days-before-close: 14 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d22c300..12316cd7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v4 with: node-version: '22' diff --git a/README.md b/README.md index 1d21b8c8..287f5e20 100644 --- a/README.md +++ b/README.md @@ -20,24 +20,15 @@ _Pangolin tunnels your services to the internet so you can access anything from Website | - - Quick Install Guide + + Install Guide | - + Contact Us - | - - Slack - - | - - Discord - -[![Slack](https://img.shields.io/badge/chat-slack-yellow?style=flat-square&logo=slack)](https://digpangolin.com/slack) [![Docker](https://img.shields.io/docker/pulls/fosrl/pangolin?style=flat-square)](https://hub.docker.com/r/fosrl/pangolin) ![Stars](https://img.shields.io/github/stars/fosrl/pangolin?style=flat-square) [![Discord](https://img.shields.io/discord/1325658630518865980?logo=discord&style=flat-square)](https://discord.gg/HCJR8Xhme4) diff --git a/blueprint.py b/blueprint.py deleted file mode 100644 index b4f1a99c..00000000 --- a/blueprint.py +++ /dev/null @@ -1,72 +0,0 @@ -import requests -import yaml -import json -import base64 - -# The file path for the YAML file to be read -# You can change this to the path of your YAML file -YAML_FILE_PATH = 'blueprint.yaml' - -# The API endpoint and headers from the curl request -API_URL = 'http://localhost:3004/v1/org/test/blueprint' -HEADERS = { - 'accept': '*/*', - 'Authorization': 'Bearer v7ix7xha1bmq2on.tzsden374mtmkeczm3tx44uzxsljnrst7nmg7ccr', - 'Content-Type': 'application/json' -} - -def convert_and_send(file_path, url, headers): - """ - Reads a YAML file, converts its content to a JSON payload, - and sends it via a PUT request to a specified URL. - """ - try: - # Read the YAML file content - with open(file_path, 'r') as file: - yaml_content = file.read() - - # Parse the YAML string to a Python dictionary - # This will be used to ensure the YAML is valid before sending - parsed_yaml = yaml.safe_load(yaml_content) - - # convert the parsed YAML to a JSON string - json_payload = json.dumps(parsed_yaml) - print("Converted JSON payload:") - print(json_payload) - - # Encode the JSON string to Base64 - encoded_json = base64.b64encode(json_payload.encode('utf-8')).decode('utf-8') - - # Create the final payload with the base64 encoded data - final_payload = { - "blueprint": encoded_json - } - - print("Sending the following Base64 encoded JSON payload:") - print(final_payload) - print("-" * 20) - - # Make the PUT request with the base64 encoded payload - response = requests.put(url, headers=headers, json=final_payload) - - # Print the API response for debugging - print(f"API Response Status Code: {response.status_code}") - print("API Response Content:") - print(response.text) - - # Raise an exception for bad status codes (4xx or 5xx) - response.raise_for_status() - - except FileNotFoundError: - print(f"Error: The file '{file_path}' was not found.") - except yaml.YAMLError as e: - print(f"Error parsing YAML file: {e}") - except requests.exceptions.RequestException as e: - print(f"An error occurred during the API request: {e}") - except Exception as e: - print(f"An unexpected error occurred: {e}") - -# Run the function -if __name__ == "__main__": - convert_and_send(YAML_FILE_PATH, API_URL, HEADERS) - diff --git a/blueprint.yaml b/blueprint.yaml deleted file mode 100644 index 03c51521..00000000 --- a/blueprint.yaml +++ /dev/null @@ -1,69 +0,0 @@ -client-resources: - client-resource-nice-id-uno: - name: this is my resource - protocol: tcp - proxy-port: 3001 - hostname: localhost - internal-port: 3000 - site: lively-yosemite-toad - client-resource-nice-id-duce: - name: this is my resource - protocol: udp - proxy-port: 3000 - hostname: localhost - internal-port: 3000 - site: lively-yosemite-toad - -proxy-resources: - resource-nice-id-uno: - name: this is my resource - protocol: http - full-domain: duce.test.example.com - host-header: example.com - tls-server-name: example.com - # auth: - # pincode: 123456 - # password: sadfasdfadsf - # sso-enabled: true - # sso-roles: - # - Member - # sso-users: - # - owen@fossorial.io - # whitelist-users: - # - owen@fossorial.io - headers: - - name: X-Example-Header - value: example-value - - name: X-Another-Header - value: another-value - rules: - - action: allow - match: ip - value: 1.1.1.1 - - action: deny - match: cidr - value: 2.2.2.2/32 - - action: pass - match: path - value: /admin - targets: - - site: lively-yosemite-toad - path: /path - pathMatchType: prefix - hostname: localhost - method: http - port: 8000 - - site: slim-alpine-chipmunk - hostname: localhost - path: /yoman - pathMatchType: exact - method: http - port: 8001 - resource-nice-id-duce: - name: this is other resource - protocol: tcp - proxy-port: 3000 - targets: - - site: lively-yosemite-toad - hostname: localhost - port: 3000 \ No newline at end of file diff --git a/config/traefik/dynamic_config.yml b/config/traefik/dynamic_config.yml index 8465a9cf..8fcf8e55 100644 --- a/config/traefik/dynamic_config.yml +++ b/config/traefik/dynamic_config.yml @@ -16,9 +16,8 @@ http: # Next.js router (handles everything except API and WebSocket paths) next-router: - rule: "Host(`{{.DashboardDomain}}`)" + rule: "Host(`{{.DashboardDomain}}`) && !PathPrefix(`/api/v1`)" service: next-service - priority: 10 entryPoints: - websecure tls: @@ -28,7 +27,15 @@ http: api-router: rule: "Host(`{{.DashboardDomain}}`) && PathPrefix(`/api/v1`)" service: api-service - priority: 100 + entryPoints: + - websecure + tls: + certResolver: letsencrypt + + # WebSocket router + ws-router: + rule: "Host(`{{.DashboardDomain}}`)" + service: api-service entryPoints: - websecure tls: diff --git a/docker-compose.yml b/docker-compose.yml index 469f9b4c..09b150d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: environment: - NODE_ENV=development - ENVIRONMENT=dev + - DB_TYPE=pg volumes: # Mount source code for hot reload - ./src:/app/src diff --git a/esbuild.mjs b/esbuild.mjs index 8086a77e..d76c0753 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -52,7 +52,7 @@ esbuild bundle: true, outfile: argv.out, format: "esm", - minify: false, + minify: true, banner: { js: banner, }, @@ -63,7 +63,7 @@ esbuild packagePath: getPackagePaths(), }), ], - sourcemap: "inline", + sourcemap: "external", target: "node22", }) .then(() => { diff --git a/install/go.mod b/install/go.mod index b1465ac5..b12f9ef4 100644 --- a/install/go.mod +++ b/install/go.mod @@ -1,10 +1,10 @@ module installer -go 1.24.0 +go 1.24 require ( - golang.org/x/term v0.35.0 + golang.org/x/term v0.34.0 gopkg.in/yaml.v3 v3.0.1 ) -require golang.org/x/sys v0.36.0 // indirect +require golang.org/x/sys v0.35.0 // indirect diff --git a/install/go.sum b/install/go.sum index 789a291b..320762f0 100644 --- a/install/go.sum +++ b/install/go.sum @@ -1,7 +1,7 @@ -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/install/main.go b/install/main.go index 1f7213a1..1d684b51 100644 --- a/install/main.go +++ b/install/main.go @@ -8,8 +8,6 @@ import ( "io" "io/fs" "math/rand" - "net" - "net/http" "os" "os/exec" "path/filepath" @@ -17,13 +15,14 @@ import ( "strings" "text/template" "time" + "net" ) // DO NOT EDIT THIS FUNCTION; IT MATCHED BY REGEX IN CICD func loadVersions(config *Config) { - config.PangolinVersion = "1.9.4" - config.GerbilVersion = "1.2.1" - config.BadgerVersion = "1.2.0" + config.PangolinVersion = "replaceme" + config.GerbilVersion = "replaceme" + config.BadgerVersion = "replaceme" } //go:embed config/* @@ -75,7 +74,7 @@ func main() { if err := checkPortsAvailable(p); err != nil { fmt.Fprintln(os.Stderr, err) - fmt.Printf("Please close any services on ports 80/443 in order to run the installation smoothly. If you already have the Pangolin stack running, shut them down before proceeding.\n") + fmt.Printf("Please close any services on ports 80/443 in order to run the installation smoothly") os.Exit(1) } } @@ -127,7 +126,7 @@ func main() { if readBool(reader, "Would you like to install and start the containers?", true) { config.InstallationContainerType = podmanOrDocker(reader) - + if !isDockerInstalled() && runtime.GOOS == "linux" && config.InstallationContainerType == Docker { if readBool(reader, "Docker is not installed. Would you like to install it?", true) { installDocker() @@ -205,17 +204,8 @@ func main() { } } - config.InstallationContainerType = podmanOrDocker(reader) - config.DoCrowdsecInstall = true - err := installCrowdsec(config) - if (err != nil) { - fmt.Printf("Error installing CrowdSec: %v\n", err) - return - } - - fmt.Println("CrowdSec installed successfully!") - return + installCrowdsec(config) } } } @@ -332,18 +322,13 @@ func collectUserInput(reader *bufio.Reader) Config { if config.HybridMode { alreadyHaveCreds := readBool(reader, "Do you already have credentials from the dashboard? If not, we will create them later", false) - + if alreadyHaveCreds { config.HybridId = readString(reader, "Enter your ID", "") config.HybridSecret = readString(reader, "Enter your secret", "") - } + } - // Try to get public IP as default - publicIP := getPublicIP() - if publicIP != "" { - fmt.Printf("Detected public IP: %s\n", publicIP) - } - config.DashboardDomain = readString(reader, "The public addressable IP address for this node or a domain pointing to it", publicIP) + config.DashboardDomain = readString(reader, "The public addressable IP address for this node or a domain pointing to it", "") config.InstallGerbil = true } else { config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "") @@ -360,7 +345,7 @@ func collectUserInput(reader *bufio.Reader) Config { // Email configuration fmt.Println("\n=== Email Configuration ===") config.EnableEmail = readBool(reader, "Enable email functionality (SMTP)", false) - + if config.EnableEmail { config.EmailSMTPHost = readString(reader, "Enter SMTP host", "") config.EmailSMTPPort = readInt(reader, "Enter SMTP port (default 587)", 587) @@ -368,7 +353,7 @@ func collectUserInput(reader *bufio.Reader) Config { config.EmailSMTPPass = readString(reader, "Enter SMTP password", "") // Should this be readPassword? config.EmailNoReply = readString(reader, "Enter no-reply email address", "") } - + // Validate required fields if config.BaseDomain == "" { fmt.Println("Error: Domain name is required") @@ -599,32 +584,6 @@ func generateRandomSecretKey() string { return string(b) } -func getPublicIP() string { - client := &http.Client{ - Timeout: 10 * time.Second, - } - - resp, err := client.Get("https://ifconfig.io/ip") - if err != nil { - return "" - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "" - } - - ip := strings.TrimSpace(string(body)) - - // Validate that it's a valid IP address - if net.ParseIP(ip) != nil { - return ip - } - - return "" -} - // Run external commands with stdio/stderr attached. func run(name string, args ...string) error { cmd := exec.Command(name, args...) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index f617a768..d17c99f3 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "An error occurred while adding user to the role.", "userSaved": "User saved", "userSavedDescription": "The user has been updated.", - "autoProvisioned": "Auto Provisioned", - "autoProvisionedDescription": "Allow this user to be automatically managed by identity provider", "accessControlsDescription": "Manage what this user can access and do in the organization", "accessControlsSubmit": "Save Access Controls", "roles": "Roles", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Invalid IP address format", "ipAddressErrorInvalidOctet": "Invalid IP address octet", "path": "Path", - "matchPath": "Match Path", "ipAddressRange": "IP Range", "rulesErrorFetch": "Failed to fetch rules", "rulesErrorFetchDescription": "An error occurred while fetching rules", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Connected", "idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.", "idpErrorNotFound": "IdP not found", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Invalid Invite", "inviteInvalidDescription": "The invite link is invalid.", "inviteErrorWrongUser": "Invite is not for this user", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Professional Edition Required", "licenseTierProfessionalRequiredDescription": "This feature is only available in the Professional Edition.", "actionGetOrg": "Get Organization", - "updateOrgUser": "Update Org User", - "createOrgUser": "Create Org User", "actionUpdateOrg": "Update Organization", "actionUpdateUser": "Update User", "actionGetUser": "Get User", @@ -998,7 +991,6 @@ "actionDeleteSite": "Delete Site", "actionGetSite": "Get Site", "actionListSites": "List Sites", - "actionApplyBlueprint": "Apply Blueprint", "setupToken": "Setup Token", "setupTokenDescription": "Enter the setup token from the server console.", "setupTokenRequired": "Setup token is required", @@ -1141,8 +1133,8 @@ "sidebarLicense": "License", "sidebarClients": "Clients (Beta)", "sidebarDomains": "Domains", - "enableDockerSocket": "Enable Docker Blueprint", - "enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to Newt.", + "enableDockerSocket": "Enable Docker Socket", + "enableDockerSocketDescription": "Enable Docker Socket discovery for populating container information. Socket path must be provided to Newt.", "enableDockerSocketLink": "Learn More", "viewDockerContainers": "View Docker Containers", "containersIn": "Containers in {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Update Available", "newtUpdateAvailableInfo": "A new version of Newt is available. Please update to the latest version for the best experience.", "domainPickerEnterDomain": "Domain", - "domainPickerPlaceholder": "myapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, or just myapp", "domainPickerDescription": "Enter the full domain of the resource to see available options.", "domainPickerDescriptionSaas": "Enter a full domain, subdomain, or just a name to see available options", "domainPickerTabAll": "All", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protocol", "editInternalResourceDialogSitePort": "Site Port", "editInternalResourceDialogTargetConfiguration": "Target Configuration", + "editInternalResourceDialogDestinationIP": "Destination IP", + "editInternalResourceDialogDestinationPort": "Destination Port", "editInternalResourceDialogCancel": "Cancel", "editInternalResourceDialogSaveResource": "Save Resource", "editInternalResourceDialogSuccess": "Success", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Site Port", "createInternalResourceDialogSitePortDescription": "Use this port to access the resource on the site when connected with a client.", "createInternalResourceDialogTargetConfiguration": "Target Configuration", - "createInternalResourceDialogDestinationIPDescription": "The IP or hostname address of the resource on the site's network.", + "createInternalResourceDialogDestinationIP": "Destination IP", + "createInternalResourceDialogDestinationIPDescription": "The IP address of the resource on the site's network.", + "createInternalResourceDialogDestinationPort": "Destination Port", "createInternalResourceDialogDestinationPortDescription": "The port on the destination IP where the resource is accessible.", "createInternalResourceDialogCancel": "Cancel", "createInternalResourceDialogCreateResource": "Create Resource", @@ -1500,24 +1496,5 @@ "convertButton": "Convert This Node to Managed Self-Hosted" }, "internationaldomaindetected": "International Domain Detected", - "willbestoredas": "Will be stored as:", - "idpGoogleDescription": "Google OAuth2/OIDC provider", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Custom Headers", - "headersValidationError": "Headers must be in the format: Header-Name: value.", - "domainPickerProvidedDomain": "Provided Domain", - "domainPickerFreeProvidedDomain": "Free Provided Domain", - "domainPickerVerified": "Verified", - "domainPickerUnverified": "Unverified", - "domainPickerInvalidSubdomainStructure": "This subdomain contains invalid characters or structure. It will be sanitized automatically when you save.", - "domainPickerError": "Error", - "domainPickerErrorLoadDomains": "Failed to load organization domains", - "domainPickerErrorCheckAvailability": "Failed to check domain availability", - "domainPickerInvalidSubdomain": "Invalid subdomain", - "domainPickerInvalidSubdomainRemoved": "The input \"{sub}\" was removed because it's not valid.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" could not be made valid for {domain}.", - "domainPickerSubdomainSanitized": "Subdomain sanitized", - "domainPickerSubdomainCorrected": "\"{sub}\" was corrected to \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Edit file: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Edit file: docker-compose.yml" + "willbestoredas": "Will be stored as:" } diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 7b391431..727d9a5e 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "An error occurred while adding user to the role.", "userSaved": "User saved", "userSavedDescription": "The user has been updated.", - "autoProvisioned": "Auto Provisioned", - "autoProvisionedDescription": "Allow this user to be automatically managed by identity provider", "accessControlsDescription": "Manage what this user can access and do in the organization", "accessControlsSubmit": "Save Access Controls", "roles": "Roles", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Invalid IP address format", "ipAddressErrorInvalidOctet": "Invalid IP address octet", "path": "Path", - "matchPath": "Match Path", "ipAddressRange": "IP Range", "rulesErrorFetch": "Failed to fetch rules", "rulesErrorFetchDescription": "An error occurred while fetching rules", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Connected", "idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.", "idpErrorNotFound": "IdP not found", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Invalid Invite", "inviteInvalidDescription": "The invite link is invalid.", "inviteErrorWrongUser": "Invite is not for this user", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Professional Edition Required", "licenseTierProfessionalRequiredDescription": "This feature is only available in the Professional Edition.", "actionGetOrg": "Get Organization", - "updateOrgUser": "Update Org User", - "createOrgUser": "Create Org User", "actionUpdateOrg": "Update Organization", "actionUpdateUser": "Update User", "actionGetUser": "Get User", @@ -998,7 +991,6 @@ "actionDeleteSite": "Delete Site", "actionGetSite": "Get Site", "actionListSites": "List Sites", - "actionApplyBlueprint": "Apply Blueprint", "setupToken": "Setup Token", "setupTokenDescription": "Enter the setup token from the server console.", "setupTokenRequired": "Setup token is required", @@ -1141,8 +1133,8 @@ "sidebarLicense": "License", "sidebarClients": "Clients (Beta)", "sidebarDomains": "Domains", - "enableDockerSocket": "Enable Docker Blueprint", - "enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to Newt.", + "enableDockerSocket": "Enable Docker Socket", + "enableDockerSocketDescription": "Enable Docker Socket discovery for populating container information. Socket path must be provided to Newt.", "enableDockerSocketLink": "Learn More", "viewDockerContainers": "View Docker Containers", "containersIn": "Containers in {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Update Available", "newtUpdateAvailableInfo": "A new version of Newt is available. Please update to the latest version for the best experience.", "domainPickerEnterDomain": "Domain", - "domainPickerPlaceholder": "myapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, or just myapp", "domainPickerDescription": "Enter the full domain of the resource to see available options.", "domainPickerDescriptionSaas": "Enter a full domain, subdomain, or just a name to see available options", "domainPickerTabAll": "All", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protocol", "editInternalResourceDialogSitePort": "Site Port", "editInternalResourceDialogTargetConfiguration": "Target Configuration", + "editInternalResourceDialogDestinationIP": "Destination IP", + "editInternalResourceDialogDestinationPort": "Destination Port", "editInternalResourceDialogCancel": "Cancel", "editInternalResourceDialogSaveResource": "Save Resource", "editInternalResourceDialogSuccess": "Success", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Site Port", "createInternalResourceDialogSitePortDescription": "Use this port to access the resource on the site when connected with a client.", "createInternalResourceDialogTargetConfiguration": "Target Configuration", - "createInternalResourceDialogDestinationIPDescription": "The IP or hostname address of the resource on the site's network.", + "createInternalResourceDialogDestinationIP": "Destination IP", + "createInternalResourceDialogDestinationIPDescription": "The IP address of the resource on the site's network.", + "createInternalResourceDialogDestinationPort": "Destination Port", "createInternalResourceDialogDestinationPortDescription": "The port on the destination IP where the resource is accessible.", "createInternalResourceDialogCancel": "Cancel", "createInternalResourceDialogCreateResource": "Create Resource", @@ -1500,24 +1496,5 @@ "convertButton": "Convert This Node to Managed Self-Hosted" }, "internationaldomaindetected": "International Domain Detected", - "willbestoredas": "Will be stored as:", - "idpGoogleDescription": "Google OAuth2/OIDC provider", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Custom Headers", - "headersValidationError": "Headers must be in the format: Header-Name: value.", - "domainPickerProvidedDomain": "Provided Domain", - "domainPickerFreeProvidedDomain": "Free Provided Domain", - "domainPickerVerified": "Verified", - "domainPickerUnverified": "Unverified", - "domainPickerInvalidSubdomainStructure": "This subdomain contains invalid characters or structure. It will be sanitized automatically when you save.", - "domainPickerError": "Error", - "domainPickerErrorLoadDomains": "Failed to load organization domains", - "domainPickerErrorCheckAvailability": "Failed to check domain availability", - "domainPickerInvalidSubdomain": "Invalid subdomain", - "domainPickerInvalidSubdomainRemoved": "The input \"{sub}\" was removed because it's not valid.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" could not be made valid for {domain}.", - "domainPickerSubdomainSanitized": "Subdomain sanitized", - "domainPickerSubdomainCorrected": "\"{sub}\" was corrected to \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Edit file: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Edit file: docker-compose.yml" + "willbestoredas": "Will be stored as:" } diff --git a/messages/de-DE.json b/messages/de-DE.json index 099bcff9..54d14c8d 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Beim Hinzufügen des Benutzers zur Rolle ist ein Fehler aufgetreten.", "userSaved": "Benutzer gespeichert", "userSavedDescription": "Der Benutzer wurde aktualisiert.", - "autoProvisioned": "Automatisch vorgesehen", - "autoProvisionedDescription": "Erlaube diesem Benutzer die automatische Verwaltung durch Identitätsanbieter", "accessControlsDescription": "Verwalten Sie, worauf dieser Benutzer in der Organisation zugreifen und was er tun kann", "accessControlsSubmit": "Zugriffskontrollen speichern", "roles": "Rollen", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Ungültiges IP-Adressformat", "ipAddressErrorInvalidOctet": "Ungültiges IP-Adress-Oktett", "path": "Pfad", - "matchPath": "Unterverzeichnis", "ipAddressRange": "IP-Bereich", "rulesErrorFetch": "Fehler beim Abrufen der Regeln", "rulesErrorFetchDescription": "Beim Abrufen der Regeln ist ein Fehler aufgetreten", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Verbunden", "idpErrorConnectingTo": "Es gab ein Problem bei der Verbindung zu {name}. Bitte kontaktieren Sie Ihren Administrator.", "idpErrorNotFound": "IdP nicht gefunden", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Ungültige Einladung", "inviteInvalidDescription": "Der Einladungslink ist ungültig.", "inviteErrorWrongUser": "Einladung ist nicht für diesen Benutzer", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Professional Edition erforderlich", "licenseTierProfessionalRequiredDescription": "Diese Funktion ist nur in der Professional Edition verfügbar.", "actionGetOrg": "Organisation abrufen", - "updateOrgUser": "Org Benutzer aktualisieren", - "createOrgUser": "Org Benutzer erstellen", "actionUpdateOrg": "Organisation aktualisieren", "actionUpdateUser": "Benutzer aktualisieren", "actionGetUser": "Benutzer abrufen", @@ -998,7 +991,6 @@ "actionDeleteSite": "Standort löschen", "actionGetSite": "Standort abrufen", "actionListSites": "Standorte auflisten", - "actionApplyBlueprint": "Blaupause anwenden", "setupToken": "Setup-Token", "setupTokenDescription": "Geben Sie das Setup-Token von der Serverkonsole ein.", "setupTokenRequired": "Setup-Token ist erforderlich", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Lizenz", "sidebarClients": "Clients (Beta)", "sidebarDomains": "Domains", - "enableDockerSocket": "Docker Blaupause aktivieren", - "enableDockerSocketDescription": "Aktiviere Docker-Socket-Label-Scraping für Blaupausenbeschriftungen. Der Socket-Pfad muss neu angegeben werden.", + "enableDockerSocket": "Docker Socket aktivieren", + "enableDockerSocketDescription": "Docker Socket-Erkennung aktivieren, um Container-Informationen zu befüllen. Socket-Pfad muss Newt bereitgestellt werden.", "enableDockerSocketLink": "Mehr erfahren", "viewDockerContainers": "Docker Container anzeigen", "containersIn": "Container in {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Update verfügbar", "newtUpdateAvailableInfo": "Eine neue Version von Newt ist verfügbar. Bitte aktualisieren Sie auf die neueste Version für das beste Erlebnis.", "domainPickerEnterDomain": "Domain", - "domainPickerPlaceholder": "myapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, oder einfach myapp", "domainPickerDescription": "Geben Sie die vollständige Domäne der Ressource ein, um verfügbare Optionen zu sehen.", "domainPickerDescriptionSaas": "Geben Sie eine vollständige Domäne, Subdomäne oder einfach einen Namen ein, um verfügbare Optionen zu sehen", "domainPickerTabAll": "Alle", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protokoll", "editInternalResourceDialogSitePort": "Site-Port", "editInternalResourceDialogTargetConfiguration": "Zielkonfiguration", + "editInternalResourceDialogDestinationIP": "Ziel-IP", + "editInternalResourceDialogDestinationPort": "Ziel-Port", "editInternalResourceDialogCancel": "Abbrechen", "editInternalResourceDialogSaveResource": "Ressource speichern", "editInternalResourceDialogSuccess": "Erfolg", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Site-Port", "createInternalResourceDialogSitePortDescription": "Verwenden Sie diesen Port, um bei Verbindung mit einem Client auf die Ressource an der Site zuzugreifen.", "createInternalResourceDialogTargetConfiguration": "Zielkonfiguration", - "createInternalResourceDialogDestinationIPDescription": "Die IP-Adresse oder Hostname Adresse der Ressource im Netzwerk der Website.", + "createInternalResourceDialogDestinationIP": "Ziel-IP", + "createInternalResourceDialogDestinationIPDescription": "Die IP-Adresse der Ressource im Netzwerkstandort der Site.", + "createInternalResourceDialogDestinationPort": "Ziel-Port", "createInternalResourceDialogDestinationPortDescription": "Der Port auf der Ziel-IP, unter dem die Ressource zugänglich ist.", "createInternalResourceDialogCancel": "Abbrechen", "createInternalResourceDialogCreateResource": "Ressource erstellen", @@ -1500,24 +1496,5 @@ "convertButton": "Diesen Knoten in Managed Self-Hosted umwandeln" }, "internationaldomaindetected": "Internationale Domain erkannt", - "willbestoredas": "Wird gespeichert als:", - "idpGoogleDescription": "Google OAuth2/OIDC Provider", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Eigene Kopfzeilen", - "headersValidationError": "Header müssen im Format Header-Name: Wert sein.", - "domainPickerProvidedDomain": "Angegebene Domain", - "domainPickerFreeProvidedDomain": "Kostenlose Domain", - "domainPickerVerified": "Verifiziert", - "domainPickerUnverified": "Nicht verifiziert", - "domainPickerInvalidSubdomainStructure": "Diese Subdomain enthält ungültige Zeichen oder Struktur. Sie wird beim Speichern automatisch bereinigt.", - "domainPickerError": "Fehler", - "domainPickerErrorLoadDomains": "Fehler beim Laden der Organisations-Domänen", - "domainPickerErrorCheckAvailability": "Fehler beim Prüfen der Domain-Verfügbarkeit", - "domainPickerInvalidSubdomain": "Ungültige Subdomain", - "domainPickerInvalidSubdomainRemoved": "Die Eingabe \"{sub}\" wurde entfernt, weil sie nicht gültig ist.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" konnte nicht für {domain} gültig gemacht werden.", - "domainPickerSubdomainSanitized": "Subdomain bereinigt", - "domainPickerSubdomainCorrected": "\"{sub}\" wurde korrigiert zu \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Datei bearbeiten: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Datei bearbeiten: docker-compose.yml" + "willbestoredas": "Wird gespeichert als:" } diff --git a/messages/en-US.json b/messages/en-US.json index a483eed2..d238f73c 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "An error occurred while adding user to the role.", "userSaved": "User saved", "userSavedDescription": "The user has been updated.", - "autoProvisioned": "Auto Provisioned", - "autoProvisionedDescription": "Allow this user to be automatically managed by identity provider", "accessControlsDescription": "Manage what this user can access and do in the organization", "accessControlsSubmit": "Save Access Controls", "roles": "Roles", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Invalid IP address format", "ipAddressErrorInvalidOctet": "Invalid IP address octet", "path": "Path", - "matchPath": "Match Path", "ipAddressRange": "IP Range", "rulesErrorFetch": "Failed to fetch rules", "rulesErrorFetchDescription": "An error occurred while fetching rules", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Connected", "idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.", "idpErrorNotFound": "IdP not found", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Invalid Invite", "inviteInvalidDescription": "The invite link is invalid.", "inviteErrorWrongUser": "Invite is not for this user", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Professional Edition Required", "licenseTierProfessionalRequiredDescription": "This feature is only available in the Professional Edition.", "actionGetOrg": "Get Organization", - "updateOrgUser": "Update Org User", - "createOrgUser": "Create Org User", "actionUpdateOrg": "Update Organization", "actionUpdateUser": "Update User", "actionGetUser": "Get User", @@ -998,7 +991,6 @@ "actionDeleteSite": "Delete Site", "actionGetSite": "Get Site", "actionListSites": "List Sites", - "actionApplyBlueprint": "Apply Blueprint", "setupToken": "Setup Token", "setupTokenDescription": "Enter the setup token from the server console.", "setupTokenRequired": "Setup token is required", @@ -1141,8 +1133,8 @@ "sidebarLicense": "License", "sidebarClients": "Clients (Beta)", "sidebarDomains": "Domains", - "enableDockerSocket": "Enable Docker Blueprint", - "enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to Newt.", + "enableDockerSocket": "Enable Docker Socket", + "enableDockerSocketDescription": "Enable Docker Socket discovery for populating container information. Socket path must be provided to Newt.", "enableDockerSocketLink": "Learn More", "viewDockerContainers": "View Docker Containers", "containersIn": "Containers in {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Update Available", "newtUpdateAvailableInfo": "A new version of Newt is available. Please update to the latest version for the best experience.", "domainPickerEnterDomain": "Domain", - "domainPickerPlaceholder": "myapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, or just myapp", "domainPickerDescription": "Enter the full domain of the resource to see available options.", "domainPickerDescriptionSaas": "Enter a full domain, subdomain, or just a name to see available options", "domainPickerTabAll": "All", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protocol", "editInternalResourceDialogSitePort": "Site Port", "editInternalResourceDialogTargetConfiguration": "Target Configuration", + "editInternalResourceDialogDestinationIP": "Destination IP", + "editInternalResourceDialogDestinationPort": "Destination Port", "editInternalResourceDialogCancel": "Cancel", "editInternalResourceDialogSaveResource": "Save Resource", "editInternalResourceDialogSuccess": "Success", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Site Port", "createInternalResourceDialogSitePortDescription": "Use this port to access the resource on the site when connected with a client.", "createInternalResourceDialogTargetConfiguration": "Target Configuration", - "createInternalResourceDialogDestinationIPDescription": "The IP or hostname address of the resource on the site's network.", + "createInternalResourceDialogDestinationIP": "Destination IP", + "createInternalResourceDialogDestinationIPDescription": "The IP address of the resource on the site's network.", + "createInternalResourceDialogDestinationPort": "Destination Port", "createInternalResourceDialogDestinationPortDescription": "The port on the destination IP where the resource is accessible.", "createInternalResourceDialogCancel": "Cancel", "createInternalResourceDialogCreateResource": "Create Resource", @@ -1500,24 +1496,5 @@ "convertButton": "Convert This Node to Managed Self-Hosted" }, "internationaldomaindetected": "International Domain Detected", - "willbestoredas": "Will be stored as:", - "idpGoogleDescription": "Google OAuth2/OIDC provider", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Custom Headers", - "headersValidationError": "Headers must be in the format: Header-Name: value.", - "domainPickerProvidedDomain": "Provided Domain", - "domainPickerFreeProvidedDomain": "Free Provided Domain", - "domainPickerVerified": "Verified", - "domainPickerUnverified": "Unverified", - "domainPickerInvalidSubdomainStructure": "This subdomain contains invalid characters or structure. It will be sanitized automatically when you save.", - "domainPickerError": "Error", - "domainPickerErrorLoadDomains": "Failed to load organization domains", - "domainPickerErrorCheckAvailability": "Failed to check domain availability", - "domainPickerInvalidSubdomain": "Invalid subdomain", - "domainPickerInvalidSubdomainRemoved": "The input \"{sub}\" was removed because it's not valid.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" could not be made valid for {domain}.", - "domainPickerSubdomainSanitized": "Subdomain sanitized", - "domainPickerSubdomainCorrected": "\"{sub}\" was corrected to \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Edit file: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Edit file: docker-compose.yml" + "willbestoredas": "Will be stored as:" } diff --git a/messages/es-ES.json b/messages/es-ES.json index 0a835b33..fe8c52d1 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Ocurrió un error mientras se añadía el usuario al rol.", "userSaved": "Usuario guardado", "userSavedDescription": "El usuario ha sido actualizado.", - "autoProvisioned": "Auto asegurado", - "autoProvisionedDescription": "Permitir a este usuario ser administrado automáticamente por el proveedor de identidad", "accessControlsDescription": "Administrar lo que este usuario puede acceder y hacer en la organización", "accessControlsSubmit": "Guardar controles de acceso", "roles": "Roles", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Formato de dirección IP inválido", "ipAddressErrorInvalidOctet": "Octet de dirección IP no válido", "path": "Ruta", - "matchPath": "Coincidir ruta", "ipAddressRange": "Rango IP", "rulesErrorFetch": "Error al obtener las reglas", "rulesErrorFetchDescription": "Se ha producido un error al recuperar las reglas", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Conectado", "idpErrorConnectingTo": "Hubo un problema al conectar con {name}. Por favor, póngase en contacto con su administrador.", "idpErrorNotFound": "IdP no encontrado", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Invitación inválida", "inviteInvalidDescription": "El enlace de invitación no es válido.", "inviteErrorWrongUser": "La invitación no es para este usuario", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Edición Profesional requerida", "licenseTierProfessionalRequiredDescription": "Esta característica sólo está disponible en la Edición Profesional.", "actionGetOrg": "Obtener organización", - "updateOrgUser": "Actualizar usuario Org", - "createOrgUser": "Crear usuario Org", "actionUpdateOrg": "Actualizar organización", "actionUpdateUser": "Actualizar usuario", "actionGetUser": "Obtener usuario", @@ -998,7 +991,6 @@ "actionDeleteSite": "Eliminar sitio", "actionGetSite": "Obtener sitio", "actionListSites": "Listar sitios", - "actionApplyBlueprint": "Aplicar plano", "setupToken": "Configuración de token", "setupTokenDescription": "Ingrese el token de configuración desde la consola del servidor.", "setupTokenRequired": "Se requiere el token de configuración", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Licencia", "sidebarClients": "Clientes (Beta)", "sidebarDomains": "Dominios", - "enableDockerSocket": "Habilitar Plano Docker", - "enableDockerSocketDescription": "Activar el raspado de etiquetas de Socket Docker para etiquetas de planos. La ruta del Socket debe proporcionarse a Newt.", + "enableDockerSocket": "Habilitar conector Docker", + "enableDockerSocketDescription": "Habilitar el descubrimiento de Docker Socket para completar la información del contenedor. La ruta del socket debe proporcionarse a Newt.", "enableDockerSocketLink": "Saber más", "viewDockerContainers": "Ver contenedores Docker", "containersIn": "Contenedores en {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Nueva actualización disponible", "newtUpdateAvailableInfo": "Hay una nueva versión de Newt disponible. Actualice a la última versión para la mejor experiencia.", "domainPickerEnterDomain": "Dominio", - "domainPickerPlaceholder": "miapp.ejemplo.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.miDominio.com, o solo myapp", "domainPickerDescription": "Ingresa el dominio completo del recurso para ver las opciones disponibles.", "domainPickerDescriptionSaas": "Ingresa un dominio completo, subdominio o simplemente un nombre para ver las opciones disponibles", "domainPickerTabAll": "Todo", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protocolo", "editInternalResourceDialogSitePort": "Puerto del sitio", "editInternalResourceDialogTargetConfiguration": "Configuración de objetivos", + "editInternalResourceDialogDestinationIP": "IP de destino", + "editInternalResourceDialogDestinationPort": "Puerto de destino", "editInternalResourceDialogCancel": "Cancelar", "editInternalResourceDialogSaveResource": "Guardar recurso", "editInternalResourceDialogSuccess": "Éxito", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Puerto del sitio", "createInternalResourceDialogSitePortDescription": "Use este puerto para acceder al recurso en el sitio cuando se conecta con un cliente.", "createInternalResourceDialogTargetConfiguration": "Configuración de objetivos", - "createInternalResourceDialogDestinationIPDescription": "La dirección IP o nombre de host del recurso en la red del sitio.", + "createInternalResourceDialogDestinationIP": "IP de destino", + "createInternalResourceDialogDestinationIPDescription": "La dirección IP del recurso en la red del sitio.", + "createInternalResourceDialogDestinationPort": "Puerto de destino", "createInternalResourceDialogDestinationPortDescription": "El puerto en la IP de destino donde el recurso es accesible.", "createInternalResourceDialogCancel": "Cancelar", "createInternalResourceDialogCreateResource": "Crear recurso", @@ -1500,24 +1496,5 @@ "convertButton": "Convierte este nodo a autoalojado administrado" }, "internationaldomaindetected": "Dominio Internacional detectado", - "willbestoredas": "Se almacenará como:", - "idpGoogleDescription": "Proveedor OAuth2/OIDC de Google", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Cabeceras personalizadas", - "headersValidationError": "Los encabezados deben estar en el formato: Nombre de cabecera: valor.", - "domainPickerProvidedDomain": "Dominio proporcionado", - "domainPickerFreeProvidedDomain": "Dominio proporcionado gratis", - "domainPickerVerified": "Verificado", - "domainPickerUnverified": "Sin verificar", - "domainPickerInvalidSubdomainStructure": "Este subdominio contiene caracteres o estructura no válidos. Se limpiará automáticamente al guardar.", - "domainPickerError": "Error", - "domainPickerErrorLoadDomains": "Error al cargar los dominios de la organización", - "domainPickerErrorCheckAvailability": "No se pudo comprobar la disponibilidad del dominio", - "domainPickerInvalidSubdomain": "Subdominio inválido", - "domainPickerInvalidSubdomainRemoved": "La entrada \"{sub}\" fue eliminada porque no es válida.", - "domainPickerInvalidSubdomainCannotMakeValid": "No se ha podido hacer válido \"{sub}\" para {domain}.", - "domainPickerSubdomainSanitized": "Subdominio saneado", - "domainPickerSubdomainCorrected": "\"{sub}\" fue corregido a \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Editar archivo: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Editar archivo: docker-compose.yml" + "willbestoredas": "Se almacenará como:" } diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 0918f943..7f51a9c8 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -10,7 +10,7 @@ "setupErrorIdentifier": "L'ID de l'organisation est déjà pris. Veuillez en choisir un autre.", "componentsErrorNoMemberCreate": "Vous n'êtes actuellement membre d'aucune organisation. Créez une organisation pour commencer.", "componentsErrorNoMember": "Vous n'êtes actuellement membre d'aucune organisation.", - "welcome": "Bienvenue sur Pangolin", + "welcome": "Bienvenue à Pangolin", "welcomeTo": "Bienvenue chez", "componentsCreateOrg": "Créer une organisation", "componentsMember": "Vous êtes membre de {count, plural, =0 {aucune organisation} one {une organisation} other {# organisations}}.", @@ -34,13 +34,13 @@ "confirmPassword": "Confirmer le mot de passe", "createAccount": "Créer un compte", "viewSettings": "Afficher les paramètres", - "delete": "Supprimer", + "delete": "Supprimez", "name": "Nom", "online": "En ligne", "offline": "Hors ligne", "site": "Site", - "dataIn": "Données reçues", - "dataOut": "Données envoyées", + "dataIn": "Données dans", + "dataOut": "Données épuisées", "connectionType": "Type de connexion", "tunnelType": "Type de tunnel", "local": "Locale", @@ -175,7 +175,7 @@ "resourceHTTPSSettingsDescription": "Configurer comment votre ressource sera accédée via HTTPS", "domainType": "Type de domaine", "subdomain": "Sous-domaine", - "baseDomain": "Domaine racine", + "baseDomain": "Domaine de base", "subdomnainDescription": "Le sous-domaine où votre ressource sera accessible.", "resourceRawSettings": "Paramètres TCP/UDP", "resourceRawSettingsDescription": "Configurer comment votre ressource sera accédée via TCP/UDP", @@ -309,7 +309,7 @@ "numberOfSites": "Nombre de sites", "licenseKeySearch": "Rechercher des clés de licence...", "licenseKeyAdd": "Ajouter une clé de licence", - "type": "Type", + "type": "Type de texte", "licenseKeyRequired": "La clé de licence est requise", "licenseTermsAgree": "Vous devez accepter les conditions de licence", "licenseErrorKeyLoad": "Impossible de charger les clés de licence", @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Une erreur s'est produite lors de l'ajout de l'utilisateur au rôle.", "userSaved": "Utilisateur enregistré", "userSavedDescription": "L'utilisateur a été mis à jour.", - "autoProvisioned": "Auto-provisionné", - "autoProvisionedDescription": "Permettre à cet utilisateur d'être géré automatiquement par le fournisseur d'identité", "accessControlsDescription": "Gérer ce que cet utilisateur peut accéder et faire dans l'organisation", "accessControlsSubmit": "Enregistrer les contrôles d'accès", "roles": "Rôles", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Format d'adresse IP invalide", "ipAddressErrorInvalidOctet": "Octet d'adresse IP invalide", "path": "Chemin", - "matchPath": "Chemin de correspondance", "ipAddressRange": "Plage IP", "rulesErrorFetch": "Échec de la récupération des règles", "rulesErrorFetchDescription": "Une erreur s'est produite lors de la récupération des règles", @@ -598,7 +595,7 @@ "newtId": "ID Newt", "newtSecretKey": "Clé secrète Newt", "architecture": "Architecture", - "sites": "Sites", + "sites": "Espaces", "siteWgAnyClients": "Utilisez n'importe quel client WireGuard pour vous connecter. Vous devrez adresser vos ressources internes en utilisant l'IP du pair.", "siteWgCompatibleAllClients": "Compatible avec tous les clients WireGuard", "siteWgManualConfigurationRequired": "Configuration manuelle requise", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Connecté", "idpErrorConnectingTo": "Un problème est survenu lors de la connexion à {name}. Veuillez contacter votre administrateur.", "idpErrorNotFound": "IdP introuvable", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Invitation invalide", "inviteInvalidDescription": "Le lien d'invitation n'est pas valide.", "inviteErrorWrongUser": "L'invitation n'est pas pour cet utilisateur", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Édition Professionnelle Requise", "licenseTierProfessionalRequiredDescription": "Cette fonctionnalité n'est disponible que dans l'Édition Professionnelle.", "actionGetOrg": "Obtenir l'organisation", - "updateOrgUser": "Mise à jour de l'utilisateur Org", - "createOrgUser": "Créer un utilisateur Org", "actionUpdateOrg": "Mettre à jour l'organisation", "actionUpdateUser": "Mettre à jour l'utilisateur", "actionGetUser": "Obtenir l'utilisateur", @@ -998,7 +991,6 @@ "actionDeleteSite": "Supprimer un site", "actionGetSite": "Obtenir un site", "actionListSites": "Lister les sites", - "actionApplyBlueprint": "Appliquer le Plan", "setupToken": "Jeton de configuration", "setupTokenDescription": "Entrez le jeton de configuration depuis la console du serveur.", "setupTokenRequired": "Le jeton de configuration est requis.", @@ -1128,7 +1120,7 @@ "sidebarOverview": "Aperçu", "sidebarHome": "Domicile", "sidebarSites": "Espaces", - "sidebarResources": "Ressources", + "sidebarResources": "Ressource", "sidebarAccessControl": "Contrôle d'accès", "sidebarUsers": "Utilisateurs", "sidebarInvitations": "Invitations", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Licence", "sidebarClients": "Clients (Bêta)", "sidebarDomains": "Domaines", - "enableDockerSocket": "Activer le Plan Docker", - "enableDockerSocketDescription": "Activer le ramassage d'étiquettes de socket Docker pour les étiquettes de plan. Le chemin de socket doit être fourni à Newt.", + "enableDockerSocket": "Activer Docker Socket", + "enableDockerSocketDescription": "Activer la découverte Docker Socket pour remplir les informations du conteneur. Le chemin du socket doit être fourni à Newt.", "enableDockerSocketLink": "En savoir plus", "viewDockerContainers": "Voir les conteneurs Docker", "containersIn": "Conteneurs en {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Mise à jour disponible", "newtUpdateAvailableInfo": "Une nouvelle version de Newt est disponible. Veuillez mettre à jour vers la dernière version pour une meilleure expérience.", "domainPickerEnterDomain": "Domaine", - "domainPickerPlaceholder": "monapp.exemple.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, ou simplement myapp", "domainPickerDescription": "Entrez le domaine complet de la ressource pour voir les options disponibles.", "domainPickerDescriptionSaas": "Entrez un domaine complet, un sous-domaine ou juste un nom pour voir les options disponibles", "domainPickerTabAll": "Tous", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protocole", "editInternalResourceDialogSitePort": "Port du site", "editInternalResourceDialogTargetConfiguration": "Configuration de la cible", + "editInternalResourceDialogDestinationIP": "IP de destination", + "editInternalResourceDialogDestinationPort": "Port de destination", "editInternalResourceDialogCancel": "Abandonner", "editInternalResourceDialogSaveResource": "Enregistrer la ressource", "editInternalResourceDialogSuccess": "Succès", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Port du site", "createInternalResourceDialogSitePortDescription": "Utilisez ce port pour accéder à la ressource sur le site lors de la connexion avec un client.", "createInternalResourceDialogTargetConfiguration": "Configuration de la cible", - "createInternalResourceDialogDestinationIPDescription": "L'adresse IP ou le nom d'hôte de la ressource sur le réseau du site.", + "createInternalResourceDialogDestinationIP": "IP de destination", + "createInternalResourceDialogDestinationIPDescription": "L'adresse IP de la ressource sur le réseau du site.", + "createInternalResourceDialogDestinationPort": "Port de destination", "createInternalResourceDialogDestinationPortDescription": "Le port sur l'IP de destination où la ressource est accessible.", "createInternalResourceDialogCancel": "Abandonner", "createInternalResourceDialogCreateResource": "Créer une ressource", @@ -1500,24 +1496,5 @@ "convertButton": "Convertir ce noeud en auto-hébergé géré" }, "internationaldomaindetected": "Domaine international détecté", - "willbestoredas": "Sera stocké comme :", - "idpGoogleDescription": "Fournisseur Google OAuth2/OIDC", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "En-têtes personnalisés", - "headersValidationError": "Les entêtes doivent être au format : Header-Name: valeur.", - "domainPickerProvidedDomain": "Domaine fourni", - "domainPickerFreeProvidedDomain": "Domaine fourni gratuitement", - "domainPickerVerified": "Vérifié", - "domainPickerUnverified": "Non vérifié", - "domainPickerInvalidSubdomainStructure": "Ce sous-domaine contient des caractères ou une structure non valide. Il sera automatiquement nettoyé lorsque vous enregistrez.", - "domainPickerError": "Erreur", - "domainPickerErrorLoadDomains": "Impossible de charger les domaines de l'organisation", - "domainPickerErrorCheckAvailability": "Impossible de vérifier la disponibilité du domaine", - "domainPickerInvalidSubdomain": "Sous-domaine invalide", - "domainPickerInvalidSubdomainRemoved": "L'entrée \"{sub}\" a été supprimée car elle n'est pas valide.", - "domainPickerInvalidSubdomainCannotMakeValid": "La «{sub}» n'a pas pu être validée pour {domain}.", - "domainPickerSubdomainSanitized": "Sous-domaine nettoyé", - "domainPickerSubdomainCorrected": "\"{sub}\" a été corrigé à \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Modifier le fichier : config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Modifier le fichier : docker-compose.yml" + "willbestoredas": "Sera stocké comme :" } diff --git a/messages/it-IT.json b/messages/it-IT.json index f0a862cd..9b935609 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Si è verificato un errore durante l'aggiunta dell'utente al ruolo.", "userSaved": "Utente salvato", "userSavedDescription": "L'utente è stato aggiornato.", - "autoProvisioned": "Auto Provisioned", - "autoProvisionedDescription": "Permetti a questo utente di essere gestito automaticamente dal provider di identità", "accessControlsDescription": "Gestisci cosa questo utente può accedere e fare nell'organizzazione", "accessControlsSubmit": "Salva Controlli di Accesso", "roles": "Ruoli", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Formato indirizzo IP non valido", "ipAddressErrorInvalidOctet": "Ottetto indirizzo IP non valido", "path": "Percorso", - "matchPath": "Corrispondenza Tracciato", "ipAddressRange": "Intervallo IP", "rulesErrorFetch": "Impossibile recuperare le regole", "rulesErrorFetchDescription": "Si è verificato un errore durante il recupero delle regole", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Connesso", "idpErrorConnectingTo": "Si è verificato un problema durante la connessione a {name}. Contatta il tuo amministratore.", "idpErrorNotFound": "IdP non trovato", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Invito Non Valido", "inviteInvalidDescription": "Il link di invito non è valido.", "inviteErrorWrongUser": "L'invito non è per questo utente", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Edizione Professional Richiesta", "licenseTierProfessionalRequiredDescription": "Questa funzionalità è disponibile solo nell'Edizione Professional.", "actionGetOrg": "Ottieni Organizzazione", - "updateOrgUser": "Aggiorna Utente Org", - "createOrgUser": "Crea Utente Org", "actionUpdateOrg": "Aggiorna Organizzazione", "actionUpdateUser": "Aggiorna Utente", "actionGetUser": "Ottieni Utente", @@ -998,7 +991,6 @@ "actionDeleteSite": "Elimina Sito", "actionGetSite": "Ottieni Sito", "actionListSites": "Elenca Siti", - "actionApplyBlueprint": "Applica Progetto", "setupToken": "Configura Token", "setupTokenDescription": "Inserisci il token di configurazione dalla console del server.", "setupTokenRequired": "Il token di configurazione è richiesto", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Licenza", "sidebarClients": "Clienti (Beta)", "sidebarDomains": "Domini", - "enableDockerSocket": "Abilita Progetto Docker", - "enableDockerSocketDescription": "Abilita la raschiatura dell'etichetta Docker Socket per le etichette dei progetti. Il percorso del socket deve essere fornito a Newt.", + "enableDockerSocket": "Abilita Docker Socket", + "enableDockerSocketDescription": "Abilita il rilevamento Docker Socket per popolare le informazioni del contenitore. Il percorso del socket deve essere fornito a Newt.", "enableDockerSocketLink": "Scopri di più", "viewDockerContainers": "Visualizza Contenitori Docker", "containersIn": "Contenitori in {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Aggiornamento Disponibile", "newtUpdateAvailableInfo": "È disponibile una nuova versione di Newt. Si prega di aggiornare all'ultima versione per la migliore esperienza.", "domainPickerEnterDomain": "Dominio", - "domainPickerPlaceholder": "myapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, o semplicemente myapp", "domainPickerDescription": "Inserisci il dominio completo della risorsa per vedere le opzioni disponibili.", "domainPickerDescriptionSaas": "Inserisci un dominio completo, un sottodominio o semplicemente un nome per vedere le opzioni disponibili", "domainPickerTabAll": "Tutti", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protocollo", "editInternalResourceDialogSitePort": "Porta del Sito", "editInternalResourceDialogTargetConfiguration": "Configurazione Target", + "editInternalResourceDialogDestinationIP": "IP di Destinazione", + "editInternalResourceDialogDestinationPort": "Porta di Destinazione", "editInternalResourceDialogCancel": "Annulla", "editInternalResourceDialogSaveResource": "Salva Risorsa", "editInternalResourceDialogSuccess": "Successo", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Porta del Sito", "createInternalResourceDialogSitePortDescription": "Usa questa porta per accedere alla risorsa nel sito quando sei connesso con un client.", "createInternalResourceDialogTargetConfiguration": "Configurazione Target", - "createInternalResourceDialogDestinationIPDescription": "L'indirizzo IP o hostname della risorsa nella rete del sito.", + "createInternalResourceDialogDestinationIP": "IP di Destinazione", + "createInternalResourceDialogDestinationIPDescription": "L'indirizzo IP della risorsa sulla rete del sito.", + "createInternalResourceDialogDestinationPort": "Porta di Destinazione", "createInternalResourceDialogDestinationPortDescription": "La porta sull'IP di destinazione dove la risorsa è accessibile.", "createInternalResourceDialogCancel": "Annulla", "createInternalResourceDialogCreateResource": "Crea Risorsa", @@ -1500,24 +1496,5 @@ "convertButton": "Converti questo nodo in auto-ospitato gestito" }, "internationaldomaindetected": "Dominio Internazionale Rilevato", - "willbestoredas": "Verrà conservato come:", - "idpGoogleDescription": "Google OAuth2/OIDC provider", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Intestazioni Personalizzate", - "headersValidationError": "Le intestazioni devono essere nel formato: Intestazione-Nome: valore.", - "domainPickerProvidedDomain": "Dominio Fornito", - "domainPickerFreeProvidedDomain": "Dominio Fornito Gratuito", - "domainPickerVerified": "Verificato", - "domainPickerUnverified": "Non Verificato", - "domainPickerInvalidSubdomainStructure": "Questo sottodominio contiene caratteri o struttura non validi. Sarà sanificato automaticamente quando si salva.", - "domainPickerError": "Errore", - "domainPickerErrorLoadDomains": "Impossibile caricare i domini dell'organizzazione", - "domainPickerErrorCheckAvailability": "Impossibile verificare la disponibilità del dominio", - "domainPickerInvalidSubdomain": "Sottodominio non valido", - "domainPickerInvalidSubdomainRemoved": "L'input \"{sub}\" è stato rimosso perché non è valido.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" non può essere reso valido per {domain}.", - "domainPickerSubdomainSanitized": "Sottodominio igienizzato", - "domainPickerSubdomainCorrected": "\"{sub}\" è stato corretto in \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Modifica file: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Modifica file: docker-compose.yml" + "willbestoredas": "Verrà conservato come:" } diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 64a449d0..2b9e7b1c 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "사용자를 역할에 추가하는 동안 오류가 발생했습니다.", "userSaved": "사용자 저장됨", "userSavedDescription": "사용자가 업데이트되었습니다.", - "autoProvisioned": "자동 프로비저닝됨", - "autoProvisionedDescription": "이 사용자가 ID 공급자에 의해 자동으로 관리될 수 있도록 허용합니다", "accessControlsDescription": "이 사용자가 조직에서 접근하고 수행할 수 있는 작업을 관리하세요", "accessControlsSubmit": "접근 제어 저장", "roles": "역할", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "잘못된 IP 주소 형식", "ipAddressErrorInvalidOctet": "유효하지 않은 IP 주소 옥텟", "path": "경로", - "matchPath": "경로 맞춤", "ipAddressRange": "IP 범위", "rulesErrorFetch": "규칙을 가져오는 데 실패했습니다.", "rulesErrorFetchDescription": "규칙을 가져오는 중 오류가 발생했습니다", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "연결됨", "idpErrorConnectingTo": "{name}에 연결하는 데 문제가 발생했습니다. 관리자에게 문의하십시오.", "idpErrorNotFound": "IdP를 찾을 수 없습니다.", - "idpGoogleAlt": "구글", - "idpAzureAlt": "애저", "inviteInvalid": "유효하지 않은 초대", "inviteInvalidDescription": "초대 링크가 유효하지 않습니다.", "inviteErrorWrongUser": "이 초대는 이 사용자에게 해당되지 않습니다", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "전문 에디션이 필요합니다.", "licenseTierProfessionalRequiredDescription": "이 기능은 Professional Edition에서만 사용할 수 있습니다.", "actionGetOrg": "조직 가져오기", - "updateOrgUser": "조직 사용자 업데이트", - "createOrgUser": "조직 사용자 생성", "actionUpdateOrg": "조직 업데이트", "actionUpdateUser": "사용자 업데이트", "actionGetUser": "사용자 조회", @@ -998,7 +991,6 @@ "actionDeleteSite": "사이트 삭제", "actionGetSite": "사이트 가져오기", "actionListSites": "사이트 목록", - "actionApplyBlueprint": "청사진 적용", "setupToken": "설정 토큰", "setupTokenDescription": "서버 콘솔에서 설정 토큰 입력.", "setupTokenRequired": "설정 토큰이 필요합니다", @@ -1141,8 +1133,8 @@ "sidebarLicense": "라이선스", "sidebarClients": "클라이언트 (Beta)", "sidebarDomains": "도메인", - "enableDockerSocket": "Docker 청사진 활성화", - "enableDockerSocketDescription": "블루프린트 레이블을 위한 Docker 소켓 레이블 수집을 활성화합니다. 소켓 경로는 Newt에 제공되어야 합니다.", + "enableDockerSocket": "Docker 소켓 활성화", + "enableDockerSocketDescription": "컨테이너 정보를 채우기 위해 Docker 소켓 검색을 활성화합니다. 소켓 경로는 Newt에 제공되어야 합니다.", "enableDockerSocketLink": "자세히 알아보기", "viewDockerContainers": "도커 컨테이너 보기", "containersIn": "{siteName}의 컨테이너", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "업데이트 가능", "newtUpdateAvailableInfo": "뉴트의 새 버전이 출시되었습니다. 최상의 경험을 위해 최신 버전으로 업데이트하세요.", "domainPickerEnterDomain": "도메인", - "domainPickerPlaceholder": "myapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, 또는 그냥 myapp", "domainPickerDescription": "리소스의 전체 도메인을 입력하여 사용 가능한 옵션을 확인하십시오.", "domainPickerDescriptionSaas": "전체 도메인, 서브도메인 또는 이름을 입력하여 사용 가능한 옵션을 확인하십시오.", "domainPickerTabAll": "모두", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "프로토콜", "editInternalResourceDialogSitePort": "사이트 포트", "editInternalResourceDialogTargetConfiguration": "대상 구성", + "editInternalResourceDialogDestinationIP": "대상 IP", + "editInternalResourceDialogDestinationPort": "대상 IP의 포트", "editInternalResourceDialogCancel": "취소", "editInternalResourceDialogSaveResource": "리소스 저장", "editInternalResourceDialogSuccess": "성공", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "사이트 포트", "createInternalResourceDialogSitePortDescription": "사이트에 연결되었을 때 리소스에 접근하기 위해 이 포트를 사용합니다.", "createInternalResourceDialogTargetConfiguration": "대상 설정", - "createInternalResourceDialogDestinationIPDescription": "사이트 네트워크의 자원 IP 또는 호스트 네임 주소입니다.", + "createInternalResourceDialogDestinationIP": "대상 IP", + "createInternalResourceDialogDestinationIPDescription": "사이트 네트워크의 자원 IP 주소입니다.", + "createInternalResourceDialogDestinationPort": "대상 포트", "createInternalResourceDialogDestinationPortDescription": "대상 IP에서 리소스에 접근할 수 있는 포트입니다.", "createInternalResourceDialogCancel": "취소", "createInternalResourceDialogCreateResource": "리소스 생성", @@ -1500,24 +1496,5 @@ "convertButton": "이 노드를 관리 자체 호스팅으로 변환" }, "internationaldomaindetected": "국제 도메인 감지됨", - "willbestoredas": "다음으로 저장됩니다:", - "idpGoogleDescription": "Google OAuth2/OIDC 공급자", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC 공급자", - "customHeaders": "사용자 정의 헤더", - "headersValidationError": "헤더는 형식이어야 합니다: 헤더명: 값.", - "domainPickerProvidedDomain": "제공된 도메인", - "domainPickerFreeProvidedDomain": "무료 제공된 도메인", - "domainPickerVerified": "검증됨", - "domainPickerUnverified": "검증되지 않음", - "domainPickerInvalidSubdomainStructure": "이 하위 도메인은 잘못된 문자 또는 구조를 포함하고 있습니다. 저장 시 자동으로 정리됩니다.", - "domainPickerError": "오류", - "domainPickerErrorLoadDomains": "조직 도메인 로드 실패", - "domainPickerErrorCheckAvailability": "도메인 가용성 확인 실패", - "domainPickerInvalidSubdomain": "잘못된 하위 도메인", - "domainPickerInvalidSubdomainRemoved": "입력 \"{sub}\"이(가) 유효하지 않으므로 제거되었습니다.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\"을(를) {domain}에 대해 유효하게 만들 수 없습니다.", - "domainPickerSubdomainSanitized": "하위 도메인 정리됨", - "domainPickerSubdomainCorrected": "\"{sub}\"이(가) \"{sanitized}\"로 수정되었습니다", - "resourceAddEntrypointsEditFile": "파일 편집: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "파일 편집: docker-compose.yml" + "willbestoredas": "다음으로 저장됩니다:" } diff --git a/messages/nb-NO.json b/messages/nb-NO.json index ef5c0d2a..6d1ae86a 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Det oppstod en feil under tilordning av brukeren til rollen.", "userSaved": "Bruker lagret", "userSavedDescription": "Brukeren har blitt oppdatert.", - "autoProvisioned": "Auto avlyst", - "autoProvisionedDescription": "Tillat denne brukeren å bli automatisk administrert av en identitetsleverandør", "accessControlsDescription": "Administrer hva denne brukeren kan få tilgang til og gjøre i organisasjonen", "accessControlsSubmit": "Lagre tilgangskontroller", "roles": "Roller", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Ugyldig IP-adresseformat", "ipAddressErrorInvalidOctet": "Ugyldig IP-adresse-oktet", "path": "Sti", - "matchPath": "Match sti", "ipAddressRange": "IP-område", "rulesErrorFetch": "Klarte ikke å hente regler", "rulesErrorFetchDescription": "Det oppsto en feil under henting av regler", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Tilkoblet", "idpErrorConnectingTo": "Det oppstod et problem med å koble til {name}. Vennligst kontakt din administrator.", "idpErrorNotFound": "IdP ikke funnet", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Ugyldig invitasjon", "inviteInvalidDescription": "Invitasjonslenken er ugyldig.", "inviteErrorWrongUser": "Invitasjonen er ikke for denne brukeren", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Profesjonell utgave påkrevd", "licenseTierProfessionalRequiredDescription": "Denne funksjonen er kun tilgjengelig i den profesjonelle utgaven.", "actionGetOrg": "Hent organisasjon", - "updateOrgUser": "Oppdater org.bruker", - "createOrgUser": "Opprett Org bruker", "actionUpdateOrg": "Oppdater organisasjon", "actionUpdateUser": "Oppdater bruker", "actionGetUser": "Hent bruker", @@ -998,7 +991,6 @@ "actionDeleteSite": "Slett område", "actionGetSite": "Hent område", "actionListSites": "List opp områder", - "actionApplyBlueprint": "Bruk blåkopi", "setupToken": "Oppsetttoken", "setupTokenDescription": "Skriv inn oppsetttoken fra serverkonsollen.", "setupTokenRequired": "Oppsetttoken er nødvendig", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Lisens", "sidebarClients": "Klienter (Beta)", "sidebarDomains": "Domener", - "enableDockerSocket": "Aktiver Docker blåkopi", - "enableDockerSocketDescription": "Aktiver skraping av Docker Socket for blueprint Etiketter. Socket bane må brukes for nye.", + "enableDockerSocket": "Aktiver Docker Socket", + "enableDockerSocketDescription": "Aktiver Docker Socket-oppdagelse for å fylle ut containerinformasjon. Socket-stien må oppgis til Newt.", "enableDockerSocketLink": "Lær mer", "viewDockerContainers": "Vis Docker-containere", "containersIn": "Containere i {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Oppdatering tilgjengelig", "newtUpdateAvailableInfo": "En ny versjon av Newt er tilgjengelig. Vennligst oppdater til den nyeste versjonen for den beste opplevelsen.", "domainPickerEnterDomain": "Domene", - "domainPickerPlaceholder": "minapp.eksempel.no", + "domainPickerPlaceholder": "minapp.eksempel.com, api.v1.mittdomene.com, eller bare minapp", "domainPickerDescription": "Skriv inn hele domenet til ressursen for å se tilgjengelige alternativer.", "domainPickerDescriptionSaas": "Skriv inn et fullt domene, underdomene eller bare et navn for å se tilgjengelige alternativer", "domainPickerTabAll": "Alle", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protokoll", "editInternalResourceDialogSitePort": "Områdeport", "editInternalResourceDialogTargetConfiguration": "Målkonfigurasjon", + "editInternalResourceDialogDestinationIP": "Destinasjons-IP", + "editInternalResourceDialogDestinationPort": "Destinasjonsport", "editInternalResourceDialogCancel": "Avbryt", "editInternalResourceDialogSaveResource": "Lagre ressurs", "editInternalResourceDialogSuccess": "Suksess", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Områdeport", "createInternalResourceDialogSitePortDescription": "Bruk denne porten for å få tilgang til ressursen på området når du er tilkoblet med en klient.", "createInternalResourceDialogTargetConfiguration": "Målkonfigurasjon", - "createInternalResourceDialogDestinationIPDescription": "IP eller vertsnavn til ressursen på nettstedets nettverk.", + "createInternalResourceDialogDestinationIP": "Destinasjons-IP", + "createInternalResourceDialogDestinationIPDescription": "IP-adressen til ressursen på områdets nettverk.", + "createInternalResourceDialogDestinationPort": "Destinasjonsport", "createInternalResourceDialogDestinationPortDescription": "Porten på destinasjons-IP-en der ressursen kan nås.", "createInternalResourceDialogCancel": "Avbryt", "createInternalResourceDialogCreateResource": "Opprett ressurs", @@ -1500,24 +1496,5 @@ "convertButton": "Konverter denne noden til manuelt bruk" }, "internationaldomaindetected": "Internasjonalt domene oppdaget", - "willbestoredas": "Vil bli lagret som:", - "idpGoogleDescription": "Google OAuth2/OIDC leverandør", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Egendefinerte topptekster", - "headersValidationError": "Topptekst må være i formatet: header-navn: verdi.", - "domainPickerProvidedDomain": "Gitt domene", - "domainPickerFreeProvidedDomain": "Gratis oppgitt domene", - "domainPickerVerified": "Bekreftet", - "domainPickerUnverified": "Uverifisert", - "domainPickerInvalidSubdomainStructure": "Dette underdomenet inneholder ugyldige tegn eller struktur. Det vil automatisk bli utsatt når du lagrer.", - "domainPickerError": "Feil", - "domainPickerErrorLoadDomains": "Kan ikke laste organisasjonens domener", - "domainPickerErrorCheckAvailability": "Kunne ikke kontrollere domenetilgjengelighet", - "domainPickerInvalidSubdomain": "Ugyldig underdomene", - "domainPickerInvalidSubdomainRemoved": "Inndata \"{sub}\" ble fjernet fordi det ikke er gyldig.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" kunne ikke gjøres gyldig for {domain}.", - "domainPickerSubdomainSanitized": "Underdomenet som ble sanivert", - "domainPickerSubdomainCorrected": "\"{sub}\" var korrigert til \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Rediger fil: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Rediger fil: docker-compose.yml" + "willbestoredas": "Vil bli lagret som:" } diff --git a/messages/nl-NL.json b/messages/nl-NL.json index ba4ab637..6252d752 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -38,12 +38,12 @@ "name": "naam", "online": "Online", "offline": "Offline", - "site": "Referentie", - "dataIn": "Dataverbruik inkomend", - "dataOut": "Dataverbruik uitgaand", + "site": "Website", + "dataIn": "Gegevens in", + "dataOut": "Data Uit", "connectionType": "Type verbinding", "tunnelType": "Tunnel type", - "local": "Lokaal", + "local": "lokaal", "edit": "Bewerken", "siteConfirmDelete": "Verwijderen van site bevestigen", "siteDelete": "Site verwijderen", @@ -55,7 +55,7 @@ "siteCreate": "Site maken", "siteCreateDescription2": "Volg de onderstaande stappen om een nieuwe site aan te maken en te verbinden", "siteCreateDescription": "Maak een nieuwe site aan om verbinding te maken met uw bronnen", - "close": "Sluiten", + "close": "Afsluiten", "siteErrorCreate": "Fout bij maken site", "siteErrorCreateKeyPair": "Key pair of site standaard niet gevonden", "siteErrorCreateDefaults": "Standaardinstellingen niet gevonden", @@ -90,7 +90,7 @@ "siteGeneralDescription": "Algemene instellingen voor deze site configureren", "siteSettingDescription": "Configureer de instellingen op uw site", "siteSetting": "{siteName} instellingen", - "siteNewtTunnel": "Newttunnel (Aanbevolen)", + "siteNewtTunnel": "Nieuwstunnel (Aanbevolen)", "siteNewtTunnelDescription": "Gemakkelijkste manier om een ingangspunt in uw netwerk te maken. Geen extra opzet.", "siteWg": "Basis WireGuard", "siteWgDescription": "Gebruik een WireGuard client om een tunnel te bouwen. Handmatige NAT installatie vereist.", @@ -104,7 +104,7 @@ "siteCredentialsSave": "Uw referenties opslaan", "siteCredentialsSaveDescription": "Je kunt dit slechts één keer zien. Kopieer het naar een beveiligde plek.", "siteInfo": "Site informatie", - "status": "Status", + "status": "status", "shareTitle": "Beheer deellinks", "shareDescription": "Maak deelbare links aan om tijdelijke of permanente toegang tot uw bronnen te verlenen", "shareSearch": "Zoek share links...", @@ -146,19 +146,19 @@ "never": "Nooit", "shareErrorSelectResource": "Selecteer een bron", "resourceTitle": "Bronnen beheren", - "resourceDescription": "Veilige proxy's voor uw privé applicaties aanmaken", + "resourceDescription": "Veilige proxy's voor uw privé applicaties maken", "resourcesSearch": "Zoek bronnen...", "resourceAdd": "Bron toevoegen", "resourceErrorDelte": "Fout bij verwijderen document", "authentication": "Authenticatie", - "protected": "Beveiligd", - "notProtected": "Niet beveiligd", + "protected": "Beschermd", + "notProtected": "Niet beschermd", "resourceMessageRemove": "Eenmaal verwijderd, zal het bestand niet langer toegankelijk zijn. Alle doelen die gekoppeld zijn aan het hulpbron, zullen ook verwijderd worden.", "resourceMessageConfirm": "Om te bevestigen, typ de naam van de bron hieronder.", "resourceQuestionRemove": "Weet u zeker dat u de resource {selectedResource} uit de organisatie wilt verwijderen?", "resourceHTTP": "HTTPS bron", "resourceHTTPDescription": "Proxy verzoeken aan uw app via HTTPS via een subdomein of basisdomein.", - "resourceRaw": "TCP/UDP bron", + "resourceRaw": "Ruwe TCP/UDP bron", "resourceRawDescription": "Proxy verzoeken naar je app via TCP/UDP met behulp van een poortnummer.", "resourceCreate": "Bron maken", "resourceCreateDescription": "Volg de onderstaande stappen om een nieuwe bron te maken", @@ -183,7 +183,7 @@ "protocolSelect": "Selecteer een protocol", "resourcePortNumber": "Nummer van poort", "resourcePortNumberDescription": "Het externe poortnummer naar proxyverzoeken.", - "cancel": "Annuleren", + "cancel": "annuleren", "resourceConfig": "Configuratie tekstbouwstenen", "resourceConfigDescription": "Kopieer en plak deze configuratie-snippets om je TCP/UDP-bron in te stellen", "resourceAddEntrypoints": "Traefik: Entrypoints toevoegen", @@ -212,7 +212,7 @@ "saveGeneralSettings": "Algemene instellingen opslaan", "saveSettings": "Instellingen opslaan", "orgDangerZone": "Gevaarlijke zone", - "orgDangerZoneDescription": "Deze instantie verwijderen is onomkeerbaar. Bevestig alstublieft dat u wilt doorgaan.", + "orgDangerZoneDescription": "Als u deze instantie verwijdert, is er geen weg terug. Wees het alstublieft zeker.", "orgDelete": "Verwijder organisatie", "orgDeleteConfirm": "Bevestig Verwijderen Organisatie", "orgMessageRemove": "Deze actie is onomkeerbaar en zal alle bijbehorende gegevens verwijderen.", @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Er is een fout opgetreden tijdens het toevoegen van de rol.", "userSaved": "Gebruiker opgeslagen", "userSavedDescription": "De gebruiker is bijgewerkt.", - "autoProvisioned": "Automatisch bevestigen", - "autoProvisionedDescription": "Toestaan dat deze gebruiker automatisch wordt beheerd door een identiteitsprovider", "accessControlsDescription": "Beheer wat deze gebruiker toegang heeft tot en doet in de organisatie", "accessControlsSubmit": "Bewaar Toegangsbesturing", "roles": "Rollen", @@ -501,8 +499,8 @@ "targetStickySessionsDescription": "Behoud verbindingen op hetzelfde backend doel voor hun hele sessie.", "methodSelect": "Selecteer methode", "targetSubmit": "Doelwit toevoegen", - "targetNoOne": "Geen doel toegevoegd. Voeg deze toe via dit formulier.", - "targetNoOneDescription": "Het toevoegen van meer dan één doel hierboven zal load balancering mogelijk maken.", + "targetNoOne": "Geen doelwitten. Voeg een doel toe via het formulier.", + "targetNoOneDescription": "Het toevoegen van meer dan één doel hierboven zal de load balancering mogelijk maken.", "targetsSubmit": "Doelstellingen opslaan", "proxyAdditional": "Extra Proxy-instellingen", "proxyAdditionalDescription": "Configureer hoe de proxy-instellingen van uw bron worden afgehandeld", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Ongeldig IP-adresformaat", "ipAddressErrorInvalidOctet": "Ongeldige IP adres octet", "path": "Pad", - "matchPath": "Overeenkomend pad", "ipAddressRange": "IP Bereik", "rulesErrorFetch": "Regels ophalen mislukt", "rulesErrorFetchDescription": "Er is een fout opgetreden bij het ophalen van de regels", @@ -598,7 +595,7 @@ "newtId": "Newt-ID", "newtSecretKey": "Nieuwe geheime sleutel", "architecture": "Architectuur", - "sites": "Verbindingen", + "sites": "Werkruimtes", "siteWgAnyClients": "Gebruik een willekeurige WireGuard client om verbinding te maken. Je moet je interne bronnen aanspreken met behulp van de peer IP.", "siteWgCompatibleAllClients": "Compatibel met alle WireGuard clients", "siteWgManualConfigurationRequired": "Handmatige configuratie vereist", @@ -729,7 +726,7 @@ "idpMessageConfirm": "Om dit te bevestigen, typt u de naam van onderstaande identiteitsprovider.", "idpConfirmDelete": "Bevestig verwijderen Identity Provider", "idpDelete": "Identity Provider verwijderen", - "idp": "Identiteitsaanbieders", + "idp": "Identiteit aanbieders", "idpSearch": "Identiteitsaanbieders zoeken...", "idpAdd": "Identity Provider toevoegen", "idpClientIdRequired": "Client-ID is vereist.", @@ -801,7 +798,7 @@ "defaultMappingsOrgDescription": "Deze expressie moet de org-ID teruggeven of waar om de gebruiker toegang te geven tot de organisatie.", "defaultMappingsSubmit": "Standaard toewijzingen opslaan", "orgPoliciesEdit": "Organisatie beleid bewerken", - "org": "Organisatie", + "org": "Rekening", "orgSelect": "Selecteer organisatie", "orgSearch": "Zoek in org", "orgNotFound": "Geen org gevonden.", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Verbonden", "idpErrorConnectingTo": "Er was een probleem bij het verbinden met {name}. Neem contact op met uw beheerder.", "idpErrorNotFound": "IdP niet gevonden", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Ongeldige uitnodiging", "inviteInvalidDescription": "Uitnodigingslink is ongeldig.", "inviteErrorWrongUser": "Uitnodiging is niet voor deze gebruiker", @@ -976,10 +971,10 @@ "supportKeyEnterDescription": "Ontmoet je eigen huisdier Pangolin!", "githubUsername": "GitHub-gebruikersnaam", "supportKeyInput": "Supporter Sleutel", - "supportKeyBuy": "Koop supportersleutel", + "supportKeyBuy": "Koop Supportersleutel", "logoutError": "Fout bij uitloggen", "signingAs": "Ingelogd als", - "serverAdmin": "Server beheer", + "serverAdmin": "Server Beheerder", "managedSelfhosted": "Beheerde Self-Hosted", "otpEnable": "Twee-factor inschakelen", "otpDisable": "Tweestapsverificatie uitschakelen", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Professionele editie vereist", "licenseTierProfessionalRequiredDescription": "Deze functie is alleen beschikbaar in de Professional Edition.", "actionGetOrg": "Krijg Organisatie", - "updateOrgUser": "Org gebruiker bijwerken", - "createOrgUser": "Org gebruiker aanmaken", "actionUpdateOrg": "Organisatie bijwerken", "actionUpdateUser": "Gebruiker bijwerken", "actionGetUser": "Gebruiker ophalen", @@ -998,7 +991,6 @@ "actionDeleteSite": "Site verwijderen", "actionGetSite": "Site ophalen", "actionListSites": "Sites weergeven", - "actionApplyBlueprint": "Blauwdruk toepassen", "setupToken": "Setup Token", "setupTokenDescription": "Voer het setup-token in vanaf de serverconsole.", "setupTokenRequired": "Setup-token is vereist", @@ -1128,7 +1120,7 @@ "sidebarOverview": "Overzicht.", "sidebarHome": "Startpagina", "sidebarSites": "Werkruimtes", - "sidebarResources": "Bronnen", + "sidebarResources": "Hulpmiddelen", "sidebarAccessControl": "Toegangs controle", "sidebarUsers": "Gebruikers", "sidebarInvitations": "Uitnodigingen", @@ -1141,13 +1133,13 @@ "sidebarLicense": "Licentie", "sidebarClients": "Clients (Bèta)", "sidebarDomains": "Domeinen", - "enableDockerSocket": "Schakel Docker Blauwdruk in", - "enableDockerSocketDescription": "Schakel Docker Socket label in voor blauwdruk labels. Pad naar Nieuw.", + "enableDockerSocket": "Docker Socket inschakelen", + "enableDockerSocketDescription": "Docker Socket-ontdekking inschakelen voor het invullen van containerinformatie. Socket-pad moet aan Newt worden verstrekt.", "enableDockerSocketLink": "Meer informatie", "viewDockerContainers": "Bekijk Docker containers", "containersIn": "Containers in {siteName}", "selectContainerDescription": "Selecteer een container om als hostnaam voor dit doel te gebruiken. Klik op een poort om een poort te gebruiken.", - "containerName": "Naam", + "containerName": "naam", "containerImage": "Afbeelding", "containerState": "Provincie", "containerNetworks": "Netwerken", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Update beschikbaar", "newtUpdateAvailableInfo": "Er is een nieuwe versie van Newt beschikbaar. Update naar de nieuwste versie voor de beste ervaring.", "domainPickerEnterDomain": "Domein", - "domainPickerPlaceholder": "mijnapp.voorbeeld.nl", + "domainPickerPlaceholder": "mijnapp.voorbeeld.com, api.v1.mijndomein.com, of gewoon mijnapp", "domainPickerDescription": "Voer de volledige domein van de bron in om beschikbare opties te zien.", "domainPickerDescriptionSaas": "Voer een volledig domein, subdomein of gewoon een naam in om beschikbare opties te zien", "domainPickerTabAll": "Alles", @@ -1349,7 +1341,7 @@ "olmId": "Olm ID", "olmSecretKey": "Olm Geheime Sleutel", "clientCredentialsSave": "Uw referenties opslaan", - "clientCredentialsSaveDescription": "Je kunt dit slechts één keer zien. Kopieer deze naar een veilige plek.", + "clientCredentialsSaveDescription": "Je kunt dit slechts één keer zien. Kopieer het naar een beveiligde plek.", "generalSettingsDescription": "Configureer de algemene instellingen voor deze client", "clientUpdated": "Klant bijgewerkt ", "clientUpdatedDescription": "De client is bijgewerkt.", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protocol", "editInternalResourceDialogSitePort": "Site Poort", "editInternalResourceDialogTargetConfiguration": "Doelconfiguratie", + "editInternalResourceDialogDestinationIP": "Bestemming IP", + "editInternalResourceDialogDestinationPort": "Bestemmingspoort", "editInternalResourceDialogCancel": "Annuleren", "editInternalResourceDialogSaveResource": "Sla bron op", "editInternalResourceDialogSuccess": "Succes", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Site Poort", "createInternalResourceDialogSitePortDescription": "Gebruik deze poort om toegang te krijgen tot de bron op de site wanneer verbonden met een client.", "createInternalResourceDialogTargetConfiguration": "Doelconfiguratie", - "createInternalResourceDialogDestinationIPDescription": "Het IP of hostnaam adres van de bron op het netwerk van de site.", + "createInternalResourceDialogDestinationIP": "Bestemming IP", + "createInternalResourceDialogDestinationIPDescription": "Het IP-adres van de bron op het netwerk van de site.", + "createInternalResourceDialogDestinationPort": "Bestemmingspoort", "createInternalResourceDialogDestinationPortDescription": "De poort op het bestemmings-IP waar de bron toegankelijk is.", "createInternalResourceDialogCancel": "Annuleren", "createInternalResourceDialogCreateResource": "Bron aanmaken", @@ -1500,24 +1496,5 @@ "convertButton": "Converteer deze node naar Beheerde Zelf-Hosted" }, "internationaldomaindetected": "Internationaal Domein Gedetecteerd", - "willbestoredas": "Zal worden opgeslagen als:", - "idpGoogleDescription": "Google OAuth2/OIDC provider", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Aangepaste headers", - "headersValidationError": "Headers moeten in het formaat zijn: Header-Naam: waarde.", - "domainPickerProvidedDomain": "Opgegeven domein", - "domainPickerFreeProvidedDomain": "Gratis verstrekt domein", - "domainPickerVerified": "Geverifieerd", - "domainPickerUnverified": "Ongeverifieerd", - "domainPickerInvalidSubdomainStructure": "Dit subdomein bevat ongeldige tekens of structuur. Het zal automatisch worden gesaneerd wanneer u opslaat.", - "domainPickerError": "Foutmelding", - "domainPickerErrorLoadDomains": "Fout bij het laden van organisatiedomeinen", - "domainPickerErrorCheckAvailability": "Kan domein beschikbaarheid niet controleren", - "domainPickerInvalidSubdomain": "Ongeldig subdomein", - "domainPickerInvalidSubdomainRemoved": "De invoer \"{sub}\" is verwijderd omdat het niet geldig is.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" kon niet geldig worden gemaakt voor {domain}.", - "domainPickerSubdomainSanitized": "Subdomein gesaniseerd", - "domainPickerSubdomainCorrected": "\"{sub}\" was gecorrigeerd op \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Bestand bewerken: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Bestand bewerken: docker-compose.yml" + "willbestoredas": "Zal worden opgeslagen als:" } diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 4fe382e1..1aee50f2 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Wystąpił błąd podczas dodawania użytkownika do roli.", "userSaved": "Użytkownik zapisany", "userSavedDescription": "Użytkownik został zaktualizowany.", - "autoProvisioned": "Przesłane automatycznie", - "autoProvisionedDescription": "Pozwól temu użytkownikowi na automatyczne zarządzanie przez dostawcę tożsamości", "accessControlsDescription": "Zarządzaj tym, do czego użytkownik ma dostęp i co może robić w organizacji", "accessControlsSubmit": "Zapisz kontrole dostępu", "roles": "Role", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Nieprawidłowy format adresu IP", "ipAddressErrorInvalidOctet": "Nieprawidłowy oktet adresu IP", "path": "Ścieżka", - "matchPath": "Ścieżka dopasowania", "ipAddressRange": "Zakres IP", "rulesErrorFetch": "Nie udało się pobrać reguł", "rulesErrorFetchDescription": "Wystąpił błąd podczas pobierania reguł", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Połączono", "idpErrorConnectingTo": "Wystąpił problem z połączeniem z {name}. Skontaktuj się z administratorem.", "idpErrorNotFound": "Nie znaleziono IdP", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Nieprawidłowe zaproszenie", "inviteInvalidDescription": "Link zapraszający jest nieprawidłowy.", "inviteErrorWrongUser": "Zaproszenie nie jest dla tego użytkownika", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Wymagana edycja Professional", "licenseTierProfessionalRequiredDescription": "Ta funkcja jest dostępna tylko w edycji Professional.", "actionGetOrg": "Pobierz organizację", - "updateOrgUser": "Aktualizuj użytkownika Org", - "createOrgUser": "Utwórz użytkownika Org", "actionUpdateOrg": "Aktualizuj organizację", "actionUpdateUser": "Zaktualizuj użytkownika", "actionGetUser": "Pobierz użytkownika", @@ -998,7 +991,6 @@ "actionDeleteSite": "Usuń witrynę", "actionGetSite": "Pobierz witrynę", "actionListSites": "Lista witryn", - "actionApplyBlueprint": "Zastosuj schemat", "setupToken": "Skonfiguruj token", "setupTokenDescription": "Wprowadź token konfiguracji z konsoli serwera.", "setupTokenRequired": "Wymagany jest token konfiguracji", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Licencja", "sidebarClients": "Klienci (Beta)", "sidebarDomains": "Domeny", - "enableDockerSocket": "Włącz schemat dokera", - "enableDockerSocketDescription": "Włącz etykietowanie kieszeni dokującej dla etykiet schematów. Ścieżka do gniazda musi być dostarczona do Newt.", + "enableDockerSocket": "Włącz gniazdo dokera", + "enableDockerSocketDescription": "Włącz wykrywanie Docker Socket w celu wypełnienia informacji o kontenerach. Ścieżka gniazda musi być dostarczona do Newt.", "enableDockerSocketLink": "Dowiedz się więcej", "viewDockerContainers": "Zobacz kontenery dokujące", "containersIn": "Pojemniki w {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Dostępna aktualizacja", "newtUpdateAvailableInfo": "Nowa wersja Newt jest dostępna. Prosimy o aktualizację do najnowszej wersji dla najlepszej pracy.", "domainPickerEnterDomain": "Domena", - "domainPickerPlaceholder": "mojapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com lub po prostu myapp", "domainPickerDescription": "Wpisz pełną domenę zasobu, aby zobaczyć dostępne opcje.", "domainPickerDescriptionSaas": "Wprowadź pełną domenę, subdomenę lub po prostu nazwę, aby zobaczyć dostępne opcje", "domainPickerTabAll": "Wszystko", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protokół", "editInternalResourceDialogSitePort": "Port witryny", "editInternalResourceDialogTargetConfiguration": "Konfiguracja celu", + "editInternalResourceDialogDestinationIP": "IP docelowe", + "editInternalResourceDialogDestinationPort": "Port docelowy", "editInternalResourceDialogCancel": "Anuluj", "editInternalResourceDialogSaveResource": "Zapisz zasób", "editInternalResourceDialogSuccess": "Sukces", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Port witryny", "createInternalResourceDialogSitePortDescription": "Użyj tego portu, aby uzyskać dostęp do zasobu na stronie, gdy połączony z klientem.", "createInternalResourceDialogTargetConfiguration": "Konfiguracja celu", - "createInternalResourceDialogDestinationIPDescription": "Adres IP lub nazwa hosta zasobu w sieci witryny.", + "createInternalResourceDialogDestinationIP": "IP docelowe", + "createInternalResourceDialogDestinationIPDescription": "Adres IP zasobu w sieci strony.", + "createInternalResourceDialogDestinationPort": "Port docelowy", "createInternalResourceDialogDestinationPortDescription": "Port na docelowym IP, gdzie zasób jest dostępny.", "createInternalResourceDialogCancel": "Anuluj", "createInternalResourceDialogCreateResource": "Utwórz zasób", @@ -1500,24 +1496,5 @@ "convertButton": "Konwertuj ten węzeł do zarządzanego samodzielnie" }, "internationaldomaindetected": "Wykryto międzynarodową domenę", - "willbestoredas": "Będą przechowywane jako:", - "idpGoogleDescription": "Dostawca Google OAuth2/OIDC", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Niestandardowe nagłówki", - "headersValidationError": "Nagłówki muszą być w formacie: Nazwa nagłówka: wartość.", - "domainPickerProvidedDomain": "Dostarczona domena", - "domainPickerFreeProvidedDomain": "Darmowa oferowana domena", - "domainPickerVerified": "Zweryfikowano", - "domainPickerUnverified": "Niezweryfikowane", - "domainPickerInvalidSubdomainStructure": "Ta subdomena zawiera nieprawidłowe znaki lub strukturę. Zostanie ona automatycznie oczyszczona po zapisaniu.", - "domainPickerError": "Błąd", - "domainPickerErrorLoadDomains": "Nie udało się załadować domen organizacji", - "domainPickerErrorCheckAvailability": "Nie udało się sprawdzić dostępności domeny", - "domainPickerInvalidSubdomain": "Nieprawidłowa subdomena", - "domainPickerInvalidSubdomainRemoved": "Wejście \"{sub}\" zostało usunięte, ponieważ jest nieprawidłowe.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" nie może być poprawne dla {domain}.", - "domainPickerSubdomainSanitized": "Poddomena oczyszczona", - "domainPickerSubdomainCorrected": "\"{sub}\" został skorygowany do \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Edytuj plik: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Edytuj plik: docker-compose.yml" + "willbestoredas": "Będą przechowywane jako:" } diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 9fd73d49..84afb6aa 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Ocorreu um erro ao adicionar usuário à função.", "userSaved": "Usuário salvo", "userSavedDescription": "O usuário foi atualizado.", - "autoProvisioned": "Auto provisionado", - "autoProvisionedDescription": "Permitir que este usuário seja gerenciado automaticamente pelo provedor de identidade", "accessControlsDescription": "Gerencie o que este usuário pode acessar e fazer na organização", "accessControlsSubmit": "Salvar Controles de Acesso", "roles": "Funções", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Formato de endereço IP inválido", "ipAddressErrorInvalidOctet": "Octeto de endereço IP inválido", "path": "Caminho", - "matchPath": "Correspondência de caminho", "ipAddressRange": "Faixa de IP", "rulesErrorFetch": "Falha ao buscar regras", "rulesErrorFetchDescription": "Ocorreu um erro ao buscar regras", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Conectado", "idpErrorConnectingTo": "Ocorreu um problema ao ligar a {name}. Por favor, contacte o seu administrador.", "idpErrorNotFound": "IdP não encontrado", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Convite Inválido", "inviteInvalidDescription": "O link do convite é inválido.", "inviteErrorWrongUser": "O convite não é para este usuário", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Edição Profissional Necessária", "licenseTierProfessionalRequiredDescription": "Esta funcionalidade só está disponível na Edição Profissional.", "actionGetOrg": "Obter Organização", - "updateOrgUser": "Atualizar usuário Org", - "createOrgUser": "Criar usuário Org", "actionUpdateOrg": "Atualizar Organização", "actionUpdateUser": "Atualizar Usuário", "actionGetUser": "Obter Usuário", @@ -998,7 +991,6 @@ "actionDeleteSite": "Eliminar Site", "actionGetSite": "Obter Site", "actionListSites": "Listar Sites", - "actionApplyBlueprint": "Aplicar Diagrama", "setupToken": "Configuração do Token", "setupTokenDescription": "Digite o token de configuração do console do servidor.", "setupTokenRequired": "Token de configuração é necessário", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Tipo:", "sidebarClients": "Clientes (Beta)", "sidebarDomains": "Domínios", - "enableDockerSocket": "Habilitar o Diagrama Docker", - "enableDockerSocketDescription": "Ativar a scraping de rótulo Docker para rótulos de diagramas. Caminho de Socket deve ser fornecido para Newt.", + "enableDockerSocket": "Habilitar Docker Socket", + "enableDockerSocketDescription": "Ativar a descoberta do Docker Socket para preencher informações do contêiner. O caminho do socket deve ser fornecido ao Newt.", "enableDockerSocketLink": "Saiba mais", "viewDockerContainers": "Ver contêineres Docker", "containersIn": "Contêineres em {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Nova Atualização Disponível", "newtUpdateAvailableInfo": "Uma nova versão do Newt está disponível. Atualize para a versão mais recente para uma melhor experiência.", "domainPickerEnterDomain": "Domínio", - "domainPickerPlaceholder": "myapp.exemplo.com", + "domainPickerPlaceholder": "meuapp.exemplo.com, api.v1.meudominio.com, ou apenas meuapp", "domainPickerDescription": "Insira o domínio completo do recurso para ver as opções disponíveis.", "domainPickerDescriptionSaas": "Insira um domínio completo, subdomínio ou apenas um nome para ver as opções disponíveis", "domainPickerTabAll": "Todos", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protocolo", "editInternalResourceDialogSitePort": "Porta do Site", "editInternalResourceDialogTargetConfiguration": "Configuração do Alvo", + "editInternalResourceDialogDestinationIP": "IP de Destino", + "editInternalResourceDialogDestinationPort": "Porta de Destino", "editInternalResourceDialogCancel": "Cancelar", "editInternalResourceDialogSaveResource": "Salvar Recurso", "editInternalResourceDialogSuccess": "Sucesso", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Porta do Site", "createInternalResourceDialogSitePortDescription": "Use esta porta para acessar o recurso no site quando conectado com um cliente.", "createInternalResourceDialogTargetConfiguration": "Configuração do Alvo", - "createInternalResourceDialogDestinationIPDescription": "O IP ou endereço do hostname do recurso na rede do site.", + "createInternalResourceDialogDestinationIP": "IP de Destino", + "createInternalResourceDialogDestinationIPDescription": "O endereço IP do recurso na rede do site.", + "createInternalResourceDialogDestinationPort": "Porta de Destino", "createInternalResourceDialogDestinationPortDescription": "A porta no IP de destino onde o recurso está acessível.", "createInternalResourceDialogCancel": "Cancelar", "createInternalResourceDialogCreateResource": "Criar Recurso", @@ -1500,24 +1496,5 @@ "convertButton": "Converter este nó para Auto-Hospedado Gerenciado" }, "internationaldomaindetected": "Domínio Internacional Detectado", - "willbestoredas": "Será armazenado como:", - "idpGoogleDescription": "Provedor Google OAuth2/OIDC", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Cabeçalhos Personalizados", - "headersValidationError": "Cabeçalhos devem estar no formato: Nome do Cabeçalho: valor.", - "domainPickerProvidedDomain": "Domínio fornecido", - "domainPickerFreeProvidedDomain": "Domínio fornecido grátis", - "domainPickerVerified": "Verificada", - "domainPickerUnverified": "Não verificado", - "domainPickerInvalidSubdomainStructure": "Este subdomínio contém caracteres ou estrutura inválidos. Ele será eliminado automaticamente quando você salvar.", - "domainPickerError": "ERRO", - "domainPickerErrorLoadDomains": "Falha ao carregar domínios da organização", - "domainPickerErrorCheckAvailability": "Não foi possível verificar a disponibilidade do domínio", - "domainPickerInvalidSubdomain": "Subdomínio inválido", - "domainPickerInvalidSubdomainRemoved": "A entrada \"{sub}\" foi removida porque ela não é válida.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" não pôde ser válido para {domain}.", - "domainPickerSubdomainSanitized": "Subdomínio banalizado", - "domainPickerSubdomainCorrected": "\"{sub}\" foi corrigido para \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Editar arquivo: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Editar arquivo: docker-compose.yml" + "willbestoredas": "Será armazenado como:" } diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 9c38cc11..ffcbe8dc 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Произошла ошибка при добавлении пользователя в роль.", "userSaved": "Пользователь сохранён", "userSavedDescription": "Пользователь был обновлён.", - "autoProvisioned": "Автоподбор", - "autoProvisionedDescription": "Разрешить автоматическое управление этим пользователем", "accessControlsDescription": "Управляйте тем, к чему этот пользователь может получить доступ и что делать в организации", "accessControlsSubmit": "Сохранить контроль доступа", "roles": "Роли", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Неверный формат IP адреса", "ipAddressErrorInvalidOctet": "Неверный октет IP адреса", "path": "Путь", - "matchPath": "Путь матча", "ipAddressRange": "Диапазон IP", "rulesErrorFetch": "Не удалось получить правила", "rulesErrorFetchDescription": "Произошла ошибка при получении правил", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Подключено", "idpErrorConnectingTo": "Возникла проблема при подключении к {name}. Пожалуйста, свяжитесь с вашим администратором.", "idpErrorNotFound": "IdP не найден", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Недействительное приглашение", "inviteInvalidDescription": "Ссылка на приглашение недействительна.", "inviteErrorWrongUser": "Приглашение не для этого пользователя", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Требуется профессиональная версия", "licenseTierProfessionalRequiredDescription": "Эта функция доступна только в профессиональной версии.", "actionGetOrg": "Получить организацию", - "updateOrgUser": "Обновить пользователя Org", - "createOrgUser": "Создать пользователя Org", "actionUpdateOrg": "Обновить организацию", "actionUpdateUser": "Обновить пользователя", "actionGetUser": "Получить пользователя", @@ -998,7 +991,6 @@ "actionDeleteSite": "Удалить сайт", "actionGetSite": "Получить сайт", "actionListSites": "Список сайтов", - "actionApplyBlueprint": "Применить чертёж", "setupToken": "Код настройки", "setupTokenDescription": "Введите токен настройки из консоли сервера.", "setupTokenRequired": "Токен настройки обязателен", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Лицензия", "sidebarClients": "Клиенты (бета)", "sidebarDomains": "Домены", - "enableDockerSocket": "Включить чертёж Docker", - "enableDockerSocketDescription": "Включить scraping ярлыка Docker Socket для ярлыков чертежей. Путь к сокету должен быть предоставлен в Newt.", + "enableDockerSocket": "Включить Docker Socket", + "enableDockerSocketDescription": "Включить обнаружение Docker Socket для заполнения информации о контейнерах. Путь к сокету должен быть предоставлен Newt.", "enableDockerSocketLink": "Узнать больше", "viewDockerContainers": "Просмотр контейнеров Docker", "containersIn": "Контейнеры в {siteName}", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Доступно обновление", "newtUpdateAvailableInfo": "Доступна новая версия Newt. Пожалуйста, обновитесь до последней версии для лучшего опыта.", "domainPickerEnterDomain": "Домен", - "domainPickerPlaceholder": "myapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, или просто myapp", "domainPickerDescription": "Введите полный домен ресурса, чтобы увидеть доступные опции.", "domainPickerDescriptionSaas": "Введите полный домен, поддомен или просто имя, чтобы увидеть доступные опции", "domainPickerTabAll": "Все", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Протокол", "editInternalResourceDialogSitePort": "Порт сайта", "editInternalResourceDialogTargetConfiguration": "Настройка цели", + "editInternalResourceDialogDestinationIP": "Целевая IP", + "editInternalResourceDialogDestinationPort": "Целевой порт", "editInternalResourceDialogCancel": "Отмена", "editInternalResourceDialogSaveResource": "Сохранить ресурс", "editInternalResourceDialogSuccess": "Успешно", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Порт сайта", "createInternalResourceDialogSitePortDescription": "Используйте этот порт для доступа к ресурсу на сайте при подключении с клиентом.", "createInternalResourceDialogTargetConfiguration": "Настройка цели", - "createInternalResourceDialogDestinationIPDescription": "IP или адрес хоста ресурса в сети сайта.", + "createInternalResourceDialogDestinationIP": "Целевая IP", + "createInternalResourceDialogDestinationIPDescription": "IP-адрес ресурса в сети сайта.", + "createInternalResourceDialogDestinationPort": "Целевой порт", "createInternalResourceDialogDestinationPortDescription": "Порт на IP-адресе назначения, где доступен ресурс.", "createInternalResourceDialogCancel": "Отмена", "createInternalResourceDialogCreateResource": "Создать ресурс", @@ -1500,24 +1496,5 @@ "convertButton": "Конвертировать этот узел в управляемый себе-хост" }, "internationaldomaindetected": "Обнаружен международный домен", - "willbestoredas": "Будет храниться как:", - "idpGoogleDescription": "Google OAuth2/OIDC провайдер", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "Пользовательские заголовки", - "headersValidationError": "Заголовки должны быть в формате: Название заголовка: значение.", - "domainPickerProvidedDomain": "Домен предоставлен", - "domainPickerFreeProvidedDomain": "Бесплатный домен", - "domainPickerVerified": "Подтверждено", - "domainPickerUnverified": "Не подтверждено", - "domainPickerInvalidSubdomainStructure": "Этот поддомен содержит недопустимые символы или структуру. Он будет очищен автоматически при сохранении.", - "domainPickerError": "Ошибка", - "domainPickerErrorLoadDomains": "Не удалось загрузить домены организации", - "domainPickerErrorCheckAvailability": "Не удалось проверить доступность домена", - "domainPickerInvalidSubdomain": "Неверный поддомен", - "domainPickerInvalidSubdomainRemoved": "Ввод \"{sub}\" был удален, потому что он недействителен.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" не может быть действительным для {domain}.", - "domainPickerSubdomainSanitized": "Субдомен очищен", - "domainPickerSubdomainCorrected": "\"{sub}\" был исправлен на \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "Редактировать файл: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Редактировать файл: docker-compose.yml" + "willbestoredas": "Будет храниться как:" } diff --git a/messages/tr-TR.json b/messages/tr-TR.json index ef812850..2253dab2 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "Kullanıcı role eklenirken bir hata oluştu.", "userSaved": "Kullanıcı kaydedildi", "userSavedDescription": "Kullanıcı güncellenmiştir.", - "autoProvisioned": "Otomatik Sağlandı", - "autoProvisionedDescription": "Bu kullanıcının kimlik sağlayıcısı tarafından otomatik olarak yönetilmesine izin ver", "accessControlsDescription": "Bu kullanıcının organizasyonda neleri erişebileceğini ve yapabileceğini yönetin", "accessControlsSubmit": "Erişim Kontrollerini Kaydet", "roles": "Roller", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "Geçersiz IP adresi formatı", "ipAddressErrorInvalidOctet": "Geçersiz IP adresi okteti", "path": "Yol", - "matchPath": "Yol Eşleştir", "ipAddressRange": "IP Aralığı", "rulesErrorFetch": "Kurallar alınamadı", "rulesErrorFetchDescription": "Kurallar alınırken bir hata oluştu", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "Bağlandı", "idpErrorConnectingTo": "{name} ile bağlantı kurarken bir sorun meydana geldi. Lütfen yöneticiye danışın.", "idpErrorNotFound": "IdP bulunamadı", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "Geçersiz Davet", "inviteInvalidDescription": "Davet bağlantısı geçersiz.", "inviteErrorWrongUser": "Davet bu kullanıcı için değil", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "Profesyonel Sürüme Gereklidir", "licenseTierProfessionalRequiredDescription": "Bu özellik yalnızca Professional Edition'da kullanılabilir.", "actionGetOrg": "Kuruluşu Al", - "updateOrgUser": "Organizasyon Kullanıcısını Güncelle", - "createOrgUser": "Organizasyon Kullanıcısı Oluştur", "actionUpdateOrg": "Kuruluşu Güncelle", "actionUpdateUser": "Kullanıcıyı Güncelle", "actionGetUser": "Kullanıcıyı Getir", @@ -998,7 +991,6 @@ "actionDeleteSite": "Siteyi Sil", "actionGetSite": "Siteyi Al", "actionListSites": "Siteleri Listele", - "actionApplyBlueprint": "Planı Uygula", "setupToken": "Kurulum Simgesi", "setupTokenDescription": "Sunucu konsolundan kurulum simgesini girin.", "setupTokenRequired": "Kurulum simgesi gerekli", @@ -1141,8 +1133,8 @@ "sidebarLicense": "Lisans", "sidebarClients": "Müşteriler (Beta)", "sidebarDomains": "Alan Adları", - "enableDockerSocket": "Docker Soketini Etkinleştir", - "enableDockerSocketDescription": "Plan etiketleri için Docker Socket etiket toplamasını etkinleştirin. Newt'e soket yolu sağlanmalıdır.", + "enableDockerSocket": "Docker Soketi Etkinleştir", + "enableDockerSocketDescription": "Konteyner bilgilerini doldurmak için Docker Socket keşfini etkinleştirin. Socket yolu Newt'e sağlanmalıdır.", "enableDockerSocketLink": "Daha fazla bilgi", "viewDockerContainers": "Docker Konteynerlerini Görüntüle", "containersIn": "{siteName} içindeki konteynerler", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "Güncelleme Mevcut", "newtUpdateAvailableInfo": "Newt'in yeni bir versiyonu mevcut. En iyi deneyim için lütfen en son sürüme güncelleyin.", "domainPickerEnterDomain": "Domain", - "domainPickerPlaceholder": "myapp.example.com", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com veya sadece myapp", "domainPickerDescription": "Mevcut seçenekleri görmek için kaynağın tam etki alanını girin.", "domainPickerDescriptionSaas": "Mevcut seçenekleri görmek için tam etki alanı, alt etki alanı veya sadece bir isim girin", "domainPickerTabAll": "Tümü", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "Protokol", "editInternalResourceDialogSitePort": "Site Bağlantı Noktası", "editInternalResourceDialogTargetConfiguration": "Hedef Yapılandırma", + "editInternalResourceDialogDestinationIP": "Hedef IP", + "editInternalResourceDialogDestinationPort": "Hedef Bağlantı Noktası", "editInternalResourceDialogCancel": "İptal", "editInternalResourceDialogSaveResource": "Kaynağı Kaydet", "editInternalResourceDialogSuccess": "Başarı", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "Site Bağlantı Noktası", "createInternalResourceDialogSitePortDescription": "İstemci ile bağlanıldığında site üzerindeki kaynağa erişmek için bu bağlantı noktasını kullanın.", "createInternalResourceDialogTargetConfiguration": "Hedef Yapılandırma", - "createInternalResourceDialogDestinationIPDescription": "Kaynağın site ağındaki IP veya ana bilgisayar adresi.", + "createInternalResourceDialogDestinationIP": "Hedef IP", + "createInternalResourceDialogDestinationIPDescription": "Site ağındaki kaynağın IP adresi.", + "createInternalResourceDialogDestinationPort": "Hedef Bağlantı Noktası", "createInternalResourceDialogDestinationPortDescription": "Kaynağa erişilebilecek hedef IP üzerindeki bağlantı noktası.", "createInternalResourceDialogCancel": "İptal", "createInternalResourceDialogCreateResource": "Kaynak Oluştur", @@ -1500,24 +1496,5 @@ "convertButton": "Bu Düğümü Yönetilen Kendi Kendine Barındırma Dönüştürün" }, "internationaldomaindetected": "Uluslararası Alan Adı Tespit Edildi", - "willbestoredas": "Şu şekilde depolanacak:", - "idpGoogleDescription": "Google OAuth2/OIDC sağlayıcısı", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC sağlayıcısı", - "customHeaders": "Özel Başlıklar", - "headersValidationError": "Başlıklar şu formatta olmalıdır: Başlık-Adı: değer.", - "domainPickerProvidedDomain": "Sağlanan Alan Adı", - "domainPickerFreeProvidedDomain": "Ücretsiz Sağlanan Alan Adı", - "domainPickerVerified": "Doğrulandı", - "domainPickerUnverified": "Doğrulanmadı", - "domainPickerInvalidSubdomainStructure": "Bu alt alan adı geçersiz karakterler veya yapı içeriyor. Kaydettiğinizde otomatik olarak temizlenecektir.", - "domainPickerError": "Hata", - "domainPickerErrorLoadDomains": "Organizasyon alan adları yüklenemedi", - "domainPickerErrorCheckAvailability": "Alan adı kullanılabilirliği kontrol edilemedi", - "domainPickerInvalidSubdomain": "Geçersiz alt alan adı", - "domainPickerInvalidSubdomainRemoved": "Girdi \"{sub}\" geçersiz olduğu için kaldırıldı.", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" {domain} için geçerli yapılamadı.", - "domainPickerSubdomainSanitized": "Alt alan adı temizlendi", - "domainPickerSubdomainCorrected": "\"{sub}\" \"{sanitized}\" olarak düzeltildi", - "resourceAddEntrypointsEditFile": "Dosyayı düzenle: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Dosyayı düzenle: docker-compose.yml" + "willbestoredas": "Şu şekilde depolanacak:" } diff --git a/messages/zh-CN.json b/messages/zh-CN.json index c78d7460..1eaa2263 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -454,8 +454,6 @@ "accessRoleErrorAddDescription": "添加用户到角色时出错。", "userSaved": "用户已保存", "userSavedDescription": "用户已更新。", - "autoProvisioned": "自动设置", - "autoProvisionedDescription": "允许此用户由身份提供商自动管理", "accessControlsDescription": "管理此用户在组织中可以访问和做什么", "accessControlsSubmit": "保存访问控制", "roles": "角色", @@ -513,7 +511,6 @@ "ipAddressErrorInvalidFormat": "无效的 IP 地址格式", "ipAddressErrorInvalidOctet": "无效的 IP 地址", "path": "路径", - "matchPath": "匹配路径", "ipAddressRange": "IP 范围", "rulesErrorFetch": "获取规则失败", "rulesErrorFetchDescription": "获取规则时出错", @@ -914,8 +911,6 @@ "idpConnectingToFinished": "已连接", "idpErrorConnectingTo": "无法连接到 {name},请联系管理员协助处理。", "idpErrorNotFound": "找不到 IdP", - "idpGoogleAlt": "Google", - "idpAzureAlt": "Azure", "inviteInvalid": "无效邀请", "inviteInvalidDescription": "邀请链接无效。", "inviteErrorWrongUser": "邀请不是该用户的", @@ -987,8 +982,6 @@ "licenseTierProfessionalRequired": "需要专业版", "licenseTierProfessionalRequiredDescription": "此功能仅在专业版可用。", "actionGetOrg": "获取组织", - "updateOrgUser": "更新组织用户", - "createOrgUser": "创建组织用户", "actionUpdateOrg": "更新组织", "actionUpdateUser": "更新用户", "actionGetUser": "获取用户", @@ -998,7 +991,6 @@ "actionDeleteSite": "删除站点", "actionGetSite": "获取站点", "actionListSites": "站点列表", - "actionApplyBlueprint": "应用蓝图", "setupToken": "设置令牌", "setupTokenDescription": "从服务器控制台输入设置令牌。", "setupTokenRequired": "需要设置令牌", @@ -1141,8 +1133,8 @@ "sidebarLicense": "证书", "sidebarClients": "客户端(测试版)", "sidebarDomains": "域", - "enableDockerSocket": "启用 Docker 蓝图", - "enableDockerSocketDescription": "启用 Docker Socket 标签擦除蓝图标签。套接字路径必须提供给新的。", + "enableDockerSocket": "启用停靠套接字", + "enableDockerSocketDescription": "启用 Docker Socket 发现以填充容器信息。必须向 Newt 提供 Socket 路径。", "enableDockerSocketLink": "了解更多", "viewDockerContainers": "查看停靠容器", "containersIn": "{siteName} 中的容器", @@ -1242,7 +1234,7 @@ "newtUpdateAvailable": "更新可用", "newtUpdateAvailableInfo": "新版本的 Newt 已可用。请更新到最新版本以获得最佳体验。", "domainPickerEnterDomain": "域名", - "domainPickerPlaceholder": "example.com", + "domainPickerPlaceholder": "myapp.example.com、api.v1.mydomain.com 或仅 myapp", "domainPickerDescription": "输入资源的完整域名以查看可用选项。", "domainPickerDescriptionSaas": "输入完整域名、子域或名称以查看可用选项。", "domainPickerTabAll": "所有", @@ -1400,6 +1392,8 @@ "editInternalResourceDialogProtocol": "协议", "editInternalResourceDialogSitePort": "站点端口", "editInternalResourceDialogTargetConfiguration": "目标配置", + "editInternalResourceDialogDestinationIP": "目标IP", + "editInternalResourceDialogDestinationPort": "目标端口", "editInternalResourceDialogCancel": "取消", "editInternalResourceDialogSaveResource": "保存资源", "editInternalResourceDialogSuccess": "成功", @@ -1430,7 +1424,9 @@ "createInternalResourceDialogSitePort": "站点端口", "createInternalResourceDialogSitePortDescription": "使用此端口在连接到客户端时访问站点上的资源。", "createInternalResourceDialogTargetConfiguration": "目标配置", - "createInternalResourceDialogDestinationIPDescription": "站点网络上资源的IP或主机名地址。", + "createInternalResourceDialogDestinationIP": "目标IP", + "createInternalResourceDialogDestinationIPDescription": "站点网络上资源的IP地址。", + "createInternalResourceDialogDestinationPort": "目标端口", "createInternalResourceDialogDestinationPortDescription": "资源在目标IP上可访问的端口。", "createInternalResourceDialogCancel": "取消", "createInternalResourceDialogCreateResource": "创建资源", @@ -1500,24 +1496,5 @@ "convertButton": "将此节点转换为管理自托管的" }, "internationaldomaindetected": "检测到国际域", - "willbestoredas": "储存为:", - "idpGoogleDescription": "Google OAuth2/OIDC 提供商", - "idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider", - "customHeaders": "自定义标题", - "headersValidationError": "头部必须是格式:头部名称:值。", - "domainPickerProvidedDomain": "提供的域", - "domainPickerFreeProvidedDomain": "免费提供的域", - "domainPickerVerified": "已验证", - "domainPickerUnverified": "未验证", - "domainPickerInvalidSubdomainStructure": "此子域包含无效的字符或结构。当您保存时,它将被自动清除。", - "domainPickerError": "错误", - "domainPickerErrorLoadDomains": "加载组织域名失败", - "domainPickerErrorCheckAvailability": "检查域可用性失败", - "domainPickerInvalidSubdomain": "无效的子域", - "domainPickerInvalidSubdomainRemoved": "输入 \"{sub}\" 已被移除,因为其无效。", - "domainPickerInvalidSubdomainCannotMakeValid": "\"{sub}\" 无法为 {domain} 变为有效。", - "domainPickerSubdomainSanitized": "子域已净化", - "domainPickerSubdomainCorrected": "\"{sub}\" 已被更正为 \"{sanitized}\"", - "resourceAddEntrypointsEditFile": "编辑文件:config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "编辑文件:docker-compose.yml" + "willbestoredas": "储存为:" } diff --git a/package-lock.json b/package-lock.json index 931e3178..2d8db128 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "SEE LICENSE IN LICENSE AND README.md", "dependencies": { "@asteasolutions/zod-to-openapi": "^7.3.4", - "@hookform/resolvers": "4.1.3", + "@hookform/resolvers": "3.9.1", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", @@ -32,15 +32,15 @@ "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-tooltip": "^1.2.8", - "@react-email/components": "0.5.3", + "@react-email/components": "0.5.0", "@react-email/render": "^1.2.0", "@react-email/tailwind": "1.2.2", - "@simplewebauthn/browser": "^13.1.2", + "@simplewebauthn/browser": "^13.1.0", "@simplewebauthn/server": "^9.0.3", "@tailwindcss/forms": "^0.5.10", "@tanstack/react-table": "8.21.3", "arctic": "^3.7.0", - "axios": "^1.12.2", + "axios": "1.11.0", "better-sqlite3": "11.7.0", "canvas-confetti": "1.9.3", "class-variance-authority": "^0.7.1", @@ -51,11 +51,11 @@ "cookies": "^0.9.1", "cors": "2.8.5", "crypto-js": "^4.2.0", - "drizzle-orm": "0.44.5", - "eslint": "9.35.0", - "eslint-config-next": "15.5.3", + "drizzle-orm": "0.44.4", + "eslint": "9.33.0", + "eslint-config-next": "15.4.6", "express": "5.1.0", - "express-rate-limit": "8.1.0", + "express-rate-limit": "8.0.1", "glob": "11.0.3", "helmet": "8.1.0", "http-errors": "2.0.0", @@ -64,29 +64,30 @@ "jmespath": "^0.16.0", "js-yaml": "4.1.0", "jsonwebtoken": "^9.0.2", - "lucide-react": "^0.544.0", + "lucide-react": "0.539.0", "moment": "2.30.1", - "next": "15.5.3", - "next-intl": "^4.3.9", + "next": "15.4.6", + "next-intl": "^4.3.4", "next-themes": "0.4.6", "node-cache": "5.1.2", "node-fetch": "3.3.2", - "nodemailer": "7.0.6", - "npm": "^11.6.0", + "nodemailer": "7.0.5", + "npm": "^11.5.2", "oslo": "1.2.1", "pg": "^8.16.2", "qrcode.react": "4.2.0", "react": "19.1.1", "react-dom": "19.1.1", - "react-easy-sort": "^1.7.0", + "react-easy-sort": "^1.6.0", "react-hook-form": "7.62.0", "react-icons": "^5.5.0", "rebuild": "0.1.2", "semver": "^7.7.2", + "source-map-support": "0.5.21", "swagger-ui-express": "^5.0.1", "tailwind-merge": "3.3.1", - "tw-animate-css": "^1.3.8", - "uuid": "^13.0.0", + "tw-animate-css": "^1.3.7", + "uuid": "^11.1.0", "vaul": "1.1.2", "winston": "3.17.0", "winston-daily-rotate-file": "5.0.0", @@ -96,9 +97,9 @@ "zod-validation-error": "3.5.2" }, "devDependencies": { - "@dotenvx/dotenvx": "1.49.1", + "@dotenvx/dotenvx": "1.49.0", "@esbuild-plugins/tsconfig-paths": "0.1.2", - "@tailwindcss/postcss": "^4.1.13", + "@tailwindcss/postcss": "^4.1.12", "@types/better-sqlite3": "7.6.12", "@types/cookie-parser": "1.4.9", "@types/cors": "2.8.19", @@ -108,25 +109,25 @@ "@types/jmespath": "^0.15.2", "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "24.5.2", - "@types/nodemailer": "7.0.1", + "@types/node": "^24", + "@types/nodemailer": "6.4.17", "@types/pg": "8.15.5", - "@types/react": "19.1.13", + "@types/react": "19.1.12", "@types/react-dom": "19.1.9", - "@types/semver": "^7.7.1", + "@types/semver": "^7.7.0", "@types/swagger-ui-express": "^4.1.8", "@types/ws": "8.18.1", "@types/yargs": "17.0.33", "drizzle-kit": "0.31.4", - "esbuild": "0.25.10", + "esbuild": "0.25.9", "esbuild-node-externals": "1.18.0", "postcss": "^8", - "react-email": "4.2.11", + "react-email": "4.2.8", "tailwindcss": "^4.1.4", "tsc-alias": "1.8.16", "tsx": "4.20.5", "typescript": "^5", - "typescript-eslint": "^8.44.0" + "typescript-eslint": "^8.40.0" } }, "node_modules/@alloc/quick-lru": { @@ -154,731 +155,6 @@ "zod": "^3.20.2" } }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sesv2": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.888.0.tgz", - "integrity": "sha512-Zy7AXvj4oVLE5Zkj61qYZxIFgJXbRgTmFJvQ/EqgxE87KPR9+gF5wtC3iqcKEmkqFlWlxWrlhV4K70Vqqj4bZQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.888.0", - "@aws-sdk/credential-provider-node": "3.888.0", - "@aws-sdk/middleware-host-header": "3.887.0", - "@aws-sdk/middleware-logger": "3.887.0", - "@aws-sdk/middleware-recursion-detection": "3.887.0", - "@aws-sdk/middleware-user-agent": "3.888.0", - "@aws-sdk/region-config-resolver": "3.887.0", - "@aws-sdk/signature-v4-multi-region": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@aws-sdk/util-endpoints": "3.887.0", - "@aws-sdk/util-user-agent-browser": "3.887.0", - "@aws-sdk/util-user-agent-node": "3.888.0", - "@smithy/config-resolver": "^4.2.1", - "@smithy/core": "^3.11.0", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.1", - "@smithy/middleware-retry": "^4.2.1", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.1", - "@smithy/util-defaults-mode-node": "^4.1.1", - "@smithy/util-endpoints": "^3.1.1", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.888.0.tgz", - "integrity": "sha512-8CLy/ehGKUmekjH+VtZJ4w40PqDg3u0K7uPziq/4P8Q7LLgsy8YQoHNbuY4am7JU3HWrqLXJI9aaz1+vPGPoWA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.888.0", - "@aws-sdk/middleware-host-header": "3.887.0", - "@aws-sdk/middleware-logger": "3.887.0", - "@aws-sdk/middleware-recursion-detection": "3.887.0", - "@aws-sdk/middleware-user-agent": "3.888.0", - "@aws-sdk/region-config-resolver": "3.887.0", - "@aws-sdk/types": "3.887.0", - "@aws-sdk/util-endpoints": "3.887.0", - "@aws-sdk/util-user-agent-browser": "3.887.0", - "@aws-sdk/util-user-agent-node": "3.888.0", - "@smithy/config-resolver": "^4.2.1", - "@smithy/core": "^3.11.0", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.1", - "@smithy/middleware-retry": "^4.2.1", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.1", - "@smithy/util-defaults-mode-node": "^4.1.1", - "@smithy/util-endpoints": "^3.1.1", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.888.0.tgz", - "integrity": "sha512-L3S2FZywACo4lmWv37Y4TbefuPJ1fXWyWwIJ3J4wkPYFJ47mmtUPqThlVrSbdTHkEjnZgJe5cRfxk0qCLsFh1w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.887.0", - "@aws-sdk/xml-builder": "3.887.0", - "@smithy/core": "^3.11.0", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/property-provider": "^4.0.5", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.1.3", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.888.0.tgz", - "integrity": "sha512-shPi4AhUKbIk7LugJWvNpeZA8va7e5bOHAEKo89S0Ac8WDZt2OaNzbh/b9l0iSL2eEyte8UgIsYGcFxOwIF1VA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/property-provider": "^4.0.5", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.888.0.tgz", - "integrity": "sha512-Jvuk6nul0lE7o5qlQutcqlySBHLXOyoPtiwE6zyKbGc7RVl0//h39Lab7zMeY2drMn8xAnIopL4606Fd8JI/Hw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/property-provider": "^4.0.5", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "@smithy/util-stream": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.888.0.tgz", - "integrity": "sha512-M82ItvS5yq+tO6ZOV1ruaVs2xOne+v8HW85GFCXnz8pecrzYdgxh6IsVqEbbWruryG/mUGkWMbkBZoEsy4MgyA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.888.0", - "@aws-sdk/credential-provider-env": "3.888.0", - "@aws-sdk/credential-provider-http": "3.888.0", - "@aws-sdk/credential-provider-process": "3.888.0", - "@aws-sdk/credential-provider-sso": "3.888.0", - "@aws-sdk/credential-provider-web-identity": "3.888.0", - "@aws-sdk/nested-clients": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/credential-provider-imds": "^4.0.7", - "@smithy/property-provider": "^4.0.5", - "@smithy/shared-ini-file-loader": "^4.0.5", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.888.0.tgz", - "integrity": "sha512-KCrQh1dCDC8Y+Ap3SZa6S81kHk+p+yAaOQ5jC3dak4zhHW3RCrsGR/jYdemTOgbEGcA6ye51UbhWfrrlMmeJSA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.888.0", - "@aws-sdk/credential-provider-http": "3.888.0", - "@aws-sdk/credential-provider-ini": "3.888.0", - "@aws-sdk/credential-provider-process": "3.888.0", - "@aws-sdk/credential-provider-sso": "3.888.0", - "@aws-sdk/credential-provider-web-identity": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/credential-provider-imds": "^4.0.7", - "@smithy/property-provider": "^4.0.5", - "@smithy/shared-ini-file-loader": "^4.0.5", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.888.0.tgz", - "integrity": "sha512-+aX6piSukPQ8DUS4JAH344GePg8/+Q1t0+kvSHAZHhYvtQ/1Zek3ySOJWH2TuzTPCafY4nmWLcQcqvU1w9+4Lw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/property-provider": "^4.0.5", - "@smithy/shared-ini-file-loader": "^4.0.5", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.888.0.tgz", - "integrity": "sha512-b1ZJji7LJ6E/j1PhFTyvp51in2iCOQ3VP6mj5H6f5OUnqn7efm41iNMoinKr87n0IKZw7qput5ggXVxEdPhouA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.888.0", - "@aws-sdk/core": "3.888.0", - "@aws-sdk/token-providers": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/property-provider": "^4.0.5", - "@smithy/shared-ini-file-loader": "^4.0.5", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.888.0.tgz", - "integrity": "sha512-7P0QNtsDzMZdmBAaY/vY1BsZHwTGvEz3bsn2bm5VSKFAeMmZqsHK1QeYdNsFjLtegnVh+wodxMq50jqLv3LFlA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.888.0", - "@aws-sdk/nested-clients": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/property-provider": "^4.0.5", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.887.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.887.0.tgz", - "integrity": "sha512-ulzqXv6NNqdu/kr0sgBYupWmahISHY+azpJidtK6ZwQIC+vBUk9NdZeqQpy7KVhIk2xd4+5Oq9rxapPwPI21CA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.887.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.887.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.887.0.tgz", - "integrity": "sha512-YbbgLI6jKp2qSoAcHnXrQ5jcuc5EYAmGLVFgMVdk8dfCfJLfGGSaOLxF4CXC7QYhO50s+mPPkhBYejCik02Kug==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.887.0", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.887.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.887.0.tgz", - "integrity": "sha512-tjrUXFtQnFLo+qwMveq5faxP5MQakoLArXtqieHphSqZTXm21wDJM73hgT4/PQQGTwgYjDKqnqsE1hvk0hcfDw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.887.0", - "@aws/lambda-invoke-store": "^0.0.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.888.0.tgz", - "integrity": "sha512-rKOFNfqgqOfrdcLGF8fcO75azWS2aq2ksRHFoIEFru5FJxzu/yDAhY4C2FKiP/X34xeIUS2SbE/gQgrgWHSN2g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@aws-sdk/util-arn-parser": "3.873.0", - "@smithy/core": "^3.11.0", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.1.3", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-stream": "^4.3.1", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.888.0.tgz", - "integrity": "sha512-ZkcUkoys8AdrNNG7ATjqw2WiXqrhTvT+r4CIK3KhOqIGPHX0p0DQWzqjaIl7ZhSUToKoZ4Ud7MjF795yUr73oA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@aws-sdk/util-endpoints": "3.887.0", - "@smithy/core": "^3.11.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.888.0.tgz", - "integrity": "sha512-py4o4RPSGt+uwGvSBzR6S6cCBjS4oTX5F8hrHFHfPCdIOMVjyOBejn820jXkCrcdpSj3Qg1yUZXxsByvxc9Lyg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.888.0", - "@aws-sdk/middleware-host-header": "3.887.0", - "@aws-sdk/middleware-logger": "3.887.0", - "@aws-sdk/middleware-recursion-detection": "3.887.0", - "@aws-sdk/middleware-user-agent": "3.888.0", - "@aws-sdk/region-config-resolver": "3.887.0", - "@aws-sdk/types": "3.887.0", - "@aws-sdk/util-endpoints": "3.887.0", - "@aws-sdk/util-user-agent-browser": "3.887.0", - "@aws-sdk/util-user-agent-node": "3.888.0", - "@smithy/config-resolver": "^4.2.1", - "@smithy/core": "^3.11.0", - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/hash-node": "^4.1.1", - "@smithy/invalid-dependency": "^4.1.1", - "@smithy/middleware-content-length": "^4.1.1", - "@smithy/middleware-endpoint": "^4.2.1", - "@smithy/middleware-retry": "^4.2.1", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-body-length-node": "^4.1.0", - "@smithy/util-defaults-mode-browser": "^4.1.1", - "@smithy/util-defaults-mode-node": "^4.1.1", - "@smithy/util-endpoints": "^3.1.1", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.1", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.887.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.887.0.tgz", - "integrity": "sha512-VdSMrIqJ3yjJb/fY+YAxrH/lCVv0iL8uA+lbMNfQGtO5tB3Zx6SU9LEpUwBNX8fPK1tUpI65CNE4w42+MY/7Mg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.887.0", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.1.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.888.0.tgz", - "integrity": "sha512-FmOHUaJzEhqfcpyh0L7HLwYcYopK13Dbmuf+oUyu56/RoeB1nLnltH1VMQVj8v3Am2IwlGR+/JpFyrdkErN+cA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/signature-v4": "^5.1.3", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.888.0.tgz", - "integrity": "sha512-WA3NF+3W8GEuCMG1WvkDYbB4z10G3O8xuhT7QSjhvLYWQ9CPt3w4VpVIfdqmUn131TCIbhCzD0KN/1VJTjAjyw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.888.0", - "@aws-sdk/nested-clients": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/property-provider": "^4.0.5", - "@smithy/shared-ini-file-loader": "^4.0.5", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.887.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.887.0.tgz", - "integrity": "sha512-fmTEJpUhsPsovQ12vZSpVTEP/IaRoJAMBGQXlQNjtCpkBp6Iq3KQDa/HDaPINE+3xxo6XvTdtibsNOd5zJLV9A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.873.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.873.0.tgz", - "integrity": "sha512-qag+VTqnJWDn8zTAXX4wiVioa0hZDQMtbZcGRERVnLar4/3/VIKBhxX2XibNQXFu1ufgcRn4YntT/XEPecFWcg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.887.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.887.0.tgz", - "integrity": "sha512-kpegvT53KT33BMeIcGLPA65CQVxLUL/C3gTz9AzlU/SDmeusBHX4nRApAicNzI/ltQ5lxZXbQn18UczzBuwF1w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.887.0", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-endpoints": "^3.1.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.873.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.873.0.tgz", - "integrity": "sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.887.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.887.0.tgz", - "integrity": "sha512-X71UmVsYc6ZTH4KU6hA5urOzYowSXc3qvroagJNLJYU1ilgZ529lP4J9XOYfEvTXkLR1hPFSRxa43SrwgelMjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.887.0", - "@smithy/types": "^4.5.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.888.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.888.0.tgz", - "integrity": "sha512-rSB3OHyuKXotIGfYEo//9sU0lXAUrTY28SUUnxzOGYuQsAt0XR5iYwBAp+RjV6x8f+Hmtbg0PdCsy1iNAXa0UQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.888.0", - "@aws-sdk/types": "3.887.0", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.887.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.887.0.tgz", - "integrity": "sha512-lMwgWK1kNgUhHGfBvO/5uLe7TKhycwOn3eRCqsKPT9aPCx/HWuTlpcQp8oW2pCRGLS7qzcxqpQulcD+bbUL7XQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", - "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1026,9 +302,9 @@ } }, "node_modules/@dotenvx/dotenvx": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.49.1.tgz", - "integrity": "sha512-LQ8cem3RU/mI2iz5Sy+ypnhfhVge3bc9tsLJg5rdf7j7u1RRTfmmSdLwSjeYI7sL9ToN7rgFkOGSBJqaBT+gSQ==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.49.0.tgz", + "integrity": "sha512-M1cyP6YstFQCjih54SAxCqHLMMi8QqV8tenpgGE48RTXWD7vfMYJiw/6xcCDpS2h28AcLpTsFCZA863Ge9yxzA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1555,9 +831,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", "cpu": [ "ppc64" ], @@ -1572,9 +848,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", "cpu": [ "arm" ], @@ -1589,9 +865,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", "cpu": [ "arm64" ], @@ -1606,9 +882,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", "cpu": [ "x64" ], @@ -1623,9 +899,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", "cpu": [ "arm64" ], @@ -1640,9 +916,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", "cpu": [ "x64" ], @@ -1657,9 +933,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", "cpu": [ "arm64" ], @@ -1674,9 +950,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", "cpu": [ "x64" ], @@ -1691,9 +967,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", "cpu": [ "arm" ], @@ -1708,9 +984,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", "cpu": [ "arm64" ], @@ -1725,9 +1001,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", "cpu": [ "ia32" ], @@ -1742,9 +1018,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", "cpu": [ "loong64" ], @@ -1759,9 +1035,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", "cpu": [ "mips64el" ], @@ -1776,9 +1052,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", "cpu": [ "ppc64" ], @@ -1793,9 +1069,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", "cpu": [ "riscv64" ], @@ -1810,9 +1086,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", "cpu": [ "s390x" ], @@ -1827,9 +1103,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", "cpu": [ "x64" ], @@ -1844,9 +1120,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", "cpu": [ "arm64" ], @@ -1861,9 +1137,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", "cpu": [ "x64" ], @@ -1878,9 +1154,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", "cpu": [ "arm64" ], @@ -1895,9 +1171,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", "cpu": [ "x64" ], @@ -1912,9 +1188,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", "cpu": [ "arm64" ], @@ -1929,9 +1205,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", "cpu": [ "x64" ], @@ -1946,9 +1222,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", "cpu": [ "arm64" ], @@ -1963,9 +1239,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", "cpu": [ "ia32" ], @@ -1980,9 +1256,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", "cpu": [ "x64" ], @@ -1997,9 +1273,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2094,9 +1370,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", - "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2232,13 +1508,10 @@ "license": "MIT" }, "node_modules/@hookform/resolvers": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-4.1.3.tgz", - "integrity": "sha512-Jsv6UOWYTrEFJ/01ZrnwVXs7KDvP8XIo115i++5PWvNkNvkrsTfGiLS6w+eJ57CYtUtDQalUWovCZDHFJ8u1VQ==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.1.tgz", + "integrity": "sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==", "license": "MIT", - "dependencies": { - "@standard-schema/utils": "^0.3.0" - }, "peerDependencies": { "react-hook-form": "^7.0.0" } @@ -2842,24 +2115,24 @@ } }, "node_modules/@next/env": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.3.tgz", - "integrity": "sha512-RSEDTRqyihYXygx/OJXwvVupfr9m04+0vH8vyy0HfZ7keRto6VX9BbEk0J2PUk0VGy6YhklJUSrgForov5F9pw==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.6.tgz", + "integrity": "sha512-yHDKVTcHrZy/8TWhj0B23ylKv5ypocuCwey9ZqPyv4rPdUdRzpGCkSi03t04KBPyU96kxVtUqx6O3nE1kpxASQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.3.tgz", - "integrity": "sha512-SdhaKdko6dpsSr0DldkESItVrnPYB1NS2NpShCSX5lc7SSQmLZt5Mug6t2xbiuVWEVDLZSuIAoQyYVBYp0dR5g==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.4.6.tgz", + "integrity": "sha512-2NOu3ln+BTcpnbIDuxx6MNq+pRrCyey4WSXGaJIyt0D2TYicHeO9QrUENNjcf673n3B1s7hsiV5xBYRCK1Q8kA==", "license": "MIT", "dependencies": { "fast-glob": "3.3.1" } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.3.tgz", - "integrity": "sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.6.tgz", + "integrity": "sha512-667R0RTP4DwxzmrqTs4Lr5dcEda9OxuZsVFsjVtxVMVhzSpo6nLclXejJVfQo2/g7/Z9qF3ETDmN3h65mTjpTQ==", "cpu": [ "arm64" ], @@ -2873,9 +2146,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.3.tgz", - "integrity": "sha512-w83w4SkOOhekJOcA5HBvHyGzgV1W/XvOfpkrxIse4uPWhYTTRwtGEM4v/jiXwNSJvfRvah0H8/uTLBKRXlef8g==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.6.tgz", + "integrity": "sha512-KMSFoistFkaiQYVQQnaU9MPWtp/3m0kn2Xed1Ces5ll+ag1+rlac20sxG+MqhH2qYWX1O2GFOATQXEyxKiIscg==", "cpu": [ "x64" ], @@ -2889,9 +2162,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.3.tgz", - "integrity": "sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.6.tgz", + "integrity": "sha512-PnOx1YdO0W7m/HWFeYd2A6JtBO8O8Eb9h6nfJia2Dw1sRHoHpNf6lN1U4GKFRzRDBi9Nq2GrHk9PF3Vmwf7XVw==", "cpu": [ "arm64" ], @@ -2905,9 +2178,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.3.tgz", - "integrity": "sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.6.tgz", + "integrity": "sha512-XBbuQddtY1p5FGPc2naMO0kqs4YYtLYK/8aPausI5lyOjr4J77KTG9mtlU4P3NwkLI1+OjsPzKVvSJdMs3cFaw==", "cpu": [ "arm64" ], @@ -2921,9 +2194,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.3.tgz", - "integrity": "sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.6.tgz", + "integrity": "sha512-+WTeK7Qdw82ez3U9JgD+igBAP75gqZ1vbK6R8PlEEuY0OIe5FuYXA4aTjL811kWPf7hNeslD4hHK2WoM9W0IgA==", "cpu": [ "x64" ], @@ -2937,9 +2210,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.3.tgz", - "integrity": "sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.6.tgz", + "integrity": "sha512-XP824mCbgQsK20jlXKrUpZoh/iO3vUWhMpxCz8oYeagoiZ4V0TQiKy0ASji1KK6IAe3DYGfj5RfKP6+L2020OQ==", "cpu": [ "x64" ], @@ -2953,9 +2226,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.3.tgz", - "integrity": "sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.6.tgz", + "integrity": "sha512-FxrsenhUz0LbgRkNWx6FRRJIPe/MI1JRA4W4EPd5leXO00AZ6YU8v5vfx4MDXTvN77lM/EqsE3+6d2CIeF5NYg==", "cpu": [ "arm64" ], @@ -2969,9 +2242,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.3.tgz", - "integrity": "sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.6.tgz", + "integrity": "sha512-T4ufqnZ4u88ZheczkBTtOF+eKaM14V8kbjud/XrAakoM5DKQWjW09vD6B9fsdsWS2T7D5EY31hRHdta7QKWOng==", "cpu": [ "x64" ], @@ -4837,9 +4110,9 @@ } }, "node_modules/@react-email/components": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.5.3.tgz", - "integrity": "sha512-8G5vsoMehuGOT4cDqaYLdpagtqCYPl4vThXNylClxO6SrN2w9Mh1+i2RNGj/rdqh/woamHORjlXMYCA/kzDMew==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.5.0.tgz", + "integrity": "sha512-esRbP+yMmSkNP9hcpiy2RwpDnvSmlxJcJ1HHbzSwlACGlCHTap+ma344QovvzhpVRhMccyWemdClLG822UvVpQ==", "license": "MIT", "dependencies": { "@react-email/body": "0.1.0", @@ -4857,7 +4130,7 @@ "@react-email/link": "0.0.12", "@react-email/markdown": "0.0.15", "@react-email/preview": "0.0.13", - "@react-email/render": "1.2.3", + "@react-email/render": "1.2.0", "@react-email/row": "0.0.12", "@react-email/section": "0.0.16", "@react-email/tailwind": "1.2.2", @@ -4991,9 +4264,9 @@ } }, "node_modules/@react-email/render": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.2.3.tgz", - "integrity": "sha512-qu3XYNkHGao3teJexVD5CrcgFkNLrzbZvpZN17a7EyQYUN3kHkTkE9saqY4VbvGx6QoNU3p8rsk/Xm++D/+pTw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.2.0.tgz", + "integrity": "sha512-5fpbV16VYR9Fmk8t7xiwPNAjxjdI8XzVtlx9J9OkhOsIHdr2s5DwAj8/MXzWa9qRYJyLirQ/l7rBSjjgyRAomw==", "license": "MIT", "dependencies": { "html-to-text": "^9.0.5", @@ -5124,646 +4397,8 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-9.0.1.tgz", "integrity": "sha512-tGSRP1QvsAvsJmnOlRQyw/mvK9gnPtjEc5fg2+m8n+QUa+D7rvrKkOYyfpy42GTs90X3RDOnqJgfHt+qO67/+w==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "license": "MIT" }, - "node_modules/@smithy/abort-controller": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.1.1.tgz", - "integrity": "sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.1.tgz", - "integrity": "sha512-FXil8q4QN7mgKwU2hCLm0ltab8NyY/1RiqEf25Jnf6WLS3wmb11zGAoLETqg1nur2Aoibun4w4MjeN9CMJ4G6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-config-provider": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.11.0.tgz", - "integrity": "sha512-Abs5rdP1o8/OINtE49wwNeWuynCu0kme1r4RI3VXVrHr4odVDG7h7mTnw1WXXfN5Il+c25QOnrdL2y56USfxkA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-body-length-browser": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-stream": "^4.3.1", - "@smithy/util-utf8": "^4.1.0", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.1.tgz", - "integrity": "sha512-1WdBfM9DwA59pnpIizxnUvBf/de18p4GP+6zP2AqrlFzoW3ERpZaT4QueBR0nS9deDMaQRkBlngpVlnkuuTisQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.2.1", - "@smithy/property-provider": "^4.1.1", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.2.1.tgz", - "integrity": "sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.2.1", - "@smithy/querystring-builder": "^4.1.1", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz", - "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "@smithy/util-buffer-from": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz", - "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.1.0.tgz", - "integrity": "sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz", - "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.2.1.tgz", - "integrity": "sha512-fUTMmQvQQZakXOuKizfu7fBLDpwvWZjfH6zUK2OLsoNZRZGbNUdNSdLJHpwk1vS208jtDjpUIskh+JoA8zMzZg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.11.0", - "@smithy/middleware-serde": "^4.1.1", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/shared-ini-file-loader": "^4.1.1", - "@smithy/types": "^4.5.0", - "@smithy/url-parser": "^4.1.1", - "@smithy/util-middleware": "^4.1.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.2.1.tgz", - "integrity": "sha512-JzfvjwSJXWRl7LkLgIRTUTd2Wj639yr3sQGpViGNEOjtb0AkAuYqRAHs+jSOI/LPC0ZTjmFVVtfrCICMuebexw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.2.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/service-error-classification": "^4.1.1", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-retry": "^4.1.1", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.1.1.tgz", - "integrity": "sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.1.1.tgz", - "integrity": "sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.2.1.tgz", - "integrity": "sha512-AIA0BJZq2h295J5NeCTKhg1WwtdTA/GqBCaVjk30bDgMHwniUETyh5cP9IiE9VrId7Kt8hS7zvREVMTv1VfA6g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.1.1", - "@smithy/shared-ini-file-loader": "^4.1.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.2.1.tgz", - "integrity": "sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/querystring-builder": "^4.1.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.1.1.tgz", - "integrity": "sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.2.1.tgz", - "integrity": "sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz", - "integrity": "sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "@smithy/util-uri-escape": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.1.1.tgz", - "integrity": "sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.1.tgz", - "integrity": "sha512-Iam75b/JNXyDE41UvrlM6n8DNOa/r1ylFyvgruTUx7h2Uk7vDNV9AAwP1vfL1fOL8ls0xArwEGVcGZVd7IO/Cw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.1.1.tgz", - "integrity": "sha512-YkpikhIqGc4sfXeIbzSj10t2bJI/sSoP5qxLue6zG+tEE3ngOBSm8sO3+djacYvS/R5DfpxN/L9CyZsvwjWOAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.2.1.tgz", - "integrity": "sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.1.0", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-hex-encoding": "^4.1.0", - "@smithy/util-middleware": "^4.1.1", - "@smithy/util-uri-escape": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.6.1.tgz", - "integrity": "sha512-WolVLDb9UTPMEPPOncrCt6JmAMCSC/V2y5gst2STWJ5r7+8iNac+EFYQnmvDCYMfOLcilOSEpm5yXZXwbLak1Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.11.0", - "@smithy/middleware-endpoint": "^4.2.1", - "@smithy/middleware-stack": "^4.1.1", - "@smithy/protocol-http": "^5.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-stream": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.5.0.tgz", - "integrity": "sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.1.1.tgz", - "integrity": "sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.1.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.1.0.tgz", - "integrity": "sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.1.0.tgz", - "integrity": "sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz", - "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.1.0.tgz", - "integrity": "sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.1.0.tgz", - "integrity": "sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.1.tgz", - "integrity": "sha512-hA1AKIHFUMa9Tl6q6y8p0pJ9aWHCCG8s57flmIyLE0W7HcJeYrYtnqXDcGnftvXEhdQnSexyegXnzzTGk8bKLA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.1.1", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.1.tgz", - "integrity": "sha512-RGSpmoBrA+5D2WjwtK7tto6Pc2wO9KSXKLpLONhFZ8VyuCbqlLdiDAfuDTNY9AJe4JoE+Cx806cpTQQoQ71zPQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.2.1", - "@smithy/credential-provider-imds": "^4.1.1", - "@smithy/node-config-provider": "^4.2.1", - "@smithy/property-provider": "^4.1.1", - "@smithy/smithy-client": "^4.6.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.1.tgz", - "integrity": "sha512-qB4R9kO0SetA11Rzu6MVGFIaGYX3p6SGGGfWwsKnC6nXIf0n/0AKVwRTsYsz9ToN8CeNNtNgQRwKFBndGJZdyw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.2.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.1.0.tgz", - "integrity": "sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.1.1.tgz", - "integrity": "sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.1.tgz", - "integrity": "sha512-jGeybqEZ/LIordPLMh5bnmnoIgsqnp4IEimmUp5c5voZ8yx+5kAlN5+juyr7p+f7AtZTgvhmInQk4Q0UVbrZ0Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.1.1", - "@smithy/types": "^4.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.3.1.tgz", - "integrity": "sha512-khKkW/Jqkgh6caxMWbMuox9+YfGlsk9OnHOYCGVEdYQb/XVzcORXHLYUubHmmda0pubEDncofUrPNniS9d+uAA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.2.1", - "@smithy/node-http-handler": "^4.2.1", - "@smithy/types": "^4.5.0", - "@smithy/util-base64": "^4.1.0", - "@smithy/util-buffer-from": "^4.1.0", - "@smithy/util-hex-encoding": "^4.1.0", - "@smithy/util-utf8": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.1.0.tgz", - "integrity": "sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", - "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -5771,12 +4406,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@standard-schema/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", - "license": "MIT" - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -5799,9 +4428,9 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", - "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz", + "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5809,15 +4438,25 @@ "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", - "magic-string": "^0.30.18", + "magic-string": "^0.30.17", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.13" + "tailwindcss": "4.1.12" + } + }, + "node_modules/@tailwindcss/node/node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz", - "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz", + "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5829,24 +4468,24 @@ "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.13", - "@tailwindcss/oxide-darwin-arm64": "4.1.13", - "@tailwindcss/oxide-darwin-x64": "4.1.13", - "@tailwindcss/oxide-freebsd-x64": "4.1.13", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", - "@tailwindcss/oxide-linux-x64-musl": "4.1.13", - "@tailwindcss/oxide-wasm32-wasi": "4.1.13", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" + "@tailwindcss/oxide-android-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-x64": "4.1.12", + "@tailwindcss/oxide-freebsd-x64": "4.1.12", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-x64-musl": "4.1.12", + "@tailwindcss/oxide-wasm32-wasi": "4.1.12", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz", - "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz", + "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==", "cpu": [ "arm64" ], @@ -5861,9 +4500,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz", - "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz", + "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==", "cpu": [ "arm64" ], @@ -5878,9 +4517,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz", - "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz", + "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==", "cpu": [ "x64" ], @@ -5895,9 +4534,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz", - "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz", + "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==", "cpu": [ "x64" ], @@ -5912,9 +4551,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz", - "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz", + "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==", "cpu": [ "arm" ], @@ -5929,9 +4568,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz", - "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz", + "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==", "cpu": [ "arm64" ], @@ -5946,9 +4585,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz", - "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz", + "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==", "cpu": [ "arm64" ], @@ -5963,9 +4602,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz", - "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz", + "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==", "cpu": [ "x64" ], @@ -5980,9 +4619,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz", - "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz", + "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==", "cpu": [ "x64" ], @@ -5997,9 +4636,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz", - "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz", + "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -6087,9 +4726,9 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz", - "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz", + "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==", "cpu": [ "arm64" ], @@ -6104,9 +4743,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz", - "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz", + "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==", "cpu": [ "x64" ], @@ -6121,17 +4760,17 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.13.tgz", - "integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.12.tgz", + "integrity": "sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.13", - "@tailwindcss/oxide": "4.1.13", + "@tailwindcss/node": "4.1.12", + "@tailwindcss/oxide": "4.1.12", "postcss": "^8.4.41", - "tailwindcss": "4.1.13" + "tailwindcss": "4.1.12" } }, "node_modules/@tanstack/react-table": { @@ -6335,23 +4974,22 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", - "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "devOptional": true, "license": "MIT", "dependencies": { - "undici-types": "~7.12.0" + "undici-types": "~7.10.0" } }, "node_modules/@types/nodemailer": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.1.tgz", - "integrity": "sha512-UfHAghPmGZVzaL8x9y+mKZMWyHC399+iq0MOmya5tIyenWX3lcdSb60vOmp0DocR6gCDTYTozv/ULQnREyyjkg==", + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", "dev": true, "license": "MIT", "dependencies": { - "@aws-sdk/client-sesv2": "^3.839.0", "@types/node": "*" } }, @@ -6382,9 +5020,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", - "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "version": "19.1.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", + "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -6402,9 +5040,9 @@ } }, "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", "dev": true, "license": "MIT" }, @@ -6448,13 +5086,6 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -6483,16 +5114,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz", - "integrity": "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", + "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/type-utils": "8.44.0", - "@typescript-eslint/utils": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/type-utils": "8.40.0", + "@typescript-eslint/utils": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -6506,7 +5137,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.44.0", + "@typescript-eslint/parser": "^8.40.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -6521,15 +5152,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.0.tgz", - "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz", + "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "debug": "^4.3.4" }, "engines": { @@ -6545,13 +5176,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.0.tgz", - "integrity": "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz", + "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.44.0", - "@typescript-eslint/types": "^8.44.0", + "@typescript-eslint/tsconfig-utils": "^8.40.0", + "@typescript-eslint/types": "^8.40.0", "debug": "^4.3.4" }, "engines": { @@ -6566,13 +5197,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.0.tgz", - "integrity": "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz", + "integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0" + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6583,9 +5214,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.0.tgz", - "integrity": "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz", + "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6599,14 +5230,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.0.tgz", - "integrity": "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz", + "integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/utils": "8.44.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/utils": "8.40.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -6623,9 +5254,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", - "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz", + "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6636,15 +5267,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.0.tgz", - "integrity": "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz", + "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.44.0", - "@typescript-eslint/tsconfig-utils": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/project-service": "8.40.0", + "@typescript-eslint/tsconfig-utils": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -6716,15 +5347,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.0.tgz", - "integrity": "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz", + "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0" + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6739,12 +5370,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.0.tgz", - "integrity": "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz", + "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/types": "8.40.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -7387,9 +6018,9 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -7506,13 +6137,6 @@ "node": ">=18" } }, - "node_modules/bowser": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", - "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", - "dev": true, - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -7569,7 +6193,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, "node_modules/bytes": { @@ -8467,9 +7090,9 @@ } }, "node_modules/drizzle-orm": { - "version": "0.44.5", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.5.tgz", - "integrity": "sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ==", + "version": "0.44.4", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.4.tgz", + "integrity": "sha512-ZyzKFpTC/Ut3fIqc2c0dPZ6nhchQXriTsqTNs4ayRgl6sZcFlMs9QZKPSHXK4bdOf41GHGWf+FrpcDDYwW+W6Q==", "license": "Apache-2.0", "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", @@ -8998,9 +7621,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -9011,32 +7634,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" } }, "node_modules/esbuild-node-externals": { @@ -9096,18 +7719,18 @@ } }, "node_modules/eslint": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", - "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.35.0", + "@eslint/js": "9.33.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -9156,12 +7779,12 @@ } }, "node_modules/eslint-config-next": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.3.tgz", - "integrity": "sha512-e6j+QhQFOr5pfsc8VJbuTD9xTXJaRvMHYjEeLPA2pFkheNlgPLCkxdvhxhfuM4KGcqSZj2qEnpHisdTVs3BxuQ==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.4.6.tgz", + "integrity": "sha512-4uznvw5DlTTjrZgYZjMciSdDDMO2SWIuQgUNaFyC2O3Zw3Z91XeIejeVa439yRq2CnJb/KEvE4U2AeN/66FpUA==", "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.5.3", + "@next/eslint-plugin-next": "15.4.6", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -9584,9 +8207,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", - "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.0.1.tgz", + "integrity": "sha512-aZVCnybn7TVmxO4BtlmnvX+nuz8qHW124KKJ8dumsBsmv5ZLxE0pYu7S2nwyRBGHHCAzdmnGyrc5U/rksSPO7Q==", "license": "MIT", "dependencies": { "ip-address": "10.0.1" @@ -9672,25 +8295,6 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "license": "MIT" }, - "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -11080,8 +9684,9 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -11649,22 +10254,22 @@ } }, "node_modules/lucide-react": { - "version": "0.544.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", - "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", + "version": "0.539.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.539.0.tgz", + "integrity": "sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/marked": { @@ -11999,12 +10604,12 @@ } }, "node_modules/next": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.3.tgz", - "integrity": "sha512-r/liNAx16SQj4D+XH/oI1dlpv9tdKJ6cONYPwwcCC46f2NjpaRWY+EKCzULfgQYV6YKXjHBchff2IZBSlZmJNw==", + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/next/-/next-15.4.6.tgz", + "integrity": "sha512-us++E/Q80/8+UekzB3SAGs71AlLDsadpFMXVNM/uQ0BMwsh9m3mr0UNQIfjKed8vpWXsASe+Qifrnu1oLIcKEQ==", "license": "MIT", "dependencies": { - "@next/env": "15.5.3", + "@next/env": "15.4.6", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -12017,14 +10622,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.3", - "@next/swc-darwin-x64": "15.5.3", - "@next/swc-linux-arm64-gnu": "15.5.3", - "@next/swc-linux-arm64-musl": "15.5.3", - "@next/swc-linux-x64-gnu": "15.5.3", - "@next/swc-linux-x64-musl": "15.5.3", - "@next/swc-win32-arm64-msvc": "15.5.3", - "@next/swc-win32-x64-msvc": "15.5.3", + "@next/swc-darwin-arm64": "15.4.6", + "@next/swc-darwin-x64": "15.4.6", + "@next/swc-linux-arm64-gnu": "15.4.6", + "@next/swc-linux-arm64-musl": "15.4.6", + "@next/swc-linux-x64-gnu": "15.4.6", + "@next/swc-linux-x64-musl": "15.4.6", + "@next/swc-win32-arm64-msvc": "15.4.6", + "@next/swc-win32-x64-msvc": "15.4.6", "sharp": "^0.34.3" }, "peerDependencies": { @@ -12051,9 +10656,9 @@ } }, "node_modules/next-intl": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.3.9.tgz", - "integrity": "sha512-4oSROHlgy8a5Qr2vH69wxo9F6K0uc6nZM2GNzqSe6ET79DEzOmBeSijCRzD5txcI4i+XTGytu4cxFsDXLKEDpQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.3.4.tgz", + "integrity": "sha512-VWLIDlGbnL/o4LnveJTJD1NOYN8lh3ZAGTWw2krhfgg53as3VsS4jzUVnArJdqvwtlpU/2BIDbWTZ7V4o1jFEw==", "funding": [ { "type": "individual", @@ -12064,7 +10669,7 @@ "dependencies": { "@formatjs/intl-localematcher": "^0.5.4", "negotiator": "^1.0.0", - "use-intl": "^4.3.9" + "use-intl": "^4.3.4" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", @@ -12178,9 +10783,9 @@ } }, "node_modules/nodemailer": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.6.tgz", - "integrity": "sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz", + "integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -12197,9 +10802,9 @@ } }, "node_modules/npm": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.6.0.tgz", - "integrity": "sha512-d/P7DbvYgYNde9Ehfeq99+13/E7E82PfZPw8uYZASr9sQ3ZhBBCA9cXSJRA1COfJ6jDLJ0K36UJnXQWhCvLXuQ==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.5.2.tgz", + "integrity": "sha512-qsEkHPw/Qdw4eA1kKVxsa5F6QeJCiLM1GaexGt/FpUpfiBxkLXVXIVtscOAeVWVe17pmYwD9Aji8dfsXR4r68w==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -12278,8 +10883,8 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.1.4", - "@npmcli/config": "^10.4.0", + "@npmcli/arborist": "^9.1.3", + "@npmcli/config": "^10.3.1", "@npmcli/fs": "^4.0.0", "@npmcli/map-workspaces": "^4.0.2", "@npmcli/package-json": "^6.2.0", @@ -12303,11 +10908,11 @@ "is-cidr": "^5.1.1", "json-parse-even-better-errors": "^4.0.0", "libnpmaccess": "^10.0.1", - "libnpmdiff": "^8.0.7", - "libnpmexec": "^10.1.6", - "libnpmfund": "^7.0.7", + "libnpmdiff": "^8.0.6", + "libnpmexec": "^10.1.5", + "libnpmfund": "^7.0.6", "libnpmorg": "^8.0.0", - "libnpmpack": "^9.0.7", + "libnpmpack": "^9.0.6", "libnpmpublish": "^11.1.0", "libnpmsearch": "^9.0.0", "libnpmteam": "^8.0.1", @@ -12459,7 +11064,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.1.4", + "version": "9.1.3", "inBundle": true, "license": "ISC", "dependencies": { @@ -12506,7 +11111,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.4.0", + "version": "10.3.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -13386,11 +11991,11 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.0.7", + "version": "8.0.6", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.4", + "@npmcli/arborist": "^9.1.3", "@npmcli/installed-package-contents": "^3.0.0", "binary-extensions": "^3.0.0", "diff": "^7.0.0", @@ -13404,11 +12009,11 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.1.6", + "version": "10.1.5", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.4", + "@npmcli/arborist": "^9.1.3", "@npmcli/package-json": "^6.1.1", "@npmcli/run-script": "^9.0.1", "ci-info": "^4.0.0", @@ -13425,11 +12030,11 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.7", + "version": "7.0.6", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.4" + "@npmcli/arborist": "^9.1.3" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -13448,11 +12053,11 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.0.7", + "version": "9.0.6", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.4", + "@npmcli/arborist": "^9.1.3", "@npmcli/run-script": "^9.0.1", "npm-package-arg": "^12.0.0", "pacote": "^21.0.0" @@ -15928,9 +14533,9 @@ } }, "node_modules/react-easy-sort": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/react-easy-sort/-/react-easy-sort-1.7.0.tgz", - "integrity": "sha512-82I63kXdawFhhlFrWPrI74DL48v2LKs7e7PLf5le2E/eIR9+XyCEdL4Pyjbru8XjvtQ60mPLb6oextc4PPR8Lg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/react-easy-sort/-/react-easy-sort-1.6.0.tgz", + "integrity": "sha512-zd9Nn90wVlZPEwJrpqElN87sf9GZnFR1StfjgNQVbSpR5QTSzCHjEYK6REuwq49Ip+76KOMSln9tg/ST2KLelg==", "license": "MIT", "dependencies": { "array-move": "^3.0.1", @@ -15951,14 +14556,15 @@ "license": "0BSD" }, "node_modules/react-email": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/react-email/-/react-email-4.2.11.tgz", - "integrity": "sha512-/7TXRgsTrXcV1u7kc5ZXDVlPvZqEBaYcflMhE2FgWIJh3OHLjj2FqctFTgYcp0iwzbR59a7gzJLmSKyD0wYJEQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/react-email/-/react-email-4.2.8.tgz", + "integrity": "sha512-Eqzs/xZnS881oghPO/4CQ1cULyESuUhEjfYboXmYNOokXnJ6QP5GKKJZ6zjkg9SnKXxSrIxSo5PxzCI5jReJMA==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.27.0", "@babel/traverse": "^7.27.0", + "chalk": "^5.0.0", "chokidar": "^4.0.3", "commander": "^13.0.0", "debounce": "^2.0.0", @@ -15981,6 +14587,19 @@ "node": ">=18.0.0" } }, + "node_modules/react-email/node_modules/chalk": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/react-email/node_modules/commander": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", @@ -16952,7 +15571,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -16971,7 +15589,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -17280,19 +15897,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -17375,23 +15979,19 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", - "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", + "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", "license": "MIT" }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { @@ -17679,9 +16279,9 @@ } }, "node_modules/tw-animate-css": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.8.tgz", - "integrity": "sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.7.tgz", + "integrity": "sha512-lvLb3hTIpB5oGsk8JmLoAjeCHV58nKa2zHYn8yWOoG5JJusH3bhJlF2DLAZ/5NmJ+jyH3ssiAx/2KmbhavJy/A==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Wombosvideo" @@ -17801,16 +16401,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.0.tgz", - "integrity": "sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.40.0.tgz", + "integrity": "sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.44.0", - "@typescript-eslint/parser": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/utils": "8.44.0" + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/utils": "8.40.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -17843,9 +16443,9 @@ } }, "node_modules/undici-types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", - "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", "devOptional": true, "license": "MIT" }, @@ -17923,9 +16523,9 @@ } }, "node_modules/use-intl": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.3.9.tgz", - "integrity": "sha512-bZu+h13HIgOvsoGleQtUe4E6gM49CRm+AH36KnJVB/qb1+Beo7jr7HNrR8YWH8oaOkQfGNm6vh0HTepxng8UTg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.3.4.tgz", + "integrity": "sha512-sHfiU0QeJ1rirNWRxvCyvlSh9+NczcOzRnPyMeo2rtHXhVnBsvMRjE+UG4eh3lRhCxrvcqei/I0lBxsc59on1w==", "license": "MIT", "dependencies": { "@formatjs/fast-memoize": "^2.2.0", @@ -17974,16 +16574,16 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist-node/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/vary": { diff --git a/package.json b/package.json index f2370e52..2c1c3fca 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,13 @@ "db:clear-migrations": "rm -rf server/migrations", "build:sqlite": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrationsSqlite.ts -o dist/migrations.mjs", "build:pg": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrationsPg.ts -o dist/migrations.mjs", - "start": "ENVIRONMENT=prod node dist/migrations.mjs && ENVIRONMENT=prod NODE_ENV=development node --enable-source-maps dist/server.mjs", + "start": "DB_TYPE=sqlite NODE_OPTIONS=--enable-source-maps NODE_ENV=development ENVIRONMENT=prod sh -c 'node dist/migrations.mjs && node dist/server.mjs'", "email": "email dev --dir server/emails/templates --port 3005", "build:cli": "node esbuild.mjs -e cli/index.ts -o dist/cli.mjs" }, "dependencies": { "@asteasolutions/zod-to-openapi": "^7.3.4", - "@hookform/resolvers": "4.1.3", + "@hookform/resolvers": "3.9.1", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", @@ -49,15 +49,15 @@ "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-tooltip": "^1.2.8", - "@react-email/components": "0.5.3", + "@react-email/components": "0.5.0", "@react-email/render": "^1.2.0", "@react-email/tailwind": "1.2.2", - "@simplewebauthn/browser": "^13.1.2", + "@simplewebauthn/browser": "^13.1.0", "@simplewebauthn/server": "^9.0.3", "@tailwindcss/forms": "^0.5.10", "@tanstack/react-table": "8.21.3", "arctic": "^3.7.0", - "axios": "^1.12.2", + "axios": "1.11.0", "better-sqlite3": "11.7.0", "canvas-confetti": "1.9.3", "class-variance-authority": "^0.7.1", @@ -68,11 +68,11 @@ "cookies": "^0.9.1", "cors": "2.8.5", "crypto-js": "^4.2.0", - "drizzle-orm": "0.44.5", - "eslint": "9.35.0", - "eslint-config-next": "15.5.3", + "drizzle-orm": "0.44.4", + "eslint": "9.33.0", + "eslint-config-next": "15.4.6", "express": "5.1.0", - "express-rate-limit": "8.1.0", + "express-rate-limit": "8.0.1", "glob": "11.0.3", "helmet": "8.1.0", "http-errors": "2.0.0", @@ -81,29 +81,30 @@ "jmespath": "^0.16.0", "js-yaml": "4.1.0", "jsonwebtoken": "^9.0.2", - "lucide-react": "^0.544.0", + "lucide-react": "0.539.0", "moment": "2.30.1", - "next": "15.5.3", - "next-intl": "^4.3.9", + "next": "15.4.6", + "next-intl": "^4.3.4", "next-themes": "0.4.6", "node-cache": "5.1.2", "node-fetch": "3.3.2", - "nodemailer": "7.0.6", - "npm": "^11.6.0", + "nodemailer": "7.0.5", + "npm": "^11.5.2", "oslo": "1.2.1", "pg": "^8.16.2", "qrcode.react": "4.2.0", "react": "19.1.1", "react-dom": "19.1.1", - "react-easy-sort": "^1.7.0", + "react-easy-sort": "^1.6.0", "react-hook-form": "7.62.0", "react-icons": "^5.5.0", "rebuild": "0.1.2", "semver": "^7.7.2", + "source-map-support": "0.5.21", "swagger-ui-express": "^5.0.1", "tailwind-merge": "3.3.1", - "tw-animate-css": "^1.3.8", - "uuid": "^13.0.0", + "tw-animate-css": "^1.3.7", + "uuid": "^11.1.0", "vaul": "1.1.2", "winston": "3.17.0", "winston-daily-rotate-file": "5.0.0", @@ -113,9 +114,9 @@ "zod-validation-error": "3.5.2" }, "devDependencies": { - "@dotenvx/dotenvx": "1.49.1", + "@dotenvx/dotenvx": "1.49.0", "@esbuild-plugins/tsconfig-paths": "0.1.2", - "@tailwindcss/postcss": "^4.1.13", + "@tailwindcss/postcss": "^4.1.12", "@types/better-sqlite3": "7.6.12", "@types/cookie-parser": "1.4.9", "@types/cors": "2.8.19", @@ -125,25 +126,25 @@ "@types/jmespath": "^0.15.2", "@types/js-yaml": "4.0.9", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "24.5.2", - "@types/nodemailer": "7.0.1", + "@types/node": "^24", + "@types/nodemailer": "6.4.17", "@types/pg": "8.15.5", - "@types/react": "19.1.13", + "@types/react": "19.1.12", "@types/react-dom": "19.1.9", - "@types/semver": "^7.7.1", + "@types/semver": "^7.7.0", "@types/swagger-ui-express": "^4.1.8", "@types/ws": "8.18.1", "@types/yargs": "17.0.33", "drizzle-kit": "0.31.4", - "esbuild": "0.25.10", + "esbuild": "0.25.9", "esbuild-node-externals": "1.18.0", "postcss": "^8", - "react-email": "4.2.11", + "react-email": "4.2.8", "tailwindcss": "^4.1.4", "tsc-alias": "1.8.16", "tsx": "4.20.5", "typescript": "^5", - "typescript-eslint": "^8.44.0" + "typescript-eslint": "^8.40.0" }, "overrides": { "emblor": { diff --git a/public/idp/azure.png b/public/idp/azure.png deleted file mode 100644 index d6ec5baf..00000000 Binary files a/public/idp/azure.png and /dev/null differ diff --git a/public/idp/google.png b/public/idp/google.png deleted file mode 100644 index da097687..00000000 Binary files a/public/idp/google.png and /dev/null differ diff --git a/server/auth/actions.ts b/server/auth/actions.ts index f020c2ff..b5e4bbb3 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -101,9 +101,7 @@ export enum ActionsEnum { getApiKey = "getApiKey", createOrgDomain = "createOrgDomain", deleteOrgDomain = "deleteOrgDomain", - restartOrgDomain = "restartOrgDomain", - updateOrgUser = "updateOrgUser", - applyBlueprint = "applyBlueprint" + restartOrgDomain = "restartOrgDomain" } export async function checkUserActionPermission( diff --git a/server/db/names.ts b/server/db/names.ts index 2da38f10..41f4c170 100644 --- a/server/db/names.ts +++ b/server/db/names.ts @@ -1,6 +1,6 @@ import { join } from "path"; import { readFileSync } from "fs"; -import { db, resources, siteResources } from "@server/db"; +import { db } from "@server/db"; import { exitNodes, sites } from "@server/db"; import { eq, and } from "drizzle-orm"; import { __DIRNAME } from "@server/lib/consts"; @@ -34,44 +34,6 @@ export async function getUniqueSiteName(orgId: string): Promise { } } -export async function getUniqueResourceName(orgId: string): Promise { - let loops = 0; - while (true) { - if (loops > 100) { - throw new Error("Could not generate a unique name"); - } - - const name = generateName(); - const count = await db - .select({ niceId: resources.niceId, orgId: resources.orgId }) - .from(resources) - .where(and(eq(resources.niceId, name), eq(resources.orgId, orgId))); - if (count.length === 0) { - return name; - } - loops++; - } -} - -export async function getUniqueSiteResourceName(orgId: string): Promise { - let loops = 0; - while (true) { - if (loops > 100) { - throw new Error("Could not generate a unique name"); - } - - const name = generateName(); - const count = await db - .select({ niceId: siteResources.niceId, orgId: siteResources.orgId }) - .from(siteResources) - .where(and(eq(siteResources.niceId, name), eq(siteResources.orgId, orgId))); - if (count.length === 0) { - return name; - } - loops++; - } -} - export async function getUniqueExitNodeEndpointName(): Promise { let loops = 0; const count = await db diff --git a/server/db/pg/driver.ts b/server/db/pg/driver.ts index c7c292f0..9625867d 100644 --- a/server/db/pg/driver.ts +++ b/server/db/pg/driver.ts @@ -50,4 +50,3 @@ function createDb() { export const db = createDb(); export default db; -export type Transaction = Parameters[0]>[0]; \ No newline at end of file diff --git a/server/db/pg/schema.ts b/server/db/pg/schema.ts index 3cb5486b..8e725ab1 100644 --- a/server/db/pg/schema.ts +++ b/server/db/pg/schema.ts @@ -71,7 +71,6 @@ export const resources = pgTable("resources", { onDelete: "cascade" }) .notNull(), - niceId: text("niceId").notNull(), name: varchar("name").notNull(), subdomain: varchar("subdomain"), fullDomain: varchar("fullDomain"), @@ -96,7 +95,6 @@ export const resources = pgTable("resources", { skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, { onDelete: "cascade" }), - headers: text("headers"), // comma-separated list of headers to add to the request }); export const targets = pgTable("targets", { @@ -115,9 +113,7 @@ export const targets = pgTable("targets", { method: varchar("method"), port: integer("port").notNull(), internalPort: integer("internalPort"), - enabled: boolean("enabled").notNull().default(true), - path: text("path"), - pathMatchType: text("pathMatchType"), // exact, prefix, regex + enabled: boolean("enabled").notNull().default(true) }); export const exitNodes = pgTable("exitNodes", { @@ -131,8 +127,7 @@ export const exitNodes = pgTable("exitNodes", { maxConnections: integer("maxConnections"), online: boolean("online").notNull().default(false), lastPing: integer("lastPing"), - type: text("type").default("gerbil"), // gerbil, remoteExitNode - region: varchar("region") + type: text("type").default("gerbil") // gerbil, remoteExitNode }); export const siteResources = pgTable("siteResources", { // this is for the clients @@ -143,7 +138,6 @@ export const siteResources = pgTable("siteResources", { // this is for the clien orgId: varchar("orgId") .notNull() .references(() => orgs.orgId, { onDelete: "cascade" }), - niceId: varchar("niceId").notNull(), name: varchar("name").notNull(), protocol: varchar("protocol").notNull(), proxyPort: integer("proxyPort").notNull(), @@ -218,8 +212,7 @@ export const userOrgs = pgTable("userOrgs", { roleId: integer("roleId") .notNull() .references(() => roles.roleId), - isOwner: boolean("isOwner").notNull().default(false), - autoProvisioned: boolean("autoProvisioned").default(false) + isOwner: boolean("isOwner").notNull().default(false) }); export const emailVerificationCodes = pgTable("emailVerificationCodes", { @@ -465,7 +458,6 @@ export const idpOidcConfig = pgTable("idpOidcConfig", { idpId: integer("idpId") .notNull() .references(() => idp.idpId, { onDelete: "cascade" }), - variant: varchar("variant").notNull().default("oidc"), clientId: varchar("clientId").notNull(), clientSecret: varchar("clientSecret").notNull(), authUrl: varchar("authUrl").notNull(), diff --git a/server/db/sqlite/driver.ts b/server/db/sqlite/driver.ts index 6369c268..124bd885 100644 --- a/server/db/sqlite/driver.ts +++ b/server/db/sqlite/driver.ts @@ -18,7 +18,6 @@ function createDb() { export const db = createDb(); export default db; -export type Transaction = Parameters[0]>[0]; function checkFileExists(filePath: string): boolean { try { diff --git a/server/db/sqlite/schema.ts b/server/db/sqlite/schema.ts index c623fae3..579ff7b4 100644 --- a/server/db/sqlite/schema.ts +++ b/server/db/sqlite/schema.ts @@ -77,7 +77,6 @@ export const resources = sqliteTable("resources", { onDelete: "cascade" }) .notNull(), - niceId: text("niceId").notNull(), name: text("name").notNull(), subdomain: text("subdomain"), fullDomain: text("fullDomain"), @@ -108,7 +107,6 @@ export const resources = sqliteTable("resources", { skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, { onDelete: "cascade" }), - headers: text("headers"), // comma-separated list of headers to add to the request }); export const targets = sqliteTable("targets", { @@ -127,9 +125,7 @@ export const targets = sqliteTable("targets", { method: text("method"), port: integer("port").notNull(), internalPort: integer("internalPort"), - enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), - path: text("path"), - pathMatchType: text("pathMatchType"), // exact, prefix, regex + enabled: integer("enabled", { mode: "boolean" }).notNull().default(true) }); export const exitNodes = sqliteTable("exitNodes", { @@ -143,28 +139,23 @@ export const exitNodes = sqliteTable("exitNodes", { maxConnections: integer("maxConnections"), online: integer("online", { mode: "boolean" }).notNull().default(false), lastPing: integer("lastPing"), - type: text("type").default("gerbil"), // gerbil, remoteExitNode - region: text("region") + type: text("type").default("gerbil") // gerbil, remoteExitNode }); -export const siteResources = sqliteTable("siteResources", { - // this is for the clients - siteResourceId: integer("siteResourceId").primaryKey({ - autoIncrement: true - }), +export const siteResources = sqliteTable("siteResources", { // this is for the clients + siteResourceId: integer("siteResourceId").primaryKey({ autoIncrement: true }), siteId: integer("siteId") .notNull() .references(() => sites.siteId, { onDelete: "cascade" }), orgId: text("orgId") .notNull() .references(() => orgs.orgId, { onDelete: "cascade" }), - niceId: text("niceId").notNull(), name: text("name").notNull(), protocol: text("protocol").notNull(), proxyPort: integer("proxyPort").notNull(), destinationPort: integer("destinationPort").notNull(), destinationIp: text("destinationIp").notNull(), - enabled: integer("enabled", { mode: "boolean" }).notNull().default(true) + enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), }); export const users = sqliteTable("user", { @@ -268,9 +259,7 @@ export const clientSites = sqliteTable("clientSites", { siteId: integer("siteId") .notNull() .references(() => sites.siteId, { onDelete: "cascade" }), - isRelayed: integer("isRelayed", { mode: "boolean" }) - .notNull() - .default(false), + isRelayed: integer("isRelayed", { mode: "boolean" }).notNull().default(false), endpoint: text("endpoint") }); @@ -328,10 +317,7 @@ export const userOrgs = sqliteTable("userOrgs", { roleId: integer("roleId") .notNull() .references(() => roles.roleId), - isOwner: integer("isOwner", { mode: "boolean" }).notNull().default(false), - autoProvisioned: integer("autoProvisioned", { - mode: "boolean" - }).default(false) + isOwner: integer("isOwner", { mode: "boolean" }).notNull().default(false) }); export const emailVerificationCodes = sqliteTable("emailVerificationCodes", { @@ -608,7 +594,6 @@ export const idpOidcConfig = sqliteTable("idpOidcConfig", { idpOauthConfigId: integer("idpOauthConfigId").primaryKey({ autoIncrement: true }), - variant: text("variant").notNull().default("oidc"), idpId: integer("idpId") .notNull() .references(() => idp.idpId, { onDelete: "cascade" }), diff --git a/server/index.ts b/server/index.ts index 5210ba5d..fb2ad396 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,5 +1,6 @@ #! /usr/bin/env node import "./extendZod.ts"; +import 'source-map-support/register.js' import { runSetupFunctions } from "./setup"; import { createApiServer } from "./apiServer"; diff --git a/server/lib/blueprints/applyBlueprint.ts b/server/lib/blueprints/applyBlueprint.ts deleted file mode 100644 index 47193420..00000000 --- a/server/lib/blueprints/applyBlueprint.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { db, newts, Target } from "@server/db"; -import { Config, ConfigSchema } from "./types"; -import { ProxyResourcesResults, updateProxyResources } from "./proxyResources"; -import { fromError } from "zod-validation-error"; -import logger from "@server/logger"; -import { resources, targets, sites } from "@server/db"; -import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm"; -import { addTargets as addProxyTargets } from "@server/routers/newt/targets"; -import { addTargets as addClientTargets } from "@server/routers/client/targets"; -import { - ClientResourcesResults, - updateClientResources -} from "./clientResources"; - -export async function applyBlueprint( - orgId: string, - configData: unknown, - siteId?: number -): Promise { - // Validate the input data - const validationResult = ConfigSchema.safeParse(configData); - if (!validationResult.success) { - throw new Error(fromError(validationResult.error).toString()); - } - - const config: Config = validationResult.data; - - try { - let proxyResourcesResults: ProxyResourcesResults = []; - let clientResourcesResults: ClientResourcesResults = []; - await db.transaction(async (trx) => { - proxyResourcesResults = await updateProxyResources( - orgId, - config, - trx, - siteId - ); - clientResourcesResults = await updateClientResources( - orgId, - config, - trx, - siteId - ); - }); - - logger.debug( - `Successfully updated proxy resources for org ${orgId}: ${JSON.stringify(proxyResourcesResults)}` - ); - - // We need to update the targets on the newts from the successfully updated information - for (const result of proxyResourcesResults) { - for (const target of result.targetsToUpdate) { - const [site] = await db - .select() - .from(sites) - .innerJoin(newts, eq(sites.siteId, newts.siteId)) - .where( - and( - eq(sites.siteId, target.siteId), - eq(sites.orgId, orgId), - eq(sites.type, "newt"), - isNotNull(sites.pubKey) - ) - ) - .limit(1); - - if (site) { - logger.debug( - `Updating target ${target.targetId} on site ${site.sites.siteId}` - ); - - await addProxyTargets( - site.newt.newtId, - [target], - result.proxyResource.protocol, - result.proxyResource.proxyPort - ); - } - } - } - - logger.debug( - `Successfully updated client resources for org ${orgId}: ${JSON.stringify(clientResourcesResults)}` - ); - - // We need to update the targets on the newts from the successfully updated information - for (const result of clientResourcesResults) { - const [site] = await db - .select() - .from(sites) - .innerJoin(newts, eq(sites.siteId, newts.siteId)) - .where( - and( - eq(sites.siteId, result.resource.siteId), - eq(sites.orgId, orgId), - eq(sites.type, "newt"), - isNotNull(sites.pubKey) - ) - ) - .limit(1); - - if (site) { - logger.debug( - `Updating client resource ${result.resource.siteResourceId} on site ${site.sites.siteId}` - ); - - await addClientTargets( - site.newt.newtId, - result.resource.destinationIp, - result.resource.destinationPort, - result.resource.protocol, - result.resource.proxyPort - ); - } - } - } catch (error) { - logger.error(`Failed to update database from config: ${error}`); - throw error; - } -} - -// await updateDatabaseFromConfig("org_i21aifypnlyxur2", { -// resources: { -// "resource-nice-id": { -// name: "this is my resource", -// protocol: "http", -// "full-domain": "level1.test.example.com", -// "host-header": "example.com", -// "tls-server-name": "example.com", -// auth: { -// pincode: 123456, -// password: "sadfasdfadsf", -// "sso-enabled": true, -// "sso-roles": ["Member"], -// "sso-users": ["owen@fossorial.io"], -// "whitelist-users": ["owen@fossorial.io"] -// }, -// targets: [ -// { -// site: "glossy-plains-viscacha-rat", -// hostname: "localhost", -// method: "http", -// port: 8000, -// healthcheck: { -// port: 8000, -// hostname: "localhost" -// } -// }, -// { -// site: "glossy-plains-viscacha-rat", -// hostname: "localhost", -// method: "http", -// port: 8001 -// } -// ] -// }, -// "resource-nice-id2": { -// name: "http server", -// protocol: "tcp", -// "proxy-port": 3000, -// targets: [ -// { -// site: "glossy-plains-viscacha-rat", -// hostname: "localhost", -// port: 3000, -// } -// ] -// } -// } -// }); diff --git a/server/lib/blueprints/applyNewtDockerBlueprint.ts b/server/lib/blueprints/applyNewtDockerBlueprint.ts deleted file mode 100644 index f69e4854..00000000 --- a/server/lib/blueprints/applyNewtDockerBlueprint.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { sendToClient } from "@server/routers/ws"; -import { processContainerLabels } from "./parseDockerContainers"; -import { applyBlueprint } from "./applyBlueprint"; -import { db, sites } from "@server/db"; -import { eq } from "drizzle-orm"; -import logger from "@server/logger"; - -export async function applyNewtDockerBlueprint( - siteId: number, - newtId: string, - containers: any -) { - const [site] = await db - .select() - .from(sites) - .where(eq(sites.siteId, siteId)) - .limit(1); - - if (!site) { - logger.warn("Site not found in applyNewtDockerBlueprint"); - return; - } - - // logger.debug(`Applying Docker blueprint to site: ${siteId}`); - // logger.debug(`Containers: ${JSON.stringify(containers, null, 2)}`); - - try { - const blueprint = processContainerLabels(containers); - - logger.debug(`Received Docker blueprint: ${JSON.stringify(blueprint)}`); - - // Update the blueprint in the database - await applyBlueprint(site.orgId, blueprint, site.siteId); - } catch (error) { - logger.error(`Failed to update database from config: ${error}`); - await sendToClient(newtId, { - type: "newt/blueprint/results", - data: { - success: false, - message: `Failed to update database from config: ${error}` - } - }); - return; - } - - await sendToClient(newtId, { - type: "newt/blueprint/results", - data: { - success: true, - message: "Config updated successfully" - } - }); -} diff --git a/server/lib/blueprints/clientResources.ts b/server/lib/blueprints/clientResources.ts deleted file mode 100644 index 59bbc346..00000000 --- a/server/lib/blueprints/clientResources.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { - SiteResource, - siteResources, - Transaction, -} from "@server/db"; -import { sites } from "@server/db"; -import { eq, and } from "drizzle-orm"; -import { - Config, -} from "./types"; -import logger from "@server/logger"; - -export type ClientResourcesResults = { - resource: SiteResource; -}[]; - -export async function updateClientResources( - orgId: string, - config: Config, - trx: Transaction, - siteId?: number -): Promise { - const results: ClientResourcesResults = []; - - for (const [resourceNiceId, resourceData] of Object.entries( - config["client-resources"] - )) { - const [existingResource] = await trx - .select() - .from(siteResources) - .where( - and( - eq(siteResources.orgId, orgId), - eq(siteResources.niceId, resourceNiceId) - ) - ) - .limit(1); - - const resourceSiteId = resourceData.site; - let site; - - if (resourceSiteId) { - // Look up site by niceId - [site] = await trx - .select({ siteId: sites.siteId }) - .from(sites) - .where( - and( - eq(sites.niceId, resourceSiteId), - eq(sites.orgId, orgId) - ) - ) - .limit(1); - } else if (siteId) { - // Use the provided siteId directly, but verify it belongs to the org - [site] = await trx - .select({ siteId: sites.siteId }) - .from(sites) - .where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))) - .limit(1); - } else { - throw new Error(`Target site is required`); - } - - if (!site) { - throw new Error( - `Site not found: ${resourceSiteId} in org ${orgId}` - ); - } - - if (existingResource) { - // Update existing resource - const [updatedResource] = await trx - .update(siteResources) - .set({ - name: resourceData.name || resourceNiceId, - siteId: site.siteId, - proxyPort: resourceData["proxy-port"]!, - destinationIp: resourceData.hostname, - destinationPort: resourceData["internal-port"], - protocol: resourceData.protocol - }) - .where( - eq( - siteResources.siteResourceId, - existingResource.siteResourceId - ) - ) - .returning(); - - results.push({ resource: updatedResource }); - } else { - // Create new resource - const [newResource] = await trx - .insert(siteResources) - .values({ - orgId: orgId, - siteId: site.siteId, - niceId: resourceNiceId, - name: resourceData.name || resourceNiceId, - proxyPort: resourceData["proxy-port"]!, - destinationIp: resourceData.hostname, - destinationPort: resourceData["internal-port"], - protocol: resourceData.protocol - }) - .returning(); - - logger.info( - `Created new client resource ${newResource.name} (${newResource.siteResourceId}) for org ${orgId}` - ); - - results.push({ resource: newResource }); - } - } - - return results; -} diff --git a/server/lib/blueprints/parseDockerContainers.ts b/server/lib/blueprints/parseDockerContainers.ts deleted file mode 100644 index 1510e6e1..00000000 --- a/server/lib/blueprints/parseDockerContainers.ts +++ /dev/null @@ -1,301 +0,0 @@ -import logger from "@server/logger"; -import { setNestedProperty } from "./parseDotNotation"; - -export type DockerLabels = { - [key: string]: string; -}; - -export type ParsedObject = { - [key: string]: any; -}; - -type ContainerPort = { - privatePort: number; - publicPort: number; - type: string; - ip: string; -}; - -type Container = { - id: string; - name: string; - image: string; - state: string; - status: string; - ports: ContainerPort[] | null; - labels: DockerLabels; - created: number; - networks: { [key: string]: any }; - hostname: string; -}; - -type Target = { - hostname?: string; - port?: number; - method?: string; - enabled?: boolean; - [key: string]: any; -}; - -type ResourceConfig = { - [key: string]: any; - targets?: (Target | null)[]; -}; - -function getContainerPort(container: Container): number | null { - if (!container.ports || container.ports.length === 0) { - return null; - } - // Return the first port's privatePort - return container.ports[0].privatePort; - // return container.ports[0].publicPort; -} - -export function processContainerLabels(containers: Container[]): { - "proxy-resources": { [key: string]: ResourceConfig }; - "client-resources": { [key: string]: ResourceConfig }; -} { - const result = { - "proxy-resources": {} as { [key: string]: ResourceConfig }, - "client-resources": {} as { [key: string]: ResourceConfig } - }; - - // Process each container - containers.forEach((container) => { - if (container.state !== "running") { - return; - } - - const proxyResourceLabels: DockerLabels = {}; - const clientResourceLabels: DockerLabels = {}; - - // Filter and separate proxy-resources and client-resources labels - Object.entries(container.labels).forEach(([key, value]) => { - if (key.startsWith("pangolin.proxy-resources.")) { - // remove the pangolin.proxy- prefix to get "resources.xxx" - const strippedKey = key.replace("pangolin.proxy-", ""); - proxyResourceLabels[strippedKey] = value; - } else if (key.startsWith("pangolin.client-resources.")) { - // remove the pangolin.client- prefix to get "resources.xxx" - const strippedKey = key.replace("pangolin.client-", ""); - clientResourceLabels[strippedKey] = value; - } - }); - - // Process proxy resources - if (Object.keys(proxyResourceLabels).length > 0) { - processResourceLabels(proxyResourceLabels, container, result["proxy-resources"]); - } - - // Process client resources - if (Object.keys(clientResourceLabels).length > 0) { - processResourceLabels(clientResourceLabels, container, result["client-resources"]); - } - }); - - return result; -} - -function processResourceLabels( - resourceLabels: DockerLabels, - container: Container, - targetResult: { [key: string]: ResourceConfig } -) { - // Parse the labels using the existing parseDockerLabels logic - const tempResult: ParsedObject = {}; - Object.entries(resourceLabels).forEach(([key, value]) => { - setNestedProperty(tempResult, key, value); - }); - - // Merge into target result - if (tempResult.resources) { - Object.entries(tempResult.resources).forEach( - ([resourceKey, resourceConfig]: [string, any]) => { - // Initialize resource if it doesn't exist - if (!targetResult[resourceKey]) { - targetResult[resourceKey] = {}; - } - - // Merge all properties except targets - Object.entries(resourceConfig).forEach( - ([propKey, propValue]) => { - if (propKey !== "targets") { - targetResult[resourceKey][propKey] = propValue; - } - } - ); - - // Handle targets specially - if ( - resourceConfig.targets && - Array.isArray(resourceConfig.targets) - ) { - const resource = targetResult[resourceKey]; - if (resource) { - if (!resource.targets) { - resource.targets = []; - } - - resourceConfig.targets.forEach( - (target: any, targetIndex: number) => { - // check if the target is an empty object - if ( - typeof target === "object" && - Object.keys(target).length === 0 - ) { - logger.debug( - `Skipping null target at index ${targetIndex} for resource ${resourceKey}` - ); - resource.targets!.push(null); - return; - } - - // Ensure targets array is long enough - while ( - resource.targets!.length <= targetIndex - ) { - resource.targets!.push({}); - } - - // Set default hostname and port if not provided - const finalTarget = { ...target }; - if (!finalTarget.hostname) { - finalTarget.hostname = - container.name || - container.hostname; - } - if (!finalTarget.port) { - const containerPort = - getContainerPort(container); - if (containerPort !== null) { - finalTarget.port = containerPort; - } - } - - // Merge with existing target data - resource.targets![targetIndex] = { - ...resource.targets![targetIndex], - ...finalTarget - }; - } - ); - } - } - } - ); - } -} - -// // Test example -// const testContainers: Container[] = [ -// { -// id: "57e056cb0e3a", -// name: "nginx1", -// image: "nginxdemos/hello", -// state: "running", -// status: "Up 4 days", -// ports: [ -// { -// privatePort: 80, -// publicPort: 8000, -// type: "tcp", -// ip: "0.0.0.0" -// } -// ], -// labels: { -// "resources.nginx.name": "nginx", -// "resources.nginx.full-domain": "nginx.example.com", -// "resources.nginx.protocol": "http", -// "resources.nginx.targets[0].enabled": "true" -// }, -// created: 1756942725, -// networks: { -// owen_default: { -// networkId: -// "cb131c0f1d5d8ef7158660e77fc370508f5a563e1f9829b53a1945ae3725b58c" -// } -// }, -// hostname: "57e056cb0e3a" -// }, -// { -// id: "58e056cb0e3b", -// name: "nginx2", -// image: "nginxdemos/hello", -// state: "running", -// status: "Up 4 days", -// ports: [ -// { -// privatePort: 80, -// publicPort: 8001, -// type: "tcp", -// ip: "0.0.0.0" -// } -// ], -// labels: { -// "resources.nginx.name": "nginx", -// "resources.nginx.full-domain": "nginx.example.com", -// "resources.nginx.protocol": "http", -// "resources.nginx.targets[1].enabled": "true" -// }, -// created: 1756942726, -// networks: { -// owen_default: { -// networkId: -// "cb131c0f1d5d8ef7158660e77fc370508f5a563e1f9829b53a1945ae3725b58c" -// } -// }, -// hostname: "58e056cb0e3b" -// }, -// { -// id: "59e056cb0e3c", -// name: "api-server", -// image: "my-api:latest", -// state: "running", -// status: "Up 2 days", -// ports: [ -// { -// privatePort: 3000, -// publicPort: 3000, -// type: "tcp", -// ip: "0.0.0.0" -// } -// ], -// labels: { -// "resources.api.name": "API Server", -// "resources.api.protocol": "http", -// "resources.api.targets[0].enabled": "true", -// "resources.api.targets[0].hostname": "custom-host", -// "resources.api.targets[0].port": "3001" -// }, -// created: 1756942727, -// networks: { -// owen_default: { -// networkId: -// "cb131c0f1d5d8ef7158660e77fc370508f5a563e1f9829b53a1945ae3725b58c" -// } -// }, -// hostname: "59e056cb0e3c" -// }, -// { -// id: "d0e29b08361c", -// name: "beautiful_wilson", -// image: "bolkedebruin/rdpgw:latest", -// state: "exited", -// status: "Exited (0) 4 hours ago", -// ports: null, -// labels: {}, -// created: 1757359039, -// networks: { -// bridge: { -// networkId: -// "ea7f56dfc9cc476b8a3560b5b570d0fe8a6a2bc5e8343ab1ed37822086e89687" -// } -// }, -// hostname: "d0e29b08361c" -// } -// ]; - -// // Test the function -// const result = processContainerLabels(testContainers); -// console.log("Processed result:"); -// console.log(JSON.stringify(result, null, 2)); diff --git a/server/lib/blueprints/parseDotNotation.ts b/server/lib/blueprints/parseDotNotation.ts deleted file mode 100644 index 87509d39..00000000 --- a/server/lib/blueprints/parseDotNotation.ts +++ /dev/null @@ -1,109 +0,0 @@ -export function setNestedProperty(obj: any, path: string, value: string): void { - const keys = path.split("."); - let current = obj; - - for (let i = 0; i < keys.length - 1; i++) { - const key = keys[i]; - - // Handle array notation like "targets[0]" - const arrayMatch = key.match(/^(.+)\[(\d+)\]$/); - - if (arrayMatch) { - const [, arrayKey, indexStr] = arrayMatch; - const index = parseInt(indexStr, 10); - - // Initialize array if it doesn't exist - if (!current[arrayKey]) { - current[arrayKey] = []; - } - - // Ensure array is long enough - while (current[arrayKey].length <= index) { - current[arrayKey].push({}); - } - - current = current[arrayKey][index]; - } else { - // Regular object property - if (!current[key]) { - current[key] = {}; - } - current = current[key]; - } - } - - // Set the final value - const finalKey = keys[keys.length - 1]; - const arrayMatch = finalKey.match(/^(.+)\[(\d+)\]$/); - - if (arrayMatch) { - const [, arrayKey, indexStr] = arrayMatch; - const index = parseInt(indexStr, 10); - - if (!current[arrayKey]) { - current[arrayKey] = []; - } - - // Ensure array is long enough - while (current[arrayKey].length <= index) { - current[arrayKey].push(null); - } - - current[arrayKey][index] = convertValue(value); - } else { - current[finalKey] = convertValue(value); - } -} - -// Helper function to convert string values to appropriate types -export function convertValue(value: string): any { - // Convert boolean strings - if (value === "true") return true; - if (value === "false") return false; - - // Convert numeric strings - if (/^\d+$/.test(value)) { - const num = parseInt(value, 10); - return num; - } - - if (/^\d*\.\d+$/.test(value)) { - const num = parseFloat(value); - return num; - } - - // Return as string - return value; -} - -// // Example usage: -// const dockerLabels: DockerLabels = { -// "resources.resource-nice-id.name": "this is my resource", -// "resources.resource-nice-id.protocol": "http", -// "resources.resource-nice-id.full-domain": "level1.test3.example.com", -// "resources.resource-nice-id.host-header": "example.com", -// "resources.resource-nice-id.tls-server-name": "example.com", -// "resources.resource-nice-id.auth.pincode": "123456", -// "resources.resource-nice-id.auth.password": "sadfasdfadsf", -// "resources.resource-nice-id.auth.sso-enabled": "true", -// "resources.resource-nice-id.auth.sso-roles[0]": "Member", -// "resources.resource-nice-id.auth.sso-users[0]": "owen@fossorial.io", -// "resources.resource-nice-id.auth.whitelist-users[0]": "owen@fossorial.io", -// "resources.resource-nice-id.targets[0].hostname": "localhost", -// "resources.resource-nice-id.targets[0].method": "http", -// "resources.resource-nice-id.targets[0].port": "8000", -// "resources.resource-nice-id.targets[0].healthcheck.port": "8000", -// "resources.resource-nice-id.targets[0].healthcheck.hostname": "localhost", -// "resources.resource-nice-id.targets[1].hostname": "localhost", -// "resources.resource-nice-id.targets[1].method": "http", -// "resources.resource-nice-id.targets[1].port": "8001", -// "resources.resource-nice-id2.name": "this is other resource", -// "resources.resource-nice-id2.protocol": "tcp", -// "resources.resource-nice-id2.proxy-port": "3000", -// "resources.resource-nice-id2.targets[0].hostname": "localhost", -// "resources.resource-nice-id2.targets[0].port": "3000" -// }; - -// // Parse the labels -// const parsed = parseDockerLabels(dockerLabels); -// console.log(JSON.stringify(parsed, null, 2)); diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts deleted file mode 100644 index 6244fefa..00000000 --- a/server/lib/blueprints/proxyResources.ts +++ /dev/null @@ -1,885 +0,0 @@ -import { - domains, - orgDomains, - Resource, - resourcePincode, - resourceRules, - resourceWhitelist, - roleResources, - roles, - Target, - Transaction, - userOrgs, - userResources, - users -} from "@server/db"; -import { resources, targets, sites } from "@server/db"; -import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm"; -import { - Config, - ConfigSchema, - isTargetsOnlyResource, - TargetData -} from "./types"; -import logger from "@server/logger"; -import { pickPort } from "@server/routers/target/helpers"; -import { resourcePassword } from "@server/db"; -import { hashPassword } from "@server/auth/password"; -import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators"; - -export type ProxyResourcesResults = { - proxyResource: Resource; - targetsToUpdate: Target[]; -}[]; - -export async function updateProxyResources( - orgId: string, - config: Config, - trx: Transaction, - siteId?: number -): Promise { - const results: ProxyResourcesResults = []; - - for (const [resourceNiceId, resourceData] of Object.entries( - config["proxy-resources"] - )) { - const targetsToUpdate: Target[] = []; - let resource: Resource; - - async function createTarget( // reusable function to create a target - resourceId: number, - targetData: TargetData - ) { - const targetSiteId = targetData.site; - let site; - - if (targetSiteId) { - // Look up site by niceId - [site] = await trx - .select({ siteId: sites.siteId }) - .from(sites) - .where( - and( - eq(sites.niceId, targetSiteId), - eq(sites.orgId, orgId) - ) - ) - .limit(1); - } else if (siteId) { - // Use the provided siteId directly, but verify it belongs to the org - [site] = await trx - .select({ siteId: sites.siteId }) - .from(sites) - .where( - and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)) - ) - .limit(1); - } else { - throw new Error(`Target site is required`); - } - - if (!site) { - throw new Error( - `Site not found: ${targetSiteId} in org ${orgId}` - ); - } - - let internalPortToCreate; - if (!targetData["internal-port"]) { - const { internalPort, targetIps } = await pickPort( - site.siteId!, - trx - ); - internalPortToCreate = internalPort; - } else { - internalPortToCreate = targetData["internal-port"]; - } - - // Create target - const [newTarget] = await trx - .insert(targets) - .values({ - resourceId: resourceId, - siteId: site.siteId, - ip: targetData.hostname, - method: targetData.method, - port: targetData.port, - enabled: targetData.enabled, - internalPort: internalPortToCreate, - path: targetData.path, - pathMatchType: targetData["path-match"] - }) - .returning(); - - targetsToUpdate.push(newTarget); - } - - // Find existing resource by niceId and orgId - const [existingResource] = await trx - .select() - .from(resources) - .where( - and( - eq(resources.niceId, resourceNiceId), - eq(resources.orgId, orgId) - ) - ) - .limit(1); - - const http = resourceData.protocol == "http"; - const protocol = - resourceData.protocol == "http" ? "tcp" : resourceData.protocol; - const resourceEnabled = - resourceData.enabled == undefined || resourceData.enabled == null - ? true - : resourceData.enabled; - const resourceSsl = - resourceData.ssl == undefined || resourceData.ssl == null - ? true - : resourceData.ssl; - let headers = ""; - for (const header of resourceData.headers || []) { - headers += `${header.name}: ${header.value},`; - } - // if there are headers, remove the trailing comma - if (headers.endsWith(",")) { - headers = headers.slice(0, -1); - } - - if (existingResource) { - let domain; - if (http) { - domain = await getDomain( - existingResource.resourceId, - resourceData["full-domain"]!, - orgId, - trx - ); - } - - // check if the only key in the resource is targets, if so, skip the update - if (isTargetsOnlyResource(resourceData)) { - logger.debug( - `Skipping update for resource ${existingResource.resourceId} as only targets are provided` - ); - resource = existingResource; - } else { - // Update existing resource - [resource] = await trx - .update(resources) - .set({ - name: resourceData.name || "Unnamed Resource", - protocol: protocol || "http", - http: http, - proxyPort: http ? null : resourceData["proxy-port"], - fullDomain: http ? resourceData["full-domain"] : null, - subdomain: domain ? domain.subdomain : null, - domainId: domain ? domain.domainId : null, - enabled: resourceEnabled, - sso: resourceData.auth?.["sso-enabled"] || false, - ssl: resourceSsl, - setHostHeader: resourceData["host-header"] || null, - tlsServerName: resourceData["tls-server-name"] || null, - emailWhitelistEnabled: resourceData.auth?.[ - "whitelist-users" - ] - ? resourceData.auth["whitelist-users"].length > 0 - : false, - headers: headers || null, - applyRules: - resourceData.rules && resourceData.rules.length > 0 - }) - .where( - eq(resources.resourceId, existingResource.resourceId) - ) - .returning(); - - await trx - .delete(resourcePassword) - .where( - eq( - resourcePassword.resourceId, - existingResource.resourceId - ) - ); - if (resourceData.auth?.password) { - const passwordHash = await hashPassword( - resourceData.auth.password - ); - - await trx.insert(resourcePassword).values({ - resourceId: existingResource.resourceId, - passwordHash - }); - } - - await trx - .delete(resourcePincode) - .where( - eq( - resourcePincode.resourceId, - existingResource.resourceId - ) - ); - if (resourceData.auth?.pincode) { - const pincodeHash = await hashPassword( - resourceData.auth.pincode.toString() - ); - - await trx.insert(resourcePincode).values({ - resourceId: existingResource.resourceId, - pincodeHash, - digitLength: 6 - }); - } - - if (resourceData.auth?.["sso-roles"]) { - const ssoRoles = resourceData.auth?.["sso-roles"]; - await syncRoleResources( - existingResource.resourceId, - ssoRoles, - orgId, - trx - ); - } - - if (resourceData.auth?.["sso-users"]) { - const ssoUsers = resourceData.auth?.["sso-users"]; - await syncUserResources( - existingResource.resourceId, - ssoUsers, - orgId, - trx - ); - } - - if (resourceData.auth?.["whitelist-users"]) { - const whitelistUsers = - resourceData.auth?.["whitelist-users"]; - await syncWhitelistUsers( - existingResource.resourceId, - whitelistUsers, - orgId, - trx - ); - } - } - - const existingResourceTargets = await trx - .select() - .from(targets) - .where(eq(targets.resourceId, existingResource.resourceId)) - .orderBy(asc(targets.targetId)); - - // Create new targets - for (const [index, targetData] of resourceData.targets.entries()) { - if ( - !targetData || - (typeof targetData === "object" && - Object.keys(targetData).length === 0) - ) { - // If targetData is null or an empty object, we can skip it - continue; - } - const existingTarget = existingResourceTargets[index]; - - if (existingTarget) { - const targetSiteId = targetData.site; - let site; - - if (targetSiteId) { - // Look up site by niceId - [site] = await trx - .select({ siteId: sites.siteId }) - .from(sites) - .where( - and( - eq(sites.niceId, targetSiteId), - eq(sites.orgId, orgId) - ) - ) - .limit(1); - } else if (siteId) { - // Use the provided siteId directly, but verify it belongs to the org - [site] = await trx - .select({ siteId: sites.siteId }) - .from(sites) - .where( - and( - eq(sites.siteId, siteId), - eq(sites.orgId, orgId) - ) - ) - .limit(1); - } else { - throw new Error(`Target site is required`); - } - - if (!site) { - throw new Error( - `Site not found: ${targetSiteId} in org ${orgId}` - ); - } - - // update this target - const [updatedTarget] = await trx - .update(targets) - .set({ - siteId: site.siteId, - ip: targetData.hostname, - method: http ? targetData.method : null, - port: targetData.port, - enabled: targetData.enabled, - path: targetData.path, - pathMatchType: targetData["path-match"] - }) - .where(eq(targets.targetId, existingTarget.targetId)) - .returning(); - - if (checkIfTargetChanged(existingTarget, updatedTarget)) { - let internalPortToUpdate; - if (!targetData["internal-port"]) { - const { internalPort, targetIps } = await pickPort( - site.siteId!, - trx - ); - internalPortToUpdate = internalPort; - } else { - internalPortToUpdate = targetData["internal-port"]; - } - - const [finalUpdatedTarget] = await trx // this double is so we can check the whole target before and after - .update(targets) - .set({ - internalPort: internalPortToUpdate - }) - .where( - eq(targets.targetId, existingTarget.targetId) - ) - .returning(); - - targetsToUpdate.push(finalUpdatedTarget); - } - } else { - await createTarget(existingResource.resourceId, targetData); - } - } - - if (existingResourceTargets.length > resourceData.targets.length) { - const targetsToDelete = existingResourceTargets.slice( - resourceData.targets.length - ); - logger.debug( - `Targets to delete: ${JSON.stringify(targetsToDelete)}` - ); - for (const target of targetsToDelete) { - if (!target) { - continue; - } - if (siteId && target.siteId !== siteId) { - logger.debug( - `Skipping target ${target.targetId} for deletion. Site ID does not match filter.` - ); - continue; // only delete targets for the specified siteId - } - logger.debug(`Deleting target ${target.targetId}`); - await trx - .delete(targets) - .where(eq(targets.targetId, target.targetId)); - } - } - - const existingRules = await trx - .select() - .from(resourceRules) - .where( - eq(resourceRules.resourceId, existingResource.resourceId) - ) - .orderBy(resourceRules.priority); - - // Sync rules - for (const [index, rule] of resourceData.rules?.entries() || []) { - const existingRule = existingRules[index]; - if (existingRule) { - if ( - existingRule.action !== getRuleAction(rule.action) || - existingRule.match !== rule.match.toUpperCase() || - existingRule.value !== rule.value - ) { - validateRule(rule); - await trx - .update(resourceRules) - .set({ - action: getRuleAction(rule.action), - match: rule.match.toUpperCase(), - value: rule.value - }) - .where( - eq(resourceRules.ruleId, existingRule.ruleId) - ); - } - } else { - validateRule(rule); - await trx.insert(resourceRules).values({ - resourceId: existingResource.resourceId, - action: getRuleAction(rule.action), - match: rule.match.toUpperCase(), - value: rule.value, - priority: index + 1 // start priorities at 1 - }); - } - } - - if (existingRules.length > (resourceData.rules?.length || 0)) { - const rulesToDelete = existingRules.slice( - resourceData.rules?.length || 0 - ); - for (const rule of rulesToDelete) { - await trx - .delete(resourceRules) - .where(eq(resourceRules.ruleId, rule.ruleId)); - } - } - - logger.debug(`Updated resource ${existingResource.resourceId}`); - } else { - // create a brand new resource - let domain; - if (http) { - domain = await getDomain( - undefined, - resourceData["full-domain"]!, - orgId, - trx - ); - } - - // Create new resource - const [newResource] = await trx - .insert(resources) - .values({ - orgId, - niceId: resourceNiceId, - name: resourceData.name || "Unnamed Resource", - protocol: resourceData.protocol || "http", - http: http, - proxyPort: http ? null : resourceData["proxy-port"], - fullDomain: http ? resourceData["full-domain"] : null, - subdomain: domain ? domain.subdomain : null, - domainId: domain ? domain.domainId : null, - enabled: resourceEnabled, - sso: resourceData.auth?.["sso-enabled"] || false, - setHostHeader: resourceData["host-header"] || null, - tlsServerName: resourceData["tls-server-name"] || null, - ssl: resourceSsl, - headers: headers || null, - applyRules: - resourceData.rules && resourceData.rules.length > 0 - }) - .returning(); - - if (resourceData.auth?.password) { - const passwordHash = await hashPassword( - resourceData.auth.password - ); - - await trx.insert(resourcePassword).values({ - resourceId: newResource.resourceId, - passwordHash - }); - } - - if (resourceData.auth?.pincode) { - const pincodeHash = await hashPassword( - resourceData.auth.pincode.toString() - ); - - await trx.insert(resourcePincode).values({ - resourceId: newResource.resourceId, - pincodeHash, - digitLength: 6 - }); - } - - resource = newResource; - - const [adminRole] = await trx - .select() - .from(roles) - .where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId))) - .limit(1); - - if (!adminRole) { - throw new Error(`Admin role not found`); - } - - await trx.insert(roleResources).values({ - roleId: adminRole.roleId, - resourceId: newResource.resourceId - }); - - if (resourceData.auth?.["sso-roles"]) { - const ssoRoles = resourceData.auth?.["sso-roles"]; - await syncRoleResources( - newResource.resourceId, - ssoRoles, - orgId, - trx - ); - } - - if (resourceData.auth?.["sso-users"]) { - const ssoUsers = resourceData.auth?.["sso-users"]; - await syncUserResources( - newResource.resourceId, - ssoUsers, - orgId, - trx - ); - } - - if (resourceData.auth?.["whitelist-users"]) { - const whitelistUsers = resourceData.auth?.["whitelist-users"]; - await syncWhitelistUsers( - newResource.resourceId, - whitelistUsers, - orgId, - trx - ); - } - - // Create new targets - for (const targetData of resourceData.targets) { - if (!targetData) { - // If targetData is null or an empty object, we can skip it - continue; - } - await createTarget(newResource.resourceId, targetData); - } - - for (const [index, rule] of resourceData.rules?.entries() || []) { - validateRule(rule); - await trx.insert(resourceRules).values({ - resourceId: newResource.resourceId, - action: getRuleAction(rule.action), - match: rule.match.toUpperCase(), - value: rule.value, - priority: index + 1 // start priorities at 1 - }); - } - - logger.debug(`Created resource ${newResource.resourceId}`); - } - - results.push({ - proxyResource: resource, - targetsToUpdate - }); - } - - return results; -} - -function getRuleAction(input: string) { - let action = "DROP"; - if (input == "allow") { - action = "ACCEPT"; - } else if (input == "deny") { - action = "DROP"; - } else if (input == "pass") { - action = "PASS"; - } - return action; -} - -function validateRule(rule: any) { - if (rule.match === "cidr") { - if (!isValidCIDR(rule.value)) { - throw new Error(`Invalid CIDR provided: ${rule.value}`); - } - } else if (rule.match === "ip") { - if (!isValidIP(rule.value)) { - throw new Error(`Invalid IP provided: ${rule.value}`); - } - } else if (rule.match === "path") { - if (!isValidUrlGlobPattern(rule.value)) { - throw new Error(`Invalid URL glob pattern: ${rule.value}`); - } - } -} - -async function syncRoleResources( - resourceId: number, - ssoRoles: string[], - orgId: string, - trx: Transaction -) { - const existingRoleResources = await trx - .select() - .from(roleResources) - .where(eq(roleResources.resourceId, resourceId)); - - for (const roleName of ssoRoles) { - if (roleName === "Admin") { - continue; // never add admin access - } - - const [role] = await trx - .select() - .from(roles) - .where(and(eq(roles.name, roleName), eq(roles.orgId, orgId))) - .limit(1); - - if (!role) { - throw new Error(`Role not found: ${roleName} in org ${orgId}`); - } - - const existingRoleResource = existingRoleResources.find( - (rr) => rr.roleId === role.roleId - ); - - if (!existingRoleResource) { - await trx.insert(roleResources).values({ - roleId: role.roleId, - resourceId: resourceId - }); - } - } - - for (const existingRoleResource of existingRoleResources) { - const [role] = await trx - .select() - .from(roles) - .where(eq(roles.roleId, existingRoleResource.roleId)) - .limit(1); - - if (role.isAdmin) { - continue; // never remove admin access - } - - if (role && !ssoRoles.includes(role.name)) { - await trx - .delete(roleResources) - .where( - and( - eq(roleResources.roleId, existingRoleResource.roleId), - eq(roleResources.resourceId, resourceId) - ) - ); - } - } -} - -async function syncUserResources( - resourceId: number, - ssoUsers: string[], - orgId: string, - trx: Transaction -) { - const existingUserResources = await trx - .select() - .from(userResources) - .where(eq(userResources.resourceId, resourceId)); - - for (const email of ssoUsers) { - const [user] = await trx - .select() - .from(users) - .innerJoin(userOrgs, eq(users.userId, userOrgs.userId)) - .where(and(eq(users.email, email), eq(userOrgs.orgId, orgId))) - .limit(1); - - if (!user) { - throw new Error(`User not found: ${email} in org ${orgId}`); - } - - const existingUserResource = existingUserResources.find( - (rr) => rr.userId === user.user.userId - ); - - if (!existingUserResource) { - await trx.insert(userResources).values({ - userId: user.user.userId, - resourceId: resourceId - }); - } - } - - for (const existingUserResource of existingUserResources) { - const [user] = await trx - .select() - .from(users) - .innerJoin(userOrgs, eq(users.userId, userOrgs.userId)) - .where( - and( - eq(users.userId, existingUserResource.userId), - eq(userOrgs.orgId, orgId) - ) - ) - .limit(1); - - if (user && user.user.email && !ssoUsers.includes(user.user.email)) { - await trx - .delete(userResources) - .where( - and( - eq(userResources.userId, existingUserResource.userId), - eq(userResources.resourceId, resourceId) - ) - ); - } - } -} - -async function syncWhitelistUsers( - resourceId: number, - whitelistUsers: string[], - orgId: string, - trx: Transaction -) { - const existingWhitelist = await trx - .select() - .from(resourceWhitelist) - .where(eq(resourceWhitelist.resourceId, resourceId)); - - for (const email of whitelistUsers) { - const [user] = await trx - .select() - .from(users) - .innerJoin(userOrgs, eq(users.userId, userOrgs.userId)) - .where(and(eq(users.email, email), eq(userOrgs.orgId, orgId))) - .limit(1); - - if (!user) { - throw new Error(`User not found: ${email} in org ${orgId}`); - } - - const existingWhitelistEntry = existingWhitelist.find( - (w) => w.email === email - ); - - if (!existingWhitelistEntry) { - await trx.insert(resourceWhitelist).values({ - email, - resourceId: resourceId - }); - } - } - - for (const existingWhitelistEntry of existingWhitelist) { - if (!whitelistUsers.includes(existingWhitelistEntry.email)) { - await trx - .delete(resourceWhitelist) - .where( - and( - eq(resourceWhitelist.resourceId, resourceId), - eq( - resourceWhitelist.email, - existingWhitelistEntry.email - ) - ) - ); - } - } -} - -function checkIfTargetChanged( - existing: Target | undefined, - incoming: Target | undefined -): boolean { - if (!existing && incoming) return true; - if (existing && !incoming) return true; - if (!existing || !incoming) return false; - - if (existing.ip !== incoming.ip) return true; - if (existing.port !== incoming.port) return true; - if (existing.siteId !== incoming.siteId) return true; - - return false; -} - -async function getDomain( - resourceId: number | undefined, - fullDomain: string, - orgId: string, - trx: Transaction -) { - const [fullDomainExists] = await trx - .select({ resourceId: resources.resourceId }) - .from(resources) - .where( - and( - eq(resources.fullDomain, fullDomain), - eq(resources.orgId, orgId), - resourceId - ? ne(resources.resourceId, resourceId) - : isNotNull(resources.resourceId) - ) - ) - .limit(1); - - if (fullDomainExists) { - throw new Error( - `Resource already exists: ${fullDomain} in org ${orgId}` - ); - } - - const domain = await getDomainId(orgId, fullDomain, trx); - - if (!domain) { - throw new Error( - `Domain not found for full-domain: ${fullDomain} in org ${orgId}` - ); - } - - return domain; -} - -async function getDomainId( - orgId: string, - fullDomain: string, - trx: Transaction -): Promise<{ subdomain: string | null; domainId: string } | null> { - const possibleDomains = await trx - .select() - .from(domains) - .innerJoin(orgDomains, eq(domains.domainId, orgDomains.domainId)) - .where(and(eq(orgDomains.orgId, orgId), eq(domains.verified, true))) - .execute(); - - if (possibleDomains.length === 0) { - return null; - } - - const validDomains = possibleDomains.filter((domain) => { - if (domain.domains.type == "ns" || domain.domains.type == "wildcard") { - return ( - fullDomain === domain.domains.baseDomain || - fullDomain.endsWith(`.${domain.domains.baseDomain}`) - ); - } else if (domain.domains.type == "cname") { - return fullDomain === domain.domains.baseDomain; - } - }); - - if (validDomains.length === 0) { - return null; - } - - const domainSelection = validDomains[0].domains; - const baseDomain = domainSelection.baseDomain; - - // remove the base domain of the domain - let subdomain = null; - if (domainSelection.type == "ns") { - if (fullDomain != baseDomain) { - subdomain = fullDomain.replace(`.${baseDomain}`, ""); - } - } - - // Return the first valid domain - return { - subdomain: subdomain, - domainId: domainSelection.domainId - }; -} diff --git a/server/lib/blueprints/types.ts b/server/lib/blueprints/types.ts deleted file mode 100644 index 9b3a7a20..00000000 --- a/server/lib/blueprints/types.ts +++ /dev/null @@ -1,366 +0,0 @@ -import { z } from "zod"; - -export const SiteSchema = z.object({ - name: z.string().min(1).max(100), - "docker-socket-enabled": z.boolean().optional().default(true) -}); - -// Schema for individual target within a resource -export const TargetSchema = z.object({ - site: z.string().optional(), - method: z.enum(["http", "https", "h2c"]).optional(), - hostname: z.string(), - port: z.number().int().min(1).max(65535), - enabled: z.boolean().optional().default(true), - "internal-port": z.number().int().min(1).max(65535).optional(), - path: z.string().optional(), - "path-match": z.enum(["exact", "prefix", "regex"]).optional().nullable() -}); -export type TargetData = z.infer; - -export const AuthSchema = z.object({ - // pincode has to have 6 digits - pincode: z.number().min(100000).max(999999).optional(), - password: z.string().min(1).optional(), - "sso-enabled": z.boolean().optional().default(false), - "sso-roles": z - .array(z.string()) - .optional() - .default([]) - .refine((roles) => !roles.includes("Admin"), { - message: "Admin role cannot be included in sso-roles" - }), - "sso-users": z.array(z.string().email()).optional().default([]), - "whitelist-users": z.array(z.string().email()).optional().default([]), -}); - -export const RuleSchema = z.object({ - action: z.enum(["allow", "deny", "pass"]), - match: z.enum(["cidr", "path", "ip", "country"]), - value: z.string() -}); - -export const HeaderSchema = z.object({ - name: z.string().min(1), - value: z.string().min(1) -}); - -// Schema for individual resource -export const ResourceSchema = z - .object({ - name: z.string().optional(), - protocol: z.enum(["http", "tcp", "udp"]).optional(), - ssl: z.boolean().optional(), - "full-domain": z.string().optional(), - "proxy-port": z.number().int().min(1).max(65535).optional(), - enabled: z.boolean().optional(), - targets: z.array(TargetSchema.nullable()).optional().default([]), - auth: AuthSchema.optional(), - "host-header": z.string().optional(), - "tls-server-name": z.string().optional(), - headers: z.array(HeaderSchema).optional(), - rules: z.array(RuleSchema).optional() - }) - .refine( - (resource) => { - if (isTargetsOnlyResource(resource)) { - return true; - } - - // Otherwise, require name and protocol for full resource definition - return ( - resource.name !== undefined && resource.protocol !== undefined - ); - }, - { - message: - "Resource must either be targets-only (only 'targets' field) or have both 'name' and 'protocol' fields at a minimum", - path: ["name", "protocol"] - } - ) - .refine( - (resource) => { - if (isTargetsOnlyResource(resource)) { - return true; - } - - // If protocol is http, all targets must have method field - if (resource.protocol === "http") { - return resource.targets.every( - (target) => target == null || target.method !== undefined - ); - } - // If protocol is tcp or udp, no target should have method field - if (resource.protocol === "tcp" || resource.protocol === "udp") { - return resource.targets.every( - (target) => target == null || target.method === undefined - ); - } - return true; - }, - (resource) => { - if (resource.protocol === "http") { - return { - message: - "When protocol is 'http', all targets must have a 'method' field", - path: ["targets"] - }; - } - return { - message: - "When protocol is 'tcp' or 'udp', targets must not have a 'method' field", - path: ["targets"] - }; - } - ) - .refine( - (resource) => { - if (isTargetsOnlyResource(resource)) { - return true; - } - - // If protocol is http, it must have a full-domain - if (resource.protocol === "http") { - return ( - resource["full-domain"] !== undefined && - resource["full-domain"].length > 0 - ); - } - return true; - }, - { - message: - "When protocol is 'http', a 'full-domain' must be provided", - path: ["full-domain"] - } - ) - .refine( - (resource) => { - if (isTargetsOnlyResource(resource)) { - return true; - } - - // If protocol is tcp or udp, it must have both proxy-port - if (resource.protocol === "tcp" || resource.protocol === "udp") { - return resource["proxy-port"] !== undefined; - } - return true; - }, - { - message: - "When protocol is 'tcp' or 'udp', 'proxy-port' must be provided", - path: ["proxy-port", "exit-node"] - } - ) - .refine( - (resource) => { - // Skip validation for targets-only resources - if (isTargetsOnlyResource(resource)) { - return true; - } - - // If protocol is tcp or udp, it must not have auth - if (resource.protocol === "tcp" || resource.protocol === "udp") { - return resource.auth === undefined; - } - return true; - }, - { - message: - "When protocol is 'tcp' or 'udp', 'auth' must not be provided", - path: ["auth"] - } - ); - -export function isTargetsOnlyResource(resource: any): boolean { - return Object.keys(resource).length === 1 && resource.targets; -} - -export const ClientResourceSchema = z.object({ - name: z.string().min(2).max(100), - site: z.string().min(2).max(100).optional(), - protocol: z.enum(["tcp", "udp"]), - "proxy-port": z.number().min(1).max(65535), - "hostname": z.string().min(1).max(255), - "internal-port": z.number().min(1).max(65535), - enabled: z.boolean().optional().default(true) -}); - -// Schema for the entire configuration object -export const ConfigSchema = z - .object({ - "proxy-resources": z.record(z.string(), ResourceSchema).optional().default({}), - "client-resources": z.record(z.string(), ClientResourceSchema).optional().default({}), - sites: z.record(z.string(), SiteSchema).optional().default({}) - }) - .refine( - // Enforce the full-domain uniqueness across resources in the same stack - (config) => { - // Extract all full-domain values with their resource keys - const fullDomainMap = new Map(); - - Object.entries(config["proxy-resources"]).forEach( - ([resourceKey, resource]) => { - const fullDomain = resource["full-domain"]; - if (fullDomain) { - // Only process if full-domain is defined - if (!fullDomainMap.has(fullDomain)) { - fullDomainMap.set(fullDomain, []); - } - fullDomainMap.get(fullDomain)!.push(resourceKey); - } - } - ); - - // Find duplicates - const duplicates = Array.from(fullDomainMap.entries()).filter( - ([_, resourceKeys]) => resourceKeys.length > 1 - ); - - return duplicates.length === 0; - }, - (config) => { - // Extract duplicates for error message - const fullDomainMap = new Map(); - - Object.entries(config["proxy-resources"]).forEach( - ([resourceKey, resource]) => { - const fullDomain = resource["full-domain"]; - if (fullDomain) { - // Only process if full-domain is defined - if (!fullDomainMap.has(fullDomain)) { - fullDomainMap.set(fullDomain, []); - } - fullDomainMap.get(fullDomain)!.push(resourceKey); - } - } - ); - - const duplicates = Array.from(fullDomainMap.entries()) - .filter(([_, resourceKeys]) => resourceKeys.length > 1) - .map( - ([fullDomain, resourceKeys]) => - `'${fullDomain}' used by resources: ${resourceKeys.join(", ")}` - ) - .join("; "); - - return { - message: `Duplicate 'full-domain' values found: ${duplicates}`, - path: ["resources"] - }; - } - ) - .refine( - // Enforce proxy-port uniqueness within proxy-resources - (config) => { - const proxyPortMap = new Map(); - - Object.entries(config["proxy-resources"]).forEach( - ([resourceKey, resource]) => { - const proxyPort = resource["proxy-port"]; - if (proxyPort !== undefined) { - if (!proxyPortMap.has(proxyPort)) { - proxyPortMap.set(proxyPort, []); - } - proxyPortMap.get(proxyPort)!.push(resourceKey); - } - } - ); - - // Find duplicates - const duplicates = Array.from(proxyPortMap.entries()).filter( - ([_, resourceKeys]) => resourceKeys.length > 1 - ); - - return duplicates.length === 0; - }, - (config) => { - // Extract duplicates for error message - const proxyPortMap = new Map(); - - Object.entries(config["proxy-resources"]).forEach( - ([resourceKey, resource]) => { - const proxyPort = resource["proxy-port"]; - if (proxyPort !== undefined) { - if (!proxyPortMap.has(proxyPort)) { - proxyPortMap.set(proxyPort, []); - } - proxyPortMap.get(proxyPort)!.push(resourceKey); - } - } - ); - - const duplicates = Array.from(proxyPortMap.entries()) - .filter(([_, resourceKeys]) => resourceKeys.length > 1) - .map( - ([proxyPort, resourceKeys]) => - `port ${proxyPort} used by proxy-resources: ${resourceKeys.join(", ")}` - ) - .join("; "); - - return { - message: `Duplicate 'proxy-port' values found in proxy-resources: ${duplicates}`, - path: ["proxy-resources"] - }; - } - ) - .refine( - // Enforce proxy-port uniqueness within client-resources - (config) => { - const proxyPortMap = new Map(); - - Object.entries(config["client-resources"]).forEach( - ([resourceKey, resource]) => { - const proxyPort = resource["proxy-port"]; - if (proxyPort !== undefined) { - if (!proxyPortMap.has(proxyPort)) { - proxyPortMap.set(proxyPort, []); - } - proxyPortMap.get(proxyPort)!.push(resourceKey); - } - } - ); - - // Find duplicates - const duplicates = Array.from(proxyPortMap.entries()).filter( - ([_, resourceKeys]) => resourceKeys.length > 1 - ); - - return duplicates.length === 0; - }, - (config) => { - // Extract duplicates for error message - const proxyPortMap = new Map(); - - Object.entries(config["client-resources"]).forEach( - ([resourceKey, resource]) => { - const proxyPort = resource["proxy-port"]; - if (proxyPort !== undefined) { - if (!proxyPortMap.has(proxyPort)) { - proxyPortMap.set(proxyPort, []); - } - proxyPortMap.get(proxyPort)!.push(resourceKey); - } - } - ); - - const duplicates = Array.from(proxyPortMap.entries()) - .filter(([_, resourceKeys]) => resourceKeys.length > 1) - .map( - ([proxyPort, resourceKeys]) => - `port ${proxyPort} used by client-resources: ${resourceKeys.join(", ")}` - ) - .join("; "); - - return { - message: `Duplicate 'proxy-port' values found in client-resources: ${duplicates}`, - path: ["client-resources"] - }; - } - ); - -// Type inference from the schema -export type Site = z.infer; -export type Target = z.infer; -export type Resource = z.infer; -export type Config = z.infer; diff --git a/server/lib/consts.ts b/server/lib/consts.ts index 506c1c8d..b9afa792 100644 --- a/server/lib/consts.ts +++ b/server/lib/consts.ts @@ -2,7 +2,7 @@ import path from "path"; import { fileURLToPath } from "url"; // This is a placeholder value replaced by the build process -export const APP_VERSION = "1.10.1"; +export const APP_VERSION = "1.9.0"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/lib/domainUtils.ts b/server/lib/domainUtils.ts deleted file mode 100644 index d043ca51..00000000 --- a/server/lib/domainUtils.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { db } from "@server/db"; -import { domains, orgDomains } from "@server/db"; -import { eq, and } from "drizzle-orm"; -import { subdomainSchema } from "@server/lib/schemas"; -import { fromError } from "zod-validation-error"; - -export type DomainValidationResult = { - success: true; - fullDomain: string; - subdomain: string | null; -} | { - success: false; - error: string; -}; - -/** - * Validates a domain and constructs the full domain based on domain type and subdomain. - * - * @param domainId - The ID of the domain to validate - * @param orgId - The organization ID to check domain access - * @param subdomain - Optional subdomain to append (for ns and wildcard domains) - * @returns DomainValidationResult with success status and either fullDomain/subdomain or error message - */ -export async function validateAndConstructDomain( - domainId: string, - orgId: string, - subdomain?: string | null -): Promise { - try { - // Query domain with organization access check - const [domainRes] = await db - .select() - .from(domains) - .where(eq(domains.domainId, domainId)) - .leftJoin( - orgDomains, - and(eq(orgDomains.orgId, orgId), eq(orgDomains.domainId, domainId)) - ); - - // Check if domain exists - if (!domainRes || !domainRes.domains) { - return { - success: false, - error: `Domain with ID ${domainId} not found` - }; - } - - // Check if organization has access to domain - if (domainRes.orgDomains && domainRes.orgDomains.orgId !== orgId) { - return { - success: false, - error: `Organization does not have access to domain with ID ${domainId}` - }; - } - - // Check if domain is verified - if (!domainRes.domains.verified) { - return { - success: false, - error: `Domain with ID ${domainId} is not verified` - }; - } - - // Construct full domain based on domain type - let fullDomain = ""; - let finalSubdomain = subdomain; - - if (domainRes.domains.type === "ns") { - if (subdomain) { - fullDomain = `${subdomain}.${domainRes.domains.baseDomain}`; - } else { - fullDomain = domainRes.domains.baseDomain; - } - } else if (domainRes.domains.type === "cname") { - fullDomain = domainRes.domains.baseDomain; - finalSubdomain = null; // CNAME domains don't use subdomains - } else if (domainRes.domains.type === "wildcard") { - if (subdomain !== undefined && subdomain !== null) { - // Validate subdomain format for wildcard domains - const parsedSubdomain = subdomainSchema.safeParse(subdomain); - if (!parsedSubdomain.success) { - return { - success: false, - error: fromError(parsedSubdomain.error).toString() - }; - } - fullDomain = `${subdomain}.${domainRes.domains.baseDomain}`; - } else { - fullDomain = domainRes.domains.baseDomain; - } - } - - // If the full domain equals the base domain, set subdomain to null - if (fullDomain === domainRes.domains.baseDomain) { - finalSubdomain = null; - } - - // Convert to lowercase - fullDomain = fullDomain.toLowerCase(); - - return { - success: true, - fullDomain, - subdomain: finalSubdomain ?? null - }; - } catch (error) { - return { - success: false, - error: `An error occurred while validating domain: ${error instanceof Error ? error.message : 'Unknown error'}` - }; - } -} diff --git a/server/lib/exitNodeComms.ts b/server/lib/exitNodeComms.ts index bcfbec3e..f79b718f 100644 --- a/server/lib/exitNodeComms.ts +++ b/server/lib/exitNodeComms.ts @@ -3,7 +3,7 @@ import logger from "@server/logger"; import { ExitNode } from "@server/db"; interface ExitNodeRequest { - remoteType?: string; + remoteType: string; localPath: string; method?: "POST" | "DELETE" | "GET" | "PUT"; data?: any; diff --git a/server/lib/exitNodes/exitNodes.ts b/server/lib/exitNodes/exitNodes.ts index 7b571682..06539bb0 100644 --- a/server/lib/exitNodes/exitNodes.ts +++ b/server/lib/exitNodes/exitNodes.ts @@ -30,8 +30,7 @@ export async function listExitNodes(orgId: string, filterOnline = false) { maxConnections: exitNodes.maxConnections, online: exitNodes.online, lastPing: exitNodes.lastPing, - type: exitNodes.type, - region: exitNodes.region + type: exitNodes.type }) .from(exitNodes); diff --git a/server/lib/traefikConfig.ts b/server/lib/traefikConfig.ts index 8b133419..e16b93d2 100644 --- a/server/lib/traefikConfig.ts +++ b/server/lib/traefikConfig.ts @@ -15,7 +15,6 @@ import { getValidCertificatesForDomains, getValidCertificatesForDomainsHybrid } from "./remoteCertificates"; -import { sendToExitNode } from "./exitNodeComms"; export class TraefikConfigManager { private intervalId: NodeJS.Timeout | null = null; @@ -404,11 +403,27 @@ export class TraefikConfigManager { [exitNode] = await db.select().from(exitNodes).limit(1); } if (exitNode) { - await sendToExitNode(exitNode, { - localPath: "/update-local-snis", - method: "POST", - data: { fullDomains: Array.from(domains) } - }); + try { + await axios.post( + `${exitNode.reachableAt}/update-local-snis`, + { fullDomains: Array.from(domains) }, + { headers: { "Content-Type": "application/json" } } + ); + } catch (error) { + // pull data out of the axios error to log + if (axios.isAxiosError(error)) { + logger.error("Error updating local SNI:", { + message: error.message, + code: error.code, + status: error.response?.status, + statusText: error.response?.statusText, + url: error.config?.url, + method: error.config?.method + }); + } else { + logger.error("Error updating local SNI:", error); + } + } } else { logger.error( "No exit node found. Has gerbil registered yet?" diff --git a/server/lib/validators.ts b/server/lib/validators.ts index 522e5018..6c581e47 100644 --- a/server/lib/validators.ts +++ b/server/lib/validators.ts @@ -129,40 +129,6 @@ export function isValidDomain(domain: string): boolean { return true; } -export function validateHeaders(headers: string): boolean { - // Validate comma-separated headers in format "Header-Name: value" - const headerPairs = headers.split(",").map((pair) => pair.trim()); - return headerPairs.every((pair) => { - // Check if the pair contains exactly one colon - const colonCount = (pair.match(/:/g) || []).length; - if (colonCount !== 1) { - return false; - } - - const colonIndex = pair.indexOf(":"); - if (colonIndex === 0 || colonIndex === pair.length - 1) { - return false; - } - - const headerName = pair.substring(0, colonIndex).trim(); - const headerValue = pair.substring(colonIndex + 1).trim(); - - // Header name should not be empty and should contain valid characters - // Header names are case-insensitive and can contain alphanumeric, hyphens - const headerNameRegex = /^[a-zA-Z0-9\-_]+$/; - if (!headerName || !headerNameRegex.test(headerName)) { - return false; - } - - // Header value should not be empty and should not contain colons - if (!headerValue || headerValue.includes(":")) { - return false; - } - - return true; - }); -} - const validTlds = [ "AAA", "AARP", diff --git a/server/middlewares/integration/verifyApiKeySetResourceUsers.ts b/server/middlewares/integration/verifyApiKeySetResourceUsers.ts index 51a8f3fc..9c96e6ec 100644 --- a/server/middlewares/integration/verifyApiKeySetResourceUsers.ts +++ b/server/middlewares/integration/verifyApiKeySetResourceUsers.ts @@ -19,11 +19,6 @@ export async function verifyApiKeySetResourceUsers( ); } - if (apiKey.isRoot) { - // Root keys can access any key in any org - return next(); - } - if (!req.apiKeyOrg) { return next( createHttpError( @@ -37,6 +32,11 @@ export async function verifyApiKeySetResourceUsers( return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid user IDs")); } + if (apiKey.isRoot) { + // Root keys can access any key in any org + return next(); + } + if (userIds.length === 0) { return next(); } diff --git a/server/routers/external.ts b/server/routers/external.ts index c48a41a7..ce44bd8a 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -343,12 +343,6 @@ authenticated.get( verifyUserHasAction(ActionsEnum.getResource), resource.getResource ); -authenticated.get( - "/org/:orgId/resource/:niceId", - verifyOrgAccess, - verifyUserHasAction(ActionsEnum.getResource), - resource.getResource -); authenticated.post( "/resource/:resourceId", verifyResourceAccess, @@ -588,14 +582,6 @@ authenticated.put( user.createOrgUser ); -authenticated.post( - "/org/:orgId/user/:userId", - verifyOrgAccess, - verifyUserAccess, - verifyUserHasAction(ActionsEnum.updateOrgUser), - user.updateOrgUser -); - authenticated.get("/org/:orgId/user/:userId", verifyOrgAccess, user.getOrgUser); authenticated.post( @@ -946,7 +932,7 @@ authRouter.post( windowMs: 15 * 60 * 1000, max: 15, keyGenerator: (req) => - `requestEmailVerificationCode:${req.user?.email || ipKeyGenerator(req.ip || "")}`, + `requestEmailVerificationCode:${req.body.email || ipKeyGenerator(req.ip || "")}`, handler: (req, res, next) => { const message = `You can only request an email verification code ${15} times every ${15} minutes. Please try again later.`; return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message)); diff --git a/server/routers/idp/listIdps.ts b/server/routers/idp/listIdps.ts index 150b9f88..2a0e5809 100644 --- a/server/routers/idp/listIdps.ts +++ b/server/routers/idp/listIdps.ts @@ -1,11 +1,11 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, idpOidcConfig } from "@server/db"; +import { db } from "@server/db"; import { domains, idp, orgDomains, users, idpOrg } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; -import { eq, sql } from "drizzle-orm"; +import { sql } from "drizzle-orm"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; @@ -33,21 +33,23 @@ async function query(limit: number, offset: number) { idpId: idp.idpId, name: idp.name, type: idp.type, - variant: idpOidcConfig.variant, - orgCount: sql`count(${idpOrg.orgId})`, - autoProvision: idp.autoProvision + orgCount: sql`count(${idpOrg.orgId})` }) .from(idp) .leftJoin(idpOrg, sql`${idp.idpId} = ${idpOrg.idpId}`) - .leftJoin(idpOidcConfig, eq(idpOidcConfig.idpId, idp.idpId)) - .groupBy(idp.idpId, idpOidcConfig.variant) + .groupBy(idp.idpId) .limit(limit) .offset(offset); return res; } export type ListIdpsResponse = { - idps: Awaited>; + idps: Array<{ + idpId: number; + name: string; + type: string; + orgCount: number; + }>; pagination: { total: number; limit: number; diff --git a/server/routers/integration.ts b/server/routers/integration.ts index 6a43aaa7..79453732 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -24,8 +24,7 @@ import { verifyApiKeyIsRoot, verifyApiKeyClientAccess, verifyClientsEnabled, - verifyApiKeySiteResourceAccess, - verifyOrgAccess + verifyApiKeySiteResourceAccess } from "@server/middlewares"; import HttpCode from "@server/types/HttpCode"; import { Router } from "express"; @@ -470,21 +469,6 @@ authenticated.get( user.listUsers ); -authenticated.put( - "/org/:orgId/user", - verifyApiKeyOrgAccess, - verifyApiKeyHasAction(ActionsEnum.createOrgUser), - user.createOrgUser -); - -authenticated.post( - "/org/:orgId/user/:userId", - verifyApiKeyOrgAccess, - verifyApiKeyUserAccess, - verifyApiKeyHasAction(ActionsEnum.updateOrgUser), - user.updateOrgUser -); - authenticated.delete( "/org/:orgId/user/:userId", verifyApiKeyOrgAccess, @@ -644,10 +628,3 @@ authenticated.post( verifyApiKeyHasAction(ActionsEnum.updateClient), client.updateClient ); - -authenticated.put( - "/org/:orgId/blueprint", - verifyApiKeyOrgAccess, - verifyApiKeyHasAction(ActionsEnum.applyBlueprint), - org.applyBlueprint -); \ No newline at end of file diff --git a/server/routers/newt/handleApplyBlueprintMessage.ts b/server/routers/newt/handleApplyBlueprintMessage.ts deleted file mode 100644 index 68158799..00000000 --- a/server/routers/newt/handleApplyBlueprintMessage.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { db, newts } from "@server/db"; -import { MessageHandler } from "../ws"; -import { exitNodes, Newt, resources, sites, Target, targets } from "@server/db"; -import { eq, and, sql, inArray } from "drizzle-orm"; -import logger from "@server/logger"; -import { applyBlueprint } from "@server/lib/blueprints/applyBlueprint"; - -export const handleApplyBlueprintMessage: MessageHandler = async (context) => { - const { message, client, sendToClient } = context; - const newt = client as Newt; - - logger.debug("Handling apply blueprint message!"); - - if (!newt) { - logger.warn("Newt not found"); - return; - } - - if (!newt.siteId) { - logger.warn("Newt has no site!"); // TODO: Maybe we create the site here? - return; - } - - // get the site - const [site] = await db - .select() - .from(sites) - .where(eq(sites.siteId, newt.siteId)); - - if (!site) { - logger.warn("Site not found for newt"); - return; - } - - const { blueprint } = message.data; - if (!blueprint) { - logger.warn("No blueprint provided"); - return; - } - - logger.debug(`Received blueprint: ${blueprint}`); - - try { - const blueprintParsed = JSON.parse(blueprint); - // Update the blueprint in the database - await applyBlueprint(site.orgId, blueprintParsed, site.siteId); - } catch (error) { - logger.error(`Failed to update database from config: ${error}`); - return { - message: { - type: "newt/blueprint/results", - data: { - success: false, - message: `Failed to update database from config: ${error}` - } - }, - broadcast: false, // Send to all clients - excludeSender: false // Include sender in broadcast - }; - } - - return { - message: { - type: "newt/blueprint/results", - data: { - success: true, - message: "Config updated successfully" - } - }, - broadcast: false, // Send to all clients - excludeSender: false // Include sender in broadcast - }; -}; diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index eef78765..3c7ecaff 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -10,7 +10,6 @@ import { getNextAvailableClientSubnet } from "@server/lib/ip"; import { selectBestExitNode, verifyExitNodeOrgAccess } from "@server/lib/exitNodes"; -import { fetchContainers } from "./dockerSocket"; export type ExitNodePingResult = { exitNodeId: number; @@ -77,15 +76,6 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { return; } - logger.debug(`Docker socket enabled: ${oldSite.dockerSocketEnabled}`); - - if (oldSite.dockerSocketEnabled) { - logger.debug( - "Site has docker socket enabled - requesting docker containers" - ); - fetchContainers(newt.newtId); - } - let siteSubnet = oldSite.subnet; let exitNodeIdToQuery = oldSite.exitNodeId; if (exitNodeId && (oldSite.exitNodeId !== exitNodeId || !oldSite.subnet)) { diff --git a/server/routers/newt/handleSocketMessages.ts b/server/routers/newt/handleSocketMessages.ts index aceca37d..01b7be60 100644 --- a/server/routers/newt/handleSocketMessages.ts +++ b/server/routers/newt/handleSocketMessages.ts @@ -2,7 +2,6 @@ import { MessageHandler } from "../ws"; import logger from "@server/logger"; import { dockerSocketCache } from "./dockerSocket"; import { Newt } from "@server/db"; -import { applyNewtDockerBlueprint } from "@server/lib/blueprints/applyNewtDockerBlueprint"; export const handleDockerStatusMessage: MessageHandler = async (context) => { const { message, client, sendToClient } = context; @@ -58,15 +57,4 @@ export const handleDockerContainersMessage: MessageHandler = async ( } else { logger.warn(`Newt ${newt.newtId} does not have Docker containers`); } - - if (!newt.siteId) { - logger.warn("Newt has no site!"); - return; - } - - await applyNewtDockerBlueprint( - newt.siteId, - newt.newtId, - containers - ); }; diff --git a/server/routers/newt/index.ts b/server/routers/newt/index.ts index 9642a637..08f047e3 100644 --- a/server/routers/newt/index.ts +++ b/server/routers/newt/index.ts @@ -4,5 +4,4 @@ export * from "./handleNewtRegisterMessage"; export * from "./handleReceiveBandwidthMessage"; export * from "./handleGetConfigMessage"; export * from "./handleSocketMessages"; -export * from "./handleNewtPingRequestMessage"; -export * from "./handleApplyBlueprintMessage"; \ No newline at end of file +export * from "./handleNewtPingRequestMessage"; \ No newline at end of file diff --git a/server/routers/org/applyBlueprint.ts b/server/routers/org/applyBlueprint.ts deleted file mode 100644 index 982258ee..00000000 --- a/server/routers/org/applyBlueprint.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { z } from "zod"; -import { db } from "@server/db"; -import { eq } from "drizzle-orm"; -import { - apiKeyOrg, - apiKeys, - domains, - Org, - orgDomains, - orgs, - roleActions, - roles, - userOrgs, - users, - actions -} from "@server/db"; -import response from "@server/lib/response"; -import HttpCode from "@server/types/HttpCode"; -import createHttpError from "http-errors"; -import logger from "@server/logger"; -import config from "@server/lib/config"; -import { fromError } from "zod-validation-error"; -import { defaultRoleAllowedActions } from "../role"; -import { OpenAPITags, registry } from "@server/openApi"; -import { isValidCIDR } from "@server/lib/validators"; -import { applyBlueprint as applyBlueprintFunc } from "@server/lib/blueprints/applyBlueprint"; - -const applyBlueprintSchema = z - .object({ - blueprint: z.string() - }) - .strict(); - -const applyBlueprintParamsSchema = z - .object({ - orgId: z.string() - }) - .strict(); - -registry.registerPath({ - method: "put", - path: "/org/{orgId}/blueprint", - description: "Apply a base64 encoded blueprint to an organization", - tags: [OpenAPITags.Org], - request: { - params: applyBlueprintParamsSchema, - body: { - content: { - "application/json": { - schema: applyBlueprintSchema - } - } - } - }, - responses: {} -}); - -export async function applyBlueprint( - req: Request, - res: Response, - next: NextFunction -): Promise { - try { - const parsedParams = applyBlueprintParamsSchema.safeParse(req.params); - if (!parsedParams.success) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - fromError(parsedParams.error).toString() - ) - ); - } - - const { orgId } = parsedParams.data; - - const parsedBody = applyBlueprintSchema.safeParse(req.body); - if (!parsedBody.success) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - fromError(parsedBody.error).toString() - ) - ); - } - - const { blueprint } = parsedBody.data; - - if (!blueprint) { - logger.warn("No blueprint provided"); - return; - } - - logger.debug(`Received blueprint: ${blueprint}`); - - try { - // first base64 decode the blueprint - const decoded = Buffer.from(blueprint, "base64").toString("utf-8"); - // then parse the json - const blueprintParsed = JSON.parse(decoded); - - // Update the blueprint in the database - await applyBlueprintFunc(orgId, blueprintParsed); - } catch (error) { - logger.error(`Failed to update database from config: ${error}`); - return next( - createHttpError( - HttpCode.BAD_REQUEST, - `Failed to update database from config: ${error}` - ) - ); - } - - return response(res, { - data: null, - success: true, - error: false, - message: "Blueprint applied successfully", - status: HttpCode.CREATED - }); - } catch (error) { - logger.error(error); - return next( - createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") - ); - } -} diff --git a/server/routers/org/index.ts b/server/routers/org/index.ts index 754def66..c9a44d8d 100644 --- a/server/routers/org/index.ts +++ b/server/routers/org/index.ts @@ -7,4 +7,3 @@ export * from "./checkId"; export * from "./getOrgOverview"; export * from "./listOrgs"; export * from "./pickOrgDefaults"; -export * from "./applyBlueprint"; \ No newline at end of file diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index 806a5a58..5b27cb41 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -21,8 +21,6 @@ import { subdomainSchema } from "@server/lib/schemas"; import config from "@server/lib/config"; import { OpenAPITags, registry } from "@server/openApi"; import { build } from "@server/build"; -import { getUniqueResourceName } from "@server/db/names"; -import { validateAndConstructDomain } from "@server/lib/domainUtils"; const createResourceParamsSchema = z .object({ @@ -195,21 +193,76 @@ async function createHttpResource( } const { name, domainId } = parsedBody.data; - const subdomain = parsedBody.data.subdomain; + let subdomain = parsedBody.data.subdomain; - // Validate domain and construct full domain - const domainResult = await validateAndConstructDomain(domainId, orgId, subdomain); + const [domainRes] = await db + .select() + .from(domains) + .where(eq(domains.domainId, domainId)) + .leftJoin( + orgDomains, + and(eq(orgDomains.orgId, orgId), eq(orgDomains.domainId, domainId)) + ); - if (!domainResult.success) { + if (!domainRes || !domainRes.domains) { return next( createHttpError( - HttpCode.BAD_REQUEST, - domainResult.error + HttpCode.NOT_FOUND, + `Domain with ID ${domainId} not found` ) ); } - const { fullDomain, subdomain: finalSubdomain } = domainResult; + if (domainRes.orgDomains && domainRes.orgDomains.orgId !== orgId) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + `Organization does not have access to domain with ID ${domainId}` + ) + ); + } + + if (!domainRes.domains.verified) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Domain with ID ${domainRes.domains.domainId} is not verified` + ) + ); + } + + let fullDomain = ""; + if (domainRes.domains.type == "ns") { + if (subdomain) { + fullDomain = `${subdomain}.${domainRes.domains.baseDomain}`; + } else { + fullDomain = domainRes.domains.baseDomain; + } + } else if (domainRes.domains.type == "cname") { + fullDomain = domainRes.domains.baseDomain; + } else if (domainRes.domains.type == "wildcard") { + if (subdomain) { + // the subdomain cant have a dot in it + const parsedSubdomain = subdomainSchema.safeParse(subdomain); + if (!parsedSubdomain.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedSubdomain.error).toString() + ) + ); + } + fullDomain = `${subdomain}.${domainRes.domains.baseDomain}`; + } else { + fullDomain = domainRes.domains.baseDomain; + } + } + + if (fullDomain === domainRes.domains.baseDomain) { + subdomain = null; + } + + fullDomain = fullDomain.toLowerCase(); logger.debug(`Full domain: ${fullDomain}`); @@ -230,18 +283,15 @@ async function createHttpResource( let resource: Resource | undefined; - const niceId = await getUniqueResourceName(orgId); - await db.transaction(async (trx) => { const newResource = await trx .insert(resources) .values({ - niceId, fullDomain, domainId, orgId, name, - subdomain: finalSubdomain, + subdomain, http: true, protocol: "tcp", ssl: true @@ -341,13 +391,10 @@ async function createRawResource( let resource: Resource | undefined; - const niceId = await getUniqueResourceName(orgId); - await db.transaction(async (trx) => { const newResource = await trx .insert(resources) .values({ - niceId, orgId, name, http, diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index d2aebedd..a2c1c0d1 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -2,72 +2,32 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; import { Resource, resources, sites } from "@server/db"; -import { eq, and } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; import { fromError } from "zod-validation-error"; import logger from "@server/logger"; -import stoi from "@server/lib/stoi"; import { OpenAPITags, registry } from "@server/openApi"; const getResourceSchema = z .object({ resourceId: z .string() - .optional() - .transform(stoi) - .pipe(z.number().int().positive().optional()) - .optional(), - niceId: z.string().optional(), - orgId: z.string().optional() + .transform(Number) + .pipe(z.number().int().positive()) }) .strict(); -async function query(resourceId?: number, niceId?: string, orgId?: string) { - if (resourceId) { - const [res] = await db - .select() - .from(resources) - .where(eq(resources.resourceId, resourceId)) - .limit(1); - return res; - } else if (niceId && orgId) { - const [res] = await db - .select() - .from(resources) - .where(and(eq(resources.niceId, niceId), eq(resources.orgId, orgId))) - .limit(1); - return res; - } -} - -export type GetResourceResponse = NonNullable>>; - -registry.registerPath({ - method: "get", - path: "/org/{orgId}/resource/{niceId}", - description: - "Get a resource by orgId and niceId. NiceId is a readable ID for the resource and unique on a per org basis.", - tags: [OpenAPITags.Org, OpenAPITags.Resource], - request: { - params: z.object({ - orgId: z.string(), - niceId: z.string() - }) - }, - responses: {} -}); +export type GetResourceResponse = Resource; registry.registerPath({ method: "get", path: "/resource/{resourceId}", - description: "Get a resource by resourceId.", + description: "Get a resource.", tags: [OpenAPITags.Resource], request: { - params: z.object({ - resourceId: z.number() - }) + params: getResourceSchema }, responses: {} }); @@ -88,18 +48,29 @@ export async function getResource( ); } - const { resourceId, niceId, orgId } = parsedParams.data; + const { resourceId } = parsedParams.data; - const resource = await query(resourceId, niceId, orgId); + const [resp] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + const resource = resp; if (!resource) { return next( - createHttpError(HttpCode.NOT_FOUND, "Resource not found") + createHttpError( + HttpCode.NOT_FOUND, + `Resource with ID ${resourceId} not found` + ) ); } - return response(res, { - data: resource, + return response(res, { + data: { + ...resource + }, success: true, error: false, message: "Resource retrieved successfully", diff --git a/server/routers/resource/getResourceAuthInfo.ts b/server/routers/resource/getResourceAuthInfo.ts index c775564b..191221f1 100644 --- a/server/routers/resource/getResourceAuthInfo.ts +++ b/server/routers/resource/getResourceAuthInfo.ts @@ -32,7 +32,6 @@ export type GetResourceAuthInfoResponse = { url: string; whitelist: boolean; skipToIdpId: number | null; - orgId: string; }; export async function getResourceAuthInfo( @@ -89,8 +88,7 @@ export async function getResourceAuthInfo( blockAccess: resource.blockAccess, url, whitelist: resource.emailWhitelistEnabled, - skipToIdpId: resource.skipToIdpId, - orgId: resource.orgId + skipToIdpId: resource.skipToIdpId }, success: true, error: false, diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 45d225ed..54878bfc 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -16,7 +16,6 @@ import logger from "@server/logger"; import stoi from "@server/lib/stoi"; import { fromZodError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; -import { warn } from "console"; const listResourcesParamsSchema = z .object({ @@ -55,8 +54,7 @@ function queryResources(accessibleResourceIds: number[], orgId: string) { protocol: resources.protocol, proxyPort: resources.proxyPort, enabled: resources.enabled, - domainId: resources.domainId, - niceId: resources.niceId + domainId: resources.domainId }) .from(resources) .leftJoin( diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 7c0f9c63..30acc0c1 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -20,8 +20,6 @@ import { tlsNameSchema } from "@server/lib/schemas"; import { subdomainSchema } from "@server/lib/schemas"; import { registry } from "@server/openApi"; import { OpenAPITags } from "@server/openApi"; -import { validateAndConstructDomain } from "@server/lib/domainUtils"; -import { validateHeaders } from "@server/lib/validators"; const updateResourceParamsSchema = z .object({ @@ -46,8 +44,7 @@ const updateHttpResourceBodySchema = z stickySession: z.boolean().optional(), tlsServerName: z.string().nullable().optional(), setHostHeader: z.string().nullable().optional(), - skipToIdpId: z.number().int().positive().nullable().optional(), - headers: z.string().nullable().optional() + skipToIdpId: z.number().int().positive().nullable().optional() }) .strict() .refine((data) => Object.keys(data).length > 0, { @@ -85,18 +82,6 @@ const updateHttpResourceBodySchema = z message: "Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header." } - ) - .refine( - (data) => { - if (data.headers) { - return validateHeaders(data.headers); - } - return true; - }, - { - message: - "Invalid headers format. Use comma-separated format: 'Header-Name: value, Another-Header: another-value'. Header values cannot contain colons." - } ); export type UpdateResourceResponse = Resource; @@ -245,19 +230,78 @@ async function updateHttpResource( if (updateData.domainId) { const domainId = updateData.domainId; - // Validate domain and construct full domain - const domainResult = await validateAndConstructDomain(domainId, resource.orgId, updateData.subdomain); - - if (!domainResult.success) { + const [domainRes] = await db + .select() + .from(domains) + .where(eq(domains.domainId, domainId)) + .leftJoin( + orgDomains, + and( + eq(orgDomains.orgId, resource.orgId), + eq(orgDomains.domainId, domainId) + ) + ); + + if (!domainRes || !domainRes.domains) { return next( createHttpError( - HttpCode.BAD_REQUEST, - domainResult.error + HttpCode.NOT_FOUND, + `Domain with ID ${updateData.domainId} not found` ) ); } - const { fullDomain, subdomain: finalSubdomain } = domainResult; + if ( + domainRes.orgDomains && + domainRes.orgDomains.orgId !== resource.orgId + ) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + `You do not have permission to use domain with ID ${updateData.domainId}` + ) + ); + } + + if (!domainRes.domains.verified) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Domain with ID ${updateData.domainId} is not verified` + ) + ); + } + + let fullDomain = ""; + if (domainRes.domains.type == "ns") { + if (updateData.subdomain) { + fullDomain = `${updateData.subdomain}.${domainRes.domains.baseDomain}`; + } else { + fullDomain = domainRes.domains.baseDomain; + } + } else if (domainRes.domains.type == "cname") { + fullDomain = domainRes.domains.baseDomain; + } else if (domainRes.domains.type == "wildcard") { + if (updateData.subdomain !== undefined) { + // the subdomain cant have a dot in it + const parsedSubdomain = subdomainSchema.safeParse( + updateData.subdomain + ); + if (!parsedSubdomain.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedSubdomain.error).toString() + ) + ); + } + fullDomain = `${updateData.subdomain}.${domainRes.domains.baseDomain}`; + } else { + fullDomain = domainRes.domains.baseDomain; + } + } + + fullDomain = fullDomain.toLowerCase(); logger.debug(`Full domain: ${fullDomain}`); @@ -288,8 +332,9 @@ async function updateHttpResource( .where(eq(resources.resourceId, resource.resourceId)); } - // Update the subdomain in the update data - updateData.subdomain = finalSubdomain; + if (fullDomain === domainRes.domains.baseDomain) { + updateData.subdomain = null; + } } const updatedResource = await db diff --git a/server/routers/site/pickSiteDefaults.ts b/server/routers/site/pickSiteDefaults.ts index 58d44744..2e705c56 100644 --- a/server/routers/site/pickSiteDefaults.ts +++ b/server/routers/site/pickSiteDefaults.ts @@ -139,7 +139,7 @@ export async function pickSiteDefaults( }, success: true, error: false, - message: "Site defaults chosen successfully", + message: "Organization retrieved successfully", status: HttpCode.OK }); } catch (error) { diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index ca223b04..da41c19c 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -10,7 +10,6 @@ import { fromError } from "zod-validation-error"; import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; import { addTargets } from "../client/targets"; -import { getUniqueSiteResourceName } from "@server/db/names"; const createSiteResourceParamsSchema = z .object({ @@ -122,14 +121,11 @@ export async function createSiteResource( ); } - const niceId = await getUniqueSiteResourceName(orgId); - // Create the site resource const [newSiteResource] = await db .insert(siteResources) .values({ siteId, - niceId, orgId, name, protocol, diff --git a/server/routers/siteResource/getSiteResource.ts b/server/routers/siteResource/getSiteResource.ts index 09c01eb0..914706cd 100644 --- a/server/routers/siteResource/getSiteResource.ts +++ b/server/routers/siteResource/getSiteResource.ts @@ -12,72 +12,21 @@ import { OpenAPITags, registry } from "@server/openApi"; const getSiteResourceParamsSchema = z .object({ - siteResourceId: z - .string() - .optional() - .transform((val) => val ? Number(val) : undefined) - .pipe(z.number().int().positive().optional()) - .optional(), + siteResourceId: z.string().transform(Number).pipe(z.number().int().positive()), siteId: z.string().transform(Number).pipe(z.number().int().positive()), - niceId: z.string().optional(), orgId: z.string() }) .strict(); -async function query(siteResourceId?: number, siteId?: number, niceId?: string, orgId?: string) { - if (siteResourceId && siteId && orgId) { - const [siteResource] = await db - .select() - .from(siteResources) - .where(and( - eq(siteResources.siteResourceId, siteResourceId), - eq(siteResources.siteId, siteId), - eq(siteResources.orgId, orgId) - )) - .limit(1); - return siteResource; - } else if (niceId && siteId && orgId) { - const [siteResource] = await db - .select() - .from(siteResources) - .where(and( - eq(siteResources.niceId, niceId), - eq(siteResources.siteId, siteId), - eq(siteResources.orgId, orgId) - )) - .limit(1); - return siteResource; - } -} - -export type GetSiteResourceResponse = NonNullable>>; +export type GetSiteResourceResponse = SiteResource; registry.registerPath({ method: "get", path: "/org/{orgId}/site/{siteId}/resource/{siteResourceId}", - description: "Get a specific site resource by siteResourceId.", + description: "Get a specific site resource.", tags: [OpenAPITags.Client, OpenAPITags.Org], request: { - params: z.object({ - siteResourceId: z.number(), - siteId: z.number(), - orgId: z.string() - }) - }, - responses: {} -}); - -registry.registerPath({ - method: "get", - path: "/org/{orgId}/site/{siteId}/resource/nice/{niceId}", - description: "Get a specific site resource by niceId.", - tags: [OpenAPITags.Client, OpenAPITags.Org], - request: { - params: z.object({ - niceId: z.string(), - siteId: z.number(), - orgId: z.string() - }) + params: getSiteResourceParamsSchema }, responses: {} }); @@ -98,10 +47,18 @@ export async function getSiteResource( ); } - const { siteResourceId, siteId, niceId, orgId } = parsedParams.data; + const { siteResourceId, siteId, orgId } = parsedParams.data; // Get the site resource - const siteResource = await query(siteResourceId, siteId, niceId, orgId); + const [siteResource] = await db + .select() + .from(siteResources) + .where(and( + eq(siteResources.siteResourceId, siteResourceId), + eq(siteResources.siteId, siteId), + eq(siteResources.orgId, orgId) + )) + .limit(1); if (!siteResource) { return next( diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index f6f71124..82e2fe68 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -28,7 +28,7 @@ const updateSiteResourceSchema = z protocol: z.enum(["tcp", "udp"]).optional(), proxyPort: z.number().int().positive().optional(), destinationPort: z.number().int().positive().optional(), - destinationIp: z.string().optional(), + destinationIp: z.string().ip().optional(), enabled: z.boolean().optional() }) .strict(); diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index fb85f566..7a3acd55 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -30,9 +30,7 @@ const createTargetSchema = z ip: z.string().refine(isTargetValid), method: z.string().optional().nullable(), port: z.number().int().min(1).max(65535), - enabled: z.boolean().default(true), - path: z.string().optional().nullable(), - pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable() + enabled: z.boolean().default(true) }) .strict(); @@ -163,7 +161,7 @@ export async function createTarget( ); } - const { internalPort, targetIps } = await pickPort(site.siteId!, db); + const { internalPort, targetIps } = await pickPort(site.siteId!); if (!internalPort) { return next( diff --git a/server/routers/target/helpers.ts b/server/routers/target/helpers.ts index 13b2ee46..4935d28a 100644 --- a/server/routers/target/helpers.ts +++ b/server/routers/target/helpers.ts @@ -1,10 +1,10 @@ -import { db, Transaction } from "@server/db"; +import { db } from "@server/db"; import { resources, targets } from "@server/db"; import { eq } from "drizzle-orm"; const currentBannedPorts: number[] = []; -export async function pickPort(siteId: number, trx: Transaction | typeof db): Promise<{ +export async function pickPort(siteId: number): Promise<{ internalPort: number; targetIps: string[]; }> { @@ -12,7 +12,7 @@ export async function pickPort(siteId: number, trx: Transaction | typeof db): Pr const targetIps: string[] = []; const targetInternalPorts: number[] = []; - const targetsRes = await trx + const targetsRes = await db .select() .from(targets) .where(eq(targets.siteId, siteId)); diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts index ca1159d2..eab8f1c8 100644 --- a/server/routers/target/listTargets.ts +++ b/server/routers/target/listTargets.ts @@ -44,9 +44,7 @@ function queryTargets(resourceId: number) { enabled: targets.enabled, resourceId: targets.resourceId, siteId: targets.siteId, - siteType: sites.type, - path: targets.path, - pathMatchType: targets.pathMatchType + siteType: sites.type }) .from(targets) .leftJoin(sites, eq(sites.siteId, targets.siteId)) diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index 928a1a55..67d9a8df 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -26,9 +26,7 @@ const updateTargetBodySchema = z ip: z.string().refine(isTargetValid), method: z.string().min(1).max(10).optional().nullable(), port: z.number().int().min(1).max(65535).optional(), - enabled: z.boolean().optional(), - path: z.string().optional().nullable(), - pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable() + enabled: z.boolean().optional() }) .strict() .refine((data) => Object.keys(data).length > 0, { @@ -155,7 +153,7 @@ export async function updateTarget( ); } - const { internalPort, targetIps } = await pickPort(site.siteId!, db); + const { internalPort, targetIps } = await pickPort(site.siteId!); if (!internalPort) { return next( diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index a1a2a7a3..1a55f2bd 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -54,8 +54,7 @@ export async function traefikConfigProvider( config.getRawConfig().traefik.site_types ); - if (traefikConfig?.http?.middlewares) { - // BECAUSE SOMETIMES THE CONFIG CAN BE EMPTY IF THERE IS NOTHING + if (traefikConfig?.http?.middlewares) { // BECAUSE SOMETIMES THE CONFIG CAN BE EMPTY IF THERE IS NOTHING traefikConfig.http.middlewares[badgerMiddlewareName] = { plugin: { [badgerMiddlewareName]: { @@ -105,112 +104,106 @@ export async function getTraefikConfig( }; }; - // Get resources with their targets and sites in a single optimized query - // Start from sites on this exit node, then join to targets and resources - const resourcesWithTargetsAndSites = await db - .select({ - // Resource fields - resourceId: resources.resourceId, - fullDomain: resources.fullDomain, - ssl: resources.ssl, - http: resources.http, - proxyPort: resources.proxyPort, - protocol: resources.protocol, - subdomain: resources.subdomain, - domainId: resources.domainId, - enabled: resources.enabled, - stickySession: resources.stickySession, - tlsServerName: resources.tlsServerName, - setHostHeader: resources.setHostHeader, - enableProxy: resources.enableProxy, - headers: resources.headers, - // Target fields - targetId: targets.targetId, - targetEnabled: targets.enabled, - ip: targets.ip, - method: targets.method, - port: targets.port, - internalPort: targets.internalPort, - path: targets.path, - pathMatchType: targets.pathMatchType, + // Get all resources with related data + const allResources = await db.transaction(async (tx) => { + // Get resources with their targets and sites in a single optimized query + // Start from sites on this exit node, then join to targets and resources + const resourcesWithTargetsAndSites = await tx + .select({ + // Resource fields + resourceId: resources.resourceId, + fullDomain: resources.fullDomain, + ssl: resources.ssl, + http: resources.http, + proxyPort: resources.proxyPort, + protocol: resources.protocol, + subdomain: resources.subdomain, + domainId: resources.domainId, + enabled: resources.enabled, + stickySession: resources.stickySession, + tlsServerName: resources.tlsServerName, + setHostHeader: resources.setHostHeader, + enableProxy: resources.enableProxy, + // Target fields + targetId: targets.targetId, + targetEnabled: targets.enabled, + ip: targets.ip, + method: targets.method, + port: targets.port, + internalPort: targets.internalPort, + // Site fields + siteId: sites.siteId, + siteType: sites.type, + siteOnline: sites.online, + subnet: sites.subnet, + exitNodeId: sites.exitNodeId + }) + .from(sites) + .innerJoin(targets, eq(targets.siteId, sites.siteId)) + .innerJoin(resources, eq(resources.resourceId, targets.resourceId)) + .where( + and( + eq(targets.enabled, true), + eq(resources.enabled, true), + or( + eq(sites.exitNodeId, exitNodeId), + isNull(sites.exitNodeId) + ), + inArray(sites.type, siteTypes), + config.getRawConfig().traefik.allow_raw_resources + ? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true + : eq(resources.http, true), + ) + ); - // Site fields - siteId: sites.siteId, - siteType: sites.type, - siteOnline: sites.online, - subnet: sites.subnet, - exitNodeId: sites.exitNodeId - }) - .from(sites) - .innerJoin(targets, eq(targets.siteId, sites.siteId)) - .innerJoin(resources, eq(resources.resourceId, targets.resourceId)) - .where( - and( - eq(targets.enabled, true), - eq(resources.enabled, true), - or(eq(sites.exitNodeId, exitNodeId), isNull(sites.exitNodeId)), - inArray(sites.type, siteTypes), - config.getRawConfig().traefik.allow_raw_resources - ? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true - : eq(resources.http, true) - ) - ); + // Group by resource and include targets with their unique site data + const resourcesMap = new Map(); - // Group by resource and include targets with their unique site data - const resourcesMap = new Map(); + resourcesWithTargetsAndSites.forEach((row) => { + const resourceId = row.resourceId; - resourcesWithTargetsAndSites.forEach((row) => { - const resourceId = row.resourceId; - const targetPath = sanitizePath(row.path) || ""; // Handle null/undefined paths - const pathMatchType = row.pathMatchType || ""; - - // Create a unique key combining resourceId and path+pathMatchType - const pathKey = [targetPath, pathMatchType].filter(Boolean).join("-"); - const mapKey = [resourceId, pathKey].filter(Boolean).join("-"); - - if (!resourcesMap.has(mapKey)) { - resourcesMap.set(mapKey, { - resourceId: row.resourceId, - fullDomain: row.fullDomain, - ssl: row.ssl, - http: row.http, - proxyPort: row.proxyPort, - protocol: row.protocol, - subdomain: row.subdomain, - domainId: row.domainId, - enabled: row.enabled, - stickySession: row.stickySession, - tlsServerName: row.tlsServerName, - setHostHeader: row.setHostHeader, - enableProxy: row.enableProxy, - targets: [], - headers: row.headers, - path: row.path, // the targets will all have the same path - pathMatchType: row.pathMatchType // the targets will all have the same pathMatchType - }); - } - - // Add target with its associated site data - resourcesMap.get(mapKey).targets.push({ - resourceId: row.resourceId, - targetId: row.targetId, - ip: row.ip, - method: row.method, - port: row.port, - internalPort: row.internalPort, - enabled: row.targetEnabled, - site: { - siteId: row.siteId, - type: row.siteType, - subnet: row.subnet, - exitNodeId: row.exitNodeId, - online: row.siteOnline + if (!resourcesMap.has(resourceId)) { + resourcesMap.set(resourceId, { + resourceId: row.resourceId, + fullDomain: row.fullDomain, + ssl: row.ssl, + http: row.http, + proxyPort: row.proxyPort, + protocol: row.protocol, + subdomain: row.subdomain, + domainId: row.domainId, + enabled: row.enabled, + stickySession: row.stickySession, + tlsServerName: row.tlsServerName, + setHostHeader: row.setHostHeader, + enableProxy: row.enableProxy, + targets: [] + }); } + + // Add target with its associated site data + resourcesMap.get(resourceId).targets.push({ + resourceId: row.resourceId, + targetId: row.targetId, + ip: row.ip, + method: row.method, + port: row.port, + internalPort: row.internalPort, + enabled: row.targetEnabled, + site: { + siteId: row.siteId, + type: row.siteType, + subnet: row.subnet, + exitNodeId: row.exitNodeId, + online: row.siteOnline + } + }); }); + + return Array.from(resourcesMap.values()); }); - // make sure we have at least one resource - if (resourcesMap.size === 0) { + if (!allResources.length) { return {}; } @@ -226,15 +219,14 @@ export async function getTraefikConfig( } }; - // get the key and the resource - for (const [key, resource] of resourcesMap.entries()) { + for (const resource of allResources) { const targets = resource.targets; - const routerName = `${key}-router`; - const serviceName = `${key}-service`; + const routerName = `${resource.resourceId}-router`; + const serviceName = `${resource.resourceId}-service`; const fullDomain = `${resource.fullDomain}`; - const transportName = `${key}-transport`; - const headersMiddlewareName = `${key}-headers-middleware`; + const transportName = `${resource.resourceId}-transport`; + const hostHeaderMiddlewareName = `${resource.resourceId}-host-header-middleware`; if (!resource.enabled) { continue; @@ -246,6 +238,9 @@ export async function getTraefikConfig( } if (!resource.fullDomain) { + logger.error( + `Resource ${resource.resourceId} has no fullDomain` + ); continue; } @@ -301,68 +296,16 @@ export async function getTraefikConfig( const additionalMiddlewares = config.getRawConfig().traefik.additional_middlewares || []; - const routerMiddlewares = [ - badgerMiddlewareName, - ...additionalMiddlewares - ]; - - if (resource.headers && resource.headers.length > 0) { - // if there are headers, parse them into an object - const headersObj: { [key: string]: string } = {}; - const headersArr = resource.headers.split(","); - for (const header of headersArr) { - const [key, value] = header - .split(":") - .map((s: string) => s.trim()); - if (key && value) { - headersObj[key] = value; - } - } - - if (resource.setHostHeader) { - headersObj["Host"] = resource.setHostHeader; - } - - // check if the object is not empty - if (Object.keys(headersObj).length > 0) { - // Add the headers middleware - if (!config_output.http.middlewares) { - config_output.http.middlewares = {}; - } - config_output.http.middlewares[headersMiddlewareName] = { - headers: { - customRequestHeaders: headersObj - } - }; - - routerMiddlewares.push(headersMiddlewareName); - } - } - - let rule = `Host(\`${fullDomain}\`)`; - let priority = 100; - if (resource.path && resource.pathMatchType) { - priority += 1; - // add path to rule based on match type - if (resource.pathMatchType === "exact") { - rule += ` && Path(\`${resource.path}\`)`; - } else if (resource.pathMatchType === "prefix") { - rule += ` && PathPrefix(\`${resource.path}\`)`; - } else if (resource.pathMatchType === "regex") { - rule += ` && PathRegexp(\`${resource.path}\`)`; - } - } - config_output.http.routers![routerName] = { entryPoints: [ resource.ssl ? config.getRawConfig().traefik.https_entrypoint : config.getRawConfig().traefik.http_entrypoint ], - middlewares: routerMiddlewares, + middlewares: [badgerMiddlewareName, ...additionalMiddlewares], service: serviceName, - rule: rule, - priority: priority, + rule: `Host(\`${fullDomain}\`)`, + priority: 100, ...(resource.ssl ? { tls } : {}) }; @@ -373,8 +316,8 @@ export async function getTraefikConfig( ], middlewares: [redirectHttpsMiddlewareName], service: serviceName, - rule: rule, - priority: priority + rule: `Host(\`${fullDomain}\`)`, + priority: 100 }; } @@ -391,64 +334,55 @@ export async function getTraefikConfig( targets as TargetWithSite[] ).some((target: TargetWithSite) => target.site.online); - return ( - (targets as TargetWithSite[]) - .filter((target: TargetWithSite) => { - if (!target.enabled) { + return (targets as TargetWithSite[]) + .filter((target: TargetWithSite) => { + if (!target.enabled) { + return false; + } + + // If any sites are online, exclude offline sites + if (anySitesOnline && !target.site.online) { + return false; + } + + if ( + target.site.type === "local" || + target.site.type === "wireguard" + ) { + if ( + !target.ip || + !target.port || + !target.method + ) { return false; } - - // If any sites are online, exclude offline sites - if (anySitesOnline && !target.site.online) { + } else if (target.site.type === "newt") { + if ( + !target.internalPort || + !target.method || + !target.site.subnet + ) { return false; } - - if ( - target.site.type === "local" || - target.site.type === "wireguard" - ) { - if ( - !target.ip || - !target.port || - !target.method - ) { - return false; - } - } else if (target.site.type === "newt") { - if ( - !target.internalPort || - !target.method || - !target.site.subnet - ) { - return false; - } - } - return true; - }) - .map((target: TargetWithSite) => { - if ( - target.site.type === "local" || - target.site.type === "wireguard" - ) { - return { - url: `${target.method}://${target.ip}:${target.port}` - }; - } else if (target.site.type === "newt") { - const ip = - target.site.subnet!.split("/")[0]; - return { - url: `${target.method}://${ip}:${target.internalPort}` - }; - } - }) - // filter out duplicates - .filter( - (v, i, a) => - a.findIndex( - (t) => t && v && t.url === v.url - ) === i - ) - ); + } + return true; + }) + .map((target: TargetWithSite) => { + if ( + target.site.type === "local" || + target.site.type === "wireguard" + ) { + return { + url: `${target.method}://${target.ip}:${target.port}` + }; + } else if (target.site.type === "newt") { + const ip = + target.site.subnet!.split("/")[0]; + return { + url: `${target.method}://${ip}:${target.internalPort}` + }; + } + }); })(), ...(resource.stickySession ? { @@ -479,6 +413,27 @@ export async function getTraefikConfig( serviceName ].loadBalancer.serversTransport = transportName; } + + // Add the host header middleware + if (resource.setHostHeader) { + if (!config_output.http.middlewares) { + config_output.http.middlewares = {}; + } + config_output.http.middlewares[hostHeaderMiddlewareName] = { + headers: { + customRequestHeaders: { + Host: resource.setHostHeader + } + } + }; + if (!config_output.http.routers![routerName].middlewares) { + config_output.http.routers![routerName].middlewares = []; + } + config_output.http.routers![routerName].middlewares = [ + ...config_output.http.routers![routerName].middlewares, + hostHeaderMiddlewareName + ]; + } } else { // Non-HTTP (TCP/UDP) configuration if (!resource.enableProxy) { @@ -574,13 +529,3 @@ export async function getTraefikConfig( } return config_output; } - -function sanitizePath(path: string | null | undefined): string | undefined { - if (!path) return undefined; - // clean any non alphanumeric characters from the path and replace with dashes - // the path cant be too long either, so limit to 50 characters - if (path.length > 50) { - path = path.substring(0, 50); - } - return path.replace(/[^a-zA-Z0-9]/g, ""); -} diff --git a/server/routers/user/createOrgUser.ts b/server/routers/user/createOrgUser.ts index 5b11c923..4419772a 100644 --- a/server/routers/user/createOrgUser.ts +++ b/server/routers/user/createOrgUser.ts @@ -84,14 +84,7 @@ export async function createOrgUser( } const { orgId } = parsedParams.data; - const { - username, - email, - name, - type, - idpId, - roleId - } = parsedBody.data; + const { username, email, name, type, idpId, roleId } = parsedBody.data; const [role] = await db .select() @@ -148,12 +141,7 @@ export async function createOrgUser( const [existingUser] = await trx .select() .from(users) - .where( - and( - eq(users.username, username), - eq(users.idpId, idpId) - ) - ); + .where(eq(users.username, username)); if (existingUser) { const [existingOrgUser] = await trx @@ -180,8 +168,7 @@ export async function createOrgUser( .values({ orgId, userId: existingUser.userId, - roleId: role.roleId, - autoProvisioned: false + roleId: role.roleId }) .returning(); } else { @@ -197,7 +184,7 @@ export async function createOrgUser( type: "oidc", idpId, dateCreated: new Date().toISOString(), - emailVerified: true, + emailVerified: true }) .returning(); @@ -206,8 +193,7 @@ export async function createOrgUser( .values({ orgId, userId: newUser.userId, - roleId: role.roleId, - autoProvisioned: false + roleId: role.roleId }) .returning(); } @@ -218,6 +204,7 @@ export async function createOrgUser( .from(userOrgs) .where(eq(userOrgs.orgId, orgId)); }); + } else { return next( createHttpError(HttpCode.BAD_REQUEST, "User type is required") diff --git a/server/routers/user/getOrgUser.ts b/server/routers/user/getOrgUser.ts index bdec2d12..38cd70a6 100644 --- a/server/routers/user/getOrgUser.ts +++ b/server/routers/user/getOrgUser.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, idp, idpOidcConfig } from "@server/db"; +import { db } from "@server/db"; import { roles, userOrgs, users } from "@server/db"; import { and, eq, sql } from "drizzle-orm"; import response from "@server/lib/response"; @@ -25,18 +25,10 @@ async function queryUser(orgId: string, userId: string) { isOwner: userOrgs.isOwner, isAdmin: roles.isAdmin, twoFactorEnabled: users.twoFactorEnabled, - autoProvisioned: userOrgs.autoProvisioned, - idpId: users.idpId, - idpName: idp.name, - idpType: idp.type, - idpVariant: idpOidcConfig.variant, - idpAutoProvision: idp.autoProvision }) .from(userOrgs) .leftJoin(roles, eq(userOrgs.roleId, roles.roleId)) .leftJoin(users, eq(userOrgs.userId, users.userId)) - .leftJoin(idp, eq(users.idpId, idp.idpId)) - .leftJoin(idpOidcConfig, eq(idp.idpId, idpOidcConfig.idpId)) .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))) .limit(1); if (typeof user.roles === "string") { diff --git a/server/routers/user/index.ts b/server/routers/user/index.ts index a54ce681..4c5dbf86 100644 --- a/server/routers/user/index.ts +++ b/server/routers/user/index.ts @@ -14,4 +14,3 @@ export * from "./removeInvitation"; export * from "./createOrgUser"; export * from "./adminUpdateUser2FA"; export * from "./adminGetUser"; -export * from "./updateOrgUser"; diff --git a/server/routers/user/listUsers.ts b/server/routers/user/listUsers.ts index 0535a79d..edcc5dce 100644 --- a/server/routers/user/listUsers.ts +++ b/server/routers/user/listUsers.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, idpOidcConfig } from "@server/db"; +import { db } from "@server/db"; import { idp, roles, userOrgs, users } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -61,15 +61,12 @@ async function queryUsers(orgId: string, limit: number, offset: number) { isOwner: userOrgs.isOwner, idpName: idp.name, idpId: users.idpId, - idpType: idp.type, - idpVariant: idpOidcConfig.variant, twoFactorEnabled: users.twoFactorEnabled, }) .from(users) .leftJoin(userOrgs, eq(users.userId, userOrgs.userId)) .leftJoin(roles, eq(userOrgs.roleId, roles.roleId)) .leftJoin(idp, eq(users.idpId, idp.idpId)) - .leftJoin(idpOidcConfig, eq(idpOidcConfig.idpId, idp.idpId)) .where(eq(userOrgs.orgId, orgId)) .groupBy(users.userId) .limit(limit) diff --git a/server/routers/user/updateOrgUser.ts b/server/routers/user/updateOrgUser.ts deleted file mode 100644 index fb00b59f..00000000 --- a/server/routers/user/updateOrgUser.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { z } from "zod"; -import { db, userOrgs } from "@server/db"; -import { and, eq } from "drizzle-orm"; -import response from "@server/lib/response"; -import HttpCode from "@server/types/HttpCode"; -import createHttpError from "http-errors"; -import logger from "@server/logger"; -import { fromError } from "zod-validation-error"; -import { OpenAPITags, registry } from "@server/openApi"; - -const paramsSchema = z - .object({ - userId: z.string(), - orgId: z.string() - }) - .strict(); - -const bodySchema = z - .object({ - autoProvisioned: z.boolean().optional() - }) - .strict() - .refine((data) => Object.keys(data).length > 0, { - message: "At least one field must be provided for update" - }); - -registry.registerPath({ - method: "post", - path: "/org/{orgId}/user/{userId}", - description: "Update a user in an org.", - tags: [OpenAPITags.Org, OpenAPITags.User], - request: { - params: paramsSchema, - body: { - content: { - "application/json": { - schema: bodySchema - } - } - } - }, - responses: {} -}); - -export async function updateOrgUser( - req: Request, - res: Response, - next: NextFunction -): Promise { - try { - const parsedParams = paramsSchema.safeParse(req.params); - if (!parsedParams.success) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - fromError(parsedParams.error).toString() - ) - ); - } - - const parsedBody = bodySchema.safeParse(req.body); - if (!parsedBody.success) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - fromError(parsedBody.error).toString() - ) - ); - } - - const { userId, orgId } = parsedParams.data; - - const [existingUser] = await db - .select() - .from(userOrgs) - .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))) - .limit(1); - - if (!existingUser) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - "User not found in this organization" - ) - ); - } - - const updateData = parsedBody.data; - - const [updatedUser] = await db - .update(userOrgs) - .set({ - ...updateData - }) - .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))) - .returning(); - - return response(res, { - data: updatedUser, - success: true, - error: false, - message: "Org user updated successfully", - status: HttpCode.OK - }); - } catch (error) { - logger.error(error); - return next( - createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") - ); - } -} diff --git a/server/routers/ws/messageHandlers.ts b/server/routers/ws/messageHandlers.ts index 8ca33b8a..a30daf43 100644 --- a/server/routers/ws/messageHandlers.ts +++ b/server/routers/ws/messageHandlers.ts @@ -4,8 +4,7 @@ import { handleGetConfigMessage, handleDockerStatusMessage, handleDockerContainersMessage, - handleNewtPingRequestMessage, - handleApplyBlueprintMessage + handleNewtPingRequestMessage } from "../newt"; import { handleOlmRegisterMessage, @@ -24,8 +23,7 @@ export const messageHandlers: Record = { "olm/ping": handleOlmPingMessage, "newt/socket/status": handleDockerStatusMessage, "newt/socket/containers": handleDockerContainersMessage, - "newt/ping/request": handleNewtPingRequestMessage, - "newt/blueprint/apply": handleApplyBlueprintMessage, + "newt/ping/request": handleNewtPingRequestMessage }; startOlmOfflineChecker(); // this is to handle the offline check for olms diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index c5950e1d..6b3f20b9 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -9,7 +9,6 @@ import m1 from "./scriptsPg/1.6.0"; import m2 from "./scriptsPg/1.7.0"; import m3 from "./scriptsPg/1.8.0"; import m4 from "./scriptsPg/1.9.0"; -import m5 from "./scriptsPg/1.10.0"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -19,8 +18,7 @@ const migrations = [ { version: "1.6.0", run: m1 }, { version: "1.7.0", run: m2 }, { version: "1.8.0", run: m3 }, - { version: "1.9.0", run: m4 }, - { version: "1.10.0", run: m5 }, + { version: "1.9.0", run: m4 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index b8fa64f0..5b0850c8 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -26,8 +26,6 @@ import m21 from "./scriptsSqlite/1.6.0"; import m22 from "./scriptsSqlite/1.7.0"; import m23 from "./scriptsSqlite/1.8.0"; import m24 from "./scriptsSqlite/1.9.0"; -import m25 from "./scriptsSqlite/1.10.0"; -import m26 from "./scriptsSqlite/1.10.1"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -53,8 +51,6 @@ const migrations = [ { version: "1.7.0", run: m22 }, { version: "1.8.0", run: m23 }, { version: "1.9.0", run: m24 }, - { version: "1.10.0", run: m25 }, - { version: "1.10.1", run: m26 }, // Add new migrations here as they are created ] as const; diff --git a/server/setup/scriptsPg/1.10.0.ts b/server/setup/scriptsPg/1.10.0.ts deleted file mode 100644 index 3be2f697..00000000 --- a/server/setup/scriptsPg/1.10.0.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { db } from "@server/db/pg/driver"; -import { sql } from "drizzle-orm"; -import { __DIRNAME, APP_PATH } from "@server/lib/consts"; -import { readFileSync } from "fs"; -import path, { join } from "path"; - -const version = "1.10.0"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - try { - const resources = await db.execute(sql` - SELECT "resourceId" FROM "resources" - `); - - const siteResources = await db.execute(sql` - SELECT "siteResourceId" FROM "siteResources" - `); - - await db.execute(sql`BEGIN`); - - await db.execute( - sql`ALTER TABLE "exitNodes" ADD COLUMN "region" text;` - ); - - await db.execute( - sql`ALTER TABLE "idpOidcConfig" ADD COLUMN "variant" text DEFAULT 'oidc' NOT NULL;` - ); - - await db.execute( - sql`ALTER TABLE "resources" ADD COLUMN "niceId" text DEFAULT '' NOT NULL;` - ); - - await db.execute( - sql`ALTER TABLE "siteResources" ADD COLUMN "niceId" text DEFAULT '' NOT NULL;` - ); - - await db.execute( - sql`ALTER TABLE "userOrgs" ADD COLUMN "autoProvisioned" boolean DEFAULT false;` - ); - - await db.execute( - sql`ALTER TABLE "targets" ADD COLUMN "pathMatchType" text;` - ); - - await db.execute(sql`ALTER TABLE "targets" ADD COLUMN "path" text;`); - - await db.execute( - sql`ALTER TABLE "resources" ADD COLUMN "headers" text;` - ); - - const usedNiceIds: string[] = []; - - for (const resource of resources.rows) { - // Generate a unique name and ensure it's unique - let niceId = ""; - let loops = 0; - while (true) { - if (loops > 100) { - throw new Error("Could not generate a unique name"); - } - - niceId = generateName(); - if (!usedNiceIds.includes(niceId)) { - usedNiceIds.push(niceId); - break; - } - loops++; - } - await db.execute(sql` - UPDATE "resources" SET "niceId" = ${niceId} WHERE "resourceId" = ${resource.resourceId} - `); - } - - for (const resource of siteResources.rows) { - // Generate a unique name and ensure it's unique - let niceId = ""; - let loops = 0; - while (true) { - if (loops > 100) { - throw new Error("Could not generate a unique name"); - } - - niceId = generateName(); - if (!usedNiceIds.includes(niceId)) { - usedNiceIds.push(niceId); - break; - } - loops++; - } - await db.execute(sql` - UPDATE "siteResources" SET "niceId" = ${niceId} WHERE "siteResourceId" = ${resource.siteResourceId} - `); - } - - // Handle auto-provisioned users for identity providers - const autoProvisionIdps = await db.execute(sql` - SELECT "idpId" FROM "idp" WHERE "autoProvision" = true - `); - - for (const idp of autoProvisionIdps.rows) { - // Get all users with this identity provider - const usersWithIdp = await db.execute(sql` - SELECT "id" FROM "user" WHERE "idpId" = ${idp.idpId} - `); - - // Update userOrgs to set autoProvisioned to true for these users - for (const user of usersWithIdp.rows) { - await db.execute(sql` - UPDATE "userOrgs" SET "autoProvisioned" = true WHERE "userId" = ${user.id} - `); - } - } - - await db.execute(sql`COMMIT`); - console.log(`Migrated database`); - } catch (e) { - await db.execute(sql`ROLLBACK`); - console.log("Failed to migrate db:", e); - throw e; - } -} - -const dev = process.env.ENVIRONMENT !== "prod"; -let file; -if (!dev) { - file = join(__DIRNAME, "names.json"); -} else { - file = join("server/db/names.json"); -} -export const names = JSON.parse(readFileSync(file, "utf-8")); - -export function generateName(): string { - const name = ( - names.descriptors[ - Math.floor(Math.random() * names.descriptors.length) - ] + - "-" + - names.animals[Math.floor(Math.random() * names.animals.length)] - ) - .toLowerCase() - .replace(/\s/g, "-"); - - // clean out any non-alphanumeric characters except for dashes - return name.replace(/[^a-z0-9-]/g, ""); -} diff --git a/server/setup/scriptsSqlite/1.10.0.ts b/server/setup/scriptsSqlite/1.10.0.ts deleted file mode 100644 index 3065a664..00000000 --- a/server/setup/scriptsSqlite/1.10.0.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { __DIRNAME, APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import { readFileSync } from "fs"; -import path, { join } from "path"; - -const version = "1.10.0"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - const location = path.join(APP_PATH, "db", "db.sqlite"); - const db = new Database(location); - - try { - const resources = db - .prepare( - "SELECT resourceId FROM resources" - ) - .all() as Array<{ resourceId: number }>; - - const siteResources = db - .prepare( - "SELECT siteResourceId FROM siteResources" - ) - .all() as Array<{ siteResourceId: number }>; - - db.transaction(() => { - db.exec(` - ALTER TABLE 'exitNodes' ADD 'region' text; - ALTER TABLE 'idpOidcConfig' ADD 'variant' text DEFAULT 'oidc' NOT NULL; - ALTER TABLE 'resources' ADD 'niceId' text DEFAULT '' NOT NULL; - ALTER TABLE 'siteResources' ADD 'niceId' text DEFAULT '' NOT NULL; - ALTER TABLE 'userOrgs' ADD 'autoProvisioned' integer DEFAULT false; - ALTER TABLE 'targets' ADD 'pathMatchType' text; - ALTER TABLE 'targets' ADD 'path' text; - ALTER TABLE 'resources' ADD 'headers' text; - `); // this diverges from the schema a bit because the schema does not have a default on niceId but was required for the migration and I dont think it will effect much down the line... - - const usedNiceIds: string[] = []; - - for (const resourceId of resources) { - // Generate a unique name and ensure it's unique - let niceId = ""; - let loops = 0; - while (true) { - if (loops > 100) { - throw new Error("Could not generate a unique name"); - } - - niceId = generateName(); - if (!usedNiceIds.includes(niceId)) { - usedNiceIds.push(niceId); - break; - } - loops++; - } - db.prepare( - `UPDATE resources SET niceId = ? WHERE resourceId = ?` - ).run(niceId, resourceId.resourceId); - } - - for (const resourceId of siteResources) { - // Generate a unique name and ensure it's unique - let niceId = ""; - let loops = 0; - while (true) { - if (loops > 100) { - throw new Error("Could not generate a unique name"); - } - - niceId = generateName(); - if (!usedNiceIds.includes(niceId)) { - usedNiceIds.push(niceId); - break; - } - loops++; - } - db.prepare( - `UPDATE siteResources SET niceId = ? WHERE siteResourceId = ?` - ).run(niceId, resourceId.siteResourceId); - } - - // Handle auto-provisioned users for identity providers - const autoProvisionIdps = db - .prepare( - "SELECT idpId FROM idp WHERE autoProvision = 1" - ) - .all() as Array<{ idpId: number }>; - - for (const idp of autoProvisionIdps) { - // Get all users with this identity provider - const usersWithIdp = db - .prepare( - "SELECT id FROM user WHERE idpId = ?" - ) - .all(idp.idpId) as Array<{ id: string }>; - - // Update userOrgs to set autoProvisioned to true for these users - for (const user of usersWithIdp) { - db.prepare( - "UPDATE userOrgs SET autoProvisioned = 1 WHERE userId = ?" - ).run(user.id); - } - } - })(); - - console.log(`Migrated database`); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } -} - -const dev = process.env.ENVIRONMENT !== "prod"; -let file; -if (!dev) { - file = join(__DIRNAME, "names.json"); -} else { - file = join("server/db/names.json"); -} -export const names = JSON.parse(readFileSync(file, "utf-8")); - -export function generateName(): string { - const name = ( - names.descriptors[ - Math.floor(Math.random() * names.descriptors.length) - ] + - "-" + - names.animals[Math.floor(Math.random() * names.animals.length)] - ) - .toLowerCase() - .replace(/\s/g, "-"); - - // clean out any non-alphanumeric characters except for dashes - return name.replace(/[^a-z0-9-]/g, ""); -} diff --git a/server/setup/scriptsSqlite/1.10.1.ts b/server/setup/scriptsSqlite/1.10.1.ts deleted file mode 100644 index 3608e92e..00000000 --- a/server/setup/scriptsSqlite/1.10.1.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.10.1"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - const location = path.join(APP_PATH, "db", "db.sqlite"); - const db = new Database(location); - - try { - db.pragma("foreign_keys = OFF"); - - db.transaction(() => { - db.exec(`ALTER TABLE "targets" RENAME TO "targets_old"; ---> statement-breakpoint -CREATE TABLE "targets" ( - "targetId" INTEGER PRIMARY KEY AUTOINCREMENT, - "resourceId" INTEGER NOT NULL, - "siteId" INTEGER NOT NULL, - "ip" TEXT NOT NULL, - "method" TEXT, - "port" INTEGER NOT NULL, - "internalPort" INTEGER, - "enabled" INTEGER NOT NULL DEFAULT 1, - "path" TEXT, - "pathMatchType" TEXT, - FOREIGN KEY ("resourceId") REFERENCES "resources"("resourceId") ON UPDATE no action ON DELETE cascade, - FOREIGN KEY ("siteId") REFERENCES "sites"("siteId") ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -INSERT INTO "targets" ( - "targetId", - "resourceId", - "siteId", - "ip", - "method", - "port", - "internalPort", - "enabled", - "path", - "pathMatchType" -) -SELECT - targetId, - resourceId, - siteId, - ip, - method, - port, - internalPort, - enabled, - path, - pathMatchType -FROM "targets_old"; ---> statement-breakpoint -DROP TABLE "targets_old";`); - })(); - - db.pragma("foreign_keys = ON"); - - console.log(`Migrated database`); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } -} diff --git a/src/components/MemberResourcesPortal.tsx b/src/app/[orgId]/MemberResourcesPortal.tsx similarity index 100% rename from src/components/MemberResourcesPortal.tsx rename to src/app/[orgId]/MemberResourcesPortal.tsx diff --git a/src/components/OrganizationLandingCard.tsx b/src/app/[orgId]/OrganizationLandingCard.tsx similarity index 100% rename from src/components/OrganizationLandingCard.tsx rename to src/app/[orgId]/OrganizationLandingCard.tsx diff --git a/src/app/[orgId]/page.tsx b/src/app/[orgId]/page.tsx index 4c3ac07b..4740198b 100644 --- a/src/app/[orgId]/page.tsx +++ b/src/app/[orgId]/page.tsx @@ -1,8 +1,8 @@ import { verifySession } from "@app/lib/auth/verifySession"; import UserProvider from "@app/providers/UserProvider"; import { cache } from "react"; -import OrganizationLandingCard from "../../components/OrganizationLandingCard"; -import MemberResourcesPortal from "../../components/MemberResourcesPortal"; +import OrganizationLandingCard from "./OrganizationLandingCard"; +import MemberResourcesPortal from "./MemberResourcesPortal"; import { GetOrgOverviewResponse } from "@server/routers/org/getOrgOverview"; import { internal } from "@app/lib/api"; import { AxiosResponse } from "axios"; diff --git a/src/components/AccessPageHeaderAndNav.tsx b/src/app/[orgId]/settings/access/AccessPageHeaderAndNav.tsx similarity index 100% rename from src/components/AccessPageHeaderAndNav.tsx rename to src/app/[orgId]/settings/access/AccessPageHeaderAndNav.tsx diff --git a/src/components/InvitationsDataTable.tsx b/src/app/[orgId]/settings/access/invitations/InvitationsDataTable.tsx similarity index 100% rename from src/components/InvitationsDataTable.tsx rename to src/app/[orgId]/settings/access/invitations/InvitationsDataTable.tsx diff --git a/src/components/InvitationsTable.tsx b/src/app/[orgId]/settings/access/invitations/InvitationsTable.tsx similarity index 97% rename from src/components/InvitationsTable.tsx rename to src/app/[orgId]/settings/access/invitations/InvitationsTable.tsx index a97220f2..dfb3d263 100644 --- a/src/components/InvitationsTable.tsx +++ b/src/app/[orgId]/settings/access/invitations/InvitationsTable.tsx @@ -9,10 +9,10 @@ import { } from "@app/components/ui/dropdown-menu"; import { Button } from "@app/components/ui/button"; import { MoreHorizontal } from "lucide-react"; -import { InvitationsDataTable } from "@app/components/InvitationsDataTable"; +import { InvitationsDataTable } from "./InvitationsDataTable"; import { useState } from "react"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; -import RegenerateInvitationForm from "@app/components/RegenerateInvitationForm"; +import RegenerateInvitationForm from "./RegenerateInvitationForm"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { toast } from "@app/hooks/useToast"; import { createApiClient } from "@app/lib/api"; diff --git a/src/components/RegenerateInvitationForm.tsx b/src/app/[orgId]/settings/access/invitations/RegenerateInvitationForm.tsx similarity index 100% rename from src/components/RegenerateInvitationForm.tsx rename to src/app/[orgId]/settings/access/invitations/RegenerateInvitationForm.tsx diff --git a/src/app/[orgId]/settings/access/invitations/page.tsx b/src/app/[orgId]/settings/access/invitations/page.tsx index d7fee322..665b9a43 100644 --- a/src/app/[orgId]/settings/access/invitations/page.tsx +++ b/src/app/[orgId]/settings/access/invitations/page.tsx @@ -1,13 +1,13 @@ import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import { AxiosResponse } from "axios"; -import InvitationsTable, { InvitationRow } from "../../../../../components/InvitationsTable"; +import InvitationsTable, { InvitationRow } from "./InvitationsTable"; import { GetOrgResponse } from "@server/routers/org"; import { cache } from "react"; import OrgProvider from "@app/providers/OrgProvider"; import UserProvider from "@app/providers/UserProvider"; import { verifySession } from "@app/lib/auth/verifySession"; -import AccessPageHeaderAndNav from "../../../../../components/AccessPageHeaderAndNav"; +import AccessPageHeaderAndNav from "../AccessPageHeaderAndNav"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { getTranslations } from 'next-intl/server'; diff --git a/src/app/[orgId]/settings/access/layout.tsx b/src/app/[orgId]/settings/access/layout.tsx index 51be3b5e..2dd20177 100644 --- a/src/app/[orgId]/settings/access/layout.tsx +++ b/src/app/[orgId]/settings/access/layout.tsx @@ -1,6 +1,7 @@ interface AccessLayoutProps { children: React.ReactNode; params: Promise<{ + resourceId: number | string; orgId: string; }>; } diff --git a/src/components/CreateRoleForm.tsx b/src/app/[orgId]/settings/access/roles/CreateRoleForm.tsx similarity index 100% rename from src/components/CreateRoleForm.tsx rename to src/app/[orgId]/settings/access/roles/CreateRoleForm.tsx diff --git a/src/components/DeleteRoleForm.tsx b/src/app/[orgId]/settings/access/roles/DeleteRoleForm.tsx similarity index 99% rename from src/components/DeleteRoleForm.tsx rename to src/app/[orgId]/settings/access/roles/DeleteRoleForm.tsx index 3516851b..f3042f71 100644 --- a/src/components/DeleteRoleForm.tsx +++ b/src/app/[orgId]/settings/access/roles/DeleteRoleForm.tsx @@ -34,7 +34,7 @@ import { SelectTrigger, SelectValue } from "@app/components/ui/select"; -import { RoleRow } from "@app/components/RolesTable"; +import { RoleRow } from "./RolesTable"; import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; diff --git a/src/components/RolesDataTable.tsx b/src/app/[orgId]/settings/access/roles/RolesDataTable.tsx similarity index 100% rename from src/components/RolesDataTable.tsx rename to src/app/[orgId]/settings/access/roles/RolesDataTable.tsx diff --git a/src/components/RolesTable.tsx b/src/app/[orgId]/settings/access/roles/RolesTable.tsx similarity index 95% rename from src/components/RolesTable.tsx rename to src/app/[orgId]/settings/access/roles/RolesTable.tsx index e92e71b6..40260fb7 100644 --- a/src/components/RolesTable.tsx +++ b/src/app/[orgId]/settings/access/roles/RolesTable.tsx @@ -13,10 +13,10 @@ import { useState } from "react"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { toast } from "@app/hooks/useToast"; -import { RolesDataTable } from "@app/components/RolesDataTable"; +import { RolesDataTable } from "./RolesDataTable"; import { Role } from "@server/db"; -import CreateRoleForm from "@app/components/CreateRoleForm"; -import DeleteRoleForm from "@app/components/DeleteRoleForm"; +import CreateRoleForm from "./CreateRoleForm"; +import DeleteRoleForm from "./DeleteRoleForm"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useTranslations } from "next-intl"; diff --git a/src/app/[orgId]/settings/access/roles/page.tsx b/src/app/[orgId]/settings/access/roles/page.tsx index cffe4ed9..8faedbf8 100644 --- a/src/app/[orgId]/settings/access/roles/page.tsx +++ b/src/app/[orgId]/settings/access/roles/page.tsx @@ -5,7 +5,7 @@ import { GetOrgResponse } from "@server/routers/org"; import { cache } from "react"; import OrgProvider from "@app/providers/OrgProvider"; import { ListRolesResponse } from "@server/routers/role"; -import RolesTable, { RoleRow } from "../../../../../components/RolesTable"; +import RolesTable, { RoleRow } from "./RolesTable"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { getTranslations } from 'next-intl/server'; diff --git a/src/components/UsersDataTable.tsx b/src/app/[orgId]/settings/access/users/UsersDataTable.tsx similarity index 100% rename from src/components/UsersDataTable.tsx rename to src/app/[orgId]/settings/access/users/UsersDataTable.tsx diff --git a/src/components/UsersTable.tsx b/src/app/[orgId]/settings/access/users/UsersTable.tsx similarity index 95% rename from src/components/UsersTable.tsx rename to src/app/[orgId]/settings/access/users/UsersTable.tsx index 57fe5c91..61567e15 100644 --- a/src/components/UsersTable.tsx +++ b/src/app/[orgId]/settings/access/users/UsersTable.tsx @@ -9,7 +9,7 @@ import { } from "@app/components/ui/dropdown-menu"; import { Button } from "@app/components/ui/button"; import { ArrowRight, ArrowUpDown, Crown, MoreHorizontal } from "lucide-react"; -import { UsersDataTable } from "@app/components/UsersDataTable"; +import { UsersDataTable } from "./UsersDataTable"; import { useState } from "react"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { useOrgContext } from "@app/hooks/useOrgContext"; @@ -21,7 +21,6 @@ import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useUserContext } from "@app/hooks/useUserContext"; import { useTranslations } from "next-intl"; -import IdpTypeBadge from "./IdpTypeBadge"; export type UserRow = { id: string; @@ -32,7 +31,6 @@ export type UserRow = { idpId: number | null; idpName: string; type: string; - idpVariant: string | null; status: string; role: string; isOwner: boolean; @@ -83,16 +81,6 @@ export default function UsersTable({ users: u }: UsersTableProps) { ); - }, - cell: ({ row }) => { - const userRow = row.original; - return ( - - ); } }, { @@ -158,7 +146,7 @@ export default function UsersTable({ users: u }: UsersTableProps) { {`${userRow.username}-${userRow.idpId}` !== - `${user?.username}-${user?.idpId}` && ( + `${user?.username}-${userRow.idpId}` && ( { setIsDeleteModalOpen( diff --git a/src/app/[orgId]/settings/access/users/[userId]/access-controls/page.tsx b/src/app/[orgId]/settings/access/users/[userId]/access-controls/page.tsx index fdce89eb..c56c45cc 100644 --- a/src/app/[orgId]/settings/access/users/[userId]/access-controls/page.tsx +++ b/src/app/[orgId]/settings/access/users/[userId]/access-controls/page.tsx @@ -8,7 +8,6 @@ import { FormLabel, FormMessage } from "@app/components/ui/form"; -import { Checkbox } from "@app/components/ui/checkbox"; import { toast } from "@app/hooks/useToast"; import { zodResolver } from "@hookform/resolvers/zod"; import { SetUserRolesResponse } from "@server/routers/user"; @@ -35,8 +34,6 @@ import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { Tag, TagInput } from "@app/components/tags/tag-input"; import { useTranslations } from "next-intl"; -import IdpTypeBadge from "@app/components/IdpTypeBadge"; -import { UserType } from "@server/types/UserTypes"; export default function AccessControlsPage() { const { orgUser: user } = userOrgUserContext(); @@ -64,16 +61,14 @@ export default function AccessControlsPage() { text: z.string() }) ) - .min(1, { message: t('accessRoleSelectPlease') }), - autoProvisioned: z.boolean() + .min(1, { message: t('accessRoleSelectPlease') }) }); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { username: user.username!, - roles: [], - autoProvisioned: user.autoProvisioned || false + roles: [] } }); @@ -85,10 +80,10 @@ export default function AccessControlsPage() { console.error(e); toast({ variant: "destructive", - title: t("accessRoleErrorFetch"), + title: t('accessRoleErrorFetch'), description: formatAxiosError( e, - t("accessRoleErrorFetchDescription") + t('accessRoleErrorFetchDescription') ) }); }); @@ -112,38 +107,31 @@ export default function AccessControlsPage() { text: i.name })) ); - form.setValue("autoProvisioned", user.autoProvisioned || false); }, []); async function onSubmit(values: z.infer) { setLoading(true); - try { - // Execute both API calls simultaneously - const [roleRes, userRes] = await Promise.all([ - api.post>(`/org/${user.orgId}/user/${user.userId}/roles`, { - roleIds: values.roles.map((r) => parseInt(r.id)) } - ), - api.post(`/org/${orgId}/user/${user.userId}`, { - autoProvisioned: values.autoProvisioned - }) - ]); - - if (roleRes.status === 200 && userRes.status === 200) { + const res = await api + .post< + AxiosResponse + >(`/org/${user.orgId}/user/${user.userId}/roles`, { roleIds: values.roles.map((r) => parseInt(r.id)) }) + .catch((e) => { toast({ - variant: "default", - title: t("userSaved"), - description: t("userSavedDescription") + variant: "destructive", + title: t('accessRoleErrorAdd'), + description: formatAxiosError( + e, + t('accessRoleErrorAddDescription') + ) }); - } - } catch (e) { + }); + + if (res && res.status === 200) { toast({ - variant: "destructive", - title: t("accessRoleErrorAdd"), - description: formatAxiosError( - e, - t("accessRoleErrorAddDescription") - ) + variant: "default", + title: t('userSaved'), + description: t('userSavedDescription') }); } @@ -154,11 +142,9 @@ export default function AccessControlsPage() { - - {t("accessControls")} - + {t('accessControls')} - {t("accessControlsDescription")} + {t('accessControlsDescription')} @@ -170,29 +156,12 @@ export default function AccessControlsPage() { className="space-y-4" id="access-controls-form" > - {/* IDP Type Display */} - {user.type !== UserType.Internal && - user.idpType && ( -
- - {t("idp")}: - - -
- )} - ( - {t('roles')} + Roles )} /> - - {user.idpAutoProvision && ( - ( - - - - -
- - {t("autoProvisioned")} - -

- {t( - "autoProvisionedDescription" - )} -

-
-
- )} - /> - )} @@ -276,7 +212,7 @@ export default function AccessControlsPage() { disabled={loading} form="access-controls-form" > - {t("accessControlsSubmit")} + {t('accessControlsSubmit')}
diff --git a/src/app/[orgId]/settings/access/users/create/page.tsx b/src/app/[orgId]/settings/access/users/create/page.tsx index 47bff1e1..6ae00b61 100644 --- a/src/app/[orgId]/settings/access/users/create/page.tsx +++ b/src/app/[orgId]/settings/access/users/create/page.tsx @@ -46,7 +46,6 @@ import { Checkbox } from "@app/components/ui/checkbox"; import { ListIdpsResponse } from "@server/routers/idp"; import { useTranslations } from "next-intl"; import { build } from "@server/build"; -import Image from "next/image"; type UserType = "internal" | "oidc"; @@ -54,17 +53,6 @@ interface IdpOption { idpId: number; name: string; type: string; - variant: string | null; -} - -interface UserOption { - id: string; - title: string; - description: string; - disabled: boolean; - icon?: React.ReactNode; - idpId?: number; - variant?: string | null; } export default function Page() { @@ -74,14 +62,14 @@ export default function Page() { const api = createApiClient({ env }); const t = useTranslations(); - const [selectedOption, setSelectedOption] = useState("internal"); + const [userType, setUserType] = useState("internal"); const [inviteLink, setInviteLink] = useState(null); const [loading, setLoading] = useState(false); const [expiresInDays, setExpiresInDays] = useState(1); const [roles, setRoles] = useState<{ roleId: number; name: string }[]>([]); const [idps, setIdps] = useState([]); const [sendEmail, setSendEmail] = useState(env.email.emailEnabled); - const [userOptions, setUserOptions] = useState([]); + const [selectedIdp, setSelectedIdp] = useState(null); const [dataLoaded, setDataLoaded] = useState(false); const internalFormSchema = z.object({ @@ -92,13 +80,7 @@ export default function Page() { roleId: z.string().min(1, { message: t("accessRoleSelectPlease") }) }); - const googleAzureFormSchema = z.object({ - email: z.string().email({ message: t("emailInvalid") }), - name: z.string().optional(), - roleId: z.string().min(1, { message: t("accessRoleSelectPlease") }) - }); - - const genericOidcFormSchema = z.object({ + const externalFormSchema = z.object({ username: z.string().min(1, { message: t("usernameRequired") }), email: z .string() @@ -106,51 +88,19 @@ export default function Page() { .optional() .or(z.literal("")), name: z.string().optional(), - roleId: z.string().min(1, { message: t("accessRoleSelectPlease") }) + roleId: z.string().min(1, { message: t("accessRoleSelectPlease") }), + idpId: z.string().min(1, { message: t("idpSelectPlease") }) }); const formatIdpType = (type: string) => { switch (type.toLowerCase()) { case "oidc": return t("idpGenericOidc"); - case "google": - return t("idpGoogleDescription"); - case "azure": - return t("idpAzureDescription"); default: return type; } }; - const getIdpIcon = (variant: string | null) => { - if (!variant) return null; - - switch (variant.toLowerCase()) { - case "google": - return ( - {t("idpGoogleAlt")} - ); - case "azure": - return ( - {t("idpAzureAlt")} - ); - default: - return null; - } - }; - const validFor = [ { hours: 24, name: t("day", { count: 1 }) }, { hours: 48, name: t("day", { count: 2 }) }, @@ -170,39 +120,45 @@ export default function Page() { } }); - const googleAzureForm = useForm>({ - resolver: zodResolver(googleAzureFormSchema), - defaultValues: { - email: "", - name: "", - roleId: "" - } - }); - - const genericOidcForm = useForm>({ - resolver: zodResolver(genericOidcFormSchema), + const externalForm = useForm>({ + resolver: zodResolver(externalFormSchema), defaultValues: { username: "", email: "", name: "", - roleId: "" + roleId: "", + idpId: "" } }); useEffect(() => { - if (selectedOption === "internal") { + if (userType === "internal") { setSendEmail(env.email.emailEnabled); internalForm.reset(); setInviteLink(null); setExpiresInDays(1); - } else if (selectedOption && selectedOption !== "internal") { - googleAzureForm.reset(); - genericOidcForm.reset(); + } else if (userType === "oidc") { + externalForm.reset(); } - }, [selectedOption, env.email.emailEnabled, internalForm, googleAzureForm, genericOidcForm]); + }, [userType, env.email.emailEnabled, internalForm, externalForm]); + + const [userTypes, setUserTypes] = useState[]>([ + { + id: "internal", + title: t("userTypeInternal"), + description: t("userTypeInternalDescription"), + disabled: false + }, + { + id: "oidc", + title: t("userTypeExternal"), + description: t("userTypeExternalDescription"), + disabled: true + } + ]); useEffect(() => { - if (!selectedOption) { + if (!userType) { return; } @@ -243,6 +199,20 @@ export default function Page() { if (res?.status === 200) { setIdps(res.data.data.idps); + + if (res.data.data.idps.length) { + setUserTypes((prev) => + prev.map((type) => { + if (type.id === "oidc") { + return { + ...type, + disabled: false + }; + } + return type; + }) + ); + } } } @@ -256,33 +226,6 @@ export default function Page() { fetchInitialData(); }, []); - // Build user options when IDPs are loaded - useEffect(() => { - const options: UserOption[] = [ - { - id: "internal", - title: t("userTypeInternal"), - description: t("userTypeInternalDescription"), - disabled: false - } - ]; - - // Add IDP options - idps.forEach((idp) => { - options.push({ - id: `idp-${idp.idpId}`, - title: idp.name, - description: formatIdpType(idp.variant || idp.type), - disabled: false, - icon: getIdpIcon(idp.variant), - idpId: idp.idpId, - variant: idp.variant - }); - }); - - setUserOptions(options); - }, [idps, t]); - async function onSubmitInternal( values: z.infer ) { @@ -331,52 +274,9 @@ export default function Page() { setLoading(false); } - async function onSubmitGoogleAzure( - values: z.infer + async function onSubmitExternal( + values: z.infer ) { - const selectedUserOption = userOptions.find(opt => opt.id === selectedOption); - if (!selectedUserOption?.idpId) return; - - setLoading(true); - - const res = await api - .put(`/org/${orgId}/user`, { - username: values.email, // Use email as username for Google/Azure - email: values.email, - name: values.name, - type: "oidc", - idpId: selectedUserOption.idpId, - roleId: parseInt(values.roleId) - }) - .catch((e) => { - toast({ - variant: "destructive", - title: t("userErrorCreate"), - description: formatAxiosError( - e, - t("userErrorCreateDescription") - ) - }); - }); - - if (res && res.status === 201) { - toast({ - variant: "default", - title: t("userCreated"), - description: t("userCreatedDescription") - }); - router.push(`/${orgId}/settings/access/users`); - } - - setLoading(false); - } - - async function onSubmitGenericOidc( - values: z.infer - ) { - const selectedUserOption = userOptions.find(opt => opt.id === selectedOption); - if (!selectedUserOption?.idpId) return; - setLoading(true); const res = await api @@ -385,7 +285,7 @@ export default function Page() { email: values.email, name: values.name, type: "oidc", - idpId: selectedUserOption.idpId, + idpId: parseInt(values.idpId), roleId: parseInt(values.roleId) }) .catch((e) => { @@ -430,7 +330,7 @@ export default function Page() {
- {!inviteLink && build !== "saas" && dataLoaded ? ( + {!inviteLink && build !== "saas" ? ( @@ -442,15 +342,15 @@ export default function Page() { { - setSelectedOption(value); + setUserType(value as UserType); if (value === "internal") { internalForm.reset(); - } else { - googleAzureForm.reset(); - genericOidcForm.reset(); + } else if (value === "oidc") { + externalForm.reset(); + setSelectedIdp(null); } }} cols={2} @@ -459,7 +359,7 @@ export default function Page() { ) : null} - {selectedOption === "internal" && dataLoaded && ( + {userType === "internal" && dataLoaded && ( <> {!inviteLink ? ( @@ -664,7 +564,71 @@ export default function Page() { )} - {selectedOption && selectedOption !== "internal" && dataLoaded && ( + {userType !== "internal" && dataLoaded && ( + <> + + + + {t("idpTitle")} + + + {t("idpSelect")} + + + + {idps.length === 0 ? ( +

+ {t("idpNotConfigured")} +

+ ) : ( +
+ ( + + ({ + id: idp.idpId.toString(), + title: idp.name, + description: + formatIdpType( + idp.type + ) + }) + )} + defaultValue={ + field.value + } + onChange={( + value + ) => { + field.onChange( + value + ); + const idp = + idps.find( + (idp) => + idp.idpId.toString() === + value + ); + setSelectedIdp( + idp || null + ); + }} + cols={2} + /> + + + )} + /> + + )} +
+
+ + {idps.length > 0 && ( @@ -676,206 +640,144 @@ export default function Page() { - {/* Google/Azure Form */} - {(() => { - const selectedUserOption = userOptions.find(opt => opt.id === selectedOption); - return selectedUserOption?.variant === "google" || selectedUserOption?.variant === "azure"; - })() && ( -
- + + ( + + + {t( + "username" + )} + + + + +

+ {t( + "usernameUniq" + )} +

+ +
)} - className="space-y-4" - id="create-user-form" - > - ( - - - {t("email")} - - - - - - - )} - /> + /> - ( - - - {t("nameOptional")} - - - - - - - )} - /> + ( + + + {t( + "emailOptional" + )} + + + + + + + )} + /> - ( - - - {t("role")} - - + + + + )} + /> + + ( + + + {t("role")} + + - - - )} - /> - - - )} - - {/* Generic OIDC Form */} - {(() => { - const selectedUserOption = userOptions.find(opt => opt.id === selectedOption); - return selectedUserOption?.variant !== "google" && selectedUserOption?.variant !== "azure"; - })() && ( -
- + + + )} - className="space-y-4" - id="create-user-form" - > - ( - - - {t("username")} - - - - -

- {t("usernameUniq")} -

- -
- )} - /> - - ( - - - {t("emailOptional")} - - - - - - - )} - /> - - ( - - - {t("nameOptional")} - - - - - - - )} - /> - - ( - - - {t("role")} - - - - - )} - /> - - - )} + /> + +
+ )} + )}
- {selectedOption && dataLoaded && ( + {userType && dataLoaded && ( - ); - } - }, { accessorKey: "protocol", header: t("protocol"), cell: ({ row }) => { const resourceRow = row.original; - return {resourceRow.http ? (resourceRow.ssl ? "HTTPS" : "HTTP") : resourceRow.protocol.toUpperCase()}; + return {resourceRow.protocol.toUpperCase()}; } }, { @@ -462,7 +444,7 @@ export default function ResourcesTable({ {t("viewSettings")} @@ -481,7 +463,7 @@ export default function ResourcesTable({ - ); - } - - return ( -
- - { - const value = e.target.value.trim(); - if (!value) { - setShowPathInput(false); - updateTarget(row.original.targetId, { - ...row.original, - path: null, - pathMatchType: null - }); - } else { - updateTarget(row.original.targetId, { - ...row.original, - path: value - }); - } - }} - /> - - - -
- ); - } - }, { accessorKey: "siteId", header: t("site"), @@ -693,7 +546,7 @@ export default function ReverseProxyTargets(props: { className={cn( "justify-between flex-1", !row.original.siteId && - "text-muted-foreground" + "text-muted-foreground" )} > {row.original.siteId @@ -744,59 +597,49 @@ export default function ReverseProxyTargets(props: { - {selectedSite && - selectedSite.type === "newt" && - (() => { - const dockerState = getDockerStateForSite( - selectedSite.siteId - ); - return ( - - refreshContainersForSite( - selectedSite.siteId - ) - } - /> - ); - })()} + {selectedSite && selectedSite.type === "newt" && (() => { + const dockerState = getDockerStateForSite(selectedSite.siteId); + return ( + refreshContainersForSite(selectedSite.siteId)} + /> + ); + })()}
); } }, ...(resource.http ? [ - { - accessorKey: "method", - header: t("method"), - cell: ({ row }: { row: Row }) => ( - - ) - } - ] + { + accessorKey: "method", + header: t("method"), + cell: ({ row }: { row: Row }) => ( + + ) + } + ] : []), { accessorKey: "ip", @@ -815,13 +658,9 @@ export default function ReverseProxyTargets(props: { if (parsed) { updateTarget(row.original.targetId, { ...row.original, - method: hasProtocol - ? parsed.protocol - : row.original.method, + method: hasProtocol ? parsed.protocol : row.original.method, ip: parsed.host, - port: hasPort - ? parsed.port - : row.original.port + port: hasPort ? parsed.port : row.original.port }); } else { updateTarget(row.original.targetId, { @@ -968,21 +807,21 @@ export default function ReverseProxyTargets(props: { className={cn( "justify-between flex-1", !field.value && - "text-muted-foreground" + "text-muted-foreground" )} > {field.value ? sites.find( - ( - site - ) => - site.siteId === - field.value - ) - ?.name + ( + site + ) => + site.siteId === + field.value + ) + ?.name : t( - "siteSelect" - )} + "siteSelect" + )} @@ -1048,35 +887,18 @@ export default function ReverseProxyTargets(props: { ); return selectedSite && selectedSite.type === - "newt" - ? (() => { - const dockerState = - getDockerStateForSite( - selectedSite.siteId - ); - return ( - - refreshContainersForSite( - selectedSite.siteId - ) - } - /> - ); - })() - : null; + "newt" ? (() => { + const dockerState = getDockerStateForSite(selectedSite.siteId); + return ( + refreshContainersForSite(selectedSite.siteId)} + /> + ); + })() : null; })()}
@@ -1142,59 +964,25 @@ export default function ReverseProxyTargets(props: { name="ip" render={({ field }) => ( - - {t("targetAddr")} - + {t("targetAddr")} { - const input = - e.target.value.trim(); - const hasProtocol = - /^(https?|h2c):\/\//.test( - input - ); - const hasPort = - /:\d+(?:\/|$)/.test( - input - ); + const input = e.target.value.trim(); + const hasProtocol = /^(https?|h2c):\/\//.test(input); + const hasPort = /:\d+(?:\/|$)/.test(input); - if ( - hasProtocol || - hasPort - ) { - const parsed = - parseHostTarget( - input - ); + if (hasProtocol || hasPort) { + const parsed = parseHostTarget(input); if (parsed) { - if ( - hasProtocol || - !addTargetForm.getValues( - "method" - ) - ) { - addTargetForm.setValue( - "method", - parsed.protocol - ); + if (hasProtocol || !addTargetForm.getValues("method")) { + addTargetForm.setValue("method", parsed.protocol); } - addTargetForm.setValue( - "ip", - parsed.host - ); - if ( - hasPort || - !addTargetForm.getValues( - "port" - ) - ) { - addTargetForm.setValue( - "port", - parsed.port - ); + addTargetForm.setValue("ip", parsed.host); + if (hasPort || !addTargetForm.getValues("port")) { + addTargetForm.setValue("port", parsed.port); } } } else { @@ -1303,12 +1091,12 @@ export default function ReverseProxyTargets(props: { {header.isPlaceholder ? null : flexRender( - header - .column - .columnDef - .header, - header.getContext() - )} + header + .column + .columnDef + .header, + header.getContext() + )} ) )} @@ -1468,31 +1256,6 @@ export default function ReverseProxyTargets(props: { )} /> - ( - - - {t("customHeaders")} - - - { - field.onChange( - value - ); - }} - rows={4} - /> - - - - )} - /> diff --git a/src/app/[orgId]/settings/resources/[niceId]/rules/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx similarity index 98% rename from src/app/[orgId]/settings/resources/[niceId]/rules/page.tsx rename to src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx index 8b5e4709..424d7973 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/rules/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx @@ -128,7 +128,7 @@ export default function ResourceRules(props: { try { const res = await api.get< AxiosResponse - >(`/resource/${resource.resourceId}/rules`); + >(`/resource/${params.resourceId}/rules`); if (res.status === 200) { setRules(res.data.data.rules); } @@ -251,7 +251,7 @@ export default function ResourceRules(props: { // Save rules enabled state const res = await api - .post(`/resource/${resource.resourceId}`, { + .post(`/resource/${params.resourceId}`, { applyRules: rulesEnabled }) .catch((err) => { @@ -336,13 +336,13 @@ export default function ResourceRules(props: { if (rule.new) { const res = await api.put( - `/resource/${resource.resourceId}/rule`, + `/resource/${params.resourceId}/rule`, data ); rule.ruleId = res.data.data.ruleId; } else if (rule.updated) { await api.post( - `/resource/${resource.resourceId}/rule/${rule.ruleId}`, + `/resource/${params.resourceId}/rule/${rule.ruleId}`, data ); } @@ -361,7 +361,7 @@ export default function ResourceRules(props: { for (const ruleId of rulesToRemove) { await api.delete( - `/resource/${resource.resourceId}/rule/${ruleId}` + `/resource/${params.resourceId}/rule/${ruleId}` ); setRules(rules.filter((r) => r.ruleId !== ruleId)); } diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx index 71628ce7..782b3135 100644 --- a/src/app/[orgId]/settings/resources/create/page.tsx +++ b/src/app/[orgId]/settings/resources/create/page.tsx @@ -58,7 +58,7 @@ import { } from "@app/components/ui/popover"; import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; import { cn } from "@app/lib/cn"; -import { ArrowRight, MoveRight, SquareArrowOutUpRight } from "lucide-react"; +import { SquareArrowOutUpRight } from "lucide-react"; import CopyTextBox from "@app/components/CopyTextBox"; import Link from "next/link"; import { useTranslations } from "next-intl"; @@ -90,9 +90,7 @@ import { ListTargetsResponse } from "@server/routers/target"; import { DockerManager, DockerState } from "@app/lib/docker"; import { parseHostTarget } from "@app/lib/parseHostTarget"; import { toASCII, toUnicode } from 'punycode'; -import { DomainRow } from "../../../../../components/DomainsTable"; -import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils"; - +import { DomainRow } from "../../domains/DomainsTable"; const baseResourceFormSchema = z.object({ name: z.string().min(1).max(255), @@ -114,42 +112,8 @@ const addTargetSchema = z.object({ ip: z.string().refine(isTargetValid), method: z.string().nullable(), port: z.coerce.number().int().positive(), - siteId: z.number().int().positive(), - path: z.string().optional().nullable(), - pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable() -}).refine( - (data) => { - // If path is provided, pathMatchType must be provided - if (data.path && !data.pathMatchType) { - return false; - } - // If pathMatchType is provided, path must be provided - if (data.pathMatchType && !data.path) { - return false; - } - // Validate path based on pathMatchType - if (data.path && data.pathMatchType) { - switch (data.pathMatchType) { - case "exact": - case "prefix": - // Path should start with / - return data.path.startsWith("/"); - case "regex": - // Validate regex - try { - new RegExp(data.path); - return true; - } catch { - return false; - } - } - } - return true; - }, - { - message: "Invalid path configuration" - } -); + siteId: z.number().int().positive() +}); type BaseResourceFormValues = z.infer; type HttpResourceFormValues = z.infer; @@ -238,9 +202,7 @@ export default function Page() { defaultValues: { ip: "", method: baseForm.watch("http") ? "http" : null, - port: "" as any as number, - path: null, - pathMatchType: null + port: "" as any as number } as z.infer }); @@ -311,8 +273,6 @@ export default function Page() { const newTarget: LocalTarget = { ...data, - path: data.path || null, - pathMatchType: data.pathMatchType || null, siteType: site?.type || null, enabled: true, targetId: new Date().getTime(), @@ -324,9 +284,7 @@ export default function Page() { addTargetForm.reset({ ip: "", method: baseForm.watch("http") ? "http" : null, - port: "" as any as number, - path: null, - pathMatchType: null + port: "" as any as number }); } @@ -368,17 +326,10 @@ export default function Page() { http: baseData.http }; - let sanitizedSubdomain: string | undefined; - if (isHttp) { const httpData = httpForm.getValues(); - - sanitizedSubdomain = httpData.subdomain - ? finalizeSubdomainSanitize(httpData.subdomain) - : undefined; - Object.assign(payload, { - subdomain: sanitizedSubdomain ? toASCII(sanitizedSubdomain) : undefined, + subdomain: httpData.subdomain ? toASCII(httpData.subdomain) : undefined, domainId: httpData.domainId, protocol: "tcp" }); @@ -408,7 +359,6 @@ export default function Page() { if (res && res.status === 201) { const id = res.data.data.resourceId; - const niceId = res.data.data.niceId; setResourceId(id); // Create targets if any exist @@ -420,9 +370,7 @@ export default function Page() { port: target.port, method: target.method, enabled: target.enabled, - siteId: target.siteId, - path: target.path, - pathMatchType: target.pathMatchType + siteId: target.siteId }; await api.put(`/resource/${id}/target`, data); @@ -441,7 +389,7 @@ export default function Page() { } if (isHttp) { - router.push(`/${orgId}/settings/resources/${niceId}`); + router.push(`/${orgId}/settings/resources/${id}`); } else { const tcpUdpData = tcpUdpForm.getValues(); // Only show config snippets if enableProxy is explicitly true @@ -545,98 +493,6 @@ export default function Page() { }, []); const columns: ColumnDef[] = [ - { - accessorKey: "path", - header: t("matchPath"), - cell: ({ row }) => { - const [showPathInput, setShowPathInput] = useState( - !!(row.original.path || row.original.pathMatchType) - ); - - if (!showPathInput) { - return ( - - ); - } - - return ( -
- - { - const value = e.target.value.trim(); - if (!value) { - setShowPathInput(false); - updateTarget(row.original.targetId, { - ...row.original, - path: null, - pathMatchType: null - }); - } else { - updateTarget(row.original.targetId, { - ...row.original, - path: value - }); - } - }} - /> - - - -
- ); - } - }, { accessorKey: "siteId", header: t("site"), @@ -774,29 +630,19 @@ export default function Page() { defaultValue={row.original.ip} className="min-w-[150px]" onBlur={(e) => { - const input = e.target.value.trim(); - const hasProtocol = /^(https?|h2c):\/\//.test(input); - const hasPort = /:\d+(?:\/|$)/.test(input); + const parsed = parseHostTarget(e.target.value); - if (hasProtocol || hasPort) { - const parsed = parseHostTarget(input); - if (parsed) { - updateTarget(row.original.targetId, { - ...row.original, - method: hasProtocol ? parsed.protocol : row.original.method, - ip: parsed.host, - port: hasPort ? parsed.port : row.original.port - }); - } else { - updateTarget(row.original.targetId, { - ...row.original, - ip: input - }); - } + if (parsed) { + updateTarget(row.original.targetId, { + ...row.original, + method: parsed.protocol, + ip: parsed.host, + port: parsed.port ? Number(parsed.port) : undefined, + }); } else { updateTarget(row.original.targetId, { ...row.original, - ip: input + ip: e.target.value, }); } }} @@ -898,11 +744,6 @@ export default function Page() {
{ - if (e.key === "Enter") { - e.preventDefault(); // block default enter refresh - } - }} className="space-y-4" id="base-resource-form" > @@ -1015,11 +856,6 @@ export default function Page() { { - if (e.key === "Enter") { - e.preventDefault(); // block default enter refresh - } - }} className="space-y-4" id="tcp-udp-settings-form" > @@ -1368,21 +1204,11 @@ export default function Page() { id="ip" {...field} onBlur={(e) => { - const input = e.target.value.trim(); - const hasProtocol = /^(https?|h2c):\/\//.test(input); - const hasPort = /:\d+(?:\/|$)/.test(input); - - if (hasProtocol || hasPort) { - const parsed = parseHostTarget(input); - if (parsed) { - if (hasProtocol || !addTargetForm.getValues("method")) { - addTargetForm.setValue("method", parsed.protocol); - } - addTargetForm.setValue("ip", parsed.host); - if (hasPort || !addTargetForm.getValues("port")) { - addTargetForm.setValue("port", parsed.port); - } - } + const parsed = parseHostTarget(e.target.value); + if (parsed) { + addTargetForm.setValue("method", parsed.protocol); + addTargetForm.setValue("ip", parsed.host); + addTargetForm.setValue("port", parsed.port); } else { field.onBlur(); } @@ -1594,9 +1420,6 @@ export default function Page() {

{t("resourceAddEntrypoints")}

-

- {t("resourceAddEntrypointsEditFile")} -

{t("resourceExposePorts")} -

- {t("resourceExposePortsEditFile")} -

; windows: Record; docker: Record; - kubernetes: Record; podman: Record; nixos: Record; }; @@ -87,7 +83,6 @@ type Commands = { const platforms = [ "linux", "docker", - "kubernetes", "podman", "mac", "windows", @@ -282,18 +277,6 @@ PersistentKeepalive = 5`; `docker run -dit fosrl/newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}` ] }, - kubernetes: { - "Helm Chart": [ - `helm repo add fossorial https://charts.fossorial.io`, - `helm repo update fossorial`, - `helm install newt fossorial/newt \\ - --create-namespace \\ - --set newtInstances[0].name="main-tunnel" \\ - --set-string newtInstances[0].auth.keys.endpointKey="${endpoint}" \\ - --set-string newtInstances[0].auth.keys.idKey="${id}" \\ - --set-string newtInstances[0].auth.keys.secretKey="${secret}"` - ] - }, podman: { "Podman Quadlet": [ `[Unit] @@ -341,8 +324,6 @@ WantedBy=default.target` return ["x64"]; case "docker": return ["Docker Compose", "Docker Run"]; - case "kubernetes": - return ["Helm Chart"]; case "podman": return ["Podman Quadlet", "Podman Run"]; case "freebsd": @@ -364,8 +345,6 @@ WantedBy=default.target` return "macOS"; case "docker": return "Docker"; - case "kubernetes": - return "Kubernetes"; case "podman": return "Podman"; case "freebsd": @@ -412,8 +391,6 @@ WantedBy=default.target` return ; case "docker": return ; - case "kubernetes": - return ; case "podman": return ; case "freebsd": @@ -643,11 +620,6 @@ WantedBy=default.target` { - if (e.key === "Enter") { - e.preventDefault(); // block default enter refresh - } - }} className="space-y-4" id="create-site-form" > diff --git a/src/app/[orgId]/settings/sites/page.tsx b/src/app/[orgId]/settings/sites/page.tsx index a854083c..10bcad53 100644 --- a/src/app/[orgId]/settings/sites/page.tsx +++ b/src/app/[orgId]/settings/sites/page.tsx @@ -2,9 +2,9 @@ import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import { ListSitesResponse } from "@server/routers/site"; import { AxiosResponse } from "axios"; -import SitesTable, { SiteRow } from "../../../../components/SitesTable"; +import SitesTable, { SiteRow } from "./SitesTable"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import SitesSplashCard from "../../../../components/SitesSplashCard"; +import SitesSplashCard from "./SitesSplashCard"; import { getTranslations } from "next-intl/server"; type SitesPageProps = { diff --git a/src/components/ApiKeysDataTable.tsx b/src/app/admin/api-keys/ApiKeysDataTable.tsx similarity index 100% rename from src/components/ApiKeysDataTable.tsx rename to src/app/admin/api-keys/ApiKeysDataTable.tsx diff --git a/src/components/ApiKeysTable.tsx b/src/app/admin/api-keys/ApiKeysTable.tsx similarity index 98% rename from src/components/ApiKeysTable.tsx rename to src/app/admin/api-keys/ApiKeysTable.tsx index 99094651..02aead9e 100644 --- a/src/components/ApiKeysTable.tsx +++ b/src/app/admin/api-keys/ApiKeysTable.tsx @@ -18,7 +18,7 @@ import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import moment from "moment"; -import { ApiKeysDataTable } from "@app/components/ApiKeysDataTable"; +import { ApiKeysDataTable } from "./ApiKeysDataTable"; import { useTranslations } from "next-intl"; export type ApiKeyRow = { diff --git a/src/app/admin/api-keys/create/page.tsx b/src/app/admin/api-keys/create/page.tsx index b5a61306..2f95c7fd 100644 --- a/src/app/admin/api-keys/create/page.tsx +++ b/src/app/admin/api-keys/create/page.tsx @@ -200,11 +200,6 @@ export default function Page() { { - if (e.key === "Enter") { - e.preventDefault(); // block default enter refresh - } - }} className="space-y-4" id="create-site-form" > diff --git a/src/app/admin/api-keys/page.tsx b/src/app/admin/api-keys/page.tsx index e518911f..22607f2f 100644 --- a/src/app/admin/api-keys/page.tsx +++ b/src/app/admin/api-keys/page.tsx @@ -3,7 +3,7 @@ import { authCookieHeader } from "@app/lib/api/cookies"; import { AxiosResponse } from "axios"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { ListRootApiKeysResponse } from "@server/routers/apiKeys"; -import ApiKeysTable, { ApiKeyRow } from "../../../components/ApiKeysTable"; +import ApiKeysTable, { ApiKeyRow } from "./ApiKeysTable"; import { getTranslations } from "next-intl/server"; type ApiKeyPageProps = {}; diff --git a/src/components/AdminIdpDataTable.tsx b/src/app/admin/idp/AdminIdpDataTable.tsx similarity index 100% rename from src/components/AdminIdpDataTable.tsx rename to src/app/admin/idp/AdminIdpDataTable.tsx diff --git a/src/components/AdminIdpTable.tsx b/src/app/admin/idp/AdminIdpTable.tsx similarity index 94% rename from src/components/AdminIdpTable.tsx rename to src/app/admin/idp/AdminIdpTable.tsx index 8849ba25..fa7de6da 100644 --- a/src/components/AdminIdpTable.tsx +++ b/src/app/admin/idp/AdminIdpTable.tsx @@ -1,7 +1,7 @@ "use client"; import { ColumnDef } from "@tanstack/react-table"; -import { IdpDataTable } from "@app/components/AdminIdpDataTable"; +import { IdpDataTable } from "./AdminIdpDataTable"; import { Button } from "@app/components/ui/button"; import { ArrowRight, ArrowUpDown, MoreHorizontal } from "lucide-react"; import { useState } from "react"; @@ -20,14 +20,12 @@ import { } from "@app/components/ui/dropdown-menu"; import Link from "next/link"; import { useTranslations } from "next-intl"; -import IdpTypeBadge from "./IdpTypeBadge"; export type IdpRow = { idpId: number; name: string; type: string; orgCount: number; - variant?: string; }; type Props = { @@ -59,6 +57,15 @@ export default function IdpTable({ idps }: Props) { } }; + const getTypeDisplay = (type: string) => { + switch (type) { + case "oidc": + return "OAuth2/OIDC"; + default: + return type; + } + }; + const columns: ColumnDef[] = [ { accessorKey: "idpId", @@ -109,8 +116,9 @@ export default function IdpTable({ idps }: Props) { }, cell: ({ row }) => { const type = row.original.type; - const variant = row.original.variant; - return ; + return ( + {getTypeDisplay(type)} + ); } }, { diff --git a/src/components/PolicyDataTable.tsx b/src/app/admin/idp/[idpId]/policies/PolicyDataTable.tsx similarity index 100% rename from src/components/PolicyDataTable.tsx rename to src/app/admin/idp/[idpId]/policies/PolicyDataTable.tsx diff --git a/src/components/PolicyTable.tsx b/src/app/admin/idp/[idpId]/policies/PolicyTable.tsx similarity index 100% rename from src/components/PolicyTable.tsx rename to src/app/admin/idp/[idpId]/policies/PolicyTable.tsx diff --git a/src/app/admin/idp/[idpId]/policies/page.tsx b/src/app/admin/idp/[idpId]/policies/page.tsx index 01b186bf..aadd6eb8 100644 --- a/src/app/admin/idp/[idpId]/policies/page.tsx +++ b/src/app/admin/idp/[idpId]/policies/page.tsx @@ -31,7 +31,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; import { InfoIcon, ExternalLink, CheckIcon } from "lucide-react"; -import PolicyTable, { PolicyRow } from "../../../../../components/PolicyTable"; +import PolicyTable, { PolicyRow } from "./PolicyTable"; import { AxiosResponse } from "axios"; import { ListOrgsResponse } from "@server/routers/org"; import { diff --git a/src/app/admin/idp/page.tsx b/src/app/admin/idp/page.tsx index fef0990c..4db77785 100644 --- a/src/app/admin/idp/page.tsx +++ b/src/app/admin/idp/page.tsx @@ -2,7 +2,7 @@ import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import { AxiosResponse } from "axios"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import IdpTable, { IdpRow } from "../../../components/AdminIdpTable"; +import IdpTable, { IdpRow } from "./AdminIdpTable"; import { getTranslations } from "next-intl/server"; export default async function IdpPage() { @@ -16,7 +16,7 @@ export default async function IdpPage() { } catch (e) { console.error(e); } - + const t = await getTranslations(); return ( diff --git a/src/components/AdminUsersDataTable.tsx b/src/app/admin/users/AdminUsersDataTable.tsx similarity index 100% rename from src/components/AdminUsersDataTable.tsx rename to src/app/admin/users/AdminUsersDataTable.tsx diff --git a/src/app/admin/users/AdminUsersTable.tsx b/src/app/admin/users/AdminUsersTable.tsx index 8e75ff24..6c5e4613 100644 --- a/src/app/admin/users/AdminUsersTable.tsx +++ b/src/app/admin/users/AdminUsersTable.tsx @@ -1,7 +1,7 @@ "use client"; import { ColumnDef } from "@tanstack/react-table"; -import { UsersDataTable } from "@app/components/AdminUsersDataTable"; +import { UsersDataTable } from "./AdminUsersDataTable"; import { Button } from "@app/components/ui/button"; import { ArrowRight, ArrowUpDown, MoreHorizontal } from "lucide-react"; import { useRouter } from "next/navigation"; diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index bf6547a3..e9673374 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -3,7 +3,7 @@ import { authCookieHeader } from "@app/lib/api/cookies"; import { AxiosResponse } from "axios"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { AdminListUsersResponse } from "@server/routers/user/adminListUsers"; -import UsersTable, { GlobalUserRow } from "../../../components/AdminUsersTable"; +import UsersTable, { GlobalUserRow } from "./AdminUsersTable"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; import { InfoIcon } from "lucide-react"; import { getTranslations } from "next-intl/server"; diff --git a/src/components/ValidateOidcToken.tsx b/src/app/auth/idp/[idpId]/oidc/callback/ValidateOidcToken.tsx similarity index 100% rename from src/components/ValidateOidcToken.tsx rename to src/app/auth/idp/[idpId]/oidc/callback/ValidateOidcToken.tsx diff --git a/src/app/auth/idp/[idpId]/oidc/callback/page.tsx b/src/app/auth/idp/[idpId]/oidc/callback/page.tsx index 2ff8d09a..1c0f8125 100644 --- a/src/app/auth/idp/[idpId]/oidc/callback/page.tsx +++ b/src/app/auth/idp/[idpId]/oidc/callback/page.tsx @@ -1,5 +1,5 @@ import { cookies } from "next/headers"; -import ValidateOidcToken from "@app/components/ValidateOidcToken"; +import ValidateOidcToken from "./ValidateOidcToken"; import { cache } from "react"; import { priv } from "@app/lib/api"; import { AxiosResponse } from "axios"; diff --git a/src/components/DashboardLoginForm.tsx b/src/app/auth/login/DashboardLoginForm.tsx similarity index 100% rename from src/components/DashboardLoginForm.tsx rename to src/app/auth/login/DashboardLoginForm.tsx diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 1068c4f7..cad0ce58 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -2,7 +2,7 @@ import { verifySession } from "@app/lib/auth/verifySession"; import Link from "next/link"; import { redirect } from "next/navigation"; import { cache } from "react"; -import DashboardLoginForm from "@app/components/DashboardLoginForm"; +import DashboardLoginForm from "./DashboardLoginForm"; import { Mail } from "lucide-react"; import { pullEnv } from "@app/lib/pullEnv"; import { cleanRedirect } from "@app/lib/cleanRedirect"; @@ -42,8 +42,7 @@ export default async function Page(props: { )(); const loginIdps = idpsRes.data.data.idps.map((idp) => ({ idpId: idp.idpId, - name: idp.name, - variant: idp.variant + name: idp.name })) as LoginFormIDP[]; const t = await getTranslations(); diff --git a/src/app/auth/reset-password/ResetPasswordForm.tsx b/src/app/auth/reset-password/ResetPasswordForm.tsx index 3d456bd9..596afb99 100644 --- a/src/app/auth/reset-password/ResetPasswordForm.tsx +++ b/src/app/auth/reset-password/ResetPasswordForm.tsx @@ -35,7 +35,7 @@ import { ResetPasswordResponse } from "@server/routers/auth"; import { Loader2 } from "lucide-react"; -import { Alert, AlertDescription } from "@app/components/ui/alert"; +import { Alert, AlertDescription } from "../../../components/ui/alert"; import { toast } from "@app/hooks/useToast"; import { useRouter } from "next/navigation"; import { formatAxiosError } from "@app/lib/api"; @@ -210,7 +210,7 @@ export default function ResetPasswordForm({ } catch (verificationError) { console.error("Failed to send verification code:", verificationError); } - + if (redirect) { router.push(`/auth/verify-email?redirect=${redirect}`); } else { @@ -254,8 +254,8 @@ export default function ResetPasswordForm({ {quickstart ? t('completeAccountSetup') : t('passwordReset')} - {quickstart - ? t('completeAccountSetupDescription') + {quickstart + ? t('completeAccountSetupDescription') : t('passwordResetDescription') } @@ -282,8 +282,8 @@ export default function ResetPasswordForm({ - {quickstart - ? t('accountSetupSent') + {quickstart + ? t('accountSetupSent') : t('passwordResetSent') } @@ -325,8 +325,8 @@ export default function ResetPasswordForm({ render={({ field }) => ( - {quickstart - ? t('accountSetupCode') + {quickstart + ? t('accountSetupCode') : t('passwordResetCode') } @@ -338,8 +338,8 @@ export default function ResetPasswordForm({ - {quickstart - ? t('accountSetupCodeDescription') + {quickstart + ? t('accountSetupCodeDescription') : t('passwordResetCodeDescription') } @@ -354,8 +354,8 @@ export default function ResetPasswordForm({ render={({ field }) => ( - {quickstart - ? t('passwordCreate') + {quickstart + ? t('passwordCreate') : t('passwordNew') } @@ -375,8 +375,8 @@ export default function ResetPasswordForm({ render={({ field }) => ( - {quickstart - ? t('passwordCreateConfirm') + {quickstart + ? t('passwordCreateConfirm') : t('passwordNewConfirm') } @@ -490,8 +490,8 @@ export default function ResetPasswordForm({ {isSubmitting && ( )} - {quickstart - ? t('accountSetupSubmit') + {quickstart + ? t('accountSetupSubmit') : t('passwordResetSubmit') } diff --git a/src/app/auth/reset-password/page.tsx b/src/app/auth/reset-password/page.tsx index 490f89f7..f06c7c4c 100644 --- a/src/app/auth/reset-password/page.tsx +++ b/src/app/auth/reset-password/page.tsx @@ -1,7 +1,7 @@ import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; import { cache } from "react"; -import ResetPasswordForm from "@app/components/ResetPasswordForm"; +import ResetPasswordForm from "./ResetPasswordForm"; import Link from "next/link"; import { cleanRedirect } from "@app/lib/cleanRedirect"; import { getTranslations } from "next-intl/server"; diff --git a/src/components/AccessToken.tsx b/src/app/auth/resource/[resourceId]/AccessToken.tsx similarity index 100% rename from src/components/AccessToken.tsx rename to src/app/auth/resource/[resourceId]/AccessToken.tsx diff --git a/src/components/AutoLoginHandler.tsx b/src/app/auth/resource/[resourceId]/AutoLoginHandler.tsx similarity index 100% rename from src/components/AutoLoginHandler.tsx rename to src/app/auth/resource/[resourceId]/AutoLoginHandler.tsx diff --git a/src/components/ResourceAccessDenied.tsx b/src/app/auth/resource/[resourceId]/ResourceAccessDenied.tsx similarity index 100% rename from src/components/ResourceAccessDenied.tsx rename to src/app/auth/resource/[resourceId]/ResourceAccessDenied.tsx diff --git a/src/components/ResourceAuthPortal.tsx b/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx similarity index 99% rename from src/components/ResourceAuthPortal.tsx rename to src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx index 1ab131e3..6f14f915 100644 --- a/src/components/ResourceAuthPortal.tsx +++ b/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx @@ -38,7 +38,7 @@ import { AuthWithPasswordResponse, AuthWithWhitelistResponse } from "@server/routers/resource"; -import ResourceAccessDenied from "@app/components/ResourceAccessDenied"; +import ResourceAccessDenied from "./ResourceAccessDenied"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; diff --git a/src/components/ResourceNotFound.tsx b/src/app/auth/resource/[resourceId]/ResourceNotFound.tsx similarity index 100% rename from src/components/ResourceNotFound.tsx rename to src/app/auth/resource/[resourceId]/ResourceNotFound.tsx diff --git a/src/app/auth/resource/[resourceId]/page.tsx b/src/app/auth/resource/[resourceId]/page.tsx index 25580ee7..347d3586 100644 --- a/src/app/auth/resource/[resourceId]/page.tsx +++ b/src/app/auth/resource/[resourceId]/page.tsx @@ -2,20 +2,20 @@ import { GetResourceAuthInfoResponse, GetExchangeTokenResponse } from "@server/routers/resource"; -import ResourceAuthPortal from "@app/components/ResourceAuthPortal"; +import ResourceAuthPortal from "./ResourceAuthPortal"; import { internal, priv } from "@app/lib/api"; import { AxiosResponse } from "axios"; import { authCookieHeader } from "@app/lib/api/cookies"; import { cache } from "react"; import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; -import ResourceNotFound from "@app/components/ResourceNotFound"; -import ResourceAccessDenied from "@app/components/ResourceAccessDenied"; -import AccessToken from "@app/components/AccessToken"; +import ResourceNotFound from "./ResourceNotFound"; +import ResourceAccessDenied from "./ResourceAccessDenied"; +import AccessToken from "./AccessToken"; import { pullEnv } from "@app/lib/pullEnv"; import { LoginFormIDP } from "@app/components/LoginForm"; import { ListIdpsResponse } from "@server/routers/idp"; -import AutoLoginHandler from "@app/components/AutoLoginHandler"; +import AutoLoginHandler from "./AutoLoginHandler"; export const dynamic = "force-dynamic"; diff --git a/src/components/SignupForm.tsx b/src/app/auth/signup/SignupForm.tsx similarity index 100% rename from src/components/SignupForm.tsx rename to src/app/auth/signup/SignupForm.tsx diff --git a/src/app/auth/signup/page.tsx b/src/app/auth/signup/page.tsx index b4f4fddd..673e69bf 100644 --- a/src/app/auth/signup/page.tsx +++ b/src/app/auth/signup/page.tsx @@ -1,4 +1,4 @@ -import SignupForm from "@app/components/SignupForm"; +import SignupForm from "@app/app/auth/signup/SignupForm"; import { verifySession } from "@app/lib/auth/verifySession"; import { cleanRedirect } from "@app/lib/cleanRedirect"; import { pullEnv } from "@app/lib/pullEnv"; @@ -11,7 +11,7 @@ import { getTranslations } from "next-intl/server"; export const dynamic = "force-dynamic"; export default async function Page(props: { - searchParams: Promise<{ + searchParams: Promise<{ redirect: string | undefined; email: string | undefined; }>; diff --git a/src/components/VerifyEmailForm.tsx b/src/app/auth/verify-email/VerifyEmailForm.tsx similarity index 99% rename from src/components/VerifyEmailForm.tsx rename to src/app/auth/verify-email/VerifyEmailForm.tsx index 9cf48a2f..e9761eef 100644 --- a/src/components/VerifyEmailForm.tsx +++ b/src/app/auth/verify-email/VerifyEmailForm.tsx @@ -30,7 +30,7 @@ import { import { AxiosResponse } from "axios"; import { VerifyEmailResponse } from "@server/routers/auth"; import { ArrowRight, IdCard, Loader2 } from "lucide-react"; -import { Alert, AlertDescription } from "./ui/alert"; +import { Alert, AlertDescription } from "../../../components/ui/alert"; import { toast } from "@app/hooks/useToast"; import { useRouter } from "next/navigation"; import { formatAxiosError } from "@app/lib/api"; diff --git a/src/app/auth/verify-email/page.tsx b/src/app/auth/verify-email/page.tsx index c549abf0..10ad809f 100644 --- a/src/app/auth/verify-email/page.tsx +++ b/src/app/auth/verify-email/page.tsx @@ -1,4 +1,4 @@ -import VerifyEmailForm from "@app/components/VerifyEmailForm"; +import VerifyEmailForm from "@app/app/auth/verify-email/VerifyEmailForm"; import { verifySession } from "@app/lib/auth/verifySession"; import { cleanRedirect } from "@app/lib/cleanRedirect"; import { pullEnv } from "@app/lib/pullEnv"; diff --git a/src/components/InviteStatusCard.tsx b/src/app/invite/InviteStatusCard.tsx similarity index 100% rename from src/components/InviteStatusCard.tsx rename to src/app/invite/InviteStatusCard.tsx diff --git a/src/app/invite/page.tsx b/src/app/invite/page.tsx index 49c5a2c5..2e0c11e2 100644 --- a/src/app/invite/page.tsx +++ b/src/app/invite/page.tsx @@ -4,7 +4,7 @@ import { verifySession } from "@app/lib/auth/verifySession"; import { AcceptInviteResponse } from "@server/routers/user"; import { AxiosResponse } from "axios"; import { redirect } from "next/navigation"; -import InviteStatusCard from "../../components/InviteStatusCard"; +import InviteStatusCard from "./InviteStatusCard"; import { formatAxiosError } from "@app/lib/api"; import { getTranslations } from "next-intl/server"; @@ -72,14 +72,14 @@ export default async function InvitePage(props: { const type = cardType(); if (!user && type === "user_does_not_exist") { - const redirectUrl = emailParam + const redirectUrl = emailParam ? `/auth/signup?redirect=/invite?token=${params.token}&email=${encodeURIComponent(emailParam)}` : `/auth/signup?redirect=/invite?token=${params.token}`; redirect(redirectUrl); } if (!user && type === "not_logged_in") { - const redirectUrl = emailParam + const redirectUrl = emailParam ? `/auth/login?redirect=/invite?token=${params.token}&email=${encodeURIComponent(emailParam)}` : `/auth/login?redirect=/invite?token=${params.token}`; redirect(redirectUrl); diff --git a/src/app/s/[accessToken]/page.tsx b/src/app/s/[accessToken]/page.tsx index 61299366..d28ff7be 100644 --- a/src/app/s/[accessToken]/page.tsx +++ b/src/app/s/[accessToken]/page.tsx @@ -1,4 +1,4 @@ -import AccessToken from "@app/components/AccessToken"; +import AccessToken from "@app/app/auth/resource/[resourceId]/AccessToken"; export default async function ResourceAuthPage(props: { params: Promise<{ accessToken: string }>; diff --git a/src/components/AdminUsersTable.tsx b/src/components/AdminUsersTable.tsx deleted file mode 100644 index 8e75ff24..00000000 --- a/src/components/AdminUsersTable.tsx +++ /dev/null @@ -1,269 +0,0 @@ -"use client"; - -import { ColumnDef } from "@tanstack/react-table"; -import { UsersDataTable } from "@app/components/AdminUsersDataTable"; -import { Button } from "@app/components/ui/button"; -import { ArrowRight, ArrowUpDown, MoreHorizontal } from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; -import { toast } from "@app/hooks/useToast"; -import { formatAxiosError } from "@app/lib/api"; -import { createApiClient } from "@app/lib/api"; -import { useEnvContext } from "@app/hooks/useEnvContext"; -import { useTranslations } from "next-intl"; -import { - DropdownMenu, - DropdownMenuItem, - DropdownMenuContent, - DropdownMenuTrigger -} from "@app/components/ui/dropdown-menu"; - -export type GlobalUserRow = { - id: string; - name: string | null; - username: string; - email: string | null; - type: string; - idpId: number | null; - idpName: string; - dateCreated: string; - twoFactorEnabled: boolean | null; - twoFactorSetupRequested: boolean | null; -}; - -type Props = { - users: GlobalUserRow[]; -}; - -export default function UsersTable({ users }: Props) { - const router = useRouter(); - const t = useTranslations(); - - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [selected, setSelected] = useState(null); - const [rows, setRows] = useState(users); - - const api = createApiClient(useEnvContext()); - - const deleteUser = (id: string) => { - api.delete(`/user/${id}`) - .catch((e) => { - console.error(t("userErrorDelete"), e); - toast({ - variant: "destructive", - title: t("userErrorDelete"), - description: formatAxiosError(e, t("userErrorDelete")) - }); - }) - .then(() => { - router.refresh(); - setIsDeleteModalOpen(false); - - const newRows = rows.filter((row) => row.id !== id); - - setRows(newRows); - }); - }; - - const columns: ColumnDef[] = [ - { - accessorKey: "id", - header: ({ column }) => { - return ( - - ); - } - }, - { - accessorKey: "username", - header: ({ column }) => { - return ( - - ); - } - }, - { - accessorKey: "email", - header: ({ column }) => { - return ( - - ); - } - }, - { - accessorKey: "name", - header: ({ column }) => { - return ( - - ); - } - }, - { - accessorKey: "idpName", - header: ({ column }) => { - return ( - - ); - } - }, - { - accessorKey: "twoFactorEnabled", - header: ({ column }) => { - return ( - - ); - }, - cell: ({ row }) => { - const userRow = row.original; - - return ( -
- - {userRow.twoFactorEnabled || - userRow.twoFactorSetupRequested ? ( - - {t("enabled")} - - ) : ( - {t("disabled")} - )} - -
- ); - } - }, - { - id: "actions", - cell: ({ row }) => { - const r = row.original; - return ( - <> -
- - - - - - { - setSelected(r); - setIsDeleteModalOpen(true); - }} - > - {t("delete")} - - - - -
- - ); - } - } - ]; - - return ( - <> - {selected && ( - { - setIsDeleteModalOpen(val); - setSelected(null); - }} - dialog={ -
-

- {t("userQuestionRemove", { - selectedUser: - selected?.email || - selected?.name || - selected?.username - })} -

- -

- {t("userMessageRemove")} -

- -

{t("userMessageConfirm")}

-
- } - buttonText={t("userDeleteConfirm")} - onConfirm={async () => deleteUser(selected!.id)} - string={ - selected.email || selected.name || selected.username - } - title={t("userDeleteServer")} - /> - )} - - - - ); -} diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx index 63dfc11d..ccfddcd8 100644 --- a/src/components/CreateInternalResourceDialog.tsx +++ b/src/components/CreateInternalResourceDialog.tsx @@ -352,7 +352,7 @@ export default function CreateInternalResourceDialog({ render={({ field }) => ( - {t("targetAddr")} + {t("createInternalResourceDialogDestinationIP")} ( - {t("targetPort")} + {t("createInternalResourceDialogDestinationPort")} void; cols?: number; - hideFreeDomain?: boolean; } export default function DomainPicker2({ orgId, onDomainChange, - cols = 2, - hideFreeDomain = false + cols = 2 }: DomainPicker2Props) { const { env } = useEnvContext(); const api = createApiClient({ env }); @@ -155,12 +153,12 @@ export default function DomainPicker2({ fullDomain: firstOrgDomain.baseDomain, baseDomain: firstOrgDomain.baseDomain }); - } else if ((build === "saas" || build === "enterprise") && !hideFreeDomain) { + } else if (build === "saas" || build === "enterprise") { // If no organization domains, select the provided domain option const domainOptionText = build === "enterprise" - ? t("domainPickerProvidedDomain") - : t("domainPickerFreeProvidedDomain"); + ? "Provided Domain" + : "Free Provided Domain"; const freeDomainOption: DomainOption = { id: "provided-search", domain: domainOptionText, @@ -173,8 +171,8 @@ export default function DomainPicker2({ console.error("Failed to load organization domains:", error); toast({ variant: "destructive", - title: t("domainPickerError"), - description: t("domainPickerErrorLoadDomains") + title: "Error", + description: "Failed to load organization domains" }); } finally { setLoadingDomains(false); @@ -182,7 +180,7 @@ export default function DomainPicker2({ }; loadOrganizationDomains(); - }, [orgId, api, hideFreeDomain]); + }, [orgId, api]); const checkAvailability = useCallback( async (input: string) => { @@ -204,8 +202,8 @@ export default function DomainPicker2({ setAvailableOptions([]); toast({ variant: "destructive", - title: t("domainPickerError"), - description: t("domainPickerErrorCheckAvailability") + title: "Error", + description: "Failed to check domain availability" }); } finally { setIsChecking(false); @@ -248,11 +246,11 @@ export default function DomainPicker2({ }); }); - if ((build === "saas" || build === "enterprise") && !hideFreeDomain) { + if (build === "saas" || build === "enterprise") { const domainOptionText = build === "enterprise" - ? t("domainPickerProvidedDomain") - : t("domainPickerFreeProvidedDomain"); + ? "Provided Domain" + : "Free Provided Domain"; options.push({ id: "provided-search", domain: domainOptionText, @@ -271,8 +269,8 @@ export default function DomainPicker2({ if (!sanitized) { toast({ variant: "destructive", - title: t("domainPickerInvalidSubdomain"), - description: t("domainPickerInvalidSubdomainRemoved", { sub }), + title: "Invalid subdomain", + description: `The input "${sub}" was removed because it's not valid.`, }); return ""; } @@ -285,16 +283,16 @@ export default function DomainPicker2({ if (!ok) { toast({ variant: "destructive", - title: t("domainPickerInvalidSubdomain"), - description: t("domainPickerInvalidSubdomainCannotMakeValid", { sub, domain: base.domain }), + title: "Invalid subdomain", + description: `"${sub}" could not be made valid for ${base.domain}.`, }); return ""; } if (sub !== sanitized) { toast({ - title: t("domainPickerSubdomainSanitized"), - description: t("domainPickerSubdomainCorrected", { sub, sanitized }), + title: "Subdomain sanitized", + description: `"${sub}" was corrected to "${sanitized}"`, }); } @@ -455,7 +453,7 @@ export default function DomainPicker2({ /> {showSubdomainInput && subdomainInput && !isValidSubdomainStructure(subdomainInput) && (

- {t("domainPickerInvalidSubdomainStructure")} + This subdomain contains invalid characters or structure. It will be sanitized automatically when you save.

)} {showSubdomainInput && !subdomainInput && ( @@ -557,8 +555,8 @@ export default function DomainPicker2({ {orgDomain.type.toUpperCase()}{" "} •{" "} {orgDomain.verified - ? t("domainPickerVerified") - : t("domainPickerUnverified")} + ? "Verified" + : "Unverified"} {(build === "saas" || - build === "enterprise") && !hideFreeDomain && ( + build === "enterprise") && ( )} )} {(build === "saas" || - build === "enterprise") && !hideFreeDomain && ( + build === "enterprise") && ( {build === "enterprise" - ? t("domainPickerProvidedDomain") - : t("domainPickerFreeProvidedDomain")} + ? "Provided Domain" + : "Free Provided Domain"} {t( @@ -773,4 +771,4 @@ function debounce any>( func(...args); }, wait); }; -} +} \ No newline at end of file diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx index d09f0b6c..adfed1b7 100644 --- a/src/components/EditInternalResourceDialog.tsx +++ b/src/components/EditInternalResourceDialog.tsx @@ -221,7 +221,7 @@ export default function EditInternalResourceDialog({ name="destinationIp" render={({ field }) => ( - {t("targetAddr")} + {t("editInternalResourceDialogDestinationIP")} @@ -235,7 +235,7 @@ export default function EditInternalResourceDialog({ name="destinationPort" render={({ field }) => ( - {t("targetPort")} + {t("editInternalResourceDialogDestinationPort")} void; - placeholder?: string; - rows?: number; - className?: string; -} - -export function HeadersInput({ - value = "", - onChange, - placeholder = `X-Example-Header: example-value -X-Another-Header: another-value`, - rows = 4, - className -}: HeadersInputProps) { - const [internalValue, setInternalValue] = useState(""); - - // Convert comma-separated to newline-separated for display - const convertToNewlineSeparated = (commaSeparated: string): string => { - if (!commaSeparated || commaSeparated.trim() === "") return ""; - - return commaSeparated - .split(',') - .map(header => header.trim()) - .filter(header => header.length > 0) - .join('\n'); - }; - - // Convert newline-separated to comma-separated for output - const convertToCommaSeparated = (newlineSeparated: string): string => { - if (!newlineSeparated || newlineSeparated.trim() === "") return ""; - - return newlineSeparated - .split('\n') - .map(header => header.trim()) - .filter(header => header.length > 0) - .join(', '); - }; - - // Update internal value when external value changes - useEffect(() => { - setInternalValue(convertToNewlineSeparated(value)); - }, [value]); - - const handleChange = (e: React.ChangeEvent) => { - const newValue = e.target.value; - setInternalValue(newValue); - - // Convert back to comma-separated format for the parent - const commaSeparatedValue = convertToCommaSeparated(newValue); - onChange(commaSeparatedValue); - }; - - return ( -