@@ -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