Skip to content

Commit 94d2503

Browse files
authored
@W-21111039: Support ODS instance details
* support ODS instance details
1 parent 87321c0 commit 94d2503

2 files changed

Lines changed: 421 additions & 101 deletions

File tree

packages/b2c-vs-extension/src/extension.ts

Lines changed: 144 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,12 @@ function getScapiExplorerWebviewContent(
4848
return html;
4949
}
5050

51-
function getOdsManagementWebviewContent(context: vscode.ExtensionContext): string {
51+
function getOdsManagementWebviewContent(context: vscode.ExtensionContext, prefill?: {defaultRealm: string}): string {
5252
const htmlPath = path.join(context.extensionPath, 'src', 'ods-management.html');
53-
return fs.readFileSync(htmlPath, 'utf-8');
53+
let html = fs.readFileSync(htmlPath, 'utf-8');
54+
const defaultRealm = prefill?.defaultRealm ?? '';
55+
html = html.replaceAll('__ODS_DEFAULT_REALM__', defaultRealm);
56+
return html;
5457
}
5558

5659
const WEBDAV_ROOT_LABELS: Record<string, string> = {
@@ -1076,27 +1079,33 @@ function activateInner(context: vscode.ExtensionContext, log: vscode.OutputChann
10761079

10771080
const DEFAULT_ODS_HOST = 'admin.dx.commercecloud.salesforce.com';
10781081

1079-
const odsManagementDisposable = vscode.commands.registerCommand('b2c-dx.odsManagement', () => {
1082+
const odsManagementDisposable = vscode.commands.registerCommand('b2c-dx.odsManagement', async () => {
10801083
const panel = vscode.window.createWebviewPanel(
10811084
'b2c-dx-ods-management',
10821085
'On Demand Sandbox (ODS) Management',
10831086
vscode.ViewColumn.One,
10841087
{enableScripts: true},
10851088
);
1086-
panel.webview.html = getOdsManagementWebviewContent(context);
1089+
let defaultRealm = '';
1090+
try {
1091+
const workingDirectory = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? context.extensionPath;
1092+
const dwPath = findDwJson(workingDirectory);
1093+
const config = dwPath ? resolveConfig({}, {configPath: dwPath}) : resolveConfig({}, {workingDirectory});
1094+
// First part of hostname, e.g. 'zyoc' from 'zyoc-003.unified.demandware.net'
1095+
const hostname = config.values.hostname;
1096+
const firstSegment = (hostname && typeof hostname === 'string' ? hostname : '').split('.')[0] ?? '';
1097+
defaultRealm = firstSegment.split('-')[0] ?? '';
1098+
} catch {
1099+
// leave defaultRealm empty
1100+
}
1101+
panel.webview.html = getOdsManagementWebviewContent(context, {defaultRealm});
10871102

10881103
async function getOdsConfig() {
10891104
const workingDirectory = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? context.extensionPath;
10901105
const dwPath = findDwJson(workingDirectory);
10911106
return dwPath ? resolveConfig({}, {configPath: dwPath}) : resolveConfig({}, {workingDirectory});
10921107
}
10931108

1094-
function realmFromHostname(hostname: string | undefined): string {
1095-
if (!hostname || typeof hostname !== 'string') return '';
1096-
const firstSegment = hostname.split('.')[0] ?? '';
1097-
return firstSegment.split('-')[0] ?? '';
1098-
}
1099-
11001109
async function fetchSandboxList(): Promise<{sandboxes: unknown[]; error?: string}> {
11011110
try {
11021111
const config = await getOdsConfig();
@@ -1123,97 +1132,138 @@ function activateInner(context: vscode.ExtensionContext, log: vscode.OutputChann
11231132
}
11241133
}
11251134

1126-
panel.webview.onDidReceiveMessage(async (msg: {type: string; sandboxId?: string}) => {
1127-
if (msg.type === 'odsListRequest') {
1128-
const {sandboxes, error} = await fetchSandboxList();
1129-
panel.webview.postMessage({type: 'odsListResult', sandboxes, error});
1130-
return;
1131-
}
1132-
if (msg.type === 'odsDeleteClick' && msg.sandboxId) {
1133-
try {
1134-
const config = await getOdsConfig();
1135-
if (!config.hasOAuthConfig()) {
1136-
vscode.window.showErrorMessage('B2C DX: OAuth credentials required for ODS. Configure dw.json.');
1137-
return;
1138-
}
1139-
const host = config.values.sandboxApiHost ?? DEFAULT_ODS_HOST;
1140-
const authStrategy = config.createOAuth();
1141-
const odsClient = createOdsClient({host}, authStrategy);
1142-
const deleteResult = await odsClient.DELETE('/sandboxes/{sandboxId}', {
1143-
params: {path: {sandboxId: msg.sandboxId}},
1144-
});
1145-
if (deleteResult.error) {
1146-
vscode.window.showErrorMessage(
1147-
`B2C DX: Delete sandbox failed. ${getApiErrorMessage(deleteResult.error, deleteResult.response)}`,
1148-
);
1149-
return;
1150-
}
1151-
vscode.window.showInformationMessage('B2C DX: Sandbox deleted.');
1135+
panel.webview.onDidReceiveMessage(
1136+
async (msg: {type: string; sandboxId?: string; realm?: string; ttl?: number; url?: string}) => {
1137+
if (msg.type === 'odsListRequest') {
11521138
const {sandboxes, error} = await fetchSandboxList();
11531139
panel.webview.postMessage({type: 'odsListResult', sandboxes, error});
1154-
} catch (err) {
1155-
const message = err instanceof Error ? err.message : String(err);
1156-
vscode.window.showErrorMessage(`B2C DX: ${message}`);
1140+
return;
11571141
}
1158-
return;
1159-
}
1160-
if (msg.type === 'odsCreateClick') {
1161-
try {
1162-
const config = await getOdsConfig();
1163-
if (!config.hasOAuthConfig()) {
1164-
vscode.window.showErrorMessage('B2C DX: OAuth credentials required for ODS. Configure dw.json.');
1165-
return;
1142+
if (msg.type === 'odsGetDefaultRealm') {
1143+
let defaultRealm = '';
1144+
try {
1145+
const config = await getOdsConfig();
1146+
const hostname = config.values.hostname;
1147+
const firstSegment = (hostname && typeof hostname === 'string' ? hostname : '').split('.')[0] ?? '';
1148+
defaultRealm = firstSegment.split('-')[0] ?? '';
1149+
} catch {
1150+
// leave defaultRealm empty
11661151
}
1167-
const hostname = config.values.hostname;
1168-
const defaultRealm = realmFromHostname(hostname as string | undefined);
1169-
1170-
const realm = await vscode.window.showInputBox({
1171-
title: 'Create ODS Sandbox',
1172-
prompt: 'Realm (four-letter ID)',
1173-
value: defaultRealm,
1174-
placeHolder: 'e.g. zyoc',
1175-
});
1176-
if (realm === undefined) return;
1177-
1178-
const ttlStr = await vscode.window.showInputBox({
1179-
title: 'Create ODS Sandbox',
1180-
prompt: 'TTL (hours). Sandbox lifetime in hours.',
1181-
value: '480',
1182-
placeHolder: '480',
1183-
});
1184-
if (ttlStr === undefined) return;
1185-
1186-
const ttl = parseInt(ttlStr.trim(), 10);
1187-
if (Number.isNaN(ttl) || ttl < 0) {
1188-
vscode.window.showErrorMessage('B2C DX: TTL must be a non-negative number.');
1189-
return;
1152+
panel.webview.postMessage({type: 'odsDefaultRealm', defaultRealm});
1153+
return;
1154+
}
1155+
if (msg.type === 'odsSandboxClick' && msg.sandboxId) {
1156+
try {
1157+
const config = await getOdsConfig();
1158+
if (!config.hasOAuthConfig()) {
1159+
panel.webview.postMessage({
1160+
type: 'odsSandboxDetailsError',
1161+
error: 'OAuth credentials required. Set clientId and clientSecret in dw.json.',
1162+
});
1163+
return;
1164+
}
1165+
const host = config.values.sandboxApiHost ?? DEFAULT_ODS_HOST;
1166+
const authStrategy = config.createOAuth();
1167+
const odsClient = createOdsClient({host}, authStrategy);
1168+
const result = await odsClient.GET('/sandboxes/{sandboxId}', {
1169+
params: {path: {sandboxId: msg.sandboxId}},
1170+
});
1171+
if (result.error || !result.data?.data) {
1172+
panel.webview.postMessage({
1173+
type: 'odsSandboxDetailsError',
1174+
error: getApiErrorMessage(result.error, result.response) || 'Sandbox not found',
1175+
});
1176+
return;
1177+
}
1178+
panel.webview.postMessage({
1179+
type: 'odsSandboxDetails',
1180+
sandbox: result.data.data,
1181+
});
1182+
} catch (err) {
1183+
const message = err instanceof Error ? err.message : String(err);
1184+
panel.webview.postMessage({type: 'odsSandboxDetailsError', error: message});
11901185
}
1191-
1192-
const host = config.values.sandboxApiHost ?? DEFAULT_ODS_HOST;
1193-
const authStrategy = config.createOAuth();
1194-
const odsClient = createOdsClient({host}, authStrategy);
1195-
const createResult = await odsClient.POST('/sandboxes', {
1196-
body: {
1197-
realm: realm.trim(),
1198-
ttl: ttl === 0 ? undefined : ttl,
1199-
analyticsEnabled: false,
1200-
},
1201-
});
1202-
if (createResult.error) {
1203-
vscode.window.showErrorMessage(
1204-
`B2C DX: Create sandbox failed. ${getApiErrorMessage(createResult.error, createResult.response)}`,
1205-
);
1206-
return;
1186+
return;
1187+
}
1188+
if (msg.type === 'odsOpenLink' && msg.url) {
1189+
try {
1190+
await vscode.env.openExternal(vscode.Uri.parse(msg.url));
1191+
} catch {
1192+
// ignore
12071193
}
1208-
vscode.window.showInformationMessage('B2C DX: Sandbox creation started.');
1209-
const {sandboxes, error} = await fetchSandboxList();
1210-
panel.webview.postMessage({type: 'odsListResult', sandboxes, error});
1211-
} catch (err) {
1212-
const message = err instanceof Error ? err.message : String(err);
1213-
vscode.window.showErrorMessage(`B2C DX: ${message}`);
1194+
return;
12141195
}
1215-
}
1216-
});
1196+
if (msg.type === 'odsDeleteClick' && msg.sandboxId) {
1197+
try {
1198+
const config = await getOdsConfig();
1199+
if (!config.hasOAuthConfig()) {
1200+
vscode.window.showErrorMessage('B2C DX: OAuth credentials required for ODS. Configure dw.json.');
1201+
return;
1202+
}
1203+
const host = config.values.sandboxApiHost ?? DEFAULT_ODS_HOST;
1204+
const authStrategy = config.createOAuth();
1205+
const odsClient = createOdsClient({host}, authStrategy);
1206+
const deleteResult = await odsClient.DELETE('/sandboxes/{sandboxId}', {
1207+
params: {path: {sandboxId: msg.sandboxId}},
1208+
});
1209+
if (deleteResult.error) {
1210+
vscode.window.showErrorMessage(
1211+
`B2C DX: Delete sandbox failed. ${getApiErrorMessage(deleteResult.error, deleteResult.response)}`,
1212+
);
1213+
return;
1214+
}
1215+
vscode.window.showInformationMessage('B2C DX: Sandbox deleted.');
1216+
const {sandboxes, error} = await fetchSandboxList();
1217+
panel.webview.postMessage({type: 'odsListResult', sandboxes, error});
1218+
} catch (err) {
1219+
const message = err instanceof Error ? err.message : String(err);
1220+
vscode.window.showErrorMessage(`B2C DX: ${message}`);
1221+
}
1222+
return;
1223+
}
1224+
if (msg.type === 'odsCreateSandbox' && msg.realm !== undefined && msg.ttl !== undefined) {
1225+
try {
1226+
const config = await getOdsConfig();
1227+
if (!config.hasOAuthConfig()) {
1228+
vscode.window.showErrorMessage('B2C DX: OAuth credentials required for ODS. Configure dw.json.');
1229+
return;
1230+
}
1231+
const realm = String(msg.realm).trim();
1232+
if (!realm) {
1233+
vscode.window.showErrorMessage('B2C DX: Realm is required.');
1234+
return;
1235+
}
1236+
const ttl = Number(msg.ttl);
1237+
if (Number.isNaN(ttl) || ttl < 0) {
1238+
vscode.window.showErrorMessage('B2C DX: TTL must be a non-negative number.');
1239+
return;
1240+
}
1241+
const host = config.values.sandboxApiHost ?? DEFAULT_ODS_HOST;
1242+
const authStrategy = config.createOAuth();
1243+
const odsClient = createOdsClient({host}, authStrategy);
1244+
const createResult = await odsClient.POST('/sandboxes', {
1245+
body: {
1246+
realm,
1247+
ttl, // 0 means no expiration
1248+
analyticsEnabled: false,
1249+
},
1250+
});
1251+
if (createResult.error) {
1252+
vscode.window.showErrorMessage(
1253+
`B2C DX: Create sandbox failed. ${getApiErrorMessage(createResult.error, createResult.response)}`,
1254+
);
1255+
return;
1256+
}
1257+
vscode.window.showInformationMessage('B2C DX: Sandbox creation started.');
1258+
const {sandboxes, error} = await fetchSandboxList();
1259+
panel.webview.postMessage({type: 'odsListResult', sandboxes, error});
1260+
} catch (err) {
1261+
const message = err instanceof Error ? err.message : String(err);
1262+
vscode.window.showErrorMessage(`B2C DX: ${message}`);
1263+
}
1264+
}
1265+
},
1266+
);
12171267
});
12181268

12191269
const storefrontNextCartridgeDisposable = vscode.commands.registerCommand(

0 commit comments

Comments
 (0)