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