Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 148 additions & 2 deletions docs/cli/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,79 @@ If detailed usage data is present (granular history, profiles, etc.), the comman

---

## b2c sandbox settings

Show effective OCAPI and WebDAV settings for a specific sandbox.

### Usage

```bash
b2c sandbox settings <SANDBOXID>
```

### Arguments

| Argument | Description | Required |
|----------|-------------|----------|
| `SANDBOXID` | Sandbox ID (UUID or realm-instance, e.g., `zzzv-123`) | Yes |

### Examples

```bash
# Show settings summary for a sandbox
b2c sandbox settings zzzz-001

# Output full settings payload as JSON
b2c sandbox settings zzzz-001 --json
```

### Output

When not using `--json`, the command prints:

- Number of OCAPI client entries
- Number of WebDAV client entries
- A short per-client breakdown for each settings type

---

## b2c sandbox storage

Show filesystem storage usage for a specific sandbox.

### Usage

```bash
b2c sandbox storage <SANDBOXID>
```

### Arguments

| Argument | Description | Required |
|----------|-------------|----------|
| `SANDBOXID` | Sandbox ID (UUID or realm-instance, e.g., `zzzv-123`) | Yes |

### Examples

```bash
# Show storage table for a sandbox
b2c sandbox storage zzzz-001

# Output raw storage response as JSON
b2c sandbox storage zzzz-001 --json
```

### Output

When not using `--json`, the command prints a table with one row per filesystem:

- Filesystem name
- Total space (MB)
- Used space (MB)
- Used percentage

---

## Sandbox Aliases

Sandbox aliases let you access a sandbox via a custom hostname instead of the default instance hostname.
Expand Down Expand Up @@ -968,9 +1041,11 @@ For the complete response including all metadata, use the `--json` flag.
Realm commands operate at the **realm** level rather than on an individual sandbox. They are available as both `realm` topic commands and as `sandbox realm` subcommands:

- `b2c realm list` (`b2c sandbox realm list`)
- `b2c realm configuration` (`b2c sandbox realm configuration`)
- `b2c realm get` (`b2c sandbox realm get`)
- `b2c realm update` (`b2c sandbox realm update`)
- `b2c realm usage` (`b2c sandbox realm usage`)
- `b2c realm usages` (`b2c sandbox realm usages`)

### Required Access for Realm Commands

Expand All @@ -990,7 +1065,7 @@ b2c realm list [REALM]

| Argument | Description | Required |
|----------|-------------|----------|
| `REALM` | Specific realm ID (four-letter ID) to get details for | No |
| `REALM` | Optional realm ID filter (four-letter ID) | No |

#### Examples

Expand All @@ -1005,7 +1080,35 @@ b2c realm list zzzz
b2c realm list --json
```

When `REALM` is omitted, the command discovers realms from the `/me` endpoint and then fetches configuration for each.
When `REALM` is omitted, the command discovers realms from the `/me` endpoint.

### b2c realm configuration

Get sandbox configuration for a specific realm.

#### Usage

```bash
b2c realm configuration <REALM>
```

#### Arguments

| Argument | Description | Required |
|----------|-------------|----------|
| `REALM` | Realm ID (four-letter ID) | Yes |

#### Examples

```bash
# Get realm sandbox configuration
b2c realm configuration zzzz

# JSON output
b2c realm configuration zzzz --json
```

When not using `--json`, the command prints configuration details such as emails, sandbox limits, TTL values, and start/stop schedulers.

### b2c realm get

Expand Down Expand Up @@ -1143,3 +1246,46 @@ When not using `--json`, the command prints a summary including:

If detailed usage is available, it prints a hint to re-run with `--json` for the full structure. If no usage data is returned for the requested period, it prints a friendly message instead of failing.

### b2c realm usages

Show usage information for multiple realms in one request.

#### Usage

```bash
b2c realm usages [FLAGS]
```

#### Flags

| Flag | Description |
|------|-------------|
| `--realm` | Realm IDs to include (repeat flag or provide comma-separated values) |
| `--from` | Earliest date to include in usage (ISO 8601) |
| `--to` | Latest date to include in usage (ISO 8601) |
| `--detailed-report` | Include detailed usage information in the response |

If `--realm` is omitted, the command auto-discovers realms from `/me` and queries usage for all discovered realms.

#### Examples

```bash
# Usage for all realms available to the current user
b2c realm usages

# Usage for two specific realms
b2c realm usages --realm zzzz --realm yyyy

# Usage for comma-separated realms and date range
b2c realm usages --realm zzzz,yyyy --from 2024-01-01 --to 2024-01-31

# Detailed report in JSON
b2c realm usages --detailed-report --json
```

When not using `--json`, the command prints one row per realm with summary metrics such as:

- Active / created / deleted sandbox counts
- Minutes up / minutes down
- Sandbox seconds

113 changes: 113 additions & 0 deletions packages/b2c-cli/src/commands/sandbox/realm/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2025, Salesforce, Inc.
* SPDX-License-Identifier: Apache-2
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/

import {Args} from '@oclif/core';
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
import {t, withDocs} from '../../../i18n/index.js';

type RealmConfigurationModel = OdsComponents['schemas']['RealmConfigurationModel'];
type RealmConfigurationResponse = OdsComponents['schemas']['RealmConfigurationResponse'];

/**
* Get realm sandbox configuration.
*/
export default class SandboxRealmConfiguration extends OdsCommand<typeof SandboxRealmConfiguration> {
static aliases = ['ods:realm:configuration', 'realm:configuration'];

static args = {
realm: Args.string({
description: 'Realm ID (four-letter ID)',
required: true,
}),
};

static description = withDocs(
t('commands.realm.configuration.description', 'Get sandbox configuration for a realm'),
'/cli/realm.html#b2c-realm-configuration',
);

static enableJsonFlag = true;

static examples = ['<%= config.bin %> <%= command.id %> zzzz', '<%= config.bin %> <%= command.id %> zzzz --json'];

async run(): Promise<RealmConfigurationModel | RealmConfigurationResponse | undefined> {
const {args} = await this.parse(SandboxRealmConfiguration);
const realm = args.realm;

this.log(t('commands.realm.configuration.fetching', 'Fetching configuration for realm {{realm}}...', {realm}));

const result = await this.odsClient.GET('/realms/{realm}/configuration', {
params: {path: {realm}},
});

if (result.error) {
this.error(
t('commands.realm.configuration.error', 'Failed to fetch configuration for realm {{realm}}: {{message}}', {
realm,
message: getApiErrorMessage(result.error, result.response),
}),
);
}

const data = (result.data as RealmConfigurationResponse | undefined)?.data;
if (!data) {
this.log(t('commands.realm.configuration.noData', 'No configuration data was returned for this realm.'));
return undefined;
}

if (this.jsonEnabled()) {
return result.data as RealmConfigurationResponse;
}

this.printConfiguration(data);
return data;
}

private printConfiguration(configuration: RealmConfigurationModel): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cfg = configuration as any;

console.log('Realm Configuration');
console.log('───────────────────');

const maxTtlRaw = cfg.sandbox?.sandboxTTL?.maximum as number | undefined;
const maxTtlDisplay = maxTtlRaw === undefined ? undefined : maxTtlRaw >= 2_147_483_647 ? '0' : String(maxTtlRaw);

const rows: Array<[string, string | undefined]> = [
['Enabled', cfg.enabled === undefined ? undefined : String(cfg.enabled)],
['Emails', Array.isArray(cfg.emails) ? cfg.emails.join(', ') : undefined],
['Limits Enabled', cfg.sandbox?.limitsEnabled === undefined ? undefined : String(cfg.sandbox.limitsEnabled)],
[
'Total Sandboxes',
cfg.sandbox?.totalNumberOfSandboxes === undefined ? undefined : String(cfg.sandbox.totalNumberOfSandboxes),
],
['Max Sandbox TTL', maxTtlDisplay],
[
'Default Sandbox TTL',
cfg.sandbox?.sandboxTTL?.defaultValue === undefined ? undefined : String(cfg.sandbox.sandboxTTL.defaultValue),
],
[
'Local Users Allowed',
cfg.sandbox?.localUsersAllowed === undefined ? undefined : String(cfg.sandbox.localUsersAllowed),
],
];

for (const [label, value] of rows) {
if (value !== undefined) {
console.log(`${label}: ${value}`);
}
}

if (cfg.sandbox?.startScheduler) {
console.log(`Start Scheduler: ${JSON.stringify(cfg.sandbox.startScheduler)}`);
}

if (cfg.sandbox?.stopScheduler) {
console.log(`Stop Scheduler: ${JSON.stringify(cfg.sandbox.stopScheduler)}`);
}
}
}
50 changes: 9 additions & 41 deletions packages/b2c-cli/src/commands/sandbox/realm/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,26 @@

import {Args} from '@oclif/core';
import {OdsCommand} from '@salesforce/b2c-tooling-sdk/cli';
import {getApiErrorMessage, type OdsComponents} from '@salesforce/b2c-tooling-sdk';
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk';
import {t, withDocs} from '../../../i18n/index.js';

type RealmConfigurationModel = OdsComponents['schemas']['RealmConfigurationModel'];

interface RealmWithUsage {
interface RealmItem {
realmId: string;
configuration?: RealmConfigurationModel;
}

interface RealmListResponse {
realms: RealmWithUsage[];
realms: RealmItem[];
}

/**
* List realms eligible for sandbox management, optionally including usage.
* List realms eligible for sandbox management.
*/
export default class SandboxRealmList extends OdsCommand<typeof SandboxRealmList> {
static aliases = ['ods:realm:list', 'realm:list'];

static args = {
realm: Args.string({
description: 'Specific realm ID (four-letter ID) to get details for',
description: 'Optional realm ID filter (four-letter ID)',
required: false,
}),
};
Expand Down Expand Up @@ -70,32 +67,7 @@ export default class SandboxRealmList extends OdsCommand<typeof SandboxRealmList
realmIds = meResult.data.data.realms ?? [];
}

const realms: RealmWithUsage[] = [];

for (const realmId of realmIds) {
// Fetch configuration for each realm
// eslint-disable-next-line no-await-in-loop -- Sequential API calls required
const configResult = await this.odsClient.GET('/realms/{realm}/configuration', {
params: {path: {realm: realmId}},
});

if (configResult.error) {
this.error(
t('commands.realm.list.configError', 'Failed to fetch configuration for realm {{realm}}: {{message}}', {
realm: realmId,
message: getApiErrorMessage(configResult.error, configResult.response),
}),
);
}

const realmEntry: RealmWithUsage = {
realmId,

configuration: (configResult.data?.data as RealmConfigurationModel | undefined) ?? undefined,
};

realms.push(realmEntry);
}
const realms: RealmItem[] = realmIds.map((realmId) => ({realmId}));

const response: RealmListResponse = {realms};

Expand All @@ -110,16 +82,12 @@ export default class SandboxRealmList extends OdsCommand<typeof SandboxRealmList

// Human-readable output: simple table-like listing

console.log('Realm Enabled');
console.log('Realm');

console.log('───── ───────');
console.log('─────');

for (const realm of realms) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const configAny = realm.configuration as any;
const enabled = configAny?.enabled ?? false;

console.log(`${realm.realmId.padEnd(5)} ${String(enabled).padEnd(7)}`);
console.log(realm.realmId);
}

return response;
Expand Down
Loading
Loading