@bongos/core 1.19.1081 → 1.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +528 -193
- package/.claude/skills/planning-session/SKILL.md +6 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +32 -0
- package/clients/bongos-client/index.cjs +32 -0
- package/clients/bongos-client/index.d.ts +51 -6
- package/clients/bongos-client/index.mjs +32 -0
- package/docs/adr/0099-delayed-redacted-mirror-export.md +5 -1
- package/docs/adr/0111-instance-hosting-provisioning-module.md +1 -0
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +17 -0
- package/docs/adr/0176-private-repo-deploy-keys.md +2 -0
- package/docs/adr/0310-a-speciality-offers-skills-and-the-adopter-chooses-them.md +1 -1
- package/docs/adr/0348-the-web-tier-may-look-read-only-at-what-an-owners-render-key-can-see.md +53 -0
- package/docs/adr/0349-a-version-preview-is-a-sandboxed-child-the-web-tier-launches.md +106 -0
- package/docs/adr/0350-the-hub-holds-a-per-project-write-deploy-key-so-a-hosted-upgrade-reaches-github.md +103 -0
- package/docs/adr/README.md +3 -0
- package/docs/api/openapi.json +851 -19
- package/docs/api-reference.md +33 -9
- package/docs/architecture.md +6 -2
- package/docs/copy-inventory.md +620 -553
- package/docs/copy-registry.json +1467 -820
- package/docs/file-map.md +2 -0
- package/docs/module-api-changelog.md +8 -0
- package/docs/modules-contract.md +1 -0
- package/docs/page-inventory.json +38 -4
- package/docs/page-readings.json +1666 -1558
- package/migrations/core_260_goals_working_area.sql +63 -0
- package/migrations/core_261_grade_attempts_verdicts.sql +24 -0
- package/migrations/core_262_drop_builder_box_blocked.sql +33 -0
- package/modules/agents/lib/validate.js +43 -0
- package/modules/autonomy/gauge.js +38 -2
- package/modules/grading/grader-subagent.js +37 -2
- package/modules/grading/grader-workers/reader.js +65 -5
- package/modules/hall-ui/public/approval-queue.css +33 -7
- package/modules/hall-ui/public/approval-queue.js +9 -2
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/brand-holes.js +121 -0
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/collab.js +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +6 -1
- package/modules/hall-ui/public/deploy.js +22 -8
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/dom-utils.js +20 -0
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-inbox.js +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/idea.js +28 -0
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/ideas.js +19 -3
- package/modules/hall-ui/public/index.html +5 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile-nudge.js +21 -4
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/profile.js +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +19 -30
- package/modules/hall-ui/public/settings.js +24 -177
- package/modules/hall-ui/public/settings.states.json +1 -1
- package/modules/hall-ui/public/shell.js +4 -2
- package/modules/hall-ui/public/studio.css +14 -8
- package/modules/hall-ui/public/studio.html +5 -4
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.css +6 -3
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +82 -1
- package/modules/hall-ui/public/tweak-editor.css +142 -11
- package/modules/hall-ui/public/tweak-editor.html +25 -9
- package/modules/hall-ui/public/tweak-editor.js +243 -10
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/approval-queue.md +6 -0
- package/modules/hall-ui/records/tweak-editor.md +6 -0
- package/modules/ideas/ratify.js +36 -8
- package/modules/ideas/routes/ratify-goal.js +6 -0
- package/modules/lifecycle/db-analytics.js +79 -10
- package/modules/lifecycle/db-claims.js +48 -1
- package/modules/lifecycle/db-deps-criteria.js +94 -40
- package/modules/lifecycle/db-goals.js +4 -1
- package/modules/lifecycle/db-grade.js +9 -1
- package/modules/lifecycle/db-relevance-flags.js +183 -0
- package/modules/lifecycle/db-versions.js +4 -3
- package/modules/lifecycle/db.js +19 -0
- package/modules/lifecycle/done-when.js +5 -3
- package/modules/lifecycle/est-advisory.js +31 -4
- package/modules/lifecycle/goal-task-relevance-advisory.js +153 -0
- package/modules/lifecycle/goal-task-relevance-judge.js +226 -0
- package/modules/lifecycle/migrations/lifecycle_014_goal_task_relevance_flags.sql +82 -0
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/routes/claims.js +8 -1
- package/modules/lifecycle/routes/goal-task-relevance.js +72 -0
- package/modules/lifecycle/routes/goals.js +1 -1
- package/modules/lifecycle/routes/tasks.js +27 -24
- package/modules/lifecycle/routes/visuals.js +9 -0
- package/modules/lifecycle/ship-preflight.js +7 -11
- package/modules/lifecycle/task-visuals.js +109 -5
- package/modules/npm-release/module.json +2 -1
- package/modules/npm-release/preview/commands.js +81 -0
- package/modules/npm-release/preview/divert.js +95 -0
- package/modules/npm-release/preview/env.js +78 -0
- package/modules/npm-release/preview/proxy.js +107 -0
- package/modules/npm-release/preview/runtime.js +83 -0
- package/modules/npm-release/preview/supervisor.js +175 -0
- package/modules/npm-release/public/work.js +65 -1
- package/modules/npm-release/routes/preview.js +133 -0
- package/modules/provisioning/app-status.js +83 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/pollers/app-liveness.js +110 -0
- package/modules/provisioning/render-lookup.js +68 -0
- package/modules/provisioning/render-standup.js +44 -1
- package/modules/provisioning/routes/render-standup.js +81 -6
- package/modules/provisioning/starter-bundles.js +4 -1
- package/modules/public-landing/public/account-private.states.json +14 -0
- package/modules/public-landing/public/account.html +513 -0
- package/modules/public-landing/public/account.probes.json +37 -0
- package/modules/public-landing/public/account.states.json +16 -0
- package/modules/public-landing/public/index.html +5 -2
- package/modules/public-landing/public/projects.html +661 -18
- package/modules/public-landing/public/projects.states.json +8 -1
- package/modules/render-deploy/deploys.js +203 -0
- package/modules/render-deploy/migrations/render_deploy_001_app.sql +22 -0
- package/modules/render-deploy/module.json +25 -0
- package/modules/render-deploy/public/deploy.css +10 -0
- package/modules/render-deploy/public/deploy.js +249 -0
- package/modules/render-deploy/render.js +56 -0
- package/modules/render-deploy/routes/act.js +88 -0
- package/modules/render-deploy/routes/app.js +65 -0
- package/modules/render-deploy/routes/history.js +38 -0
- package/modules/specialities/db.js +27 -8
- package/modules/specialities/migrations/specialities_003_skills.sql +46 -0
- package/modules/specialities/routes/specialities.js +51 -3
- package/modules/specialities/skills.js +94 -0
- package/modules/specialities/specialities.js +56 -9
- package/modules/ui-design/kit/fixtures/me-cross-project-private.json +12 -0
- package/modules/ui-design/kit/fixtures/me__cross-project.json +15 -0
- package/modules/ui-design/kit/fixtures/provisioning-instance-render.json +39 -0
- package/modules/ui-design/kit/lib.js +3 -1
- package/modules/ui-design/kit/serve.js +96 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +169 -0
- package/scripts/gds/agents-sync.js +13 -3
- package/scripts/gds/autobongos-run.js +80 -2
- package/scripts/gds/autobongos-service.cmd +12 -0
- package/scripts/gds/copy-apply.js +14 -0
- package/scripts/gds/dev-box-guard.js +3 -2
- package/scripts/gds/fitness.js +9 -0
- package/scripts/gds/grade-correlation-audit.js +42 -12
- package/scripts/gds/grade-replay.js +1 -1
- package/scripts/gds/provision-core-upgrade.js +38 -7
- package/scripts/gds/provision-pin-key.js +176 -0
- package/scripts/gds/provision-render.js +62 -31
- package/scripts/gds/provision-teardown.js +7 -2
- package/scripts/gds/provision-units.js +31 -0
- package/scripts/gds/provision.js +10 -10
- package/scripts/gds/publish-manifest.js +1 -0
- package/scripts/gds/render-api.js +19 -0
- package/scripts/gds/render-payload.js +39 -65
- package/scripts/gds/run-unit-tests.js +10 -0
- package/scripts/gds/ship-finish.js +19 -20
- package/scripts/gds/smoke-dependencies.sh +37 -8
- package/scripts/gds/status.js +12 -4
- package/scripts/gds/upgrade.js +2 -2
- package/scripts/public-mirror-export.js +7 -1
- package/src/bongos/module-scope-map.js +13 -0
- package/src/bongos/serve-internal.js +3 -1
- package/src/module-api.js +10 -1
- package/src/platform-server.js +39 -0
- package/tests/account_privacy_flags.mjs +10 -6
- package/tests/agents_authoring.mjs +15 -1
- package/tests/agents_sync.mjs +106 -5
- package/tests/agents_validate.mjs +45 -0
- package/tests/api_path_404.mjs +6 -0
- package/tests/autobongos_cadence.mjs +7 -0
- package/tests/autobongos_loop.mjs +136 -1
- package/tests/autonomy_gauge.mjs +62 -0
- package/tests/blocker_hall_live_proof.mjs +275 -0
- package/tests/claim_gate_ci_unblock.mjs +116 -0
- package/tests/claim_gate_rebase.mjs +15 -3
- package/tests/collab_page.mjs +10 -0
- package/tests/conductor_main_e2e.mjs +97 -0
- package/tests/core_262_drop_box_blocked_db.mjs +143 -0
- package/tests/core_upgrade_runner.mjs +1 -0
- package/tests/dependency_writes_atomic.mjs +191 -0
- package/tests/deploy_page_projects.mjs +2 -1
- package/tests/effective_visibility_predicate.mjs +15 -0
- package/tests/est_advisory.mjs +27 -0
- package/tests/goal_map_page.mjs +26 -0
- package/tests/goal_task_relevance.mjs +518 -0
- package/tests/goal_working_area.mjs +146 -0
- package/tests/grade_attempts.mjs +61 -0
- package/tests/grade_attribution.mjs +23 -0
- package/tests/grade_correlation_audit.mjs +54 -1
- package/tests/grader_reader_lens.mjs +55 -2
- package/tests/grader_root_outage.mjs +6 -1
- package/tests/grader_subagent_tools_arg.mjs +102 -0
- package/tests/hall_approval_queue.mjs +54 -2
- package/tests/hall_settings_world.mjs +3 -1
- package/tests/hall_tweak_editor.mjs +316 -11
- package/tests/hub_account_page.mjs +394 -0
- package/tests/idea_detail_page.mjs +64 -0
- package/tests/idea_goal_ratification.mjs +64 -0
- package/tests/idea_spark_hall.mjs +60 -9
- package/tests/ideator_full_idea_shapes_space_proof.mjs +1 -1
- package/tests/landing_page.mjs +6 -3
- package/tests/module-scope-map.mjs +31 -8
- package/tests/module_api.mjs +1 -0
- package/tests/module_loader.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/no_phantom_mirror_workflow.mjs +24 -0
- package/tests/npm_release_preview_commands.mjs +89 -0
- package/tests/npm_release_preview_divert.mjs +164 -0
- package/tests/npm_release_preview_env.mjs +100 -0
- package/tests/npm_release_preview_proxy.mjs +162 -0
- package/tests/npm_release_preview_routes.mjs +208 -0
- package/tests/npm_release_preview_supervisor.mjs +203 -0
- package/tests/pin_write_key.mjs +383 -0
- package/tests/planning_session_skill.mjs +27 -0
- package/tests/platform_boot.mjs +71 -0
- package/tests/profile_nudge_links.mjs +86 -0
- package/tests/profile_ui_cross_project.mjs +1 -1
- package/tests/projects_hub.mjs +2 -2
- package/tests/projects_hub_app_status.mjs +249 -0
- package/tests/projects_hub_app_step.mjs +282 -52
- package/tests/projects_hub_render_connect.mjs +295 -0
- package/tests/provision_render.mjs +121 -28
- package/tests/provision_settings_apply.mjs +62 -0
- package/tests/provisioning_app_status.mjs +204 -0
- package/tests/provisioning_render_route.mjs +126 -6
- package/tests/render_check.mjs +2 -1
- package/tests/render_deploy.mjs +495 -0
- package/tests/rot_inbox.mjs +8 -0
- package/tests/ship_preflight.mjs +61 -8
- package/tests/smoke_dependencies_witness.mjs +46 -0
- package/tests/speciality_skills.mjs +214 -0
- package/tests/studio_room_height.mjs +70 -0
- package/tests/task_detail_includes.mjs +10 -0
- package/tests/task_visuals_instance_root.mjs +150 -0
- package/tests/tweak_batch_apply.mjs +21 -0
- package/tests/ui_design_kit.mjs +36 -4
- package/tests/upgrade.mjs +1 -1
- package/tests/upgrade_persist_pin.mjs +24 -0
|
@@ -116,15 +116,15 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
|
|
|
116
116
|
.wizField small{font-family:var(--font-body);font-size:12.5px;letter-spacing:0;text-transform:none;color:var(--ink-faint);}
|
|
117
117
|
/* the About step's description is prose, so it is a textarea — it wears the
|
|
118
118
|
same control skin as every other field (task 1002334), never its own */
|
|
119
|
-
#view-new input[type=text], #view-new textarea{
|
|
119
|
+
#view-new input[type=text], #view-new input[type=password], #view-new textarea{
|
|
120
120
|
width:100%;min-height:44px;font-family:var(--font-body);font-size:17px;font-weight:400;color:var(--ink);
|
|
121
121
|
background:none;border:0;border-bottom:1px solid var(--rule);border-radius:0;padding:8px 0;outline:none;
|
|
122
122
|
transition:border-color .15s ease;
|
|
123
123
|
}
|
|
124
124
|
#view-new textarea{font-size:15.5px;line-height:1.5;resize:vertical;min-height:64px;display:block;}
|
|
125
125
|
/* no fade toward the card — a 70% mix washed placeholders to 2.89:1 (task 1002749) */
|
|
126
|
-
#view-new input[type=text]::placeholder, #view-new textarea::placeholder{color:var(--ink-faint);font-weight:400;}
|
|
127
|
-
#view-new input[type=text]:focus, #view-new textarea:focus{border-bottom-color:var(--ink);box-shadow:none;}
|
|
126
|
+
#view-new input[type=text]::placeholder, #view-new input[type=password]::placeholder, #view-new textarea::placeholder{color:var(--ink-faint);font-weight:400;}
|
|
127
|
+
#view-new input[type=text]:focus, #view-new input[type=password]:focus, #view-new textarea:focus{border-bottom-color:var(--ink);box-shadow:none;}
|
|
128
128
|
#view-new input[type=text].bad{border-bottom-color:var(--err);}
|
|
129
129
|
.hint{font-size:13.5px;font-weight:400;color:var(--ink-faint);line-height:1.45;margin-top:8px;min-height:18px;}
|
|
130
130
|
.hint.ok{color:var(--ok);}
|
|
@@ -316,6 +316,50 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
316
316
|
.appOpt span span{display:block;font-size:14px;font-weight:400;color:var(--ink-soft);line-height:1.5;max-width:62ch;}
|
|
317
317
|
.appOpt.soon{cursor:default;}
|
|
318
318
|
.appOpt.soon b{color:var(--ink-soft);}
|
|
319
|
+
/* the Render setup (task 1004352): shown only while Render is the choice. Every
|
|
320
|
+
list in it is a native radio group, and nothing in it is pre-picked for the owner. */
|
|
321
|
+
/* the app's setup status (task 1004353): the same block in the wizard's done panel and
|
|
322
|
+
on the project's page. Three stages on a hairline, then one sentence; a failure is a
|
|
323
|
+
sentence and a Retry the owner starts — the platform never retries by itself. */
|
|
324
|
+
.appStat{margin:14px 0 0;}
|
|
325
|
+
.appStat .appStages{list-style:none;display:flex;gap:0;margin:0 0 14px;padding:0;border-top:1px solid var(--rule);}
|
|
326
|
+
.appStat .appStages li{flex:1;padding:8px 0 0;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);border-top:2px solid transparent;margin-top:-1px;}
|
|
327
|
+
.appStat .appStages li.done{color:var(--ink-soft);border-top-color:var(--ink-soft);}
|
|
328
|
+
.appStat .appStages li.now{color:var(--ink);border-top-color:var(--accent);}
|
|
329
|
+
.appStat p{font-size:14.5px;font-weight:400;color:var(--ink-soft);line-height:1.55;max-width:62ch;margin:0 0 10px;}
|
|
330
|
+
.appStat p.err{color:var(--err);}
|
|
331
|
+
.appStat a{display:inline-block;padding:3px 0;color:var(--link-ink);font-weight:700;text-decoration:none;}
|
|
332
|
+
.appStat h3{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;font-weight:400;color:var(--ink-faint);margin:18px 0 8px;}
|
|
333
|
+
.appStat .appVars{list-style:none;margin:0 0 8px;padding:0;}
|
|
334
|
+
.appStat .appVars li{padding:6px 0;border-bottom:1px solid var(--rule);font-size:14px;color:var(--ink-soft);}
|
|
335
|
+
.appStat .appVars code{color:var(--ink);}
|
|
336
|
+
.appStat .appRetry{margin-top:12px;}
|
|
337
|
+
.appStat .appRetry[hidden]{display:none;}
|
|
338
|
+
.appStat input[type=password]{width:100%;min-height:44px;font-family:var(--font-body);font-size:16px;color:var(--ink);background:none;border:0;border-bottom:1px solid var(--rule);border-radius:0;padding:8px 0;outline:none;}
|
|
339
|
+
.appStat input[type=password]:focus{border-bottom-color:var(--ink);}
|
|
340
|
+
.appStat .btn{min-height:44px;margin-top:10px;}
|
|
341
|
+
.renderSetup{margin:4px 0 18px;padding:18px 0 0;}
|
|
342
|
+
.renderSetup[hidden], .renderSetup [hidden]{display:none;}
|
|
343
|
+
.renderWhy{font-size:14px;font-weight:400;color:var(--ink-soft);line-height:1.55;max-width:62ch;margin:0 0 18px;}
|
|
344
|
+
.renderWhy a{display:inline-block;padding:3px 0;color:var(--link-ink);font-weight:700;text-decoration:none;}
|
|
345
|
+
.renderKeyRow{display:flex;align-items:flex-end;gap:14px;}
|
|
346
|
+
.renderKeyRow .wizField{flex:1;margin-bottom:0;}
|
|
347
|
+
.renderKeyRow .btn{flex:none;min-height:44px;}
|
|
348
|
+
.renderSetup .appOpts{margin-top:22px;}
|
|
349
|
+
.renderSetup .appOpt.off{cursor:default;}
|
|
350
|
+
.renderSetup .appOpt.off b{color:var(--ink-soft);}
|
|
351
|
+
.renderNew{margin-top:22px;}
|
|
352
|
+
#view-new .renderSetup select{width:100%;min-height:44px;font-family:var(--font-body);font-size:16px;color:var(--ink);background:none;border:0;border-bottom:1px solid var(--rule);border-radius:0;padding:8px 0;outline:none;}
|
|
353
|
+
#view-new .renderSetup select:focus{border-bottom-color:var(--ink);}
|
|
354
|
+
#view-new .renderSetup select option{background:var(--card);color:var(--ink);}
|
|
355
|
+
/* the same step on a project's page (task 1004184) wears the wizard's control skin */
|
|
356
|
+
.appConnect{margin:14px 0 0;}
|
|
357
|
+
.appConnect .finePrint{max-width:62ch;}
|
|
358
|
+
.appConnect .actions{display:flex;gap:12px;flex-wrap:wrap;}
|
|
359
|
+
.appConnect .renderSetup input[type=text], .appConnect .renderSetup input[type=password], .appConnect .renderSetup select{width:100%;min-height:44px;font-family:var(--font-body);font-size:16px;color:var(--ink);background:none;border:0;border-bottom:1px solid var(--rule);border-radius:0;padding:8px 0;outline:none;}
|
|
360
|
+
.appConnect .renderSetup input[type=text]:focus, .appConnect .renderSetup input[type=password]:focus, .appConnect .renderSetup select:focus{border-bottom-color:var(--ink);}
|
|
361
|
+
.appConnect .renderSetup input::placeholder{color:var(--ink-faint);}
|
|
362
|
+
.appConnect .renderSetup select option{background:var(--card);color:var(--ink);}
|
|
319
363
|
.appOpt .soonTag{display:inline-block;margin-left:8px;padding:0 8px;border:1px solid var(--rule-soft);border-radius:999px;font-size:12px;font-weight:500;line-height:1.6;color:var(--ink-soft);vertical-align:1px;}
|
|
320
364
|
|
|
321
365
|
/* GitHub repo picker (task 1002099): hairline rows, the chosen one marked on
|
|
@@ -471,11 +515,11 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
471
515
|
<p class="bandWho" id="bandWho"></p>
|
|
472
516
|
<ul class="bandRow">
|
|
473
517
|
<li class="bandCell"><a class="bandT" href="?view=mine" data-nav-to="mine">My projects</a></li>
|
|
474
|
-
<!-- the
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
518
|
+
<!-- the slot that waited for a settings page (task 1004259): the hub's
|
|
519
|
+
account page, where the person's own settings live (ADR 0337 D4.3).
|
|
520
|
+
Apex-only because the page is — /account is served only where
|
|
521
|
+
platform-identity runs, so a child instance loses the item whole. -->
|
|
522
|
+
<!--apex-only:start--><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
|
|
479
523
|
<!-- the honest hover target; the delegated click upgrades it to the POST -->
|
|
480
524
|
<li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
|
|
481
525
|
</ul>
|
|
@@ -782,6 +826,27 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
782
826
|
Hidden outright when the server sent no verdict: a platform with no
|
|
783
827
|
public map has nothing to say here, and inventing a local rule to
|
|
784
828
|
fill the silence is exactly what the seam exists to prevent. -->
|
|
829
|
+
<!-- where its app runs (task 1004353): the app's setup on the owner's Render
|
|
830
|
+
account — its stage, its address, the way to Render, the env var NAMES still
|
|
831
|
+
to set there, and on a failure one sentence and a Retry that asks for the key
|
|
832
|
+
again. For a project whose app has no platform chosen, the OWNER is offered
|
|
833
|
+
Connect Render (task 1004184): the setup wizard's own Render step, cloned into
|
|
834
|
+
#mAppConnectForm on first open, so there is one flow and one set of words. -->
|
|
835
|
+
<div class="card mSec" id="mAppCard" hidden>
|
|
836
|
+
<h2>Where its app runs</h2>
|
|
837
|
+
<div id="mApp"></div>
|
|
838
|
+
<div class="appConnect" id="mAppConnect" hidden>
|
|
839
|
+
<p class="finePrint">Your app has no platform yet. We keep hosting this project’s Builders' hall either way; you can deploy the app itself to your own Render account.</p>
|
|
840
|
+
<button type="button" class="btn small" id="mAppConnectOpen">Connect Render</button>
|
|
841
|
+
<div class="appConnectForm" id="mAppConnectForm" hidden>
|
|
842
|
+
<div class="actions">
|
|
843
|
+
<button type="button" class="btn" id="mAppConnectGo">Connect Render</button>
|
|
844
|
+
<button type="button" class="btn ghost" id="mAppConnectCancel">Cancel</button>
|
|
845
|
+
</div>
|
|
846
|
+
</div>
|
|
847
|
+
</div>
|
|
848
|
+
</div>
|
|
849
|
+
|
|
785
850
|
<div class="card mSec" id="mPublish" hidden>
|
|
786
851
|
<h2>On the public map</h2>
|
|
787
852
|
<div id="mPub"><p class="finePrint">Reading the project…</p></div>
|
|
@@ -1317,10 +1382,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1317
1382
|
</section>
|
|
1318
1383
|
|
|
1319
1384
|
<!-- step 7 · where the app runs (task 1004351, ADR 0345). The hall is ours to
|
|
1320
|
-
host, so this step is only about the owner's APP.
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1385
|
+
host, so this step is only about the owner's APP. "Decide later" is the
|
|
1386
|
+
owner's ruling and is always the default. Render (task 1004352) asks for
|
|
1387
|
+
the owner's key, checks it, and makes them CHOOSE the workspace — a
|
|
1388
|
+
Render key reaches every workspace its user is in, so nothing is
|
|
1389
|
+
pre-picked. The key lives in a variable only: never in the draft, a
|
|
1390
|
+
URL, a log or any browser store. -->
|
|
1324
1391
|
<section class="panel" id="panel-app" aria-labelledby="app-h">
|
|
1325
1392
|
<h1 id="app-h">Where your app will run</h1>
|
|
1326
1393
|
<p class="appHall"><b>We host your Builders' hall</b> — the place where the project gets built — at the address you just chose. There is nothing for you to set up.</p>
|
|
@@ -1328,9 +1395,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1328
1395
|
<form data-next="8">
|
|
1329
1396
|
<fieldset class="appOpts" id="appOpts">
|
|
1330
1397
|
<legend>Where your app runs</legend>
|
|
1331
|
-
<label class="appOpt
|
|
1332
|
-
<input type="radio" name="appPlatform" value="render"
|
|
1333
|
-
<span><b>Render
|
|
1398
|
+
<label class="appOpt" id="appOptRender">
|
|
1399
|
+
<input type="radio" name="appPlatform" value="render" aria-describedby="appRenderWhat">
|
|
1400
|
+
<span><b>Render</b>
|
|
1334
1401
|
<span id="appRenderWhat">We set your app up on your own Render account. You lend us your Render key once, and Render bills you directly.</span></span>
|
|
1335
1402
|
</label>
|
|
1336
1403
|
<label class="appOpt">
|
|
@@ -1339,6 +1406,49 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1339
1406
|
<span>Create the project now. You can choose where your app runs afterwards.</span></span>
|
|
1340
1407
|
</label>
|
|
1341
1408
|
</fieldset>
|
|
1409
|
+
<div class="renderSetup" id="renderSetup" hidden>
|
|
1410
|
+
<p class="renderWhy">Your Render key is used once, to create your app on your own Render account, and then thrown away — it is not kept, not even in this browser. Render bills you directly. A key can act on your whole Render account, so only paste it here. <a href="https://dashboard.render.com/u/settings#api-keys" target="_blank" rel="noopener noreferrer">Create a key in Render</a></p>
|
|
1411
|
+
<div class="renderKeyRow">
|
|
1412
|
+
<label class="wizField"><span>Render API key</span>
|
|
1413
|
+
<input type="password" id="f-renderKey" autocomplete="off" spellcheck="false" maxlength="200" placeholder="rnd_…" aria-describedby="h-renderKey">
|
|
1414
|
+
</label>
|
|
1415
|
+
<button type="button" class="btn ghost small" id="renderCheck">Check key</button>
|
|
1416
|
+
</div>
|
|
1417
|
+
<div class="hint" id="h-renderKey" role="status"> </div>
|
|
1418
|
+
<fieldset class="appOpts" id="renderWs" hidden>
|
|
1419
|
+
<legend>Which Render workspace</legend>
|
|
1420
|
+
<div id="renderWsList"></div>
|
|
1421
|
+
</fieldset>
|
|
1422
|
+
<fieldset class="appOpts" id="renderSvc" hidden>
|
|
1423
|
+
<legend>A new app, or one you already run</legend>
|
|
1424
|
+
<div id="renderSvcList"></div>
|
|
1425
|
+
</fieldset>
|
|
1426
|
+
<div class="renderNew" id="renderNew" hidden>
|
|
1427
|
+
<label class="wizField"><span>Your app is built with</span>
|
|
1428
|
+
<select id="f-renderRuntime">
|
|
1429
|
+
<option value="node">Node.js</option><option value="python">Python</option><option value="ruby">Ruby</option>
|
|
1430
|
+
<option value="go">Go</option><option value="rust">Rust</option><option value="elixir">Elixir</option>
|
|
1431
|
+
<option value="docker">Docker — use my repository’s Dockerfile</option>
|
|
1432
|
+
</select>
|
|
1433
|
+
</label>
|
|
1434
|
+
<label class="wizField" id="renderBuildWrap"><span>Build command <small>— leave empty if there is none</small></span>
|
|
1435
|
+
<input type="text" id="f-renderBuild" autocomplete="off" spellcheck="false" maxlength="500" placeholder="npm ci">
|
|
1436
|
+
</label>
|
|
1437
|
+
<label class="wizField" id="renderStartWrap"><span>Start command</span>
|
|
1438
|
+
<input type="text" id="f-renderStart" autocomplete="off" spellcheck="false" maxlength="500" placeholder="npm start">
|
|
1439
|
+
</label>
|
|
1440
|
+
<label class="wizField"><span>Plan</span>
|
|
1441
|
+
<select id="f-renderPlan"></select>
|
|
1442
|
+
</label>
|
|
1443
|
+
<label class="addrOpt"><input type="checkbox" id="f-renderDb">
|
|
1444
|
+
<span>My app needs a database (Render Postgres)</span>
|
|
1445
|
+
</label>
|
|
1446
|
+
<label class="wizField" id="renderDbPlanWrap" hidden><span>Database plan</span>
|
|
1447
|
+
<select id="f-renderDbPlan"></select>
|
|
1448
|
+
</label>
|
|
1449
|
+
</div>
|
|
1450
|
+
<div class="hint" id="h-render" role="status"> </div>
|
|
1451
|
+
</div>
|
|
1342
1452
|
<div class="nav">
|
|
1343
1453
|
<button type="button" class="btn ghost" data-back="6">← Back</button>
|
|
1344
1454
|
<button type="submit" class="btn">Next →</button>
|
|
@@ -1366,6 +1476,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1366
1476
|
<h1 id="done-h">The drums are rolling</h1>
|
|
1367
1477
|
</div>
|
|
1368
1478
|
<p class="lede" id="doneLede"></p>
|
|
1479
|
+
<p class="hint" id="doneRender" role="status" hidden></p>
|
|
1480
|
+
<div id="doneApp" hidden></div>
|
|
1369
1481
|
<!-- the STABLE live region: the strip inside is rebuilt every 5s tick,
|
|
1370
1482
|
and a live region that is itself replaced never announces -->
|
|
1371
1483
|
<div id="liveProgress" role="status" aria-live="polite"></div>
|
|
@@ -1512,6 +1624,390 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1512
1624
|
});
|
|
1513
1625
|
}
|
|
1514
1626
|
|
|
1627
|
+
/* ── the app's setup on Render (task 1004353) ──
|
|
1628
|
+
GET /provisioning/instances/:id/render is the one source, on both the wizard's done
|
|
1629
|
+
panel and the project's page, so the two can never tell the owner different stories.
|
|
1630
|
+
The server has already shaped it: the stage, the app's address (only ever an
|
|
1631
|
+
onrender.com one), the links (built from a checked service id), the failure as one
|
|
1632
|
+
sentence, and the answers a Retry re-sends. A Retry asks for the key again — it is
|
|
1633
|
+
never kept — and is always the owner's to start.
|
|
1634
|
+
Built node by node with textContent, never from an HTML string (this module's rule):
|
|
1635
|
+
nothing the server sends can become markup, whatever a later field adds. */
|
|
1636
|
+
var APP_STAGES = [['creating', 'Creating'], ['building', 'Building'], ['live', 'Live']];
|
|
1637
|
+
var APP_HREF_RE = /^https:\/\/([a-z0-9-]{1,63}\.onrender\.com|dashboard\.render\.com)(\/[A-Za-z0-9\/_-]*)?$/;
|
|
1638
|
+
function appEl(tag, cls, text) {
|
|
1639
|
+
var e = document.createElement(tag);
|
|
1640
|
+
if (cls) e.className = cls;
|
|
1641
|
+
if (text != null) e.textContent = text;
|
|
1642
|
+
return e;
|
|
1643
|
+
}
|
|
1644
|
+
function appLink(href, text) {
|
|
1645
|
+
if (!APP_HREF_RE.test(String(href || ''))) return document.createTextNode(text);
|
|
1646
|
+
var a = appEl('a', null, text);
|
|
1647
|
+
a.href = href; a.target = '_blank'; a.rel = 'noopener noreferrer';
|
|
1648
|
+
return a;
|
|
1649
|
+
}
|
|
1650
|
+
function appPara(parts, cls) {
|
|
1651
|
+
var p = appEl('p', cls || null);
|
|
1652
|
+
parts.forEach(function (x) { p.appendChild(typeof x === 'string' ? document.createTextNode(x) : x); });
|
|
1653
|
+
return p;
|
|
1654
|
+
}
|
|
1655
|
+
/* The view for one answer: its root, and — on a failure — the Retry controls the watch
|
|
1656
|
+
wires. null when there is nothing to show (no platform chosen). */
|
|
1657
|
+
function appStatusView(data) {
|
|
1658
|
+
var app = data && data.app;
|
|
1659
|
+
if (!app) return null;
|
|
1660
|
+
var v = { root: appEl('div', 'appStat') };
|
|
1661
|
+
if (app.state === 'disconnected') {
|
|
1662
|
+
v.root.appendChild(appPara(['This project is no longer connected to Render. Its services there are still yours; remove them in Render if you want to stop paying.']));
|
|
1663
|
+
return v;
|
|
1664
|
+
}
|
|
1665
|
+
if (app.error) {
|
|
1666
|
+
v.root.appendChild(appPara([String(app.error)], 'err'));
|
|
1667
|
+
v.root.appendChild(appPara(['Nothing is retried by itself: your Render key was used once and thrown away, so a retry needs it again.']));
|
|
1668
|
+
v.retryBtn = appEl('button', 'btn ghost small appRetryBtn', 'Retry');
|
|
1669
|
+
v.retryBtn.type = 'button';
|
|
1670
|
+
v.root.appendChild(v.retryBtn);
|
|
1671
|
+
v.retryForm = appEl('div', 'appRetry');
|
|
1672
|
+
v.retryForm.hidden = true;
|
|
1673
|
+
var lab = appEl('label', 'wizField');
|
|
1674
|
+
lab.appendChild(appEl('span', null, 'Render API key'));
|
|
1675
|
+
v.keyEl = appEl('input', 'appRetryKey');
|
|
1676
|
+
v.keyEl.type = 'password'; v.keyEl.autocomplete = 'off'; v.keyEl.spellcheck = false; v.keyEl.maxLength = 200; v.keyEl.placeholder = 'rnd_…';
|
|
1677
|
+
lab.appendChild(v.keyEl);
|
|
1678
|
+
v.retryForm.appendChild(lab);
|
|
1679
|
+
v.sendBtn = appEl('button', 'btn small', 'Retry with this key');
|
|
1680
|
+
v.sendBtn.type = 'button';
|
|
1681
|
+
v.retryForm.appendChild(v.sendBtn);
|
|
1682
|
+
v.hint = appEl('div', 'hint appRetryHint', ' ');
|
|
1683
|
+
v.hint.setAttribute('role', 'status');
|
|
1684
|
+
v.retryForm.appendChild(v.hint);
|
|
1685
|
+
v.root.appendChild(v.retryForm);
|
|
1686
|
+
} else {
|
|
1687
|
+
var at = APP_STAGES.map(function (s) { return s[0]; }).indexOf(app.stage);
|
|
1688
|
+
var ol = appEl('ol', 'appStages');
|
|
1689
|
+
APP_STAGES.forEach(function (s, i) {
|
|
1690
|
+
var li = appEl('li', i < at || app.stage === 'live' ? 'done' : (i === at ? 'now' : ''), s[1]);
|
|
1691
|
+
if (i === at) li.setAttribute('aria-current', 'step');
|
|
1692
|
+
ol.appendChild(li);
|
|
1693
|
+
});
|
|
1694
|
+
v.root.appendChild(ol);
|
|
1695
|
+
if (app.stage === 'live') {
|
|
1696
|
+
v.root.appendChild(app.url
|
|
1697
|
+
? appPara(['Your app is live at ', appLink(app.url, String(app.url).replace(/^https:\/\//, '')), '.'])
|
|
1698
|
+
: appPara(['Your app is live on Render.']));
|
|
1699
|
+
} else if (app.stage === 'building') {
|
|
1700
|
+
v.root.appendChild(appPara([app.mode === 'adopt' ? 'Linked. Waiting for your app to answer at its Render address.' : 'Render is building your app. The first build usually takes a few minutes.']));
|
|
1701
|
+
} else {
|
|
1702
|
+
v.root.appendChild(appPara([app.waiting ? 'Waiting for the project to finish setting up — then your app is created on Render.' : 'Creating your app on your Render account.']));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
if (app.links) v.root.appendChild(appPara([appLink(app.links.service, 'Open it on Render')]));
|
|
1706
|
+
var vars = (data.env_vars || []).filter(function (x) { return !x.set_for_you; });
|
|
1707
|
+
if (vars.length) {
|
|
1708
|
+
v.root.appendChild(appEl('h3', null, 'Still to set on Render'));
|
|
1709
|
+
v.root.appendChild(appPara(['Bongos keeps the names only. Set each value on the service’s environment page in Render.']));
|
|
1710
|
+
var ul = appEl('ul', 'appVars');
|
|
1711
|
+
vars.forEach(function (x) {
|
|
1712
|
+
var li = appEl('li');
|
|
1713
|
+
li.appendChild(appEl('code', null, String(x.name)));
|
|
1714
|
+
if (!x.required) li.appendChild(document.createTextNode(' — optional'));
|
|
1715
|
+
ul.appendChild(li);
|
|
1716
|
+
});
|
|
1717
|
+
v.root.appendChild(ul);
|
|
1718
|
+
if (app.links) v.root.appendChild(appPara([appLink(app.links.env, 'Open the environment page on Render')]));
|
|
1719
|
+
}
|
|
1720
|
+
return v;
|
|
1721
|
+
}
|
|
1722
|
+
function appTerminal(data) { var a = data && data.app; return !a || a.state === 'disconnected' || !!a.error || a.stage === 'live'; }
|
|
1723
|
+
function fetchAppStatus(id) {
|
|
1724
|
+
return fetch(API + '/provisioning/instances/' + encodeURIComponent(id) + '/render', { credentials: 'same-origin' })
|
|
1725
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
1726
|
+
.catch(function () { return null; });
|
|
1727
|
+
}
|
|
1728
|
+
/* Paint the status into `box` and keep it current until it settles. `alive` says whether
|
|
1729
|
+
the surface is still showing this project, so a watch never paints a page that moved on.
|
|
1730
|
+
`card` (default: the box itself) is what hides while there is nothing to show.
|
|
1731
|
+
`offer` (optional) is asked on every paint whether it has something to show instead
|
|
1732
|
+
— the project page's Connect Render (task 1004184) — and keeps the card up if so.
|
|
1733
|
+
Returns a stop function. */
|
|
1734
|
+
function watchApp(id, box, alive, everyMs, card, offer) {
|
|
1735
|
+
var t = null, stopped = false, last = null, view = null, ticks = 0;
|
|
1736
|
+
function paint(data) {
|
|
1737
|
+
if (stopped || !alive()) return;
|
|
1738
|
+
last = data;
|
|
1739
|
+
/* never repaint over a Retry the owner has opened — it holds a key they are typing */
|
|
1740
|
+
/* …whether this watch opened it or an earlier one on the same box did (the project
|
|
1741
|
+
page starts a fresh watch on every reload of the project) */
|
|
1742
|
+
var open = (view && view.retryForm && !view.retryForm.hidden) || !!box.querySelector('.appRetry:not([hidden])');
|
|
1743
|
+
if (!open) {
|
|
1744
|
+
view = appStatusView(data);
|
|
1745
|
+
box.textContent = '';
|
|
1746
|
+
if (view) { box.appendChild(view.root); wire(view); }
|
|
1747
|
+
var offered = offer ? offer(data) : false;
|
|
1748
|
+
(card || box).hidden = !view && !offered;
|
|
1749
|
+
}
|
|
1750
|
+
if (!appTerminal(data) && ticks++ < 360) t = setTimeout(tick, everyMs || 5000);
|
|
1751
|
+
}
|
|
1752
|
+
function tick() { fetchAppStatus(id).then(paint); }
|
|
1753
|
+
function wire(v) {
|
|
1754
|
+
if (!v.retryBtn) return;
|
|
1755
|
+
v.retryBtn.addEventListener('click', function () {
|
|
1756
|
+
v.retryForm.hidden = false; v.retryBtn.hidden = true; v.keyEl.focus();
|
|
1757
|
+
});
|
|
1758
|
+
v.sendBtn.addEventListener('click', function () {
|
|
1759
|
+
if (!last || !last.app || !last.app.retry) return;
|
|
1760
|
+
var key = String(v.keyEl.value || '').trim();
|
|
1761
|
+
if (!/^rnd_[A-Za-z0-9]{16,128}$/.test(key)) {
|
|
1762
|
+
v.hint.textContent = 'That doesn’t look like a Render API key. It starts with “rnd_”.'; v.hint.className = 'hint appRetryHint err'; return;
|
|
1763
|
+
}
|
|
1764
|
+
var body = {};
|
|
1765
|
+
Object.keys(last.app.retry).forEach(function (k) { if (last.app.retry[k] != null) body[k] = last.app.retry[k]; });
|
|
1766
|
+
body.key = key;
|
|
1767
|
+
v.keyEl.value = ''; key = '';
|
|
1768
|
+
v.sendBtn.disabled = true; v.hint.textContent = 'Handing it to Render again…'; v.hint.className = 'hint appRetryHint';
|
|
1769
|
+
fetch(API + '/provisioning/instances/' + encodeURIComponent(id) + '/render', {
|
|
1770
|
+
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
|
|
1771
|
+
}).then(function (res) {
|
|
1772
|
+
body.key = '';
|
|
1773
|
+
return res.json().catch(function () { return {}; }).then(function (d) {
|
|
1774
|
+
if (!res.ok) throw new Error((d.error && d.error.message) || 'The retry could not be queued.');
|
|
1775
|
+
v.retryForm.hidden = true; view = null; clearTimeout(t); ticks = 0; tick(); // queued: close the form so the next answer can paint
|
|
1776
|
+
});
|
|
1777
|
+
}).catch(function (err) {
|
|
1778
|
+
body.key = '';
|
|
1779
|
+
v.sendBtn.disabled = false; v.hint.textContent = err.message; v.hint.className = 'hint appRetryHint err';
|
|
1780
|
+
});
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
tick();
|
|
1784
|
+
return function stop() { stopped = true; clearTimeout(t); };
|
|
1785
|
+
}
|
|
1786
|
+
/* end of the app's setup on Render */
|
|
1787
|
+
|
|
1788
|
+
/* ── connecting Render (task 1004352, ADR 0348; shared with the project page by task 1004184) ──
|
|
1789
|
+
ONE flow, run by the setup wizard's app step and by an existing project's "Where its
|
|
1790
|
+
app runs" card: check the key, choose the workspace, a new app or one to link, the plan
|
|
1791
|
+
and the database. Each caller hands in how to find its elements (`q`, by the wizard's
|
|
1792
|
+
own ids), where the answers live (`st`) and three hooks: save (the wizard's draft), repo
|
|
1793
|
+
(the project's owner/repo) and on (is Render the chosen platform).
|
|
1794
|
+
The KEY lives in this closure and the password field: never on `st` (the wizard's st is
|
|
1795
|
+
its whole sessionStorage draft), never in a URL, a log or any browser store. It is sent
|
|
1796
|
+
twice at most — to the read-only lookup, and with the setup request — and `drop` forgets
|
|
1797
|
+
it once that is sent. The workspace list is Render's answer; the owner must CHOOSE one —
|
|
1798
|
+
a key reaches every workspace its user is in, so nothing is pre-picked (ADR 0327).
|
|
1799
|
+
Prices are Render's list prices per month: starter and basic_256mb were measured by the
|
|
1800
|
+
pilot (task 1004142); Render's own pricing page is the source of truth, and the review
|
|
1801
|
+
says Render bills the owner. Lists are built node by node, never from an HTML string. */
|
|
1802
|
+
var RENDER_WEB_PLANS = [['starter', 'Starter', 7], ['standard', 'Standard', 25], ['pro', 'Pro', 85]];
|
|
1803
|
+
var RENDER_DB_PLANS = [['basic_256mb', 'Basic 256 MB', 6], ['basic_1gb', 'Basic 1 GB', 19], ['basic_4gb', 'Basic 4 GB', 75]];
|
|
1804
|
+
var RENDER_RUNTIMES = { node: 'Node.js', python: 'Python', ruby: 'Ruby', go: 'Go', rust: 'Rust', elixir: 'Elixir', docker: 'Docker' };
|
|
1805
|
+
var RENDER_KEY_RE = /^rnd_[A-Za-z0-9]{16,128}$/;
|
|
1806
|
+
function planLabel(list, v) {
|
|
1807
|
+
var p = list.filter(function (x) { return x[0] === v; })[0] || list[0];
|
|
1808
|
+
return p[1] + ', about $' + p[2] + '/month';
|
|
1809
|
+
}
|
|
1810
|
+
function runtimeLabel(v) { return RENDER_RUNTIMES[v] || RENDER_RUNTIMES.node; }
|
|
1811
|
+
function repoOf(url) { var m = String(url || '').replace(/\.git$/, '').match(/github\.com\/([^/]+\/[^/]+)$/i); return m ? m[1].toLowerCase() : ''; }
|
|
1812
|
+
function renderConnect(q, st, hooks) {
|
|
1813
|
+
var key = '';
|
|
1814
|
+
var workspaces = null; // Render's answer for the current key, or null
|
|
1815
|
+
var services = null; // the chosen workspace's web services, or null
|
|
1816
|
+
var wsName = (hooks.ns || '') + 'renderWs', svcName = (hooks.ns || '') + 'renderSvc';
|
|
1817
|
+
function hint(id, msg, err) { var h = q(id); h.textContent = msg || ' '; h.className = 'hint' + (err ? ' err' : ''); }
|
|
1818
|
+
function repo() { return String(hooks.repo() || ''); }
|
|
1819
|
+
function lookup(ownerId) {
|
|
1820
|
+
return fetch(API + '/provisioning/render/lookup', {
|
|
1821
|
+
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' },
|
|
1822
|
+
body: JSON.stringify(ownerId ? { key: key, owner_id: ownerId } : { key: key })
|
|
1823
|
+
}).then(function (res) {
|
|
1824
|
+
return res.json().catch(function () { return {}; }).then(function (data) {
|
|
1825
|
+
if (!res.ok) throw new Error((data.error && data.error.message) || 'Render couldn’t be reached. Try again in a moment.');
|
|
1826
|
+
return data;
|
|
1827
|
+
});
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
function fillPlans(sel, list, v) {
|
|
1831
|
+
sel.textContent = '';
|
|
1832
|
+
list.forEach(function (p) {
|
|
1833
|
+
var o = appEl('option', null, p[1] + ' — about $' + p[2] + '/month');
|
|
1834
|
+
o.value = p[0];
|
|
1835
|
+
if (p[0] === v) o.selected = true;
|
|
1836
|
+
sel.appendChild(o);
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
// One choice row: a radio, its title and a line under it (strings or nodes).
|
|
1840
|
+
function choice(cls, name, attrs, title, sub) {
|
|
1841
|
+
var lab = appEl('label', cls);
|
|
1842
|
+
var input = appEl('input');
|
|
1843
|
+
input.type = 'radio'; input.name = name;
|
|
1844
|
+
Object.keys(attrs).forEach(function (k) {
|
|
1845
|
+
if (k === 'checked' || k === 'disabled') input[k] = !!attrs[k]; else input.setAttribute(k, attrs[k]);
|
|
1846
|
+
});
|
|
1847
|
+
lab.appendChild(input);
|
|
1848
|
+
var body = appEl('span');
|
|
1849
|
+
body.appendChild(appEl('b', null, title));
|
|
1850
|
+
var line = appEl('span');
|
|
1851
|
+
sub.forEach(function (x) { line.appendChild(typeof x === 'string' ? document.createTextNode(x) : x); });
|
|
1852
|
+
body.appendChild(line);
|
|
1853
|
+
lab.appendChild(body);
|
|
1854
|
+
return lab;
|
|
1855
|
+
}
|
|
1856
|
+
function paintWorkspaces() {
|
|
1857
|
+
var list = workspaces || [];
|
|
1858
|
+
var box = q('renderWsList');
|
|
1859
|
+
q('renderWs').hidden = !workspaces;
|
|
1860
|
+
box.textContent = '';
|
|
1861
|
+
list.forEach(function (w, i) {
|
|
1862
|
+
box.appendChild(choice('appOpt', wsName, { 'data-i': String(i), checked: w.id === st.renderOwnerId }, String(w.name),
|
|
1863
|
+
[(w.type === 'user' ? 'Personal workspace' : 'Team workspace') + ' · ' + String(w.id)]));
|
|
1864
|
+
});
|
|
1865
|
+
if (!list.length) box.appendChild(appEl('p', 'hint err', 'This key reaches no workspace.'));
|
|
1866
|
+
}
|
|
1867
|
+
function paintServices() {
|
|
1868
|
+
var list = services || [];
|
|
1869
|
+
var want = repo().toLowerCase();
|
|
1870
|
+
var box = q('renderSvcList');
|
|
1871
|
+
q('renderSvc').hidden = !services;
|
|
1872
|
+
box.textContent = '';
|
|
1873
|
+
box.appendChild(choice('appOpt', svcName, { value: 'new', checked: st.renderMode === 'greenfield' }, 'Create a new app',
|
|
1874
|
+
['Render builds it from ', appEl('code', null, repo()), '.']));
|
|
1875
|
+
list.forEach(function (s, i) {
|
|
1876
|
+
var mine = repoOf(s.repo) === want;
|
|
1877
|
+
box.appendChild(choice('appOpt' + (mine ? '' : ' off'), svcName,
|
|
1878
|
+
{ 'data-i': String(i), disabled: !mine, checked: mine && st.renderMode === 'adopt' && st.renderServiceId === s.id },
|
|
1879
|
+
'Link ' + String(s.name),
|
|
1880
|
+
[mine ? 'Already on Render — nothing on it is changed.' : 'Builds ' + (repoOf(s.repo) || 'another repository') + ', not this project, so it can’t be linked.']));
|
|
1881
|
+
});
|
|
1882
|
+
syncNew();
|
|
1883
|
+
}
|
|
1884
|
+
function syncNew() {
|
|
1885
|
+
var fresh = !!services && st.renderMode === 'greenfield';
|
|
1886
|
+
q('renderNew').hidden = !fresh;
|
|
1887
|
+
if (!fresh) return;
|
|
1888
|
+
q('f-renderRuntime').value = st.renderRuntime || 'node';
|
|
1889
|
+
var docker = q('f-renderRuntime').value === 'docker';
|
|
1890
|
+
q('renderBuildWrap').hidden = docker; q('renderStartWrap').hidden = docker;
|
|
1891
|
+
q('f-renderBuild').value = st.renderBuild || '';
|
|
1892
|
+
q('f-renderStart').value = st.renderStart || '';
|
|
1893
|
+
fillPlans(q('f-renderPlan'), RENDER_WEB_PLANS, st.renderPlan || 'starter');
|
|
1894
|
+
q('f-renderDb').checked = !!st.renderDb;
|
|
1895
|
+
q('renderDbPlanWrap').hidden = !st.renderDb;
|
|
1896
|
+
fillPlans(q('f-renderDbPlan'), RENDER_DB_PLANS, st.renderDbPlan || 'basic_256mb');
|
|
1897
|
+
}
|
|
1898
|
+
function sync() {
|
|
1899
|
+
var on = hooks.on();
|
|
1900
|
+
q('renderSetup').hidden = !on;
|
|
1901
|
+
if (!on) return;
|
|
1902
|
+
if (!key && st.renderOwnerId) hint('h-renderKey', 'Enter your Render key again — it is not kept, not even in this browser.');
|
|
1903
|
+
paintWorkspaces();
|
|
1904
|
+
if (!workspaces) q('renderWs').hidden = true;
|
|
1905
|
+
paintServices();
|
|
1906
|
+
if (!services) q('renderSvc').hidden = true;
|
|
1907
|
+
}
|
|
1908
|
+
function forget() {
|
|
1909
|
+
workspaces = null; services = null;
|
|
1910
|
+
q('renderWs').hidden = true; q('renderSvc').hidden = true; q('renderNew').hidden = true;
|
|
1911
|
+
}
|
|
1912
|
+
q('f-renderKey').addEventListener('input', function () {
|
|
1913
|
+
key = String(q('f-renderKey').value || '').trim();
|
|
1914
|
+
forget(); // an answer belongs to the key that was checked, not to this one
|
|
1915
|
+
hint('h-renderKey', '');
|
|
1916
|
+
});
|
|
1917
|
+
q('renderCheck').addEventListener('click', function () {
|
|
1918
|
+
var btn = q('renderCheck');
|
|
1919
|
+
if (!RENDER_KEY_RE.test(key)) return hint('h-renderKey', 'That doesn’t look like a Render API key. It starts with “rnd_”.', true);
|
|
1920
|
+
btn.disabled = true; hint('h-renderKey', 'Checking with Render…');
|
|
1921
|
+
lookup(null).then(function (data) {
|
|
1922
|
+
workspaces = data.workspaces || [];
|
|
1923
|
+
if (!workspaces.some(function (w) { return w.id === st.renderOwnerId; })) { st.renderOwnerId = ''; st.renderOwnerName = ''; }
|
|
1924
|
+
hint('h-renderKey', workspaces.length > 1
|
|
1925
|
+
? 'Render accepted the key. It reaches ' + workspaces.length + ' workspaces — choose the one your app belongs in.'
|
|
1926
|
+
: 'Render accepted the key. Choose the workspace to use.');
|
|
1927
|
+
paintWorkspaces();
|
|
1928
|
+
if (st.renderOwnerId) chooseWorkspace(st.renderOwnerId);
|
|
1929
|
+
}, function (err) {
|
|
1930
|
+
forget();
|
|
1931
|
+
hint('h-renderKey', err.message, true);
|
|
1932
|
+
}).then(function () { btn.disabled = false; });
|
|
1933
|
+
});
|
|
1934
|
+
function chooseWorkspace(id) {
|
|
1935
|
+
var w = (workspaces || []).filter(function (x) { return x.id === id; })[0];
|
|
1936
|
+
if (!w) return;
|
|
1937
|
+
if (st.renderOwnerId !== id) { st.renderMode = ''; st.renderServiceId = ''; st.renderServiceName = ''; }
|
|
1938
|
+
st.renderOwnerId = w.id; st.renderOwnerName = w.name; hooks.save();
|
|
1939
|
+
services = null; q('renderSvc').hidden = true; q('renderNew').hidden = true;
|
|
1940
|
+
hint('h-render', 'Looking at what is in ' + w.name + '…');
|
|
1941
|
+
lookup(w.id).then(function (data) {
|
|
1942
|
+
if (st.renderOwnerId !== w.id) return; // the owner moved on while Render answered
|
|
1943
|
+
services = data.services || [];
|
|
1944
|
+
hint('h-render', '');
|
|
1945
|
+
paintServices();
|
|
1946
|
+
}, function (err) { hint('h-render', err.message, true); });
|
|
1947
|
+
}
|
|
1948
|
+
q('renderWsList').addEventListener('change', function (ev) {
|
|
1949
|
+
var i = Number(ev.target && ev.target.getAttribute('data-i'));
|
|
1950
|
+
var w = (workspaces || [])[i];
|
|
1951
|
+
if (w) chooseWorkspace(w.id);
|
|
1952
|
+
});
|
|
1953
|
+
q('renderSvcList').addEventListener('change', function (ev) {
|
|
1954
|
+
var t = ev.target;
|
|
1955
|
+
if (!t || t.name !== svcName || t.disabled) return;
|
|
1956
|
+
if (t.value === 'new') { st.renderMode = 'greenfield'; st.renderServiceId = ''; st.renderServiceName = ''; }
|
|
1957
|
+
else {
|
|
1958
|
+
var s = (services || [])[Number(t.getAttribute('data-i'))];
|
|
1959
|
+
if (!s) return;
|
|
1960
|
+
st.renderMode = 'adopt'; st.renderServiceId = s.id; st.renderServiceName = s.name;
|
|
1961
|
+
}
|
|
1962
|
+
hooks.save(); syncNew(); hint('h-render', '');
|
|
1963
|
+
});
|
|
1964
|
+
q('renderNew').addEventListener('change', function () {
|
|
1965
|
+
st.renderRuntime = q('f-renderRuntime').value;
|
|
1966
|
+
st.renderPlan = q('f-renderPlan').value;
|
|
1967
|
+
st.renderDb = q('f-renderDb').checked;
|
|
1968
|
+
st.renderDbPlan = q('f-renderDbPlan').value;
|
|
1969
|
+
hooks.save(); syncNew();
|
|
1970
|
+
});
|
|
1971
|
+
q('renderNew').addEventListener('input', function (ev) {
|
|
1972
|
+
if (ev.target.id === q('f-renderBuild').id) st.renderBuild = ev.target.value;
|
|
1973
|
+
if (ev.target.id === q('f-renderStart').id) st.renderStart = ev.target.value;
|
|
1974
|
+
hooks.save();
|
|
1975
|
+
});
|
|
1976
|
+
function validate() {
|
|
1977
|
+
if (!hooks.on()) return true;
|
|
1978
|
+
var bad = function (msg) { hint('h-render', msg, true); return false; };
|
|
1979
|
+
if (!key || !workspaces) return bad(hooks.noKey || 'Check your Render key first.');
|
|
1980
|
+
if (!st.renderOwnerId || !workspaces.some(function (w) { return w.id === st.renderOwnerId; })) return bad('Choose the Render workspace to use.');
|
|
1981
|
+
if (!services) return bad('Wait a moment — Render is still answering.');
|
|
1982
|
+
if (st.renderMode === 'adopt') {
|
|
1983
|
+
if (!services.some(function (s) { return s.id === st.renderServiceId && repoOf(s.repo) === repo().toLowerCase(); })) return bad('Choose the app to link, or create a new one.');
|
|
1984
|
+
} else if (st.renderMode === 'greenfield') {
|
|
1985
|
+
if ((st.renderRuntime || 'node') !== 'docker' && !String(st.renderStart || '').trim()) return bad('Say how your app starts — the command Render should run, for example “npm start”.');
|
|
1986
|
+
} else return bad('Choose whether to create a new app or link one you already run.');
|
|
1987
|
+
hint('h-render', '');
|
|
1988
|
+
return true;
|
|
1989
|
+
}
|
|
1990
|
+
// What the setup request carries — the owner's answers, and the key for this one send.
|
|
1991
|
+
function requestBody() {
|
|
1992
|
+
var b = { key: key, owner_id: st.renderOwnerId, mode: st.renderMode };
|
|
1993
|
+
if (st.renderMode === 'adopt') { b.service_id = st.renderServiceId; return b; }
|
|
1994
|
+
b.runtime = st.renderRuntime || 'node';
|
|
1995
|
+
if (b.runtime !== 'docker') {
|
|
1996
|
+
b.start_command = String(st.renderStart || '').trim();
|
|
1997
|
+
if (String(st.renderBuild || '').trim()) b.build_command = String(st.renderBuild).trim();
|
|
1998
|
+
}
|
|
1999
|
+
b.web_plan = st.renderPlan || 'starter';
|
|
2000
|
+
b.database = !!st.renderDb;
|
|
2001
|
+
if (b.database) b.db_plan = st.renderDbPlan || 'basic_256mb';
|
|
2002
|
+
return b;
|
|
2003
|
+
}
|
|
2004
|
+
// Forget the key — the closure's copy, the field's, and every answer it bought.
|
|
2005
|
+
function drop() { key = ''; q('f-renderKey').value = ''; forget(); }
|
|
2006
|
+
return { sync: sync, forget: forget, validate: validate, requestBody: requestBody, drop: drop, hint: hint,
|
|
2007
|
+
hasKey: function () { return !!key; } };
|
|
2008
|
+
}
|
|
2009
|
+
/* end of connecting Render */
|
|
2010
|
+
|
|
1515
2011
|
/* HELD, not sampled once: a visitor who flips "reduce motion" mid-session must
|
|
1516
2012
|
stop the sky then and there (and get it back if they flip it off), instead
|
|
1517
2013
|
of being stuck with whatever the preference was at load. addListener is the
|
|
@@ -3560,7 +4056,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3560
4056
|
a handle nobody has asked the server about, and a handle free ten minutes ago
|
|
3561
4057
|
may not be now (task 1002898 N10) */
|
|
3562
4058
|
if (step === 6) { checkSlugAvailable(); renderAddrPanel(); }
|
|
3563
|
-
if (step === 7) syncAppStep();
|
|
4059
|
+
if (step === 7) { syncAppStep(); syncRenderSetup(); }
|
|
3564
4060
|
if (step === 8) renderReview();
|
|
3565
4061
|
}
|
|
3566
4062
|
|
|
@@ -4160,6 +4656,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4160
4656
|
|
|
4161
4657
|
/* per-panel validation on Next */
|
|
4162
4658
|
function validateStep(step) {
|
|
4659
|
+
if (step === 7) return validateRender();
|
|
4163
4660
|
if (step === 1) {
|
|
4164
4661
|
if (!state.name.trim()) { $('h-name').textContent = 'Give it a name to start.'; $('h-name').className = 'hint err'; return false; }
|
|
4165
4662
|
return true;
|
|
@@ -4263,12 +4760,60 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4263
4760
|
root.querySelectorAll('#appOpts input[name=appPlatform]').forEach(function (i) { i.checked = i.value === pick; });
|
|
4264
4761
|
}
|
|
4265
4762
|
function appRow() {
|
|
4266
|
-
|
|
4763
|
+
if (appPlatformChoice() !== 'render') return 'Not chosen yet — you can decide later';
|
|
4764
|
+
var ws = esc(state.renderOwnerName || state.renderOwnerId || '');
|
|
4765
|
+
if (state.renderMode === 'adopt') return 'Render, workspace ' + ws + ' — link <code>' + esc(state.renderServiceName || state.renderServiceId || '') + '</code>, changing nothing on it';
|
|
4766
|
+
var plan = planLabel(RENDER_WEB_PLANS, state.renderPlan || 'starter');
|
|
4767
|
+
var db = state.renderDb ? ', with a database (' + planLabel(RENDER_DB_PLANS, state.renderDbPlan || 'basic_256mb') + ')' : ', no database';
|
|
4768
|
+
return 'Render, workspace ' + ws + ' — a new app (' + esc(runtimeLabel(state.renderRuntime || 'node')) + ', ' + plan + ')' + db + '. Render bills you directly.';
|
|
4267
4769
|
}
|
|
4268
4770
|
var appOptsEl = root.querySelector('#appOpts');
|
|
4269
4771
|
if (appOptsEl) appOptsEl.addEventListener('change', function (ev) {
|
|
4270
|
-
if (ev.target && ev.target.name === 'appPlatform' && !ev.target.disabled) { state.appPlatform = ev.target.value; saveDraft(); }
|
|
4772
|
+
if (ev.target && ev.target.name === 'appPlatform' && !ev.target.disabled) { state.appPlatform = ev.target.value; saveDraft(); syncRenderSetup(); }
|
|
4271
4773
|
});
|
|
4774
|
+
|
|
4775
|
+
/* ── Render (task 1004352, ADR 0348) ──
|
|
4776
|
+
The flow itself is renderConnect(), shared with an existing project's page (task
|
|
4777
|
+
1004184). Here it reads and writes the draft (`state`), so every answer but the key
|
|
4778
|
+
survives a reload. The key is never on state, so a resumed draft asks for it again,
|
|
4779
|
+
and says why. */
|
|
4780
|
+
var rc = renderConnect($, state, {
|
|
4781
|
+
save: saveDraft,
|
|
4782
|
+
repo: function () { return String(state.owner || '') + '/' + String(state.repo || ''); },
|
|
4783
|
+
on: function () { return appPlatformChoice() === 'render'; },
|
|
4784
|
+
noKey: 'Check your Render key first — or choose “Decide later”.'
|
|
4785
|
+
});
|
|
4786
|
+
function syncRenderSetup() { rc.sync(); }
|
|
4787
|
+
function forgetRenderAnswers() { rc.forget(); }
|
|
4788
|
+
function validateRender() { return rc.validate(); }
|
|
4789
|
+
function renderRequestBody() { return rc.requestBody(); }
|
|
4790
|
+
// Send the setup request for a just-created project, then drop the key whatever the
|
|
4791
|
+
// answer. The project exists either way; a refusal here is the APP's, and the done
|
|
4792
|
+
// panel says so in plain words.
|
|
4793
|
+
function sendRenderSetup(instanceId) {
|
|
4794
|
+
if (appPlatformChoice() !== 'render' || !rc.hasKey() || !instanceId) return;
|
|
4795
|
+
var body = renderRequestBody();
|
|
4796
|
+
rc.drop();
|
|
4797
|
+
var note = $('doneRender');
|
|
4798
|
+
note.hidden = false; note.className = 'hint'; note.textContent = 'Handing your app to Render…';
|
|
4799
|
+
fetch(API + '/provisioning/instances/' + encodeURIComponent(instanceId) + '/render', {
|
|
4800
|
+
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
|
|
4801
|
+
}).then(function (res) {
|
|
4802
|
+
return res.json().catch(function () { return {}; }).then(function (data) {
|
|
4803
|
+
if (!res.ok) throw new Error((data.error && data.error.message) || 'Render setup could not be queued.');
|
|
4804
|
+
note.textContent = body.mode === 'adopt'
|
|
4805
|
+
? 'Once the project is up, we link your Render app. Your key is used once, then thrown away.'
|
|
4806
|
+
: 'Once the project is up, we create your app on your Render account. Your key is used once, then thrown away.';
|
|
4807
|
+
/* then the app's own progress, from the same read the project's page uses (task 1004353) */
|
|
4808
|
+
var box = $('doneApp');
|
|
4809
|
+
if (box) watchApp(instanceId, box, function () { return state.step === 9; }, 5000);
|
|
4810
|
+
});
|
|
4811
|
+
}).catch(function (err) {
|
|
4812
|
+
note.className = 'hint err';
|
|
4813
|
+
note.textContent = 'Your project is being created, but your app was not handed to Render: ' + err.message + ' You can connect Render from the project’s page once it is live.';
|
|
4814
|
+
});
|
|
4815
|
+
body.key = '';
|
|
4816
|
+
}
|
|
4272
4817
|
function renderReview() {
|
|
4273
4818
|
/* every road back to this step gets a live button — the create path
|
|
4274
4819
|
disables it and only the failure branch used to restore it, which
|
|
@@ -4432,6 +4977,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4432
4977
|
} catch (e) {}
|
|
4433
4978
|
renderDone();
|
|
4434
4979
|
show(9, true);
|
|
4980
|
+
sendRenderSetup(data.instance && data.instance.id); // the key rides the new project's own setup run (task 1004352)
|
|
4435
4981
|
startProgress(data.instance && data.instance.id);
|
|
4436
4982
|
}).catch(function (err) {
|
|
4437
4983
|
if (String(err && err.message) === 'signing in') return;
|
|
@@ -4662,6 +5208,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4662
5208
|
|
|
4663
5209
|
function renderDone() {
|
|
4664
5210
|
var adopt = state.mode === 'adopt';
|
|
5211
|
+
$('doneRender').hidden = true; // sendRenderSetup shows it again when Render is the choice
|
|
5212
|
+
$('doneApp').hidden = true; $('doneApp').textContent = '';
|
|
4665
5213
|
$('doneLede').innerHTML = 'Provisioning is requested for <strong>' + esc(state.name || state.slug) + '</strong>' +
|
|
4666
5214
|
(adopt ? ' — adopting your existing repo' : '') +
|
|
4667
5215
|
'. The platform stands up the server, database, and address on its own — watch it right here, or any time on <a href="?view=mine">your projects page</a>.';
|
|
@@ -6227,6 +6775,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6227
6775
|
/* the SECOND, GitHub-backed read — fired only here (entry + tab
|
|
6228
6776
|
return), never from renderManage, which the fetch-free tick calls */
|
|
6229
6777
|
if (!keepRepo) loadRepoVisibility(inst);
|
|
6778
|
+
loadAppStatus(inst.id || asked, inst); /* where its app runs (task 1004353; Connect Render, task 1004184) */
|
|
6230
6779
|
/* the settings row + the project's own manifest — the same cadence as
|
|
6231
6780
|
the GitHub read: entry + tab return, never the in-flight tick. The
|
|
6232
6781
|
three cards below share ONE read of the row (task 1003582), so
|
|
@@ -6363,6 +6912,100 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6363
6912
|
}
|
|
6364
6913
|
}
|
|
6365
6914
|
|
|
6915
|
+
var stopAppWatch = null;
|
|
6916
|
+
function loadAppStatus(id, inst) {
|
|
6917
|
+
if (stopAppWatch) stopAppWatch();
|
|
6918
|
+
var box = $('mApp'), card = $('mAppCard');
|
|
6919
|
+
var mine = String(id);
|
|
6920
|
+
if (appConnectFor !== mine) resetAppConnect(mine, inst);
|
|
6921
|
+
else appConnectInst = inst || appConnectInst;
|
|
6922
|
+
stopAppWatch = watchApp(id, box, function () { return String(manageId) === mine; }, 10000, card, offerAppConnect);
|
|
6923
|
+
}
|
|
6924
|
+
|
|
6925
|
+
/* ── Connect Render from the project's page (task 1004184) ──
|
|
6926
|
+
Offered only on the owner's own answer that the app has NO platform (the status read
|
|
6927
|
+
is owner-only, so anyone else gets nothing back and sees nothing), and only for a
|
|
6928
|
+
project the server would take it for: not removed, and with a repository to build.
|
|
6929
|
+
The flow is the wizard's own — its markup cloned from #renderSetup with every id
|
|
6930
|
+
prefixed `m-`, and its logic renderConnect() — so the two can never ask different
|
|
6931
|
+
questions. The answers live in a plain object that is dropped with the project; the
|
|
6932
|
+
key, as in the wizard, lives only in renderConnect's closure and the field. */
|
|
6933
|
+
var appConnect = null, appConnectFor = null, appConnectInst = null, appConnectSt = {};
|
|
6934
|
+
function appConnectQ(id) { return document.getElementById('m-' + id); }
|
|
6935
|
+
function appConnectable(inst) {
|
|
6936
|
+
return !!(inst && inst.target_ref && inst.status !== 'torn_down' && inst.status !== 'tearing_down');
|
|
6937
|
+
}
|
|
6938
|
+
function offerAppConnect(data) {
|
|
6939
|
+
var show = !!(data && data.ok && data.app === null && appConnectable(appConnectInst));
|
|
6940
|
+
$('mAppConnect').hidden = !show;
|
|
6941
|
+
if (!show) closeAppConnect();
|
|
6942
|
+
return show;
|
|
6943
|
+
}
|
|
6944
|
+
function closeAppConnect() {
|
|
6945
|
+
/* what Render said belongs to the key just forgotten */
|
|
6946
|
+
if (appConnect) { appConnect.drop(); appConnect.hint('h-renderKey', ''); appConnect.hint('h-render', ''); }
|
|
6947
|
+
$('mAppConnectForm').hidden = true;
|
|
6948
|
+
$('mAppConnectOpen').hidden = false;
|
|
6949
|
+
}
|
|
6950
|
+
function resetAppConnect(id, inst) {
|
|
6951
|
+
closeAppConnect();
|
|
6952
|
+
appConnectFor = id; appConnectInst = inst || null;
|
|
6953
|
+
Object.keys(appConnectSt).forEach(function (k) { delete appConnectSt[k]; });
|
|
6954
|
+
}
|
|
6955
|
+
function buildAppConnect() {
|
|
6956
|
+
if (appConnect) return appConnect;
|
|
6957
|
+
var copy = $('renderSetup').cloneNode(true);
|
|
6958
|
+
copy.id = 'm-renderSetup';
|
|
6959
|
+
copy.querySelectorAll('[id]').forEach(function (e) { e.id = 'm-' + e.id; });
|
|
6960
|
+
copy.querySelectorAll('[aria-describedby]').forEach(function (e) { e.setAttribute('aria-describedby', 'm-' + e.getAttribute('aria-describedby')); });
|
|
6961
|
+
var form = $('mAppConnectForm');
|
|
6962
|
+
form.insertBefore(copy, form.firstChild);
|
|
6963
|
+
appConnect = renderConnect(appConnectQ, appConnectSt, {
|
|
6964
|
+
ns: 'm-',
|
|
6965
|
+
save: function () {},
|
|
6966
|
+
repo: function () { return appConnectInst ? String(appConnectInst.target_ref || '') : ''; },
|
|
6967
|
+
on: function () { return true; }
|
|
6968
|
+
});
|
|
6969
|
+
/* a clone carries whatever the wizard's copy held in this tab — start it empty */
|
|
6970
|
+
closeAppConnect();
|
|
6971
|
+
return appConnect;
|
|
6972
|
+
}
|
|
6973
|
+
$('mAppConnectOpen').addEventListener('click', function () {
|
|
6974
|
+
var rc = buildAppConnect();
|
|
6975
|
+
rc.sync();
|
|
6976
|
+
$('mAppConnectForm').hidden = false;
|
|
6977
|
+
this.hidden = true;
|
|
6978
|
+
appConnectQ('f-renderKey').focus();
|
|
6979
|
+
});
|
|
6980
|
+
$('mAppConnectCancel').addEventListener('click', function () {
|
|
6981
|
+
closeAppConnect();
|
|
6982
|
+
$('mAppConnectOpen').focus();
|
|
6983
|
+
});
|
|
6984
|
+
$('mAppConnectGo').addEventListener('click', function () {
|
|
6985
|
+
var btn = this, rc = appConnect, id = appConnectFor, inst = appConnectInst;
|
|
6986
|
+
if (!rc || !id || !rc.validate()) return;
|
|
6987
|
+
var body = rc.requestBody();
|
|
6988
|
+
rc.drop(); rc.hint('h-renderKey', '');
|
|
6989
|
+
btn.disabled = true;
|
|
6990
|
+
rc.hint('h-render', 'Handing your app to Render…');
|
|
6991
|
+
fetch(API + '/provisioning/instances/' + encodeURIComponent(id) + '/render', {
|
|
6992
|
+
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
|
|
6993
|
+
}).then(function (res) {
|
|
6994
|
+
body.key = '';
|
|
6995
|
+
return res.json().catch(function () { return {}; }).then(function (d) {
|
|
6996
|
+
if (!res.ok) throw new Error((d.error && d.error.message) || 'Render setup could not be queued.');
|
|
6997
|
+
rc.hint('h-render', '');
|
|
6998
|
+
closeAppConnect();
|
|
6999
|
+
if (String(manageId) === id) loadAppStatus(id, inst); // the status read now tells the rest
|
|
7000
|
+
});
|
|
7001
|
+
}).catch(function (err) {
|
|
7002
|
+
body.key = '';
|
|
7003
|
+
rc.hint('h-render', err.message + ' Your key was not kept, so check it again to retry.', true);
|
|
7004
|
+
}).then(function () { btn.disabled = false; });
|
|
7005
|
+
body.key = '';
|
|
7006
|
+
});
|
|
7007
|
+
/* end of Connect Render from the project's page */
|
|
7008
|
+
|
|
6366
7009
|
function loadRepoVisibility(inst) {
|
|
6367
7010
|
manageRepo = null;
|
|
6368
7011
|
if (!inst.target_ref) {
|