Skip to content

Commit 563b085

Browse files
hotlongCopilot
andcommitted
feat(env): polish detail page — kill 'Admin config' badge, surface Install CTA, add visible CEL predicates
- sys_environment.managedBy: 'config' → 'platform'. Environments are user-owned resources (their cloud envs), not admin configuration. The 'config' bucket surfaced a misleading 'Admin config' badge in the detail header that made the page feel like an internal admin tool. The runtime affordances (no New button — provision-via-action only) are still enforced via the existing userActions block. - Reorder actions so install_application is FIRST, promoting it from the '更多操作 / More' dropdown into the visible record-header toolbar. This answers the most common post-create question: 'I just made an env, now what?'. - Add 'visible' CEL predicates to status-machine actions so renderers that honour them hide no-ops (Suspend on suspended, Resume on active, Set-as-Default on the default env, install/change/archive on archived envs). Verified live via chrome-devtools: Resume and Archive no longer appear in the header/dropdown on an active env; only Suspend does. Verified via Console UI at http://localhost:4100/_console/apps/cloud_control/sys_environment: - header: 编辑 · Install Application · Suspend · 更多操作 - 更多操作 dropdown: Set as Default · Change Plan · Change Hostname · Archive · 分享 - no 'Admin config' badge Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d6dc8c1 commit 563b085

1 file changed

Lines changed: 76 additions & 54 deletions

File tree

packages/services/service-tenant/src/objects/sys-environment.object.ts

Lines changed: 76 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ export const SysEnvironment = ObjectSchema.create({
4444
pluralLabel: 'Environments',
4545
icon: 'globe',
4646
isSystem: true,
47-
managedBy: 'config',
47+
// `platform` — these are user-owned resources (their cloud environments).
48+
// Previously `config`, which surfaced an "Admin config" badge in the
49+
// detail header and made the page feel like an internal admin tool.
50+
// Affordances (no New button) are still enforced via userActions below.
51+
managedBy: 'platform',
4852
description: 'Your cloud environments. Each environment has its own URL, database, and plan.',
4953
titleFormat: '{display_name}',
5054
compactLayout: ['display_name', 'plan', 'status', 'hostname', 'is_default'],
@@ -192,8 +196,66 @@ export const SysEnvironment = ObjectSchema.create({
192196
],
193197
},
194198

199+
// ────────────────────────────────────────────────────────────────────
200+
// Install Application — primary CTA on an environment detail page.
201+
// Solves "I just created an env, now what?": user picks a package
202+
// (with a record-picker if the renderer supports it; otherwise paste
203+
// its ID) and the package is installed into the current env.
204+
//
205+
// Listed FIRST so renderers that surface only the top N actions in the
206+
// record header (and put the rest under "更多操作 / More") promote this
207+
// CTA into the visible toolbar instead of burying it in a dropdown.
208+
//
209+
// The endpoint is env-keyed (recordIdParam: 'id' → /:id/install-package)
210+
// so a record-header click already knows the target env and only needs
211+
// to collect the package_id + optional seed flag.
212+
// ────────────────────────────────────────────────────────────────────
213+
{
214+
name: 'install_application',
215+
label: 'Install Application',
216+
icon: 'download-cloud',
217+
variant: 'primary',
218+
type: 'script',
219+
locations: ['list_item', 'record_header'],
220+
target: 'install_application',
221+
// Only meaningful for an env you can actually deploy to. Hide while the
222+
// environment is being provisioned, has failed, or has been archived.
223+
// (Forward-compat: evaluated when the renderer supports `visible` CEL.)
224+
visible: 'status == "active" || status == "suspended"',
225+
successMessage: 'Application installed. Open your environment to see it.',
226+
refreshAfter: true,
227+
params: [
228+
{
229+
// Borrow the typed lookup config from sys_package_installation
230+
// so renderers that support it can show a record picker;
231+
// otherwise users can paste the Package ID from the Marketplace.
232+
name: 'package_id',
233+
field: 'package_id',
234+
objectOverride: 'sys_package_installation',
235+
label: 'Application',
236+
type: 'lookup',
237+
required: true,
238+
placeholder: 'Pick an application from the Marketplace',
239+
helpText: 'Application package to install. Browse the Marketplace tab to see what is available.',
240+
},
241+
{
242+
name: 'seed_sample_data',
243+
label: 'Include sample data',
244+
type: 'boolean',
245+
required: false,
246+
defaultValue: false,
247+
helpText: 'Pre-populate the environment with the package\'s demo records ' +
248+
'(e.g. example Accounts, Contacts, Leads) so you can explore the app immediately. ' +
249+
'Recommended for first-time users; leave unchecked for a clean production environment.',
250+
},
251+
],
252+
},
253+
195254
// ────────────────────────────────────────────────────────────────────
196255
// Status-machine row actions (replace direct status field edits).
256+
// Each carries a `visible` CEL predicate so renderers that honour it
257+
// hide the action when it would be a no-op (Suspend on a suspended
258+
// env, Resume on an active env, Set-as-Default on the default env).
197259
// ────────────────────────────────────────────────────────────────────
198260
{
199261
name: 'suspend_environment',
@@ -202,6 +264,7 @@ export const SysEnvironment = ObjectSchema.create({
202264
variant: 'secondary',
203265
type: 'script',
204266
locations: ['list_item', 'record_header'],
267+
visible: 'status == "active"',
205268
confirmText: 'Suspend this environment? All runtime traffic to it will be blocked until you resume.',
206269
successMessage: 'Environment suspended.',
207270
refreshAfter: true,
@@ -213,30 +276,18 @@ export const SysEnvironment = ObjectSchema.create({
213276
variant: 'secondary',
214277
type: 'script',
215278
locations: ['list_item', 'record_header'],
279+
visible: 'status == "suspended"',
216280
successMessage: 'Environment resumed.',
217281
refreshAfter: true,
218282
},
219-
{
220-
name: 'archive_environment',
221-
label: 'Archive',
222-
icon: 'archive',
223-
variant: 'danger',
224-
type: 'script',
225-
locations: ['list_item', 'record_header'],
226-
confirmText: 'Archive this environment? It will be removed from active views. Data is retained for 30 days before deletion.',
227-
successMessage: 'Environment archived.',
228-
refreshAfter: true,
229-
params: [
230-
{ name: 'reason', label: 'Reason (optional)', type: 'text', required: false },
231-
],
232-
},
233283
{
234284
name: 'set_default_environment',
235285
label: 'Set as Default',
236286
icon: 'star',
237287
variant: 'secondary',
238288
type: 'script',
239289
locations: ['list_item', 'record_header'],
290+
visible: 'is_default != true',
240291
successMessage: 'Default environment updated.',
241292
refreshAfter: true,
242293
},
@@ -247,6 +298,7 @@ export const SysEnvironment = ObjectSchema.create({
247298
variant: 'secondary',
248299
type: 'script',
249300
locations: ['list_item', 'record_header'],
301+
visible: 'status != "archived"',
250302
successMessage: 'Plan updated.',
251303
refreshAfter: true,
252304
params: [
@@ -272,6 +324,7 @@ export const SysEnvironment = ObjectSchema.create({
272324
variant: 'secondary',
273325
type: 'script',
274326
locations: ['list_item', 'record_header'],
327+
visible: 'status != "archived"',
275328
successMessage: 'Hostname updated.',
276329
refreshAfter: true,
277330
params: [
@@ -286,50 +339,19 @@ export const SysEnvironment = ObjectSchema.create({
286339
},
287340
],
288341
},
289-
// ────────────────────────────────────────────────────────────────────
290-
// Install Application — primary CTA on an environment detail page.
291-
// Solves "I just created an env, now what?": user picks a package
292-
// (with a record-picker if the renderer supports it; otherwise paste
293-
// its ID) and the package is installed into the current env.
294-
//
295-
// The endpoint is env-keyed (recordIdParam: 'id' → /:id/install-package)
296-
// so a record-header click already knows the target env and only needs
297-
// to collect the package_id + optional seed flag.
298-
// ────────────────────────────────────────────────────────────────────
299342
{
300-
name: 'install_application',
301-
label: 'Install Application',
302-
icon: 'download-cloud',
303-
variant: 'primary',
343+
name: 'archive_environment',
344+
label: 'Archive',
345+
icon: 'archive',
346+
variant: 'danger',
304347
type: 'script',
305348
locations: ['list_item', 'record_header'],
306-
target: 'install_application',
307-
successMessage: 'Application installed. Open your environment to see it.',
349+
visible: 'status != "archived"',
350+
confirmText: 'Archive this environment? It will be removed from active views. Data is retained for 30 days before deletion.',
351+
successMessage: 'Environment archived.',
308352
refreshAfter: true,
309353
params: [
310-
{
311-
// Borrow the typed lookup config from sys_package_installation
312-
// so renderers that support it can show a record picker;
313-
// otherwise users can paste the Package ID from the Marketplace.
314-
name: 'package_id',
315-
field: 'package_id',
316-
objectOverride: 'sys_package_installation',
317-
label: 'Application',
318-
type: 'lookup',
319-
required: true,
320-
placeholder: 'Pick an application from the Marketplace',
321-
helpText: 'Application package to install. Browse the Marketplace tab to see what is available.',
322-
},
323-
{
324-
name: 'seed_sample_data',
325-
label: 'Include sample data',
326-
type: 'boolean',
327-
required: false,
328-
defaultValue: false,
329-
helpText: 'Pre-populate the environment with the package\'s demo records ' +
330-
'(e.g. example Accounts, Contacts, Leads) so you can explore the app immediately. ' +
331-
'Recommended for first-time users; leave unchecked for a clean production environment.',
332-
},
354+
{ name: 'reason', label: 'Reason (optional)', type: 'text', required: false },
333355
],
334356
},
335357
],

0 commit comments

Comments
 (0)