{{define "app_settings"}}{{template "top" .}} {{if .Flash}}{{end}} {{if .FlashErr}}{{end}} {{template "appnav" .}}
{{if eq .SettingsSection "general"}}

Identity

AppAccount
{{.Detail.App}}{{.Account.Name}} {{.Account.Sqid}}

Release pipeline — the channels a release moves through, and what each one takes to promote onto.

{{if .IsOwner}} {{if .Settings.ControlPlane}}

This app is marked as the control plane — it is this deployment’s own platform. Renaming, moving and deleting it are refused while the flag is set, and a service credential cannot promote it.

{{else if .Settings.CanRename}}

This app has never shipped, so its name is still only a record here — after the first ship, the name is wired into addresses and bucket keys and stays.

{{else}}

Renaming: {{.Settings.RenameBlocked}}

{{end}} {{end}} {{end}} {{if eq .SettingsSection "domains"}}

Domains & TLS

{{if .Settings.Domains}}
{{range $i, $d := .Settings.Domains}} {{with $d.Explain}} {{end}} {{if and $.IsOwner $d.Claimed}} {{end}} {{end}}
DomainTypeStatusCertificate
{{$d.Domain}} {{$d.Type}} {{with $d.Status}}{{.}}{{else}}—{{end}} {{if $d.TLS}}{{$d.TLS}}{{else}}—{{end}}
{{.}}
{{else}}

No domains yet — the first shipped route brings one.

{{end}} {{if .IsOwner}}

Attach a custom domain

{{if .Settings.OnlyPlatformDomain}}

This app already answers at its platform domain; attaching adds addresses, it never removes one.

{{end}}
{{if .Settings.NeedsChannel}} {{end}}

Point the domain's DNS at this deployment — once it is attached, the row above says exactly what to create. The platform obtains the certificate itself and renews it, so nothing has to visit the domain first.

For an app with instances, attaching *.example.com makes each instance also answer at <label>.example.com, where the label is the first part of its own hostname. example.com must already be attached to an app in this account, and a wildcard DNS record (*) must point here.

{{end}}

Password gate

One shared password in front of a hostname — for a static site or a prototype that should not be world-readable. Visitors see a plain page asking for it and stay signed in for a week. It is set per instance.

{{if .Settings.GatesUnavailable}}

{{.Settings.GatesUnavailable}}

{{else if .Settings.Gates}}
{{range $i, $g := .Settings.Gates}} {{end}}
HostState
{{$g.Host}} {{if $g.Gated}}Password set{{else}}Open{{end}}
{{if $g.Gated}}
{{end}}
{{else}}

No instances yet. A password gate is set per instance.

{{end}} {{end}} {{if eq .SettingsSection "general"}} {{if and .Settings .Settings.MismatchTarget}}

Account

This app's hosts read releases from account {{.Settings.MismatchTarget}}, but the console's record says {{.Account.Name}} — so promotes from here reach nothing.

{{if and .IsOwner .Settings.TargetKnown .Settings.ViewerInTarget}}

Move the record to where the platform already deploys from:

{{else}}

{{if not .IsOwner}}Moving the record is the account owner's to do{{else if not .Settings.TargetKnown}}The routes name an account this console does not know{{else}}You are not a member of that account{{end}} — the operator path is console migrate-app --app {{.Detail.App}} --to {{.Settings.MismatchTarget}} on the console's box.

{{end}} {{else if .AccountReview.NoInstances}}

Account

{{.MismatchCopy.NoInstances}} Instances

{{else}}{{if .IsOwner}}

Account

This app deploys from {{.Account.Name}}. Moving an app to a different account moves its bucket objects and re-stamps its routes — carlos accounts migrate, an operator act; once run, this page offers the final console-record step.

{{end}}{{end}} {{end}} {{if eq .SettingsSection "environments"}} {{if .Settings.NoBucket}}{{end}} {{if .Settings.EnvsUnavailable}}{{end}} {{if .Settings}}

Environments

A named environment is a config bundle that overlays this app's default one, per key. An instance bound to it reads the app's config with these values on top — which is how two instances of one app serve different origins, and how one of them gets a bucket of its own.

{{if .Settings.Envs}} {{range .Settings.Envs}}

{{.Name}}

SetsRead byObject store
{{if .Vars}}{{range $i, $v := .Vars}}{{if $i}}, {{end}}{{$v.Name}}{{if $v.Secret}} sealed{{end}}{{end}}{{else}}—{{end}} {{if .Hosts}}{{range $i, $h := .Hosts}}{{if $i}}, {{end}}{{$h}}{{end}}{{else}}no instances{{end}} {{if .HasStore}}{{if .StoreBucket}}{{.StoreBucket}}{{else}}declared{{end}}{{else}}—{{end}}
{{if and .HasStore (not .Hosts)}}

This environment has an object store but no instance reads it. The bucket still exists and still costs — destroying a store is always deliberate, so nothing has removed it. Its card is under Storage.

{{end}} {{end}}

Values are not shown here. Read them with carlos env list --app {{.Detail.App}} --environment <name>; secrets list by name only, wherever you ask.

{{else}}{{if not .Settings.EnvsUnavailable}}

No named environments. Every instance of this app reads its default config bundle. Add one with carlos env set --app {{.Detail.App}} --environment <name> KEY=value, then bind an instance to it with carlos instances set-environment --app {{.Detail.App}} --host <h> --environment <name>.

{{end}}{{end}} {{end}} {{end}} {{if eq .SettingsSection "storage"}} {{if .Settings.NoBucket}}{{end}} {{if .Settings.StoreUnavailable}}{{end}} {{if .Settings}}{{with .Settings.Store}}

Object storage

{{if or .Declared .Granted .Provisioned}}
Env prefixBackendBucketAccess keyKey age
{{if .EnvPrefix}}{{.EnvPrefix}}_*{{else}}—{{end}} {{if .Backend}}{{.Backend}}{{else}}—{{end}} {{if .Bucket}}{{.Bucket}}{{else}}—{{end}} {{if .KeyID}}{{.KeyID}}{{else}}—{{end}} {{if .KeyAge}}{{.KeyAge}}{{else}}—{{end}}
{{if .Provisioned}}

Key age is the last credential change — a rotation and a repaired grant both move it, so it is not when the store was created.

{{end}} {{if and (not .Granted) (not .Provisioned)}}

Declared and waiting: an operator of this deployment creates the bucket and delivers the credentials. Until then the {{if .EnvPrefix}}{{.EnvPrefix}}_*{{else}}store{{end}} variables are not in this app's config.

{{end}} {{if and .Granted (not .Provisioned)}}

A grant is half-done: the bucket was claimed but no credentials are recorded yet. Running the grant again repairs it — an operator of this deployment does that.

{{end}} {{if .PendingFinish}}

A rotation is half-finished — two keys open this bucket until you retire the old one:

carlos store rotate --finish --app {{$.Detail.App}}
{{end}} {{if $.IsOwner}}

Destroy this store

{{if .CanDestroy}}

Destroying empties and deletes {{if .Bucket}}{{.Bucket}}{{else}}the store{{end}}, deletes the credentials that opened it, and removes the delivered variables from this app's config. There is no trash for a store: the objects in it are gone, and nothing here brings them back.

Confirms with your passkey.

{{else}}

This console has no storage backend configured (CARLOS_STORE_BACKEND), so it cannot delete this bucket or its credentials — an operator of this deployment has to tear it down.

{{end}} {{end}} {{else}}

No object store. Ask for one with carlos store create --app {{$.Detail.App}}; an operator of this deployment then creates the bucket and delivers its credentials into this app's config.

{{end}} {{end}}{{end}} {{with .Settings}}{{range .StoreEnvs}}

Object storage — {{.Environment}} environment

Its own bucket and its own credential, delivered into the {{.Environment}} config bundle — so only the instances bound to that environment can reach it.

Env prefixBackendBucketAccess keyKey age
{{if .EnvPrefix}}{{.EnvPrefix}}_*{{else}}—{{end}} {{if .Backend}}{{.Backend}}{{else}}—{{end}} {{if .Bucket}}{{.Bucket}}{{else}}—{{end}} {{if .KeyID}}{{.KeyID}}{{else}}—{{end}} {{if .KeyAge}}{{.KeyAge}}{{else}}—{{end}}
{{if and (not .Granted) (not .Provisioned)}}

Declared and waiting: an operator of this deployment creates the bucket and delivers the credentials.

{{end}} {{if .PendingFinish}}

A rotation is half-finished — two keys open this bucket until you retire the old one:

carlos store rotate --finish --app {{$.Detail.App}} --environment {{.Environment}}
{{end}} {{if $.IsOwner}}

Destroy this store

{{if .CanDestroy}}

Destroying empties and deletes {{if .Bucket}}{{.Bucket}}{{else}}the store{{end}}, deletes the credentials that opened it, and removes the delivered variables from the {{.Environment}} bundle. The app's own store is not touched. There is no trash for a store: the objects in it are gone.

Confirms with your passkey.

{{else}}

This console has no storage backend configured (CARLOS_STORE_BACKEND), so it cannot delete this bucket or its credentials — an operator of this deployment has to tear it down.

{{end}} {{end}} {{end}}{{end}} {{end}} {{if eq .SettingsSection "general"}}

Errors

{{if .ErrLevelUnread}}

This app’s Errors log level could not be read just now, so it is not shown and cannot be changed from here. Reload before concluding anything.

{{else if .IsOwner}}
currently {{.ErrLevelSetting.LogLevel}}, from the {{.ErrLevelSetting.LogLevelFrom}}
{{else}}

Log lines at {{.ErrLevelSetting.LogLevel}} and above are counted as errors for this app, from the {{.ErrLevelSetting.LogLevelFrom}}. Only the account owner can change it.

{{end}}

The Errors tab always lets you widen one look to warn without changing this.

Analytics

{{if .AnalyticsUnread}}

This app’s analytics setting could not be read just now. Reload before concluding anything.

{{else if .IsOwner}}
currently {{.AnalyticsSetting.Count}}, from the {{.AnalyticsSetting.CountFrom}}
{{else}}

Analytics counting is {{.AnalyticsSetting.Count}} for this app, from the {{.AnalyticsSetting.CountFrom}}. Only the account owner can change it.

{{end}}

When on, the edge counts HTML page views and daily visitors as it serves them: no JavaScript, no cookie, no stored IP. A visitor is a salted hash that changes every day and cannot be joined across days. Whether that needs consent where your visitors are is your call, which is why it is off until you turn it on.

{{.SwapCopy.Heading}}

{{if .SwapUnread}}

{{.SwapCopy.Unread}}

{{else}}

{{.SwapCopy.Intro}}

{{.SwapCopy.MigrationsNote}}

{{end}} {{end}} {{if eq .SettingsSection "features"}} {{if .Settings.NoBucket}}{{end}} {{if .Settings.FeaturesUnavailable}}{{end}} {{if .Settings}}{{with .Settings.Features}}

Feature flags

{{if .Flags}}
{{range .Flags}} {{end}}
KeyValue
{{.Key}}{{.Value}}

Last changed {{.SetAt}}{{if .SetBy}} by {{.SetBy}}{{end}}. The platform stores and serves these; what each one means is this app's own business. It reads them off the deployment block its instance token fetches, so a change lands on its next poll — no restart.

{{else}}

No feature flags set.

{{end}}

Set or clear one with carlos features set --app {{$.Detail.App}} key=value (key= clears); every change is audited under Activity.

{{end}}{{end}} {{end}} {{if eq .SettingsSection "email"}} {{if .Settings.NoBucket}}{{end}} {{if .Settings.MailUnavailable}}{{end}} {{if .Settings}}{{with .Settings.Mail}}

Email

{{if or .Declared .Granted .Provisioned .Standalone}}
Env prefixFrom addressSending domainsDaily capSMTP usernameKey age
{{if .EnvPrefix}}{{.EnvPrefix}}_*{{else}}—{{end}} {{if .From}}{{.From}}{{else}}—{{end}} {{if .Domains}}{{range $i, $d := .Domains}}{{if $i}}, {{end}}{{$d}}{{end}}{{else}}—{{end}} {{if .Granted}}{{.DailyCap}}{{else}}—{{end}} {{if .KeyID}}{{.KeyID}}{{else}}—{{end}} {{if .KeyAge}}{{.KeyAge}}{{else}}—{{end}}
{{if .Provisioned}}

Key age is the last credential change — a rotation moves it. The SMTP username above IS this credential's access key id; there is no separate password to show here.

{{end}} {{if .Paused}} {{end}} {{if .PendingFinish}}

A rotation is half-finished — two keys open this credential until you retire the old one:

carlos email rotate --finish --app {{$.Detail.App}}
{{end}} {{if and .Declared (not .Granted) (not .Provisioned)}}

Declared and waiting on a verified sending domain — add one with carlos email domains add --app {{$.Detail.App}} <domain>, then carlos email enable --app {{$.Detail.App}}.

{{end}} {{if and .Granted (not .Provisioned)}}

Authorised to send but not provisioned yet — run carlos email enable --app {{$.Detail.App}} to finish it.

{{end}} {{if .Standalone}}

Standalone credentials

{{range .Standalone}} {{end}}
NameSMTP usernameKey age
{{.Name}} {{.KeyID}} {{if .Age}}{{.Age}}{{else}}—{{end}}

No password is shown here — standalone credentials have none on the record; it was handed over once, at creation.

{{end}} {{else}} {{if .Available}}

No email for this app yet — get it with carlos email enable --app {{$.Detail.App}}.

{{else}}

Email is not available on this deployment.

{{end}} {{end}} {{end}}{{end}} {{end}} {{if eq .SettingsSection "advanced"}} {{if and .Settings .IsOwner}}

Control plane

{{if .Settings.ControlPlane}}

{{.Detail.App}} is marked as the control plane: it is what serves this deployment, so its release history is what the fleet is running. Clearing the flag removes that protection — trash, permanent deletion, rename and the account move all become available, and a service credential can promote it again.

Confirms with your passkey — clearing is the dangerous direction, so it is the one that asks. Marking an app again does not.

{{else}}

{{.Detail.App}} is an ordinary app. Marking it as the control plane says it IS this deployment’s platform: trashing, permanent deletion, renaming and the account move are then refused, the account holding it cannot be retired, and a service credential can neither promote it nor shorten its release history. A person with ops membership still deploys it exactly as before.

No passkey: marking only adds refusals, and it is undone from this same card.

{{end}} {{end}} {{end}} {{if eq .SettingsSection "delete"}} {{if .IsOwner}}

Delete this app

Deleting puts {{.Detail.App}} in the account's trash: hosts stop serving it on their next pass (custom domains included), and it is restorable for {{if .ExpiresAt.IsZero}}{{trashCooloffDays}}{{else}}{{temporaryCooloffDays}}{{end}} days before the platform destroys its releases for good.{{if not .ExpiresAt.IsZero}} It is a temporary app, so its window is shorter than usual.{{end}}{{if .Detail.Production.On}} It is marked as production right now, which blocks deletion — turn the safety delay off on the {{.DeploymentsCopy.History}} tab first.{{end}}

Confirms with your passkey.

{{end}} {{end}}
{{template "bottom" .}}{{end}}