diff --git a/src/db-init.sql b/src/db-init.sql index 9869079..4f55c2a 100644 --- a/src/db-init.sql +++ b/src/db-init.sql @@ -4,12 +4,20 @@ create table users( email varchar(255) not null, username varchar(255) not null unique, key_key varchar(255), - key_iv varchar(255)); + key_iv varchar(255) +); create unique index users_auth_user_id_idx on users (auth_user_id); +create table instances( + instance_id bigserial primary key, + user_id integer not null references users on delete cascade +); +create unique index instances_user_id_instance_id_idx on instances (instance_id, user_id); + create table user_service_configs( id bigserial primary key, - user_id integer unique not null references users on delete cascade, + user_id integer not null references users on delete cascade, + instance_id integer not null references instances on delete cascade, cloudflare_api_token_enc varchar(255), cloudflare_account_id_enc varchar(255), cloudflare_zone_id_enc varchar(255), @@ -20,25 +28,27 @@ create table user_service_configs( backblaze_key_id_enc varchar(255), backblaze_bucket_url_enc varchar(255) ); -create unique index user_service_configs_user_id_idx on user_service_configs (user_id); +create unique index user_service_configs_user_id_instance_id_idx on user_service_configs (user_id, instance_id); create table user_selected_apps( id bigserial primary key, - user_id integer unique not null references users on delete cascade, + user_id integer not null references users on delete cascade, + instance_id integer not null references instances on delete cascade, wg_easy_version varchar(100), nextcloud_version varchar(100), log_viewer_version varchar(100) ); -create unique index user_selected_apps_user_id_idx on user_selected_apps (user_id); +create unique index user_selected_apps_user_id_instance_id_idx on user_selected_apps (user_id, instance_id); create table user_app_configs( id bigserial primary key, - user_id integer unique not null references users on delete cascade, + user_id integer not null references users on delete cascade, + instance_id integer not null references instances on delete cascade, root_domain varchar(100), config_enc text ); -create unique index user_app_configs_user_id_idx on user_app_configs (user_id); +create unique index user_app_configs_user_id_instance_id_idx on user_app_configs (user_id, instance_id); create type deployment_status as enum ('queued', 'in-progress', 'complete', 'failed'); @@ -46,6 +56,7 @@ create type deployment_status as enum ('queued', 'in-progress', 'complete', 'fai create table deployments( id bigserial primary key, user_id integer not null references users on delete cascade, + instance_id integer not null references instances on delete cascade, started timestamptz, finished timestamptz, status deployment_status not null default 'queued', @@ -62,13 +73,14 @@ create table deployments( log_enc text ); -create index deployments_user_id_idx on deployments (user_id); +create index deployments_user_id_instance_id_idx on deployments (user_id, instance_id); create table user_terraform_state( id bigserial primary key, - user_id integer unique not null references users on delete cascade, + user_id integer not null references users on delete cascade, + instance_id integer not null references instances on delete cascade, state_enc text, state_backup_enc text ); -create unique index user_terraform_state_user_id_idx on user_terraform_state (user_id); +create unique index user_terraform_state_user_id_instance_id_idx on user_terraform_state (user_id, instance_id); diff --git a/src/db.scm b/src/db.scm index a5a7ea9..51777d4 100644 --- a/src/db.scm +++ b/src/db.scm @@ -6,6 +6,7 @@ with-db with-db/transaction create-user delete-user + create-instance get-user-instances update-user-service-config get-user-service-config update-user-selected-apps get-user-selected-apps update-user-app-config get-user-app-config @@ -135,15 +136,25 @@ returning users.user_id;" (string-append (blob->hexstring/uppercase (string->blob enc-user-key)) (blob->hexstring/uppercase (string->blob tag))) user-iv)))) - (query conn "insert into user_service_configs(user_id) values ($1);" user-id) - (query conn "insert into user_selected_apps(user_id) values ($1);" user-id) - (query conn "insert into user_app_configs(user_id) values ($1);" user-id) - (query conn "insert into user_terraform_state(user_id) values ($1);" user-id) user-id)))) (define (delete-user conn user-id) (query conn "delete from users where user_id=$1;" user-id)) +(define (create-instance conn user-id) + (let ((instance-id + (value-at + (query conn + "insert into instances(user_id) values ($1) returning instances.instance_id;" user-id)))) + (query conn "insert into user_service_configs(user_id, instance_id) values ($1, $2);" user-id instance-id) + (query conn "insert into user_selected_apps(user_id, instance_id) values ($1, $2);" user-id instance-id) + (query conn "insert into user_app_configs(user_id, instance_id) values ($1, $2);" user-id instance-id) + (query conn "insert into user_terraform_state(user_id, instance_id) values ($1, $2);" user-id instance-id) + instance-id)) + +(define (get-user-instances conn user-id) + (column-values (query conn "select instance_id from instances where user_id=$1;" user-id))) + (define *user-service-configs-column-map* '((cloudflare-api-token . ("cloudflare_api_token_enc" #t)) (cloudflare-account-id . ("cloudflare_account_id_enc" #t)) @@ -160,7 +171,7 @@ returning users.user_id;" `(,(string->symbol (cadr config)) . (,(car config) ,(caddr config)))) *user-service-configs-column-map*)) -(define (update-user-service-config conn user-id update-alist) +(define (update-user-service-config conn user-id instance-id update-alist) (let ((valid-keys (map car *user-service-configs-column-map*))) (for-each (lambda (update) (if (not (memq (car update) valid-keys)) @@ -176,17 +187,18 @@ returning users.user_id;" (conc (car (alist-ref (car update) *user-service-configs-column-map*)) "=$" i)) update-alist - (iota (length update-alist) 2)) + (iota (length update-alist) 3)) ", ") - " where user_id=$1;") - (cons user-id - (map-in-order (lambda (update) - (if (cadr (alist-ref (car update) *user-service-configs-column-map*)) - (user-encrypt-for-db (cdr update) user-key user-iv user-id) - (cdr update))) - update-alist))))) - -(define (get-user-service-config conn user-id) + " where user_id=$1 and instance_id=$2;") + `(,user-id + ,instance-id + ,@(map-in-order (lambda (update) + (if (cadr (alist-ref (car update) *user-service-configs-column-map*)) + (user-encrypt-for-db (cdr update) user-key user-iv user-id) + (cdr update))) + update-alist))))) + +(define (get-user-service-config conn user-id instance-id) (receive (user-key user-iv auth-user-id) (get-decrypted-user-key-and-iv conn user-id) (let ((res (row-alist @@ -198,8 +210,8 @@ returning users.user_id;" (car (alist-ref (car update) *user-service-configs-column-map*))) *user-service-configs-column-map*) ", ") - " from user_service_configs where user_id=$1;") - user-id)))) + " from user_service_configs where user_id=$1 and instance_id=$2;") + user-id instance-id)))) (map (lambda (item) (let* ((key (car item)) (value (cdr item)) @@ -221,7 +233,7 @@ returning users.user_id;" `(,(string->symbol (cdr config)) . ,(car config))) *user-selected-apps-column-map*)) -(define (update-user-selected-apps conn user-id app-alist) +(define (update-user-selected-apps conn user-id instance-id app-alist) (let ((valid-keys (map car *user-selected-apps-column-map*))) (for-each (lambda (app) (if (not (memq (car app) valid-keys)) @@ -235,13 +247,14 @@ returning users.user_id;" (conc (alist-ref (car app) *user-selected-apps-column-map*) "=$" i)) app-alist - (iota (length app-alist) 2)) + (iota (length app-alist) 3)) ", ") - " where user_id=$1;") - (cons user-id - (map-in-order cdr app-alist)))) + " where user_id=$1 and instance_id=$2;") + `(,user-id + ,instance-id + ,@(map-in-order cdr app-alist)))) -(define (get-user-selected-apps conn user-id) +(define (get-user-selected-apps conn user-id instance-id) (let ((res (row-alist (query conn (string-append @@ -249,8 +262,8 @@ returning users.user_id;" (string-intersperse (map-in-order cdr *user-selected-apps-column-map*) ", ") - " from user_selected_apps where user_id=$1;") - user-id)))) + " from user_selected_apps where user_id=$1 and instance_id=$2;") + user-id instance-id)))) (map (lambda (item) (let* ((key (car item)) (value (cdr item)) @@ -260,30 +273,31 @@ returning users.user_id;" value)))) res))) -(define (update-user-app-config conn user-id config) +(define (update-user-app-config conn user-id instance-id config) (receive (user-key user-iv auth-user-id) (get-decrypted-user-key-and-iv conn user-id) (query conn - "update user_app_configs set config_enc=$1 where user_id=$2;" + "update user_app_configs set config_enc=$1 where user_id=$2 and instance_id=$3;" (user-encrypt-for-db (with-output-to-string (lambda () (write config))) user-key user-iv user-id) - user-id))) + user-id instance-id))) -(define (update-root-domain conn user-id root-domain) +(define (update-root-domain conn user-id instance-id root-domain) (query conn - "update user_app_configs set root_domain=$1 where user_id=$2;" + "update user_app_configs set root_domain=$1 where user_id=$2 and instance_id=$3;" root-domain - user-id)) + user-id + instance-id)) -(define (get-user-app-config conn user-id) +(define (get-user-app-config conn user-id instance-id) (receive (user-key user-iv auth-user-id) (get-decrypted-user-key-and-iv conn user-id) (let ((res (row-alist (query conn - "select root_domain, config_enc from user_app_configs where user_id=$1;" - user-id)))) + "select root_domain, config_enc from user_app_configs where user_id=$1 and instance_id=$2;" + user-id instance-id)))) `((root-domain . ,(if (sql-null? (alist-ref 'root_domain res)) #f (alist-ref 'root_domain res))) @@ -298,11 +312,11 @@ returning users.user_id;" (in-progress . "in-progress") (complete . "complete") (failed . "failed"))) -(define (create-deployment conn user-id) +(define (create-deployment conn user-id instance-id) (value-at (query conn - "insert into deployments(user_id, started) values($1, now()) returning deployments.id;" - user-id))) + "insert into deployments(user_id, instance_id, started) values($1, $2, now()) returning deployments.id;" + user-id instance-id))) (define (update-deployment-in-progress conn deployment-id pid) (query conn "update deployments set status=$1, pid=$2 where id=$3;" @@ -319,8 +333,8 @@ returning users.user_id;" (define (get-deployment-status conn deployment-id) (value-at (query conn "select status from deployments where id=$1;" deployment-id))) -(define (get-most-recent-deployment-status conn user-id) - (value-at (query conn "select status from deployments where user_id=$1 order by id DESC limit 1;" user-id))) +(define (get-most-recent-deployment-status conn user-id instance-id) + (value-at (query conn "select status from deployments where user_id=$1 and instance_id=$2 order by id DESC limit 1;" user-id instance-id))) (define *deployments-column-map* '((generate-configs . "generate_configs") @@ -328,7 +342,8 @@ returning users.user_id;" (machine-create . "terraform_machine_create") (machine-destroy . "terraform_machine_destroy") (status . "status") - (id . "id"))) + (id . "id") + (instance-id . "instance_id"))) (define *deployments-reverse-column-map* (map (lambda (config) @@ -374,7 +389,7 @@ returning users.user_id;" (string->symbol value))))) res))) -(define (get-most-recent-deployment-progress conn user-id) +(define (get-most-recent-deployment-progress conn user-id instance-id) (let ((res (row-alist (query conn (string-append @@ -382,57 +397,64 @@ returning users.user_id;" (string-intersperse (map-in-order cdr *deployments-column-map*) ", ") - " from deployments where user_id=$1 order by id DESC limit 1;") - user-id)))) + " from deployments where user_id=$1 and instance_id=$2 order by id DESC limit 1;") + user-id instance-id)))) (map (lambda (item) (let* ((key (car item)) (value (cdr item)) (config (alist-ref key *deployments-reverse-column-map*))) `(,config . ,(if (sql-null? value) #f - (string->symbol value))))) + (if (string? value) + (string->symbol value) + value))))) res))) (define (get-user-deployments conn user-id) - (let ((res (row-alist - (query conn - (string-append - "select " - (string-intersperse - (map-in-order (lambda (d) (string-append "d." (cdr d))) *deployments-column-map*) - ", ") - ", uac.root_domain" - " from deployments as d " - "join user_app_configs uac on uac.user_id = d.user_id" - " where d.user_id=$1 order by d.id DESC limit 1;") - user-id)))) - (list - (map (lambda (item) - (let* ((key (car item)) - (value (cdr item)) - (config (alist-ref key (cons '(root_domain . root-domain) *deployments-reverse-column-map*)))) - `(,config . ,(if (sql-null? value) - #f - (if (string? value) - (string->symbol value) - value))))) - res)))) - -(define (update-user-terraform-state conn user-id state backup) + (let* ((res-raw + (query conn + (string-append + "select " + (string-intersperse + (map-in-order (lambda (d) (string-append "d." (cdr d))) *deployments-column-map*) + ", ") + ", uac.root_domain" + " from deployments as d " + "join user_app_configs uac on uac.user_id = d.user_id and uac.instance_id = d.instance_id" + " where d.user_id=$1 order by d.id DESC limit 1;") + user-id)) + (res (if (> (row-count res-raw) 0) (row-alist res-raw) '()))) + (if (null? res) + '() + ;; I think this is just a hack as currently we only return 1 deployment + (list + (map (lambda (item) + (let* ((key (car item)) + (value (cdr item)) + (config (alist-ref key (cons '(root_domain . root-domain) *deployments-reverse-column-map*)))) + `(,config . ,(if (sql-null? value) + #f + (if (string? value) + (string->symbol value) + value))))) + res))))) + +(define (update-user-terraform-state conn user-id instance-id state backup) (receive (user-key user-iv auth-user-id) (get-decrypted-user-key-and-iv conn user-id) (query conn - "update user_terraform_state set state_enc=$1, state_backup_enc=$2 where user_id=$3;" + "update user_terraform_state set state_enc=$1, state_backup_enc=$2 where user_id=$3 and instance_id=$4;" (user-encrypt-for-db state user-key user-iv user-id) (user-encrypt-for-db backup user-key user-iv user-id) - user-id))) + user-id + instance-id))) -(define (get-user-terraform-state conn user-id) +(define (get-user-terraform-state conn user-id instance-id) (receive (user-key user-iv auth-user-id) (get-decrypted-user-key-and-iv conn user-id) (let ((res (row-alist (query conn - "select state_enc, state_backup_enc from user_terraform_state where user_id=$1;" - user-id)))) + "select state_enc, state_backup_enc from user_terraform_state where user_id=$1 and instance_id=$2;" + user-id instance-id)))) `((state . ,(if (sql-null? (alist-ref 'config_enc res)) "" (user-decrypt-from-db (alist-ref 'state_enc res) user-key user-iv user-id))) @@ -440,15 +462,15 @@ returning users.user_id;" "" (user-decrypt-from-db (alist-ref 'state_backup_enc res) user-key user-iv user-id))))))) -;; (with-db/transaction (lambda (db) (get-user-deployments db 7))) +;; (with-db/transaction (lambda (db) (get-user-deployments db 1))) ;; (with-db/transaction (lambda (db) (get-most-recent-deployment-progress db 7))) ;; (with-db/transaction (lambda (db) (get-deployment-progress db 14))) ;; (with-db/transaction (lambda (db) (update-deployment-progress db 14 '((generate-configs . complete) (custom-image . in-progress) (machine-create . queued))))) ;; (with-db/transaction ;; (lambda (db) -;; (update-user-terraform-state db 7 -;; (with-input-from-file "src/deploy-bak/terraform.tfstate" read-string) -;; (with-input-from-file "src/deploy-bak/terraform.tfstate.backup" read-string)))) +;; (update-user-terraform-state db 1 22 +;; (with-input-from-file "src/deploy-7/terraform.tfstate" read-string) +;; (with-input-from-file "src/deploy-7/terraform.tfstate.backup" read-string)))) ;; (with-db/transaction (lambda (db) (get-user-terraform-state db 7))) ;; (with-db/transaction (lambda (db) (create-deployment db 7))) @@ -458,6 +480,7 @@ returning users.user_id;" ;; (with-db/transaction (lambda (db) (get-most-recent-deployment-status db 7))) ;; (with-db/transaction (lambda (db) (create-user db 1 "t@thintz.com" "thecombjelly"))) +;; (with-db/transaction (lambda (db) (create-instance db 2))) ;; (let ((user-id 7)) ;; (with-db/transaction diff --git a/src/nassella.scm b/src/nassella.scm index de2a6dd..4ff36e9 100644 --- a/src/nassella.scm +++ b/src/nassella.scm @@ -288,7 +288,7 @@ h1, h2, h3, h4, h5, h6 { (lambda () (use-middleware! (session-middleware "your-secret-key-here")))) ;; TODO generate better one -(define test-user-id (make-parameter 7)) +(define test-user-id (make-parameter 1)) (define (session-user-id) (or (session-get "user-id") (test-user-id))) @@ -576,14 +576,26 @@ h1, h2, h3, h4, h5, h6 { (with-schematra-app app (lambda () +(post "/config/wizard/create-instance" + (let ((instance-id (with-db/transaction + (lambda (db) + (create-instance db (session-user-id)))))) + (redirect (conc "/config/wizard/services/" instance-id)))) + +;; TODO should all these key related form fields be of type password +;; so the browser doesn't save them??? (get/widgets - ("/config/wizard/services") - (let ((config (with-db/transaction (lambda (db) (get-user-service-config db (session-user-id)))))) + ("/config/wizard/services/:id") + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (config (with-db/transaction + (lambda (db) + (get-user-service-config db (session-user-id) + instance-id))))) `(App (Configuration-Wizard (@ (step "Services")) (form - (@ (action "/config/wizard/services-submit") + (@ (action ,(conc "/config/wizard/services-submit/" instance-id)) (method POST)) (VStack (Fieldset @@ -601,102 +613,111 @@ h1, h2, h3, h4, h5, h6 { (Field (@ (name "backblaze-bucket-url") (label ("Bucket URL")) (value ,(alist-ref 'backblaze-bucket-url config))))) (Form-Nav))))))) -(post "/config/wizard/services-submit" - (with-db/transaction - (lambda (db) - (update-user-service-config - db - (session-user-id) - `((cloudflare-api-token . ,(alist-ref 'cloudflare-api-token (current-params))) - (cloudflare-account-id . ,(alist-ref 'cloudflare-account-id (current-params))) - (cloudflare-zone-id . ,(alist-ref 'cloudflare-zone-id (current-params))) - (digitalocean-api-token . ,(alist-ref 'digitalocean-api-token (current-params))) - (backblaze-application-key . ,(alist-ref 'backblaze-application-key (current-params))) - (backblaze-key-id . ,(alist-ref 'backblaze-key-id (current-params))) - (backblaze-bucket-url . ,(alist-ref 'backblaze-bucket-url (current-params))))))) - (redirect "/config/wizard/services-success")) +(post "/config/wizard/services-submit/:id" + (let ((instance-id (alist-ref "id" (current-params) equal?))) + (with-db/transaction + (lambda (db) + (update-user-service-config + db + (session-user-id) + instance-id + `((cloudflare-api-token . ,(alist-ref 'cloudflare-api-token (current-params))) + (cloudflare-account-id . ,(alist-ref 'cloudflare-account-id (current-params))) + (cloudflare-zone-id . ,(alist-ref 'cloudflare-zone-id (current-params))) + (digitalocean-api-token . ,(alist-ref 'digitalocean-api-token (current-params))) + (backblaze-application-key . ,(alist-ref 'backblaze-application-key (current-params))) + (backblaze-key-id . ,(alist-ref 'backblaze-key-id (current-params))) + (backblaze-bucket-url . ,(alist-ref 'backblaze-bucket-url (current-params))))))) + (redirect (conc "/config/wizard/services-success/" instance-id)))) (get/widgets - ("/config/wizard/services-success") - `(App - (Configuration-Wizard - (@ (step "Services")) - (form - (@ (action "/config/wizard/apps")) - (VStack - (Fieldset - (@ (title "Cloudflare")) - (h3 "Connected") - (p "Your Cloudflare account was successfully connected!")) - (Fieldset - (@ (title "DigitalOcean")) - (h3 "Connected") - (p "Your DigitalOcean account was successfully connected!")) - (Fieldset - (@ (title "Backblaze")) - (h3 "Connected") - (p "Your Backblaze account was successfully connected!")) - (Form-Nav (@ (back-to "/config/wizard/services")))))))) + ("/config/wizard/services-success/:id") + (let ((instance-id (alist-ref "id" (current-params) equal?))) + `(App + (Configuration-Wizard + (@ (step "Services")) + (form + (@ (action ,(conc "/config/wizard/apps/" instance-id))) + (VStack + (Fieldset + (@ (title "Cloudflare")) + (h3 "Connected") + (p "Your Cloudflare account was successfully connected!")) + (Fieldset + (@ (title "DigitalOcean")) + (h3 "Connected") + (p "Your DigitalOcean account was successfully connected!")) + (Fieldset + (@ (title "Backblaze")) + (h3 "Connected") + (p "Your Backblaze account was successfully connected!")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/services/" instance-id)))))))))) (get/widgets - ("/config/wizard/apps") - (let ((results - (with-db/transaction - (lambda (db) - `((selected-apps . ,(map - car - (filter cdr - (get-user-selected-apps db (session-user-id))))) - (app-config . ,(get-user-app-config db (session-user-id)))))))) + ("/config/wizard/apps/:id") + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (results + (with-db/transaction + (lambda (db) + `((selected-apps . ,(map + car + (filter cdr + (get-user-selected-apps db (session-user-id) instance-id)))) + (app-config . ,(get-user-app-config db (session-user-id) instance-id))))))) `(App (Configuration-Wizard (@ (step "Apps")) (form - (@ (action "/config/wizard/apps-submit") (method POST)) + (@ (action ,(conc "/config/wizard/apps-submit/" instance-id)) (method POST)) (VStack (Fieldset (@ (title "Root Domain")) (Field (@ (element select) (name "root-domain")) - (option (@ (value ,(alist-ref 'root-domain (alist-ref 'app-config results)))) "nassella.cc"))) ;; TODO fetch from cloudflare API? + (option (@ (value ,(or (alist-ref 'root-domain (alist-ref 'app-config results)) "nassella.cc"))) "nassella.cc"))) ;; TODO fetch from cloudflare API? (Fieldset (@ (title "Selected Apps")) (Field (@ (name "wg-easy") (type "checkbox") (label ("WG Easy")) (checked ,(member 'wg-easy (alist-ref 'selected-apps results))))) (Field (@ (name "nextcloud") (type "checkbox") (label ("NextCloud")) (checked ,(member 'nextcloud (alist-ref 'selected-apps results))))) (Field (@ (name "log-viewer") (type "checkbox") (label ("Log Viewer")) (checked #t) (disabled "disabled")))) - (Form-Nav (@ (back-to "/config/wizard/services-success"))))))))) - -(post "/config/wizard/apps-submit" - (with-db/transaction - (lambda (db) - (update-user-selected-apps - db - (session-user-id) - `((wg-easy . ,(or (and (alist-ref 'wg-easy (current-params)) "0.0") (sql-null))) - (nextcloud . ,(or (and (alist-ref 'nextcloud (current-params)) "0.0") (sql-null))))) - (update-root-domain db - (session-user-id) - (alist-ref 'root-domain (current-params))))) - (redirect "/config/wizard/apps2")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/services-success/" instance-id)))))))))) + +(post "/config/wizard/apps-submit/:id" + (display "root domain: ") (print (alist-ref 'root-domain (current-params))) + (let ((instance-id (alist-ref "id" (current-params) equal?))) + (with-db/transaction + (lambda (db) + (update-user-selected-apps + db + (session-user-id) + instance-id + `((wg-easy . ,(or (and (alist-ref 'wg-easy (current-params)) "0.0") (sql-null))) + (nextcloud . ,(or (and (alist-ref 'nextcloud (current-params)) "0.0") (sql-null))))) + (update-root-domain db + (session-user-id) + instance-id + (alist-ref 'root-domain (current-params))))) + (redirect (conc "/config/wizard/apps2/" instance-id)))) ;; TODO should this even allow changing existing username/passwords like for db? ;; wouldn't that break the db connection and you would lose data? (get/widgets - ("/config/wizard/apps2") - (let* ((results + ("/config/wizard/apps2/:id") + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (results (with-db/transaction (lambda (db) `((selected-apps . ,(map car (filter cdr - (get-user-selected-apps db (session-user-id))))) - (app-config . ,(get-user-app-config db (session-user-id))))))) + (get-user-selected-apps db (session-user-id) instance-id)))) + (app-config . ,(get-user-app-config db (session-user-id) instance-id)))))) (selected-apps (alist-ref 'selected-apps results)) (app-config (alist-ref 'config (alist-ref 'app-config results)))) `(App (Configuration-Wizard (@ (step "Apps")) (form - (@ (action "/config/wizard/apps2-submit") (method POST)) + (@ (action ,(conc "/config/wizard/apps2-submit/" instance-id)) (method POST)) (VStack ,@(if (member 'wg-easy selected-apps) `((Fieldset @@ -721,33 +742,36 @@ h1, h2, h3, h4, h5, h6 { (value ,(alist-ref 'user (alist-ref 'log-viewer app-config eq? '()) eq? "")))) (Field (@ (name "log-viewer-password") (label ("Password")) (type "password") (value ,(alist-ref 'password (alist-ref 'log-viewer app-config eq? '()) eq? ""))))) - (Form-Nav (@ (back-to "/config/wizard/apps"))))))))) - -(post "/config/wizard/apps2-submit" - (with-db/transaction - (lambda (db) - (update-user-app-config - db - (session-user-id) - `((wg-easy . ((subdomain . ,(alist-ref 'wg-easy-subdomain (current-params))))) - (nextcloud . ((subdomain . ,(alist-ref 'nextcloud-subdomain (current-params))) - (admin-user . ,(alist-ref 'nextcloud-admin-user (current-params))) - (admin-password . ,(alist-ref 'nextcloud-admin-password (current-params))))) - (log-viewer . ((subdomain . ,(alist-ref 'log-viewer-subdomain (current-params))) - (user . ,(alist-ref 'log-viewer-user (current-params))) - (password . ,(alist-ref 'log-viewer-password (current-params))))))))) - (redirect "/config/wizard/machine")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/apps/" instance-id)))))))))) + +(post "/config/wizard/apps2-submit/:id" + (let ((instance-id (alist-ref "id" (current-params) equal?))) + (with-db/transaction + (lambda (db) + (update-user-app-config + db + (session-user-id) + instance-id + `((wg-easy . ((subdomain . ,(alist-ref 'wg-easy-subdomain (current-params))))) + (nextcloud . ((subdomain . ,(alist-ref 'nextcloud-subdomain (current-params))) + (admin-user . ,(alist-ref 'nextcloud-admin-user (current-params))) + (admin-password . ,(alist-ref 'nextcloud-admin-password (current-params))))) + (log-viewer . ((subdomain . ,(alist-ref 'log-viewer-subdomain (current-params))) + (user . ,(alist-ref 'log-viewer-user (current-params))) + (password . ,(alist-ref 'log-viewer-password (current-params))))))))) + (redirect (conc "/config/wizard/machine/" instance-id)))) (get/widgets - ("/config/wizard/machine") - (let ((config (with-db/transaction - (lambda (db) - (get-user-service-config db (session-user-id)))))) + ("/config/wizard/machine/:id") + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (config (with-db/transaction + (lambda (db) + (get-user-service-config db (session-user-id) instance-id))))) `(App (Configuration-Wizard (@ (step "Machine")) (form - (@ (action "/config/wizard/machine-submit") + (@ (action ,(conc "/config/wizard/machine-submit/" instance-id)) (method POST)) (VStack (Fieldset @@ -757,22 +781,25 @@ h1, h2, h3, h4, h5, h6 { ,@(map (lambda (r) `(option (@ (value ,(alist-ref 'slug r))) ,(alist-ref 'name r))) (get-digital-ocean-regions (alist-ref 'digitalocean-api-token config))))) - (Form-Nav (@ (back-to ,(conc "/config/wizard/apps2")))))))))) - -(post "/config/wizard/machine-submit" - (with-db/transaction - (lambda (db) - (update-user-service-config - db - (session-user-id) - `((digitalocean-region . ,(alist-ref 'region (current-params))))))) - (redirect "/config/wizard/machine2")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/apps2/" instance-id)))))))))) + +(post "/config/wizard/machine-submit/:id" + (let ((instance-id (alist-ref "id" (current-params) equal?))) + (with-db/transaction + (lambda (db) + (update-user-service-config + db + (session-user-id) + instance-id + `((digitalocean-region . ,(alist-ref 'region (current-params))))))) + (redirect (conc "/config/wizard/machine2/" instance-id)))) (get/widgets - ("/config/wizard/machine2") - (let* ((config (with-db/transaction + ("/config/wizard/machine2/:id") + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (config (with-db/transaction (lambda (db) - (get-user-service-config db (session-user-id))))) + (get-user-service-config db (session-user-id) instance-id)))) (region (alist-ref 'digitalocean-region config)) (all-sizes (get-digital-ocean-sizes (alist-ref 'digitalocean-api-token config))) (sizes (filter (lambda (s) (member region (alist-ref 'regions s))) all-sizes))) @@ -780,7 +807,7 @@ h1, h2, h3, h4, h5, h6 { (Configuration-Wizard (@ (step "Machine")) (form - (@ (action "/config/wizard/machine2-submit") + (@ (action ,(conc "/config/wizard/machine2-submit/" instance-id)) (method POST)) (VStack (Fieldset @@ -794,28 +821,31 @@ h1, h2, h3, h4, h5, h6 { " Disk: " ,(alist-ref 'disk s) ") " ,(alist-ref 'description s))) sizes))) - (Form-Nav (@ (back-to ,(conc "/config/wizard/machine")))))))))) - -(post "/config/wizard/machine2-submit" - (with-db/transaction - (lambda (db) - (update-user-service-config - db - (session-user-id) - `((digitalocean-size . ,(alist-ref 'size (current-params))))))) - (redirect "/config/wizard/review")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/machine/" instance-id)))))))))) + +(post "/config/wizard/machine2-submit/:id" + (let ((instance-id (alist-ref "id" (current-params) equal?))) + (with-db/transaction + (lambda (db) + (update-user-service-config + db + (session-user-id) + instance-id + `((digitalocean-size . ,(alist-ref 'size (current-params))))))) + (redirect (conc "/config/wizard/review/" instance-id)))) (get/widgets - ("/config/wizard/review") - (let* ((results + ("/config/wizard/review/:id") + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (results (with-db/transaction (lambda (db) `((selected-apps . ,(map car (filter cdr - (get-user-selected-apps db (session-user-id))))) - (app-config . ,(get-user-app-config db (session-user-id))) - (service-config . ,(get-user-service-config db (session-user-id))))))) + (get-user-selected-apps db (session-user-id) instance-id)))) + (app-config . ,(get-user-app-config db (session-user-id) instance-id)) + (service-config . ,(get-user-service-config db (session-user-id) instance-id)))))) (selected-apps (cons 'log-viewer (alist-ref 'selected-apps results))) (app-config (alist-ref 'app-config results)) (config (alist-ref 'config app-config)) @@ -836,21 +866,24 @@ h1, h2, h3, h4, h5, h6 { (ul (li "Region: " ,(alist-ref 'digitalocean-region service-config)) (li "Size: " ,(alist-ref 'digitalocean-size service-config))) (form - (@ (action "/config/wizard/review-submit") (method POST)) + (@ (action ,(conc "/config/wizard/review-submit/" instance-id)) (method POST)) (VStack - (Form-Nav (@ (back-to ,(conc "/config/wizard/machine2")) (submit-button "Launch"))))))))) + (Form-Nav (@ (back-to ,(conc "/config/wizard/machine2/" instance-id)) (submit-button "Launch"))))))))) -(post "/config/wizard/review-submit" - (let* ((results +;; TODO this can only handle a user deploying one instance at a time! +;; the folder used should be the user-id PLUS the instance id +(post "/config/wizard/review-submit/:id" + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (results (with-db/transaction (lambda (db) `((selected-apps . ,(map car (filter cdr - (get-user-selected-apps db (session-user-id))))) - (app-config . ,(get-user-app-config db (session-user-id))) - (service-config . ,(get-user-service-config db (session-user-id))) - (terraform-state . ,(get-user-terraform-state db (session-user-id))))))) + (get-user-selected-apps db (session-user-id) instance-id)))) + (app-config . ,(get-user-app-config db (session-user-id) instance-id)) + (service-config . ,(get-user-service-config db (session-user-id) instance-id)) + (terraform-state . ,(get-user-terraform-state db (session-user-id) instance-id)))))) (selected-apps (cons 'log-viewer (alist-ref 'selected-apps results))) (app-config (alist-ref 'app-config results)) (config (alist-ref 'config app-config)) @@ -896,8 +929,9 @@ h1, h2, h3, h4, h5, h6 { ("datacenter" . ,(alist-ref 'digitalocean-region service-config)) ("flatcar_stable_version" . "4230.2.3"))) (display "ssh_keys=[\"") (display (with-input-from-file (string-append dir "/config/ssh-keys") read-string)) (print "\"]")))) - (let* ((user-id (session-user-id)) - (deployment-id (with-db/transaction (lambda (db) (create-deployment db user-id)))) + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (user-id (session-user-id)) + (deployment-id (with-db/transaction (lambda (db) (create-deployment db user-id instance-id)))) (dir (deployment-directory user-id))) (thread-start! (lambda () @@ -918,9 +952,9 @@ h1, h2, h3, h4, h5, h6 { (update-deployment-progress db deployment-id progress)))) (loop)) (let ((progress (parse-deployment-log - (with-input-from-file - (string-append (deployment-directory user-id) "/make-out") - read-string)))) + (with-input-from-file + (string-append (deployment-directory user-id) "/make-out") + read-string)))) (with-db/transaction (lambda (db) (update-deployment-progress db deployment-id progress) @@ -934,17 +968,18 @@ h1, h2, h3, h4, h5, h6 { db user-id deployment-id (if exit-normal 'complete 'failed) (with-input-from-file (string-append dir "/make-out") read-string)) - (update-user-terraform-state db user-id + (update-user-terraform-state db user-id instance-id (with-input-from-file (string-append dir "/terraform.tfstate") read-string) (with-input-from-file (string-append dir "/terraform.tfstate.backup") read-string)))))))))))) - (redirect "/config/wizard/success")) + (redirect (conc "/config/wizard/success/" (alist-ref "id" (current-params) equal?)))) (get/widgets - ("/config/wizard/success") - (let* ((res (with-db/transaction + ("/config/wizard/success/:id") + (let* ((instance-id (alist-ref "id" (current-params) equal?)) + (res (with-db/transaction (lambda (db) - `((status . ,(get-most-recent-deployment-status db (session-user-id))) - (progress . ,(get-most-recent-deployment-progress db (session-user-id))))))) + `((status . ,(get-most-recent-deployment-status db (session-user-id) instance-id)) + (progress . ,(get-most-recent-deployment-progress db (session-user-id) instance-id)))))) (output (with-input-from-file (string-append (deployment-directory (session-user-id)) "/make-out") read-string)) (progress (alist-ref 'progress res)) (status (alist-ref 'status res))) @@ -967,10 +1002,13 @@ h1, h2, h3, h4, h5, h6 { `(App (Main-Container (main - (h1 (@ (style ((font-size ,($ 'font.size.xxl))))) "Deployments") - (Button "Setup New Deployment") + (h1 (@ (style ((font-size ,($ 'font.size.xxl))))) "Instances") + (form + (@ (action "/config/wizard/create-instance") + (method POST)) + (Button "Setup New Instance")) (ul ,@(map (lambda (deployment) - `(li (a (@ (href ,(string-append "/deployments/" (number->string (alist-ref 'id deployment))))) + `(li (a (@ (href ,(conc "/deployments/" (alist-ref 'id deployment)))) ,(alist-ref 'root-domain deployment)) " - ",(alist-ref 'status deployment))) (with-db/transaction diff --git a/src/test.scm b/src/test.scm index 6564794..9ee1b6f 100644 --- a/src/test.scm +++ b/src/test.scm @@ -2,6 +2,7 @@ (import test spiffy (schematra test) postgresql uri-common (chicken sort) + (chicken string) srfi-13) (define (setup-test-data) @@ -29,14 +30,31 @@ (begin (test-route-body app 'GET path) (last-request-body-widget-sxml))) +(define instance-id (make-parameter #f)) + (setup-test-data) (test-mode #t) -(test "starting empty, GET /config/wizard/services" - '(App (Configuration-Wizard +(test "create instance, POST /config/wizard/create-instance" + '(found) + (let* ((resp + (test-route + app 'POST "/config/wizard/create-instance"))) + `(,(car resp)))) + +(test-assert "instance id exists" + (let ((instance-ids (with-db/transaction (lambda (db) (get-user-instances db (test-user-id)))))) + (and (list? instance-ids) + (not (null? instance-ids)) + (car (with-db/transaction (lambda (db) (get-user-instances db (test-user-id)))))))) + +(instance-id (car (with-db/transaction (lambda (db) (get-user-instances db (test-user-id)))))) + +(test "starting empty, GET /config/wizard/services/:id" + `(App (Configuration-Wizard (@ (step "Services")) - (form (@ (action "/config/wizard/services-submit") (method POST)) + (form (@ (action ,(conc "/config/wizard/services-submit/" (instance-id))) (method POST)) (VStack (Fieldset (@ (title "Cloudflare")) @@ -66,7 +84,7 @@ (label ("Bucket URL")) (value "")))) (Form-Nav))))) - (test-widget-get "/config/wizard/services")) + (test-widget-get (conc "/config/wizard/services/" (instance-id)))) (define *vars* `((cloudflare-api-token . "cloudflare-api-token") @@ -88,7 +106,7 @@ (log-viewer-user . "log-viewer-user") (log-viewer-password . "log-viewer-password"))) -(test "POST /config/wizard/services-submit" +(test "POST /config/wizard/services-submit/:id" `(found ,(alist-ref 'cloudflare-api-token *vars*) ,(alist-ref 'cloudflare-account-id *vars*) @@ -99,7 +117,7 @@ ,(alist-ref 'backblaze-bucket-url *vars*)) (let* ((resp (test-route - app 'POST "/config/wizard/services-submit" + app 'POST (conc "/config/wizard/services-submit/" (instance-id)) headers: '((content-type . (#(application/x-www-form-urlencoded ())))) body: (form-urlencode `((cloudflare-api-token . ,(alist-ref 'cloudflare-api-token *vars*)) @@ -109,7 +127,7 @@ (backblaze-application-key . ,(alist-ref 'backblaze-application-key *vars*)) (backblaze-key-id . ,(alist-ref 'backblaze-key-id *vars*)) (backblaze-bucket-url . ,(alist-ref 'backblaze-bucket-url *vars*)))))) - (c (with-db/transaction (lambda (db) (get-user-service-config db (test-user-id)))))) + (c (with-db/transaction (lambda (db) (get-user-service-config db (test-user-id) (instance-id)))))) `(,(car resp) ,(alist-ref 'cloudflare-api-token c) ,(alist-ref 'cloudflare-account-id c) @@ -119,45 +137,45 @@ ,(alist-ref 'backblaze-key-id c) ,(alist-ref 'backblaze-bucket-url c)))) -(test "after services-submit, GET /config/wizard/services" - '(App (Configuration-Wizard - (@ (step "Services")) - (form (@ (action "/config/wizard/services-submit") (method POST)) - (VStack - (Fieldset - (@ (title "Cloudflare")) - (Field (@ (name "cloudflare-api-token") - (label ("API Token")) - (value "cloudflare-api-token"))) - (Field (@ (name "cloudflare-zone-id") - (label ("Zone ID")) - (value "cloudflare-zone-id"))) - (Field (@ (name "cloudflare-account-id") - (label ("Account ID")) - (value "cloudflare-account-id")))) - (Fieldset - (@ (title "DigitalOcean")) - (Field (@ (name "digitalocean-api-token") - (label ("API Token")) - (value "do-api-token")))) - (Fieldset - (@ (title "Backblaze")) - (Field (@ (name "backblaze-application-key") - (label ("Application Key")) - (value "bb-app-key"))) - (Field (@ (name "backblaze-key-id") - (label ("Key ID")) - (value "bb-key-id"))) - (Field (@ (name "backblaze-bucket-url") - (label ("Bucket URL")) - (value "bb-bucket-url")))) - (Form-Nav))))) - (test-widget-get "/config/wizard/services")) +(test "after services-submit, GET /config/wizard/services/:id" + `(App (Configuration-Wizard + (@ (step "Services")) + (form (@ (action ,(conc "/config/wizard/services-submit/" (instance-id))) (method POST)) + (VStack + (Fieldset + (@ (title "Cloudflare")) + (Field (@ (name "cloudflare-api-token") + (label ("API Token")) + (value "cloudflare-api-token"))) + (Field (@ (name "cloudflare-zone-id") + (label ("Zone ID")) + (value "cloudflare-zone-id"))) + (Field (@ (name "cloudflare-account-id") + (label ("Account ID")) + (value "cloudflare-account-id")))) + (Fieldset + (@ (title "DigitalOcean")) + (Field (@ (name "digitalocean-api-token") + (label ("API Token")) + (value "do-api-token")))) + (Fieldset + (@ (title "Backblaze")) + (Field (@ (name "backblaze-application-key") + (label ("Application Key")) + (value "bb-app-key"))) + (Field (@ (name "backblaze-key-id") + (label ("Key ID")) + (value "bb-key-id"))) + (Field (@ (name "backblaze-bucket-url") + (label ("Bucket URL")) + (value "bb-bucket-url")))) + (Form-Nav))))) + (test-widget-get (conc "/config/wizard/services/" (instance-id)))) -(test "starting empty, GET /config/wizard/services-success" - '(App (Configuration-Wizard +(test "starting empty, GET /config/wizard/services-success/:id" + `(App (Configuration-Wizard (@ (step "Services")) - (form (@ (action "/config/wizard/apps")) + (form (@ (action ,(conc "/config/wizard/apps/" (instance-id)))) (VStack (Fieldset (@ (title "Cloudflare")) @@ -171,13 +189,13 @@ (@ (title "Backblaze")) (h3 "Connected") (p "Your Backblaze account was successfully connected!")) - (Form-Nav (@ (back-to "/config/wizard/services"))))))) - (test-widget-get "/config/wizard/services-success")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/services/" (instance-id))))))))) + (test-widget-get (conc "/config/wizard/services-success/" (instance-id)))) -(test "starting empty, GET /config/wizard/apps" - '(App (Configuration-Wizard +(test "starting empty, GET /config/wizard/apps/:id" + `(App (Configuration-Wizard (@ (step "Apps")) - (form (@ (action "/config/wizard/apps-submit") (method POST)) + (form (@ (action ,(conc "/config/wizard/apps-submit/" (instance-id))) (method POST)) (VStack (Fieldset (@ (title "Root Domain")) @@ -198,31 +216,31 @@ (label ("Log Viewer")) (checked #t) (disabled "disabled")))) - (Form-Nav (@ (back-to "/config/wizard/services-success"))))))) - (test-widget-get "/config/wizard/apps")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/services-success/" (instance-id))))))))) + (test-widget-get (conc "/config/wizard/apps/" (instance-id)))) -(test "POST /config/wizard/apps-submit" +(test "POST /config/wizard/apps-submit/:id" `(found "log-viewer" ,@(sort (map symbol->string (alist-ref 'apps *vars*)) string<) ,(alist-ref 'root-domain *vars*)) (let* ((resp (test-route - app 'POST "/config/wizard/apps-submit" + app 'POST (conc "/config/wizard/apps-submit/" (instance-id)) headers: '((content-type . (#(application/x-www-form-urlencoded ())))) body: (form-urlencode `(,@(map (lambda (x) `(,x . "true")) (alist-ref 'apps *vars*)) (root-domain . ,(alist-ref 'root-domain *vars*)))))) - (apps (with-db/transaction (lambda (db) (get-user-selected-apps db (test-user-id))))) - (config (with-db/transaction (lambda (db) (get-user-app-config db (test-user-id)))))) + (apps (with-db/transaction (lambda (db) (get-user-selected-apps db (test-user-id) (instance-id))))) + (config (with-db/transaction (lambda (db) (get-user-app-config db (test-user-id) (instance-id)))))) `(,(car resp) ,@(sort (map (lambda (app) (symbol->string (car app))) apps) string<) ,(alist-ref 'root-domain config)))) -(test "after apps-submit, GET /config/wizard/apps" - '(App (Configuration-Wizard +(test "after apps-submit, GET /config/wizard/apps/:id" + `(App (Configuration-Wizard (@ (step "Apps")) - (form (@ (action "/config/wizard/apps-submit") (method POST)) + (form (@ (action ,(conc "/config/wizard/apps-submit/" (instance-id))) (method POST)) (VStack (Fieldset (@ (title "Root Domain")) @@ -243,13 +261,13 @@ (label ("Log Viewer")) (checked #t) (disabled "disabled")))) - (Form-Nav (@ (back-to "/config/wizard/services-success"))))))) - (test-widget-get "/config/wizard/apps")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/services-success/" (instance-id))))))))) + (test-widget-get (conc "/config/wizard/apps/" (instance-id)))) -(test "before submit, GET /config/wizard/apps2" - '(App (Configuration-Wizard +(test "before submit, GET /config/wizard/apps2/:id" + `(App (Configuration-Wizard (@ (step "Apps")) - (form (@ (action "/config/wizard/apps2-submit") (method POST)) + (form (@ (action ,(conc "/config/wizard/apps2-submit/" (instance-id))) (method POST)) (VStack (Fieldset (@ (title "WG-Easy")) @@ -280,10 +298,10 @@ (label ("Password")) (type "password") (value "")))) - (Form-Nav (@ (back-to "/config/wizard/apps"))))))) - (test-widget-get "/config/wizard/apps2")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/apps/" (instance-id))))))))) + (test-widget-get (conc "/config/wizard/apps2/" (instance-id)))) -(test "POST /config/wizard/apps2-submit" +(test "POST /config/wizard/apps2-submit/:id" `(found ,(alist-ref 'wg-easy-subdomain *vars*) ,(alist-ref 'nextcloud-subdomain *vars*) @@ -294,7 +312,7 @@ ,(alist-ref 'log-viewer-password *vars*)) (let* ((resp (test-route - app 'POST "/config/wizard/apps2-submit" + app 'POST (conc "/config/wizard/apps2-submit/" (instance-id)) headers: '((content-type . (#(application/x-www-form-urlencoded ())))) body: (form-urlencode `((wg-easy-subdomain . ,(alist-ref 'wg-easy-subdomain *vars*)) @@ -305,7 +323,7 @@ (log-viewer-user . ,(alist-ref 'log-viewer-user *vars*)) (log-viewer-password . ,(alist-ref 'log-viewer-password *vars*)))))) (c (alist-ref 'config - (with-db/transaction (lambda (db) (get-user-app-config db (test-user-id))))))) + (with-db/transaction (lambda (db) (get-user-app-config db (test-user-id) (instance-id))))))) `(,(car resp) ,(alist-ref 'subdomain (alist-ref 'wg-easy c)) ,(alist-ref 'subdomain (alist-ref 'nextcloud c)) @@ -315,10 +333,10 @@ ,(alist-ref 'user (alist-ref 'log-viewer c)) ,(alist-ref 'password (alist-ref 'log-viewer c))))) -(test "after submit, GET /config/wizard/apps2" - '(App (Configuration-Wizard +(test "after submit, GET /config/wizard/apps2/:id" + `(App (Configuration-Wizard (@ (step "Apps")) - (form (@ (action "/config/wizard/apps2-submit") (method POST)) + (form (@ (action ,(conc "/config/wizard/apps2-submit/" (instance-id))) (method POST)) (VStack (Fieldset (@ (title "WG-Easy")) @@ -349,13 +367,13 @@ (label ("Password")) (type "password") (value "log-viewer-password")))) - (Form-Nav (@ (back-to "/config/wizard/apps"))))))) - (test-widget-get "/config/wizard/apps2")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/apps/" (instance-id))))))))) + (test-widget-get (conc "/config/wizard/apps2/" (instance-id)))) -(test "before submit, GET /config/wizard/machine" - '(App (Configuration-Wizard +(test "before submit, GET /config/wizard/machine/:id" + `(App (Configuration-Wizard (@ (step "Machine")) - (form (@ (action "/config/wizard/machine-submit") (method POST)) + (form (@ (action ,(conc "/config/wizard/machine-submit/" (instance-id))) (method POST)) (VStack (Fieldset (@ (title "Region")) @@ -374,41 +392,41 @@ (option (@ (value "sfo3")) "San Francisco 3") (option (@ (value "syd1")) "Sydney 1") (option (@ (value "atl1")) "Atlanta 1"))) - (Form-Nav (@ (back-to "/config/wizard/apps2"))))))) - (test-widget-get "/config/wizard/machine")) + (Form-Nav (@ (back-to ,(conc "/config/wizard/apps2/" (instance-id))))))))) + (test-widget-get (conc "/config/wizard/machine/" (instance-id)))) -(test "POST /config/wizard/machine-submit" +(test "POST /config/wizard/machine-submit/:id" `(found "sfo3") (let* ((resp (test-route - app 'POST "/config/wizard/machine-submit" + app 'POST (conc "/config/wizard/machine-submit/" (instance-id)) headers: '((content-type . (#(application/x-www-form-urlencoded ())))) body: (form-urlencode `((region . "sfo3"))))) - (c (with-db/transaction (lambda (db) (get-user-service-config db (test-user-id)))))) + (c (with-db/transaction (lambda (db) (get-user-service-config db (test-user-id) (instance-id)))))) `(,(car resp) ,(alist-ref 'digitalocean-region c)))) -(test "before submit, GET /config/wizard/machine2" - '(App (Configuration-Wizard (@ (step "Machine")) (form (@ (action "/config/wizard/machine2-submit") (method POST)) (VStack (Fieldset (@ (title "Size")) (Field (@ (element select) (name "size") (input-style ((max-width "100%")))) (option (@ (value "s-1vcpu-512mb-10gb")) "$" 4 " (CPU: " 1 " Mem: " 1/2 " Disk: " 10 ") " "Basic") (option (@ (value "s-1vcpu-1gb")) "$" 6 " (CPU: " 1 " Mem: " 1 " Disk: " 25 ") " "Basic") (option (@ (value "s-1vcpu-1gb-amd")) "$" 7 " (CPU: " 1 " Mem: " 1 " Disk: " 25 ") " "Basic AMD") (option (@ (value "s-1vcpu-1gb-intel")) "$" 7 " (CPU: " 1 " Mem: " 1 " Disk: " 25 ") " "Basic Intel") (option (@ (value "s-1vcpu-1gb-35gb-intel")) "$" 8 " (CPU: " 1 " Mem: " 1 " Disk: " 35 ") " "Basic Intel") (option (@ (value "s-1vcpu-2gb")) "$" 12 " (CPU: " 1 " Mem: " 2 " Disk: " 50 ") " "Basic") (option (@ (value "s-1vcpu-2gb-amd")) "$" 14 " (CPU: " 1 " Mem: " 2 " Disk: " 50 ") " "Basic AMD") (option (@ (value "s-1vcpu-2gb-intel")) "$" 14 " (CPU: " 1 " Mem: " 2 " Disk: " 50 ") " "Basic Intel") (option (@ (value "s-1vcpu-2gb-70gb-intel")) "$" 16 " (CPU: " 1 " Mem: " 2 " Disk: " 70 ") " "Basic Intel") (option (@ (value "s-2vcpu-2gb") (selected "selected")) "$" 18 " (CPU: " 2 " Mem: " 2 " Disk: " 60 ") " "Basic") (option (@ (value "s-2vcpu-2gb-amd")) "$" 21 " (CPU: " 2 " Mem: " 2 " Disk: " 60 ") " "Basic AMD") (option (@ (value "s-2vcpu-2gb-intel")) "$" 21 " (CPU: " 2 " Mem: " 2 " Disk: " 60 ") " "Basic Intel") (option (@ (value "s-2vcpu-2gb-90gb-intel")) "$" 24 " (CPU: " 2 " Mem: " 2 " Disk: " 90 ") " "Basic Intel") (option (@ (value "s-2vcpu-4gb")) "$" 24 " (CPU: " 2 " Mem: " 4 " Disk: " 80 ") " "Basic") (option (@ (value "s-2vcpu-4gb-amd")) "$" 28 " (CPU: " 2 " Mem: " 4 " Disk: " 80 ") " "Basic AMD") (option (@ (value "s-2vcpu-4gb-intel")) "$" 28 " (CPU: " 2 " Mem: " 4 " Disk: " 80 ") " "Basic Intel") (option (@ (value "s-2vcpu-4gb-120gb-intel")) "$" 32 " (CPU: " 2 " Mem: " 4 " Disk: " 120 ") " "Basic Intel") (option (@ (value "s-2vcpu-8gb-amd")) "$" 42 " (CPU: " 2 " Mem: " 8 " Disk: " 100 ") " "Basic AMD") (option (@ (value "c-2")) "$" 42 " (CPU: " 2 " Mem: " 4 " Disk: " 25 ") " "CPU-Optimized") (option (@ (value "c2-2vcpu-4gb")) "$" 47 " (CPU: " 2 " Mem: " 4 " Disk: " 50 ") " "CPU-Optimized 2x SSD") (option (@ (value "s-2vcpu-8gb-160gb-intel")) "$" 48 " (CPU: " 2 " Mem: " 8 " Disk: " 160 ") " "Basic Intel") (option (@ (value "s-4vcpu-8gb")) "$" 48 " (CPU: " 4 " Mem: " 8 " Disk: " 160 ") " "Basic") (option (@ (value "s-4vcpu-8gb-amd")) "$" 56 " (CPU: " 4 " Mem: " 8 " Disk: " 160 ") " "Basic AMD") (option (@ (value "s-4vcpu-8gb-intel")) "$" 56 " (CPU: " 4 " Mem: " 8 " Disk: " 160 ") " "Basic Intel") (option (@ (value "c5-2vcpu-4gb")) "$" 62 " (CPU: " 2 " Mem: " 4 " Disk: " 125 ") " "CPU Intensive 5x SSD") (option (@ (value "g-2vcpu-8gb")) "$" 63 " (CPU: " 2 " Mem: " 8 " Disk: " 25 ") " "General Purpose") (option (@ (value "s-4vcpu-8gb-240gb-intel")) "$" 64 " (CPU: " 4 " Mem: " 8 " Disk: " 240 ") " "Basic Intel") (option (@ (value "gd-2vcpu-8gb")) "$" 68 " (CPU: " 2 " Mem: " 8 " Disk: " 50 ") " "General Purpose 2x SSD") (option (@ (value "g-2vcpu-8gb-intel")) "$" 76 " (CPU: " 2 " Mem: " 8 " Disk: " 30 ") " "General Purpose — Premium Intel") (option (@ (value "gd-2vcpu-8gb-intel")) "$" 79 " (CPU: " 2 " Mem: " 8 " Disk: " 60 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "s-4vcpu-16gb-amd")) "$" 84 " (CPU: " 4 " Mem: " 16 " Disk: " 200 ") " "Basic AMD") (option (@ (value "m-2vcpu-16gb")) "$" 84 " (CPU: " 2 " Mem: " 16 " Disk: " 50 ") " "Memory-Optimized") (option (@ (value "c-4")) "$" 84 " (CPU: " 4 " Mem: " 8 " Disk: " 50 ") " "CPU-Optimized") (option (@ (value "g6_5-2vcpu-8gb")) "$" 90.6 " (CPU: " 2 " Mem: " 8 " Disk: " 163 ") " "General Purpose 6.5x SSD") (option (@ (value "g5_5-2vcpu-8gb-intel")) "$" 92.2 " (CPU: " 2 " Mem: " 8 " Disk: " 165 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "c2-4vcpu-8gb")) "$" 94 " (CPU: " 4 " Mem: " 8 " Disk: " 100 ") " "CPU-Optimized 2x SSD") (option (@ (value "s-4vcpu-16gb-320gb-intel")) "$" 96 " (CPU: " 4 " Mem: " 16 " Disk: " 320 ") " "Basic Intel") (option (@ (value "s-8vcpu-16gb")) "$" 96 " (CPU: " 8 " Mem: " 16 " Disk: " 320 ") " "Basic") (option (@ (value "m-2vcpu-16gb-intel")) "$" 99 " (CPU: " 2 " Mem: " 16 " Disk: " 50 ") " "Premium Memory-Optimized") (option (@ (value "m3-2vcpu-16gb")) "$" 104 " (CPU: " 2 " Mem: " 16 " Disk: " 150 ") " "Memory-Optimized 3x SSD") (option (@ (value "c-4-intel")) "$" 109 " (CPU: " 4 " Mem: " 8 " Disk: " 50 ") " "Premium Intel") (option (@ (value "m3-2vcpu-16gb-intel")) "$" 110 " (CPU: " 2 " Mem: " 16 " Disk: " 150 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "s-8vcpu-16gb-amd")) "$" 112 " (CPU: " 8 " Mem: " 16 " Disk: " 320 ") " "Basic AMD") (option (@ (value "s-8vcpu-16gb-intel")) "$" 112 " (CPU: " 8 " Mem: " 16 " Disk: " 320 ") " "Basic Intel") (option (@ (value "c2-4vcpu-8gb-intel")) "$" 122 " (CPU: " 4 " Mem: " 8 " Disk: " 100 ") " "Premium Intel") (option (@ (value "c5-4vcpu-8gb")) "$" 124 " (CPU: " 4 " Mem: " 8 " Disk: " 250 ") " "CPU Intensive 5x SSD") (option (@ (value "g-4vcpu-16gb")) "$" 126 " (CPU: " 4 " Mem: " 16 " Disk: " 50 ") " "General Purpose") (option (@ (value "s-8vcpu-16gb-480gb-intel")) "$" 128 " (CPU: " 8 " Mem: " 16 " Disk: " 480 ") " "Basic Intel") (option (@ (value "so-2vcpu-16gb-intel")) "$" 131 " (CPU: " 2 " Mem: " 16 " Disk: " 300 ") " "Premium Storage-Optimized") (option (@ (value "so-2vcpu-16gb")) "$" 131 " (CPU: " 2 " Mem: " 16 " Disk: " 300 ") " "Storage-Optimized") (option (@ (value "m6-2vcpu-16gb")) "$" 131 " (CPU: " 2 " Mem: " 16 " Disk: " 300 ") " "Memory-Optimized 6x SSD") (option (@ (value "gd-4vcpu-16gb")) "$" 136 " (CPU: " 4 " Mem: " 16 " Disk: " 100 ") " "General Purpose 2x SSD") (option (@ (value "so1_5-2vcpu-16gb-intel")) "$" 139 " (CPU: " 2 " Mem: " 16 " Disk: " 450 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "g-4vcpu-16gb-intel")) "$" 151 " (CPU: " 4 " Mem: " 16 " Disk: " 60 ") " "General Purpose — Premium Intel") (option (@ (value "gd-4vcpu-16gb-intel")) "$" 158 " (CPU: " 4 " Mem: " 16 " Disk: " 120 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-4vcpu-8gb-intel")) "$" 161 " (CPU: " 4 " Mem: " 8 " Disk: " 250 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-2vcpu-16gb")) "$" 163 " (CPU: " 2 " Mem: " 16 " Disk: " 450 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "s-8vcpu-32gb-amd")) "$" 168 " (CPU: " 8 " Mem: " 32 " Disk: " 400 ") " "Basic AMD") (option (@ (value "m-4vcpu-32gb")) "$" 168 " (CPU: " 4 " Mem: " 32 " Disk: " 100 ") " "Memory-Optimized") (option (@ (value "c-8")) "$" 168 " (CPU: " 8 " Mem: " 16 " Disk: " 100 ") " "CPU-Optimized") (option (@ (value "g6_5-4vcpu-16gb")) "$" 181 " (CPU: " 4 " Mem: " 16 " Disk: " 325 ") " "General Purpose 6.5x SSD") (option (@ (value "g5_5-4vcpu-16gb-intel")) "$" 183.4 " (CPU: " 4 " Mem: " 16 " Disk: " 330 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "c2-8vcpu-16gb")) "$" 188 " (CPU: " 8 " Mem: " 16 " Disk: " 200 ") " "CPU-Optimized 2x SSD") (option (@ (value "s-8vcpu-32gb-640gb-intel")) "$" 192 " (CPU: " 8 " Mem: " 32 " Disk: " 640 ") " "Basic Intel") (option (@ (value "m-4vcpu-32gb-intel")) "$" 198 " (CPU: " 4 " Mem: " 32 " Disk: " 100 ") " "Premium Memory-Optimized") (option (@ (value "m3-4vcpu-32gb")) "$" 208 " (CPU: " 4 " Mem: " 32 " Disk: " 300 ") " "Memory-Optimized 3x SSD") (option (@ (value "c-8-intel")) "$" 218 " (CPU: " 8 " Mem: " 16 " Disk: " 100 ") " "Premium Intel") (option (@ (value "m3-4vcpu-32gb-intel")) "$" 220 " (CPU: " 4 " Mem: " 32 " Disk: " 300 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "c2-8vcpu-16gb-intel")) "$" 244 " (CPU: " 8 " Mem: " 16 " Disk: " 200 ") " "Premium Intel") (option (@ (value "c5-8vcpu-16gb")) "$" 248 " (CPU: " 8 " Mem: " 16 " Disk: " 500 ") " "CPU Intensive 5x SSD") (option (@ (value "g-8vcpu-32gb")) "$" 252 " (CPU: " 8 " Mem: " 32 " Disk: " 100 ") " "General Purpose") (option (@ (value "so-4vcpu-32gb-intel")) "$" 262 " (CPU: " 4 " Mem: " 32 " Disk: " 600 ") " "Premium Storage-Optimized") (option (@ (value "so-4vcpu-32gb")) "$" 262 " (CPU: " 4 " Mem: " 32 " Disk: " 600 ") " "Storage-Optimized") (option (@ (value "m6-4vcpu-32gb")) "$" 262 " (CPU: " 4 " Mem: " 32 " Disk: " 600 ") " "Memory-Optimized 6x SSD") (option (@ (value "gd-8vcpu-32gb")) "$" 272 " (CPU: " 8 " Mem: " 32 " Disk: " 200 ") " "General Purpose 2x SSD") (option (@ (value "so1_5-4vcpu-32gb-intel")) "$" 278 " (CPU: " 4 " Mem: " 32 " Disk: " 900 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "g-8vcpu-32gb-intel")) "$" 302 " (CPU: " 8 " Mem: " 32 " Disk: " 120 ") " "General Purpose — Premium Intel") (option (@ (value "gd-8vcpu-32gb-intel")) "$" 317 " (CPU: " 8 " Mem: " 32 " Disk: " 240 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-8vcpu-16gb-intel")) "$" 322 " (CPU: " 8 " Mem: " 16 " Disk: " 500 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-4vcpu-32gb")) "$" 326 " (CPU: " 4 " Mem: " 32 " Disk: " 900 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "m-8vcpu-64gb")) "$" 336 " (CPU: " 8 " Mem: " 64 " Disk: " 200 ") " "Memory-Optimized") (option (@ (value "c-16")) "$" 336 " (CPU: " 16 " Mem: " 32 " Disk: " 200 ") " "CPU-Optimized") (option (@ (value "g6_5-8vcpu-32gb")) "$" 362 " (CPU: " 8 " Mem: " 32 " Disk: " 650 ") " "General Purpose 6.5x SSD") (option (@ (value "g5_5-8vcpu-32gb-intel")) "$" 366.8 " (CPU: " 8 " Mem: " 32 " Disk: " 660 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "c2-16vcpu-32gb")) "$" 376 " (CPU: " 16 " Mem: " 32 " Disk: " 400 ") " "CPU-Optimized 2x SSD") (option (@ (value "m-8vcpu-64gb-intel")) "$" 396 " (CPU: " 8 " Mem: " 64 " Disk: " 200 ") " "Premium Memory-Optimized") (option (@ (value "m3-8vcpu-64gb")) "$" 416 " (CPU: " 8 " Mem: " 64 " Disk: " 600 ") " "Memory-Optimized 3x SSD") (option (@ (value "c-16-intel")) "$" 437 " (CPU: " 16 " Mem: " 32 " Disk: " 200 ") " "Premium Intel") (option (@ (value "m3-8vcpu-64gb-intel")) "$" 440 " (CPU: " 8 " Mem: " 64 " Disk: " 600 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "c2-16vcpu-32gb-intel")) "$" 489 " (CPU: " 16 " Mem: " 32 " Disk: " 400 ") " "Premium Intel") (option (@ (value "c5-16vcpu-32gb")) "$" 496 " (CPU: " 16 " Mem: " 32 " Disk: " 1000 ") " "CPU Intensive 5x SSD") (option (@ (value "g-16vcpu-64gb")) "$" 504 " (CPU: " 16 " Mem: " 64 " Disk: " 200 ") " "General Purpose") (option (@ (value "so-8vcpu-64gb-intel")) "$" 524 " (CPU: " 8 " Mem: " 64 " Disk: " 1200 ") " "Premium Storage-Optimized") (option (@ (value "so-8vcpu-64gb")) "$" 524 " (CPU: " 8 " Mem: " 64 " Disk: " 1200 ") " "Storage-Optimized") (option (@ (value "m6-8vcpu-64gb")) "$" 524 " (CPU: " 8 " Mem: " 64 " Disk: " 1200 ") " "Memory-Optimized 6x SSD") (option (@ (value "gd-16vcpu-64gb")) "$" 544 " (CPU: " 16 " Mem: " 64 " Disk: " 400 ") " "General Purpose 2x SSD") (option (@ (value "so1_5-8vcpu-64gb-intel")) "$" 556 " (CPU: " 8 " Mem: " 64 " Disk: " 1800 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "g-16vcpu-64gb-intel")) "$" 605 " (CPU: " 16 " Mem: " 64 " Disk: " 240 ") " "General Purpose — Premium Intel") (option (@ (value "gd-16vcpu-64gb-intel")) "$" 634 " (CPU: " 16 " Mem: " 64 " Disk: " 480 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-16vcpu-32gb-intel")) "$" 645 " (CPU: " 16 " Mem: " 32 " Disk: " 1000 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-8vcpu-64gb")) "$" 652 " (CPU: " 8 " Mem: " 64 " Disk: " 1800 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "m-16vcpu-128gb")) "$" 672 " (CPU: " 16 " Mem: " 128 " Disk: " 400 ") " "Memory-Optimized") (option (@ (value "c-32")) "$" 672 " (CPU: " 32 " Mem: " 64 " Disk: " 400 ") " "CPU-Optimized") (option (@ (value "g6_5-16vcpu-64gb")) "$" 724 " (CPU: " 16 " Mem: " 64 " Disk: " 1300 ") " "General Purpose 6.5x SSD") (option (@ (value "g5_5-16vcpu-64gb-intel")) "$" 734.6 " (CPU: " 16 " Mem: " 64 " Disk: " 1320 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "c2-32vcpu-64gb")) "$" 752 " (CPU: " 32 " Mem: " 64 " Disk: " 800 ") " "CPU-Optimized 2x SSD") (option (@ (value "m-16vcpu-128gb-intel")) "$" 792 " (CPU: " 16 " Mem: " 128 " Disk: " 400 ") " "Premium Memory-Optimized") (option (@ (value "m3-16vcpu-128gb")) "$" 832 " (CPU: " 16 " Mem: " 128 " Disk: " 1200 ") " "Memory-Optimized 3x SSD") (option (@ (value "c-32-intel")) "$" 874 " (CPU: " 32 " Mem: " 64 " Disk: " 400 ") " "Premium Intel") (option (@ (value "m3-16vcpu-128gb-intel")) "$" 880 " (CPU: " 16 " Mem: " 128 " Disk: " 1200 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "c2-32vcpu-64gb-intel")) "$" 978 " (CPU: " 32 " Mem: " 64 " Disk: " 800 ") " "Premium Intel") (option (@ (value "c5-32vcpu-64gb")) "$" 992 " (CPU: " 32 " Mem: " 64 " Disk: " 2000 ") " "CPU Intensive 5x SSD") (option (@ (value "c-48")) "$" 1008 " (CPU: " 48 " Mem: " 96 " Disk: " 600 ") " "CPU-Optimized") (option (@ (value "m-24vcpu-192gb")) "$" 1008 " (CPU: " 24 " Mem: " 192 " Disk: " 600 ") " "Memory-Optimized") (option (@ (value "g-32vcpu-128gb")) "$" 1008 " (CPU: " 32 " Mem: " 128 " Disk: " 400 ") " "General Purpose") (option (@ (value "so-16vcpu-128gb-intel")) "$" 1048 " (CPU: " 16 " Mem: " 128 " Disk: " 2400 ") " "Premium Storage-Optimized") (option (@ (value "so-16vcpu-128gb")) "$" 1048 " (CPU: " 16 " Mem: " 128 " Disk: " 2400 ") " "Storage-Optimized") (option (@ (value "m6-16vcpu-128gb")) "$" 1048 " (CPU: " 16 " Mem: " 128 " Disk: " 2400 ") " "Memory-Optimized 6x SSD") (option (@ (value "gd-32vcpu-128gb")) "$" 1088 " (CPU: " 32 " Mem: " 128 " Disk: " 800 ") " "General Purpose 2x SSD") (option (@ (value "so1_5-16vcpu-128gb-intel")) "$" 1112 " (CPU: " 16 " Mem: " 128 " Disk: " 3600 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "c2-48vcpu-96gb")) "$" 1128 " (CPU: " 48 " Mem: " 96 " Disk: " 1200 ") " "CPU-Optimized 2x SSD") (option (@ (value "m-24vcpu-192gb-intel")) "$" 1188 " (CPU: " 24 " Mem: " 192 " Disk: " 600 ") " "Premium Memory-Optimized") (option (@ (value "g-32vcpu-128gb-intel")) "$" 1210 " (CPU: " 32 " Mem: " 128 " Disk: " 480 ") " "General Purpose — Premium Intel") (option (@ (value "m3-24vcpu-192gb")) "$" 1248 " (CPU: " 24 " Mem: " 192 " Disk: " 1800 ") " "Memory-Optimized 3x SSD") (option (@ (value "g-40vcpu-160gb")) "$" 1260 " (CPU: " 40 " Mem: " 160 " Disk: " 500 ") " "General Purpose") (option (@ (value "gd-32vcpu-128gb-intel")) "$" 1268 " (CPU: " 32 " Mem: " 128 " Disk: " 960 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-32vcpu-64gb-intel")) "$" 1290 " (CPU: " 32 " Mem: " 64 " Disk: " 2000 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-16vcpu-128gb")) "$" 1304 " (CPU: " 16 " Mem: " 128 " Disk: " 3600 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "c-48-intel")) "$" 1310 " (CPU: " 48 " Mem: " 96 " Disk: " 600 ") " "Premium Intel") (option (@ (value "m3-24vcpu-192gb-intel")) "$" 1320 " (CPU: " 24 " Mem: " 192 " Disk: " 1800 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "m-32vcpu-256gb")) "$" 1344 " (CPU: " 32 " Mem: " 256 " Disk: " 800 ") " "Memory-Optimized") (option (@ (value "gd-40vcpu-160gb")) "$" 1360 " (CPU: " 40 " Mem: " 160 " Disk: " 1000 ") " "General Purpose 2x SSD") (option (@ (value "g6_5-32vcpu-128gb")) "$" 1448 " (CPU: " 32 " Mem: " 128 " Disk: " 2600 ") " "General Purpose 6.5x SSD") (option (@ (value "c2-48vcpu-96gb-intel")) "$" 1466 " (CPU: " 48 " Mem: " 96 " Disk: " 1200 ") " "Premium Intel") (option (@ (value "g5_5-32vcpu-128gb-intel")) "$" 1469.2 " (CPU: " 32 " Mem: " 128 " Disk: " 2640 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "so-24vcpu-192gb-intel")) "$" 1572 " (CPU: " 24 " Mem: " 192 " Disk: " 3600 ") " "Premium Storage-Optimized") (option (@ (value "so-24vcpu-192gb")) "$" 1572 " (CPU: " 24 " Mem: " 192 " Disk: " 3600 ") " "Storage-Optimized") (option (@ (value "m6-24vcpu-192gb")) "$" 1572 " (CPU: " 24 " Mem: " 192 " Disk: " 3600 ") " "Memory-Optimized 6x SSD") (option (@ (value "m-32vcpu-256gb-intel")) "$" 1584 " (CPU: " 32 " Mem: " 256 " Disk: " 800 ") " "Premium Memory-Optimized") (option (@ (value "c-60-intel")) "$" 1639 " (CPU: " 60 " Mem: " 120 " Disk: " 750 ") " "Premium Intel") (option (@ (value "m3-32vcpu-256gb")) "$" 1664 " (CPU: " 32 " Mem: " 256 " Disk: " 2400 ") " "Memory-Optimized 3x SSD") (option (@ (value "so1_5-24vcpu-192gb-intel")) "$" 1668 " (CPU: " 24 " Mem: " 192 " Disk: " 5400 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "m3-32vcpu-256gb-intel")) "$" 1760 " (CPU: " 32 " Mem: " 256 " Disk: " 2400 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "g6_5-40vcpu-160gb")) "$" 1810 " (CPU: " 40 " Mem: " 160 " Disk: " 3250 ") " "General Purpose 6.5x SSD") (option (@ (value "g-48vcpu-192gb-intel")) "$" 1814 " (CPU: " 48 " Mem: " 192 " Disk: " 720 ") " "General Purpose — Premium Intel") (option (@ (value "c2-60vcpu-120gb-intel")) "$" 1834 " (CPU: " 60 " Mem: " 120 " Disk: " 1500 ") " "Premium Intel") (option (@ (value "gd-48vcpu-192gb-intel")) "$" 1901 " (CPU: " 48 " Mem: " 192 " Disk: " 1440 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-48vcpu-96gb-intel")) "$" 1934 " (CPU: " 48 " Mem: " 96 " Disk: " 3000 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-24vcpu-192gb")) "$" 1956 " (CPU: " 24 " Mem: " 192 " Disk: " 5400 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "so-32vcpu-256gb-intel")) "$" 2096 " (CPU: " 32 " Mem: " 256 " Disk: " 4800 ") " "Premium Storage-Optimized") (option (@ (value "so-32vcpu-256gb")) "$" 2096 " (CPU: " 32 " Mem: " 256 " Disk: " 4800 ") " "Storage-Optimized") (option (@ (value "m6-32vcpu-256gb")) "$" 2096 " (CPU: " 32 " Mem: " 256 " Disk: " 4800 ") " "Memory-Optimized 6x SSD") (option (@ (value "g5_5-48vcpu-192gb-intel")) "$" 2202.8 " (CPU: " 48 " Mem: " 192 " Disk: " 3960 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "so1_5-32vcpu-256gb-intel")) "$" 2224 " (CPU: " 32 " Mem: " 256 " Disk: " 7200 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "g-60vcpu-240gb-intel")) "$" 2269 " (CPU: " 60 " Mem: " 240 " Disk: " 900 ") " "General Purpose — Premium Intel") (option (@ (value "m-48vcpu-384gb-intel")) "$" 2376 " (CPU: " 48 " Mem: " 384 " Disk: " 1200 ") " "Premium Memory-Optimized") (option (@ (value "gd-60vcpu-240gb-intel")) "$" 2378 " (CPU: " 60 " Mem: " 240 " Disk: " 1800 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-60vcpu-120gb-intel")) "$" 2419 " (CPU: " 60 " Mem: " 120 " Disk: " 3750 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-32vcpu-256gb")) "$" 2608 " (CPU: " 32 " Mem: " 256 " Disk: " 7200 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "m3-48vcpu-384gb-intel")) "$" 2640 " (CPU: " 48 " Mem: " 384 " Disk: " 3600 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "g5_5-60vcpu-240gb-intel")) "$" 2755 " (CPU: " 60 " Mem: " 240 " Disk: " 4950 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "so-48vcpu-384gb-intel")) "$" 3144 " (CPU: " 48 " Mem: " 384 " Disk: " 7000 ") " "Premium Storage-Optimized"))) (Form-Nav (@ (back-to "/config/wizard/machine"))))))) - (test-widget-get "/config/wizard/machine2")) +(test "before submit, GET /config/wizard/machine2/:id" + `(App (Configuration-Wizard (@ (step "Machine")) (form (@ (action ,(conc "/config/wizard/machine2-submit/" (instance-id))) (method POST)) (VStack (Fieldset (@ (title "Size")) (Field (@ (element select) (name "size") (input-style ((max-width "100%")))) (option (@ (value "s-1vcpu-512mb-10gb")) "$" 4 " (CPU: " 1 " Mem: " 1/2 " Disk: " 10 ") " "Basic") (option (@ (value "s-1vcpu-1gb")) "$" 6 " (CPU: " 1 " Mem: " 1 " Disk: " 25 ") " "Basic") (option (@ (value "s-1vcpu-1gb-amd")) "$" 7 " (CPU: " 1 " Mem: " 1 " Disk: " 25 ") " "Basic AMD") (option (@ (value "s-1vcpu-1gb-intel")) "$" 7 " (CPU: " 1 " Mem: " 1 " Disk: " 25 ") " "Basic Intel") (option (@ (value "s-1vcpu-1gb-35gb-intel")) "$" 8 " (CPU: " 1 " Mem: " 1 " Disk: " 35 ") " "Basic Intel") (option (@ (value "s-1vcpu-2gb")) "$" 12 " (CPU: " 1 " Mem: " 2 " Disk: " 50 ") " "Basic") (option (@ (value "s-1vcpu-2gb-amd")) "$" 14 " (CPU: " 1 " Mem: " 2 " Disk: " 50 ") " "Basic AMD") (option (@ (value "s-1vcpu-2gb-intel")) "$" 14 " (CPU: " 1 " Mem: " 2 " Disk: " 50 ") " "Basic Intel") (option (@ (value "s-1vcpu-2gb-70gb-intel")) "$" 16 " (CPU: " 1 " Mem: " 2 " Disk: " 70 ") " "Basic Intel") (option (@ (value "s-2vcpu-2gb") (selected "selected")) "$" 18 " (CPU: " 2 " Mem: " 2 " Disk: " 60 ") " "Basic") (option (@ (value "s-2vcpu-2gb-amd")) "$" 21 " (CPU: " 2 " Mem: " 2 " Disk: " 60 ") " "Basic AMD") (option (@ (value "s-2vcpu-2gb-intel")) "$" 21 " (CPU: " 2 " Mem: " 2 " Disk: " 60 ") " "Basic Intel") (option (@ (value "s-2vcpu-2gb-90gb-intel")) "$" 24 " (CPU: " 2 " Mem: " 2 " Disk: " 90 ") " "Basic Intel") (option (@ (value "s-2vcpu-4gb")) "$" 24 " (CPU: " 2 " Mem: " 4 " Disk: " 80 ") " "Basic") (option (@ (value "s-2vcpu-4gb-amd")) "$" 28 " (CPU: " 2 " Mem: " 4 " Disk: " 80 ") " "Basic AMD") (option (@ (value "s-2vcpu-4gb-intel")) "$" 28 " (CPU: " 2 " Mem: " 4 " Disk: " 80 ") " "Basic Intel") (option (@ (value "s-2vcpu-4gb-120gb-intel")) "$" 32 " (CPU: " 2 " Mem: " 4 " Disk: " 120 ") " "Basic Intel") (option (@ (value "s-2vcpu-8gb-amd")) "$" 42 " (CPU: " 2 " Mem: " 8 " Disk: " 100 ") " "Basic AMD") (option (@ (value "c-2")) "$" 42 " (CPU: " 2 " Mem: " 4 " Disk: " 25 ") " "CPU-Optimized") (option (@ (value "c2-2vcpu-4gb")) "$" 47 " (CPU: " 2 " Mem: " 4 " Disk: " 50 ") " "CPU-Optimized 2x SSD") (option (@ (value "s-2vcpu-8gb-160gb-intel")) "$" 48 " (CPU: " 2 " Mem: " 8 " Disk: " 160 ") " "Basic Intel") (option (@ (value "s-4vcpu-8gb")) "$" 48 " (CPU: " 4 " Mem: " 8 " Disk: " 160 ") " "Basic") (option (@ (value "s-4vcpu-8gb-amd")) "$" 56 " (CPU: " 4 " Mem: " 8 " Disk: " 160 ") " "Basic AMD") (option (@ (value "s-4vcpu-8gb-intel")) "$" 56 " (CPU: " 4 " Mem: " 8 " Disk: " 160 ") " "Basic Intel") (option (@ (value "c5-2vcpu-4gb")) "$" 62 " (CPU: " 2 " Mem: " 4 " Disk: " 125 ") " "CPU Intensive 5x SSD") (option (@ (value "g-2vcpu-8gb")) "$" 63 " (CPU: " 2 " Mem: " 8 " Disk: " 25 ") " "General Purpose") (option (@ (value "s-4vcpu-8gb-240gb-intel")) "$" 64 " (CPU: " 4 " Mem: " 8 " Disk: " 240 ") " "Basic Intel") (option (@ (value "gd-2vcpu-8gb")) "$" 68 " (CPU: " 2 " Mem: " 8 " Disk: " 50 ") " "General Purpose 2x SSD") (option (@ (value "g-2vcpu-8gb-intel")) "$" 76 " (CPU: " 2 " Mem: " 8 " Disk: " 30 ") " "General Purpose — Premium Intel") (option (@ (value "gd-2vcpu-8gb-intel")) "$" 79 " (CPU: " 2 " Mem: " 8 " Disk: " 60 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "s-4vcpu-16gb-amd")) "$" 84 " (CPU: " 4 " Mem: " 16 " Disk: " 200 ") " "Basic AMD") (option (@ (value "m-2vcpu-16gb")) "$" 84 " (CPU: " 2 " Mem: " 16 " Disk: " 50 ") " "Memory-Optimized") (option (@ (value "c-4")) "$" 84 " (CPU: " 4 " Mem: " 8 " Disk: " 50 ") " "CPU-Optimized") (option (@ (value "g6_5-2vcpu-8gb")) "$" 90.6 " (CPU: " 2 " Mem: " 8 " Disk: " 163 ") " "General Purpose 6.5x SSD") (option (@ (value "g5_5-2vcpu-8gb-intel")) "$" 92.2 " (CPU: " 2 " Mem: " 8 " Disk: " 165 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "c2-4vcpu-8gb")) "$" 94 " (CPU: " 4 " Mem: " 8 " Disk: " 100 ") " "CPU-Optimized 2x SSD") (option (@ (value "s-4vcpu-16gb-320gb-intel")) "$" 96 " (CPU: " 4 " Mem: " 16 " Disk: " 320 ") " "Basic Intel") (option (@ (value "s-8vcpu-16gb")) "$" 96 " (CPU: " 8 " Mem: " 16 " Disk: " 320 ") " "Basic") (option (@ (value "m-2vcpu-16gb-intel")) "$" 99 " (CPU: " 2 " Mem: " 16 " Disk: " 50 ") " "Premium Memory-Optimized") (option (@ (value "m3-2vcpu-16gb")) "$" 104 " (CPU: " 2 " Mem: " 16 " Disk: " 150 ") " "Memory-Optimized 3x SSD") (option (@ (value "c-4-intel")) "$" 109 " (CPU: " 4 " Mem: " 8 " Disk: " 50 ") " "Premium Intel") (option (@ (value "m3-2vcpu-16gb-intel")) "$" 110 " (CPU: " 2 " Mem: " 16 " Disk: " 150 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "s-8vcpu-16gb-amd")) "$" 112 " (CPU: " 8 " Mem: " 16 " Disk: " 320 ") " "Basic AMD") (option (@ (value "s-8vcpu-16gb-intel")) "$" 112 " (CPU: " 8 " Mem: " 16 " Disk: " 320 ") " "Basic Intel") (option (@ (value "c2-4vcpu-8gb-intel")) "$" 122 " (CPU: " 4 " Mem: " 8 " Disk: " 100 ") " "Premium Intel") (option (@ (value "c5-4vcpu-8gb")) "$" 124 " (CPU: " 4 " Mem: " 8 " Disk: " 250 ") " "CPU Intensive 5x SSD") (option (@ (value "g-4vcpu-16gb")) "$" 126 " (CPU: " 4 " Mem: " 16 " Disk: " 50 ") " "General Purpose") (option (@ (value "s-8vcpu-16gb-480gb-intel")) "$" 128 " (CPU: " 8 " Mem: " 16 " Disk: " 480 ") " "Basic Intel") (option (@ (value "so-2vcpu-16gb-intel")) "$" 131 " (CPU: " 2 " Mem: " 16 " Disk: " 300 ") " "Premium Storage-Optimized") (option (@ (value "so-2vcpu-16gb")) "$" 131 " (CPU: " 2 " Mem: " 16 " Disk: " 300 ") " "Storage-Optimized") (option (@ (value "m6-2vcpu-16gb")) "$" 131 " (CPU: " 2 " Mem: " 16 " Disk: " 300 ") " "Memory-Optimized 6x SSD") (option (@ (value "gd-4vcpu-16gb")) "$" 136 " (CPU: " 4 " Mem: " 16 " Disk: " 100 ") " "General Purpose 2x SSD") (option (@ (value "so1_5-2vcpu-16gb-intel")) "$" 139 " (CPU: " 2 " Mem: " 16 " Disk: " 450 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "g-4vcpu-16gb-intel")) "$" 151 " (CPU: " 4 " Mem: " 16 " Disk: " 60 ") " "General Purpose — Premium Intel") (option (@ (value "gd-4vcpu-16gb-intel")) "$" 158 " (CPU: " 4 " Mem: " 16 " Disk: " 120 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-4vcpu-8gb-intel")) "$" 161 " (CPU: " 4 " Mem: " 8 " Disk: " 250 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-2vcpu-16gb")) "$" 163 " (CPU: " 2 " Mem: " 16 " Disk: " 450 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "s-8vcpu-32gb-amd")) "$" 168 " (CPU: " 8 " Mem: " 32 " Disk: " 400 ") " "Basic AMD") (option (@ (value "m-4vcpu-32gb")) "$" 168 " (CPU: " 4 " Mem: " 32 " Disk: " 100 ") " "Memory-Optimized") (option (@ (value "c-8")) "$" 168 " (CPU: " 8 " Mem: " 16 " Disk: " 100 ") " "CPU-Optimized") (option (@ (value "g6_5-4vcpu-16gb")) "$" 181 " (CPU: " 4 " Mem: " 16 " Disk: " 325 ") " "General Purpose 6.5x SSD") (option (@ (value "g5_5-4vcpu-16gb-intel")) "$" 183.4 " (CPU: " 4 " Mem: " 16 " Disk: " 330 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "c2-8vcpu-16gb")) "$" 188 " (CPU: " 8 " Mem: " 16 " Disk: " 200 ") " "CPU-Optimized 2x SSD") (option (@ (value "s-8vcpu-32gb-640gb-intel")) "$" 192 " (CPU: " 8 " Mem: " 32 " Disk: " 640 ") " "Basic Intel") (option (@ (value "m-4vcpu-32gb-intel")) "$" 198 " (CPU: " 4 " Mem: " 32 " Disk: " 100 ") " "Premium Memory-Optimized") (option (@ (value "m3-4vcpu-32gb")) "$" 208 " (CPU: " 4 " Mem: " 32 " Disk: " 300 ") " "Memory-Optimized 3x SSD") (option (@ (value "c-8-intel")) "$" 218 " (CPU: " 8 " Mem: " 16 " Disk: " 100 ") " "Premium Intel") (option (@ (value "m3-4vcpu-32gb-intel")) "$" 220 " (CPU: " 4 " Mem: " 32 " Disk: " 300 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "c2-8vcpu-16gb-intel")) "$" 244 " (CPU: " 8 " Mem: " 16 " Disk: " 200 ") " "Premium Intel") (option (@ (value "c5-8vcpu-16gb")) "$" 248 " (CPU: " 8 " Mem: " 16 " Disk: " 500 ") " "CPU Intensive 5x SSD") (option (@ (value "g-8vcpu-32gb")) "$" 252 " (CPU: " 8 " Mem: " 32 " Disk: " 100 ") " "General Purpose") (option (@ (value "so-4vcpu-32gb-intel")) "$" 262 " (CPU: " 4 " Mem: " 32 " Disk: " 600 ") " "Premium Storage-Optimized") (option (@ (value "so-4vcpu-32gb")) "$" 262 " (CPU: " 4 " Mem: " 32 " Disk: " 600 ") " "Storage-Optimized") (option (@ (value "m6-4vcpu-32gb")) "$" 262 " (CPU: " 4 " Mem: " 32 " Disk: " 600 ") " "Memory-Optimized 6x SSD") (option (@ (value "gd-8vcpu-32gb")) "$" 272 " (CPU: " 8 " Mem: " 32 " Disk: " 200 ") " "General Purpose 2x SSD") (option (@ (value "so1_5-4vcpu-32gb-intel")) "$" 278 " (CPU: " 4 " Mem: " 32 " Disk: " 900 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "g-8vcpu-32gb-intel")) "$" 302 " (CPU: " 8 " Mem: " 32 " Disk: " 120 ") " "General Purpose — Premium Intel") (option (@ (value "gd-8vcpu-32gb-intel")) "$" 317 " (CPU: " 8 " Mem: " 32 " Disk: " 240 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-8vcpu-16gb-intel")) "$" 322 " (CPU: " 8 " Mem: " 16 " Disk: " 500 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-4vcpu-32gb")) "$" 326 " (CPU: " 4 " Mem: " 32 " Disk: " 900 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "m-8vcpu-64gb")) "$" 336 " (CPU: " 8 " Mem: " 64 " Disk: " 200 ") " "Memory-Optimized") (option (@ (value "c-16")) "$" 336 " (CPU: " 16 " Mem: " 32 " Disk: " 200 ") " "CPU-Optimized") (option (@ (value "g6_5-8vcpu-32gb")) "$" 362 " (CPU: " 8 " Mem: " 32 " Disk: " 650 ") " "General Purpose 6.5x SSD") (option (@ (value "g5_5-8vcpu-32gb-intel")) "$" 366.8 " (CPU: " 8 " Mem: " 32 " Disk: " 660 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "c2-16vcpu-32gb")) "$" 376 " (CPU: " 16 " Mem: " 32 " Disk: " 400 ") " "CPU-Optimized 2x SSD") (option (@ (value "m-8vcpu-64gb-intel")) "$" 396 " (CPU: " 8 " Mem: " 64 " Disk: " 200 ") " "Premium Memory-Optimized") (option (@ (value "m3-8vcpu-64gb")) "$" 416 " (CPU: " 8 " Mem: " 64 " Disk: " 600 ") " "Memory-Optimized 3x SSD") (option (@ (value "c-16-intel")) "$" 437 " (CPU: " 16 " Mem: " 32 " Disk: " 200 ") " "Premium Intel") (option (@ (value "m3-8vcpu-64gb-intel")) "$" 440 " (CPU: " 8 " Mem: " 64 " Disk: " 600 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "c2-16vcpu-32gb-intel")) "$" 489 " (CPU: " 16 " Mem: " 32 " Disk: " 400 ") " "Premium Intel") (option (@ (value "c5-16vcpu-32gb")) "$" 496 " (CPU: " 16 " Mem: " 32 " Disk: " 1000 ") " "CPU Intensive 5x SSD") (option (@ (value "g-16vcpu-64gb")) "$" 504 " (CPU: " 16 " Mem: " 64 " Disk: " 200 ") " "General Purpose") (option (@ (value "so-8vcpu-64gb-intel")) "$" 524 " (CPU: " 8 " Mem: " 64 " Disk: " 1200 ") " "Premium Storage-Optimized") (option (@ (value "so-8vcpu-64gb")) "$" 524 " (CPU: " 8 " Mem: " 64 " Disk: " 1200 ") " "Storage-Optimized") (option (@ (value "m6-8vcpu-64gb")) "$" 524 " (CPU: " 8 " Mem: " 64 " Disk: " 1200 ") " "Memory-Optimized 6x SSD") (option (@ (value "gd-16vcpu-64gb")) "$" 544 " (CPU: " 16 " Mem: " 64 " Disk: " 400 ") " "General Purpose 2x SSD") (option (@ (value "so1_5-8vcpu-64gb-intel")) "$" 556 " (CPU: " 8 " Mem: " 64 " Disk: " 1800 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "g-16vcpu-64gb-intel")) "$" 605 " (CPU: " 16 " Mem: " 64 " Disk: " 240 ") " "General Purpose — Premium Intel") (option (@ (value "gd-16vcpu-64gb-intel")) "$" 634 " (CPU: " 16 " Mem: " 64 " Disk: " 480 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-16vcpu-32gb-intel")) "$" 645 " (CPU: " 16 " Mem: " 32 " Disk: " 1000 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-8vcpu-64gb")) "$" 652 " (CPU: " 8 " Mem: " 64 " Disk: " 1800 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "m-16vcpu-128gb")) "$" 672 " (CPU: " 16 " Mem: " 128 " Disk: " 400 ") " "Memory-Optimized") (option (@ (value "c-32")) "$" 672 " (CPU: " 32 " Mem: " 64 " Disk: " 400 ") " "CPU-Optimized") (option (@ (value "g6_5-16vcpu-64gb")) "$" 724 " (CPU: " 16 " Mem: " 64 " Disk: " 1300 ") " "General Purpose 6.5x SSD") (option (@ (value "g5_5-16vcpu-64gb-intel")) "$" 734.6 " (CPU: " 16 " Mem: " 64 " Disk: " 1320 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "c2-32vcpu-64gb")) "$" 752 " (CPU: " 32 " Mem: " 64 " Disk: " 800 ") " "CPU-Optimized 2x SSD") (option (@ (value "m-16vcpu-128gb-intel")) "$" 792 " (CPU: " 16 " Mem: " 128 " Disk: " 400 ") " "Premium Memory-Optimized") (option (@ (value "m3-16vcpu-128gb")) "$" 832 " (CPU: " 16 " Mem: " 128 " Disk: " 1200 ") " "Memory-Optimized 3x SSD") (option (@ (value "c-32-intel")) "$" 874 " (CPU: " 32 " Mem: " 64 " Disk: " 400 ") " "Premium Intel") (option (@ (value "m3-16vcpu-128gb-intel")) "$" 880 " (CPU: " 16 " Mem: " 128 " Disk: " 1200 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "c2-32vcpu-64gb-intel")) "$" 978 " (CPU: " 32 " Mem: " 64 " Disk: " 800 ") " "Premium Intel") (option (@ (value "c5-32vcpu-64gb")) "$" 992 " (CPU: " 32 " Mem: " 64 " Disk: " 2000 ") " "CPU Intensive 5x SSD") (option (@ (value "c-48")) "$" 1008 " (CPU: " 48 " Mem: " 96 " Disk: " 600 ") " "CPU-Optimized") (option (@ (value "m-24vcpu-192gb")) "$" 1008 " (CPU: " 24 " Mem: " 192 " Disk: " 600 ") " "Memory-Optimized") (option (@ (value "g-32vcpu-128gb")) "$" 1008 " (CPU: " 32 " Mem: " 128 " Disk: " 400 ") " "General Purpose") (option (@ (value "so-16vcpu-128gb-intel")) "$" 1048 " (CPU: " 16 " Mem: " 128 " Disk: " 2400 ") " "Premium Storage-Optimized") (option (@ (value "so-16vcpu-128gb")) "$" 1048 " (CPU: " 16 " Mem: " 128 " Disk: " 2400 ") " "Storage-Optimized") (option (@ (value "m6-16vcpu-128gb")) "$" 1048 " (CPU: " 16 " Mem: " 128 " Disk: " 2400 ") " "Memory-Optimized 6x SSD") (option (@ (value "gd-32vcpu-128gb")) "$" 1088 " (CPU: " 32 " Mem: " 128 " Disk: " 800 ") " "General Purpose 2x SSD") (option (@ (value "so1_5-16vcpu-128gb-intel")) "$" 1112 " (CPU: " 16 " Mem: " 128 " Disk: " 3600 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "c2-48vcpu-96gb")) "$" 1128 " (CPU: " 48 " Mem: " 96 " Disk: " 1200 ") " "CPU-Optimized 2x SSD") (option (@ (value "m-24vcpu-192gb-intel")) "$" 1188 " (CPU: " 24 " Mem: " 192 " Disk: " 600 ") " "Premium Memory-Optimized") (option (@ (value "g-32vcpu-128gb-intel")) "$" 1210 " (CPU: " 32 " Mem: " 128 " Disk: " 480 ") " "General Purpose — Premium Intel") (option (@ (value "m3-24vcpu-192gb")) "$" 1248 " (CPU: " 24 " Mem: " 192 " Disk: " 1800 ") " "Memory-Optimized 3x SSD") (option (@ (value "g-40vcpu-160gb")) "$" 1260 " (CPU: " 40 " Mem: " 160 " Disk: " 500 ") " "General Purpose") (option (@ (value "gd-32vcpu-128gb-intel")) "$" 1268 " (CPU: " 32 " Mem: " 128 " Disk: " 960 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-32vcpu-64gb-intel")) "$" 1290 " (CPU: " 32 " Mem: " 64 " Disk: " 2000 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-16vcpu-128gb")) "$" 1304 " (CPU: " 16 " Mem: " 128 " Disk: " 3600 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "c-48-intel")) "$" 1310 " (CPU: " 48 " Mem: " 96 " Disk: " 600 ") " "Premium Intel") (option (@ (value "m3-24vcpu-192gb-intel")) "$" 1320 " (CPU: " 24 " Mem: " 192 " Disk: " 1800 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "m-32vcpu-256gb")) "$" 1344 " (CPU: " 32 " Mem: " 256 " Disk: " 800 ") " "Memory-Optimized") (option (@ (value "gd-40vcpu-160gb")) "$" 1360 " (CPU: " 40 " Mem: " 160 " Disk: " 1000 ") " "General Purpose 2x SSD") (option (@ (value "g6_5-32vcpu-128gb")) "$" 1448 " (CPU: " 32 " Mem: " 128 " Disk: " 2600 ") " "General Purpose 6.5x SSD") (option (@ (value "c2-48vcpu-96gb-intel")) "$" 1466 " (CPU: " 48 " Mem: " 96 " Disk: " 1200 ") " "Premium Intel") (option (@ (value "g5_5-32vcpu-128gb-intel")) "$" 1469.2 " (CPU: " 32 " Mem: " 128 " Disk: " 2640 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "so-24vcpu-192gb-intel")) "$" 1572 " (CPU: " 24 " Mem: " 192 " Disk: " 3600 ") " "Premium Storage-Optimized") (option (@ (value "so-24vcpu-192gb")) "$" 1572 " (CPU: " 24 " Mem: " 192 " Disk: " 3600 ") " "Storage-Optimized") (option (@ (value "m6-24vcpu-192gb")) "$" 1572 " (CPU: " 24 " Mem: " 192 " Disk: " 3600 ") " "Memory-Optimized 6x SSD") (option (@ (value "m-32vcpu-256gb-intel")) "$" 1584 " (CPU: " 32 " Mem: " 256 " Disk: " 800 ") " "Premium Memory-Optimized") (option (@ (value "c-60-intel")) "$" 1639 " (CPU: " 60 " Mem: " 120 " Disk: " 750 ") " "Premium Intel") (option (@ (value "m3-32vcpu-256gb")) "$" 1664 " (CPU: " 32 " Mem: " 256 " Disk: " 2400 ") " "Memory-Optimized 3x SSD") (option (@ (value "so1_5-24vcpu-192gb-intel")) "$" 1668 " (CPU: " 24 " Mem: " 192 " Disk: " 5400 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "m3-32vcpu-256gb-intel")) "$" 1760 " (CPU: " 32 " Mem: " 256 " Disk: " 2400 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "g6_5-40vcpu-160gb")) "$" 1810 " (CPU: " 40 " Mem: " 160 " Disk: " 3250 ") " "General Purpose 6.5x SSD") (option (@ (value "g-48vcpu-192gb-intel")) "$" 1814 " (CPU: " 48 " Mem: " 192 " Disk: " 720 ") " "General Purpose — Premium Intel") (option (@ (value "c2-60vcpu-120gb-intel")) "$" 1834 " (CPU: " 60 " Mem: " 120 " Disk: " 1500 ") " "Premium Intel") (option (@ (value "gd-48vcpu-192gb-intel")) "$" 1901 " (CPU: " 48 " Mem: " 192 " Disk: " 1440 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-48vcpu-96gb-intel")) "$" 1934 " (CPU: " 48 " Mem: " 96 " Disk: " 3000 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-24vcpu-192gb")) "$" 1956 " (CPU: " 24 " Mem: " 192 " Disk: " 5400 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "so-32vcpu-256gb-intel")) "$" 2096 " (CPU: " 32 " Mem: " 256 " Disk: " 4800 ") " "Premium Storage-Optimized") (option (@ (value "so-32vcpu-256gb")) "$" 2096 " (CPU: " 32 " Mem: " 256 " Disk: " 4800 ") " "Storage-Optimized") (option (@ (value "m6-32vcpu-256gb")) "$" 2096 " (CPU: " 32 " Mem: " 256 " Disk: " 4800 ") " "Memory-Optimized 6x SSD") (option (@ (value "g5_5-48vcpu-192gb-intel")) "$" 2202.8 " (CPU: " 48 " Mem: " 192 " Disk: " 3960 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "so1_5-32vcpu-256gb-intel")) "$" 2224 " (CPU: " 32 " Mem: " 256 " Disk: " 7200 ") " "Premium Storage-Optimized 1.5x SSD") (option (@ (value "g-60vcpu-240gb-intel")) "$" 2269 " (CPU: " 60 " Mem: " 240 " Disk: " 900 ") " "General Purpose — Premium Intel") (option (@ (value "m-48vcpu-384gb-intel")) "$" 2376 " (CPU: " 48 " Mem: " 384 " Disk: " 1200 ") " "Premium Memory-Optimized") (option (@ (value "gd-60vcpu-240gb-intel")) "$" 2378 " (CPU: " 60 " Mem: " 240 " Disk: " 1800 ") " "General Purpose — Premium Intel 2x SSD") (option (@ (value "c5-60vcpu-120gb-intel")) "$" 2419 " (CPU: " 60 " Mem: " 120 " Disk: " 3750 ") " "CPU Optimized - Premium Intel 5x SSD") (option (@ (value "so1_5-32vcpu-256gb")) "$" 2608 " (CPU: " 32 " Mem: " 256 " Disk: " 7200 ") " "Storage-Optimized 1.5x SSD") (option (@ (value "m3-48vcpu-384gb-intel")) "$" 2640 " (CPU: " 48 " Mem: " 384 " Disk: " 3600 ") " "Premium Memory-Optimized 3x SSD") (option (@ (value "g5_5-60vcpu-240gb-intel")) "$" 2755 " (CPU: " 60 " Mem: " 240 " Disk: " 4950 ") " "General Purpose - Premium Intel 5.5x SSD") (option (@ (value "so-48vcpu-384gb-intel")) "$" 3144 " (CPU: " 48 " Mem: " 384 " Disk: " 7000 ") " "Premium Storage-Optimized"))) (Form-Nav (@ (back-to ,(conc "/config/wizard/machine/" (instance-id))))))))) + (test-widget-get (conc "/config/wizard/machine2/" (instance-id)))) -(test "POST /config/wizard/machine2-submit" +(test "POST /config/wizard/machine2-submit/:id" `(found "s-1vcpu-1gb") (let* ((resp (test-route - app 'POST "/config/wizard/machine2-submit" + app 'POST (conc "/config/wizard/machine2-submit/" (instance-id)) headers: '((content-type . (#(application/x-www-form-urlencoded ())))) body: (form-urlencode `((size . "s-1vcpu-1gb"))))) - (c (with-db/transaction (lambda (db) (get-user-service-config db (test-user-id)))))) + (c (with-db/transaction (lambda (db) (get-user-service-config db (test-user-id) (instance-id)))))) `(,(car resp) ,(alist-ref 'digitalocean-size c)))) -(test "before submit, GET /config/wizard/review" - '(App (Configuration-Wizard +(test "before submit, GET /config/wizard/review/:id" + `(App (Configuration-Wizard (@ (step "Review")) (h2 "Root Domain") "root-domain.com" @@ -418,11 +436,11 @@ (li nextcloud " @ " "nextcloud-sub" "." "root-domain.com")) (h2 "Machine") (ul (li "Region: " "sfo3") (li "Size: " "s-1vcpu-1gb")) - (form (@ (action "/config/wizard/review-submit") (method POST)) + (form (@ (action ,(conc "/config/wizard/review-submit/" (instance-id))) (method POST)) (VStack (Form-Nav - (@ (back-to "/config/wizard/machine2") + (@ (back-to ,(conc "/config/wizard/machine2/" (instance-id))) (submit-button "Launch"))))))) - (test-widget-get "/config/wizard/review")) + (test-widget-get (conc "/config/wizard/review/" (instance-id)))) (cleanup-test-data)