@trops/dash-core 0.1.86 → 0.1.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -588,10 +588,12 @@ var clientCacheEvents$1 = {
588
588
 
589
589
  const DASHBOARD_CONFIG_EXPORT$1 = "dashboard-config-export";
590
590
  const DASHBOARD_CONFIG_IMPORT$1 = "dashboard-config-import";
591
+ const DASHBOARD_CONFIG_INSTALL$1 = "dashboard-config-install";
591
592
 
592
593
  var dashboardConfigEvents$1 = {
593
- DASHBOARD_CONFIG_EXPORT: DASHBOARD_CONFIG_EXPORT$1,
594
- DASHBOARD_CONFIG_IMPORT: DASHBOARD_CONFIG_IMPORT$1,
594
+ DASHBOARD_CONFIG_EXPORT: DASHBOARD_CONFIG_EXPORT$1,
595
+ DASHBOARD_CONFIG_IMPORT: DASHBOARD_CONFIG_IMPORT$1,
596
+ DASHBOARD_CONFIG_INSTALL: DASHBOARD_CONFIG_INSTALL$1,
595
597
  };
596
598
 
597
599
  /**
@@ -9974,134 +9976,292 @@ async function importDashboardConfig$1(win, appId, widgetRegistry = null) {
9974
9976
  // Apply defaults to fill in optional fields
9975
9977
  dashboardConfig = applyDefaults(dashboardConfig);
9976
9978
 
9977
- // 3. Auto-install missing widgets from registry
9978
- const installSummary = {
9979
- installed: [],
9980
- alreadyInstalled: [],
9981
- failed: [],
9979
+ // Delegate to shared import pipeline
9980
+ return await processDashboardConfig(
9981
+ win,
9982
+ appId,
9983
+ dashboardConfig,
9984
+ widgetRegistry,
9985
+ );
9986
+ } catch (error) {
9987
+ console.error(
9988
+ "[DashboardConfigController] Error importing dashboard:",
9989
+ error,
9990
+ );
9991
+ return {
9992
+ success: false,
9993
+ error: error.message,
9982
9994
  };
9995
+ }
9996
+ }
9983
9997
 
9984
- if (
9985
- widgetRegistry &&
9986
- dashboardConfig.widgets &&
9987
- dashboardConfig.widgets.length
9988
- ) {
9989
- const installedWidgets = widgetRegistry.getWidgets();
9990
- const installedPackages = new Set(installedWidgets.map((w) => w.name));
9998
+ /**
9999
+ * Shared import pipeline: install widgets, create workspace, wire events.
10000
+ * Used by both importDashboardConfig (ZIP) and installDashboardFromRegistry.
10001
+ *
10002
+ * @param {BrowserWindow} win - The main window
10003
+ * @param {string} appId - Application identifier
10004
+ * @param {Object} dashboardConfig - Validated dashboard config object
10005
+ * @param {Object} widgetRegistry - WidgetRegistry instance
10006
+ * @param {Object} options - Additional options
10007
+ * @param {string} options.source - Source label ("zip" or "registry")
10008
+ * @returns {Promise<Object>} Result with success, workspace, and summary
10009
+ */
10010
+ async function processDashboardConfig(
10011
+ win,
10012
+ appId,
10013
+ dashboardConfig,
10014
+ widgetRegistry = null,
10015
+ options = {},
10016
+ ) {
10017
+ const source = options.source || "zip";
9991
10018
 
9992
- for (const widgetDep of dashboardConfig.widgets) {
9993
- const packageName = widgetDep.package;
10019
+ // 1. Auto-install missing widgets from registry
10020
+ const installSummary = {
10021
+ installed: [],
10022
+ alreadyInstalled: [],
10023
+ failed: [],
10024
+ };
9994
10025
 
9995
- if (installedPackages.has(packageName)) {
9996
- installSummary.alreadyInstalled.push(packageName);
9997
- continue;
9998
- }
10026
+ if (
10027
+ widgetRegistry &&
10028
+ dashboardConfig.widgets &&
10029
+ dashboardConfig.widgets.length
10030
+ ) {
10031
+ const installedWidgets = widgetRegistry.getWidgets();
10032
+ const installedPackages = new Set(installedWidgets.map((w) => w.name));
9999
10033
 
10000
- // Try to find the widget in the registry and install it
10001
- try {
10002
- const registryPkg = await getPackage(packageName);
10003
- if (registryPkg && registryPkg.downloadUrl) {
10004
- await widgetRegistry.downloadWidget(
10005
- packageName,
10006
- registryPkg.downloadUrl,
10007
- registryPkg.dashConfigUrl || null,
10008
- );
10009
- installSummary.installed.push(packageName);
10010
- installedPackages.add(packageName);
10011
- } else {
10012
- installSummary.failed.push({
10013
- package: packageName,
10014
- reason: "Not found in registry",
10015
- });
10016
- }
10017
- } catch (installError) {
10034
+ for (const widgetDep of dashboardConfig.widgets) {
10035
+ const packageName = widgetDep.package;
10036
+
10037
+ if (installedPackages.has(packageName)) {
10038
+ installSummary.alreadyInstalled.push(packageName);
10039
+ continue;
10040
+ }
10041
+
10042
+ // Try to find the widget in the registry and install it
10043
+ try {
10044
+ const registryPkg = await getPackage(packageName);
10045
+ if (registryPkg && registryPkg.downloadUrl) {
10046
+ await widgetRegistry.downloadWidget(
10047
+ packageName,
10048
+ registryPkg.downloadUrl,
10049
+ registryPkg.dashConfigUrl || null,
10050
+ );
10051
+ installSummary.installed.push(packageName);
10052
+ installedPackages.add(packageName);
10053
+ } else {
10018
10054
  installSummary.failed.push({
10019
10055
  package: packageName,
10020
- reason: installError.message,
10056
+ reason: "Not found in registry",
10021
10057
  });
10022
10058
  }
10059
+ } catch (installError) {
10060
+ installSummary.failed.push({
10061
+ package: packageName,
10062
+ reason: installError.message,
10063
+ });
10023
10064
  }
10024
10065
  }
10066
+ }
10025
10067
 
10026
- // 4. Build workspace from config
10027
- const workspace = dashboardConfig.workspace;
10068
+ // 2. Build workspace from config
10069
+ const workspace = { ...dashboardConfig.workspace };
10028
10070
 
10029
- if (!workspace) {
10071
+ if (!workspace || !workspace.layout) {
10072
+ return {
10073
+ success: false,
10074
+ error: "Dashboard config has no workspace data",
10075
+ };
10076
+ }
10077
+
10078
+ // Generate a unique ID for the imported workspace
10079
+ workspace.id = Date.now();
10080
+
10081
+ // 3. Apply event wiring to layout
10082
+ const eventWiringSummary = [];
10083
+ if (
10084
+ dashboardConfig.eventWiring &&
10085
+ dashboardConfig.eventWiring.length &&
10086
+ workspace.layout
10087
+ ) {
10088
+ applyEventWiringToLayout(workspace.layout, dashboardConfig.eventWiring);
10089
+ for (const wire of dashboardConfig.eventWiring) {
10090
+ eventWiringSummary.push(
10091
+ `${wire.source?.widget}.${wire.source?.event} → ${wire.target?.widget}.${wire.target?.handler}`,
10092
+ );
10093
+ }
10094
+ }
10095
+
10096
+ // 4. Mark as not shareable (imported dashboards cannot be re-published)
10097
+ workspace._dashboardConfig = {
10098
+ shareable: false,
10099
+ source,
10100
+ importedFrom: dashboardConfig.name,
10101
+ importedAt: new Date().toISOString(),
10102
+ originalAuthor: dashboardConfig.author,
10103
+ schemaVersion: dashboardConfig.schemaVersion,
10104
+ };
10105
+
10106
+ // Save workspace to workspaces.json
10107
+ const workspaceController = workspaceController_1;
10108
+ const saveResult = workspaceController.saveWorkspaceForApplication(
10109
+ win,
10110
+ appId,
10111
+ workspace,
10112
+ );
10113
+
10114
+ if (saveResult.error) {
10115
+ return {
10116
+ success: false,
10117
+ error: `Failed to save workspace: ${saveResult.message}`,
10118
+ };
10119
+ }
10120
+
10121
+ // Build provider requirements summary
10122
+ const providerSummary = (dashboardConfig.providers || []).map((p) => ({
10123
+ type: p.type,
10124
+ providerClass: p.providerClass,
10125
+ required: p.required,
10126
+ usedBy: p.usedBy,
10127
+ }));
10128
+
10129
+ console.log(
10130
+ `[DashboardConfigController] Imported dashboard "${dashboardConfig.name}" (${source}) as workspace ${workspace.id}`,
10131
+ );
10132
+
10133
+ return {
10134
+ success: true,
10135
+ workspace,
10136
+ summary: {
10137
+ name: dashboardConfig.name,
10138
+ description: dashboardConfig.description || "",
10139
+ author: dashboardConfig.author,
10140
+ widgets: installSummary,
10141
+ eventsWired: eventWiringSummary,
10142
+ providersRequired: providerSummary,
10143
+ },
10144
+ };
10145
+ }
10146
+
10147
+ /**
10148
+ * Install a dashboard from the registry by package name.
10149
+ *
10150
+ * Fetches the dashboard ZIP from the registry, extracts the .dashboard.json,
10151
+ * validates it, and delegates to the shared import pipeline.
10152
+ *
10153
+ * @param {BrowserWindow} win - The main window
10154
+ * @param {string} appId - Application identifier
10155
+ * @param {string} packageName - Registry package name for the dashboard
10156
+ * @param {Object} widgetRegistry - WidgetRegistry instance
10157
+ * @returns {Promise<Object>} Result with success, workspace, and summary
10158
+ */
10159
+ async function installDashboardFromRegistry$1(
10160
+ win,
10161
+ appId,
10162
+ packageName,
10163
+ widgetRegistry = null,
10164
+ ) {
10165
+ try {
10166
+ // 1. Look up the dashboard package in the registry
10167
+ const registryPkg = await getPackage(packageName);
10168
+ if (!registryPkg) {
10030
10169
  return {
10031
10170
  success: false,
10032
- error: "Dashboard config has no workspace data",
10171
+ error: `Dashboard package not found in registry: ${packageName}`,
10033
10172
  };
10034
10173
  }
10035
10174
 
10036
- // Generate a unique ID for the imported workspace
10037
- workspace.id = Date.now();
10038
-
10039
- // 5. Apply event wiring to layout
10040
- const eventWiringSummary = [];
10041
- if (
10042
- dashboardConfig.eventWiring &&
10043
- dashboardConfig.eventWiring.length &&
10044
- workspace.layout
10045
- ) {
10046
- applyEventWiringToLayout(workspace.layout, dashboardConfig.eventWiring);
10047
- for (const wire of dashboardConfig.eventWiring) {
10048
- eventWiringSummary.push(
10049
- `${wire.source?.widget}.${wire.source?.event} → ${wire.target?.widget}.${wire.target?.handler}`,
10050
- );
10051
- }
10175
+ if (!registryPkg.downloadUrl) {
10176
+ return {
10177
+ success: false,
10178
+ error: `Dashboard package has no download URL: ${packageName}`,
10179
+ };
10052
10180
  }
10053
10181
 
10054
- // 6. Mark as not shareable (imported dashboards cannot be re-published)
10055
- workspace._dashboardConfig = {
10056
- shareable: false,
10057
- importedFrom: dashboardConfig.name,
10058
- importedAt: new Date().toISOString(),
10059
- originalAuthor: dashboardConfig.author,
10060
- schemaVersion: dashboardConfig.schemaVersion,
10061
- };
10182
+ // 2. Resolve the download URL and fetch the ZIP
10183
+ const version = registryPkg.version || "1.0.0";
10184
+ let downloadUrl = registryPkg.downloadUrl;
10185
+ downloadUrl = downloadUrl.replace("{version}", version);
10186
+ downloadUrl = downloadUrl.replace("{name}", packageName);
10062
10187
 
10063
- // Save workspace to workspaces.json
10064
- const workspaceController = workspaceController_1;
10065
- const saveResult = workspaceController.saveWorkspaceForApplication(
10066
- win,
10067
- appId,
10068
- workspace,
10188
+ // Enforce HTTPS
10189
+ const parsedUrl = new URL(downloadUrl);
10190
+ if (parsedUrl.protocol !== "https:") {
10191
+ return {
10192
+ success: false,
10193
+ error: `Dashboard downloads must use HTTPS. Refusing: ${downloadUrl}`,
10194
+ };
10195
+ }
10196
+
10197
+ console.log(
10198
+ `[DashboardConfigController] Fetching dashboard from: ${downloadUrl}`,
10069
10199
  );
10070
10200
 
10071
- if (saveResult.error) {
10201
+ const response = await fetch(downloadUrl);
10202
+ if (!response.ok) {
10072
10203
  return {
10073
10204
  success: false,
10074
- error: `Failed to save workspace: ${saveResult.message}`,
10205
+ error: `Failed to download dashboard: ${response.status} ${response.statusText}`,
10075
10206
  };
10076
10207
  }
10077
10208
 
10078
- // Build provider requirements summary
10079
- const providerSummary = (dashboardConfig.providers || []).map((p) => ({
10080
- type: p.type,
10081
- providerClass: p.providerClass,
10082
- required: p.required,
10083
- usedBy: p.usedBy,
10084
- }));
10209
+ const buffer = await response.arrayBuffer();
10210
+ const zip = new AdmZip(Buffer.from(buffer));
10085
10211
 
10086
- console.log(
10087
- `[DashboardConfigController] Imported dashboard "${dashboardConfig.name}" as workspace ${workspace.id}`,
10212
+ // 3. Validate ZIP entries
10213
+ const tempDir = path.join(app.getPath("temp"), "dash-registry-import");
10214
+ const { validateZipEntries } = widgetRegistryExports;
10215
+ validateZipEntries(zip, tempDir);
10216
+
10217
+ // 4. Find and parse .dashboard.json
10218
+ const entries = zip.getEntries();
10219
+ const configEntry = entries.find((e) =>
10220
+ e.entryName.endsWith(".dashboard.json"),
10088
10221
  );
10089
10222
 
10090
- return {
10091
- success: true,
10092
- workspace,
10093
- summary: {
10094
- name: dashboardConfig.name,
10095
- description: dashboardConfig.description || "",
10096
- author: dashboardConfig.author,
10097
- widgets: installSummary,
10098
- eventsWired: eventWiringSummary,
10099
- providersRequired: providerSummary,
10223
+ if (!configEntry) {
10224
+ return {
10225
+ success: false,
10226
+ error: "No .dashboard.json file found in downloaded archive",
10227
+ };
10228
+ }
10229
+
10230
+ const configJson = configEntry.getData().toString("utf-8");
10231
+ let dashboardConfig;
10232
+ try {
10233
+ dashboardConfig = JSON.parse(configJson);
10234
+ } catch (parseError) {
10235
+ return {
10236
+ success: false,
10237
+ error: `Invalid JSON in dashboard config: ${parseError.message}`,
10238
+ };
10239
+ }
10240
+
10241
+ // 5. Validate against schema
10242
+ const validation = validateDashboardConfig(dashboardConfig);
10243
+ if (!validation.valid) {
10244
+ return {
10245
+ success: false,
10246
+ error: `Invalid dashboard config: ${validation.errors.join(", ")}`,
10247
+ };
10248
+ }
10249
+
10250
+ dashboardConfig = applyDefaults(dashboardConfig);
10251
+
10252
+ // 6. Delegate to shared import pipeline
10253
+ return await processDashboardConfig(
10254
+ win,
10255
+ appId,
10256
+ dashboardConfig,
10257
+ widgetRegistry,
10258
+ {
10259
+ source: "registry",
10100
10260
  },
10101
- };
10261
+ );
10102
10262
  } catch (error) {
10103
10263
  console.error(
10104
- "[DashboardConfigController] Error importing dashboard:",
10264
+ "[DashboardConfigController] Error installing dashboard from registry:",
10105
10265
  error,
10106
10266
  );
10107
10267
  return {
@@ -10114,6 +10274,7 @@ async function importDashboardConfig$1(win, appId, widgetRegistry = null) {
10114
10274
  var dashboardConfigController$1 = {
10115
10275
  exportDashboardConfig: exportDashboardConfig$1,
10116
10276
  importDashboardConfig: importDashboardConfig$1,
10277
+ installDashboardFromRegistry: installDashboardFromRegistry$1,
10117
10278
  };
10118
10279
 
10119
10280
  /**
@@ -10205,6 +10366,7 @@ const { install: pluginInstall } = pluginController_1;
10205
10366
  const {
10206
10367
  exportDashboardConfig,
10207
10368
  importDashboardConfig,
10369
+ installDashboardFromRegistry,
10208
10370
  } = dashboardConfigController$1;
10209
10371
 
10210
10372
  var controller = {
@@ -10250,6 +10412,7 @@ var controller = {
10250
10412
  searchIndex,
10251
10413
  exportDashboardConfig,
10252
10414
  importDashboardConfig,
10415
+ installDashboardFromRegistry,
10253
10416
  };
10254
10417
 
10255
10418
  const { ipcRenderer: ipcRenderer$i } = require$$0$1;
@@ -11626,36 +11789,52 @@ var clientCacheApi_1 = clientCacheApi$2;
11626
11789
 
11627
11790
  const { ipcRenderer: ipcRenderer$1 } = require$$0$1;
11628
11791
  const {
11629
- DASHBOARD_CONFIG_EXPORT,
11630
- DASHBOARD_CONFIG_IMPORT,
11792
+ DASHBOARD_CONFIG_EXPORT,
11793
+ DASHBOARD_CONFIG_IMPORT,
11794
+ DASHBOARD_CONFIG_INSTALL,
11631
11795
  } = events$8;
11632
11796
 
11633
11797
  const dashboardConfigApi$2 = {
11634
- /**
11635
- * Export a workspace as a dashboard config ZIP file.
11636
- *
11637
- * @param {string} appId - Application identifier
11638
- * @param {number|string} workspaceId - ID of the workspace to export
11639
- * @param {Object} options - Export options (authorName, authorId, description, tags, icon)
11640
- * @returns {Promise<Object>} Result with success, filePath, and config
11641
- */
11642
- exportDashboardConfig: (appId, workspaceId, options = {}) =>
11643
- ipcRenderer$1.invoke(DASHBOARD_CONFIG_EXPORT, {
11644
- appId,
11645
- workspaceId,
11646
- options,
11647
- }),
11648
-
11649
- /**
11650
- * Import a dashboard config from a ZIP file.
11651
- * Shows a file picker, validates the config, installs missing widgets,
11652
- * creates the workspace, and applies event wiring.
11653
- *
11654
- * @param {string} appId - Application identifier
11655
- * @returns {Promise<Object>} Result with success, workspace, and summary
11656
- */
11657
- importDashboardConfig: (appId) =>
11658
- ipcRenderer$1.invoke(DASHBOARD_CONFIG_IMPORT, { appId }),
11798
+ /**
11799
+ * Export a workspace as a dashboard config ZIP file.
11800
+ *
11801
+ * @param {string} appId - Application identifier
11802
+ * @param {number|string} workspaceId - ID of the workspace to export
11803
+ * @param {Object} options - Export options (authorName, authorId, description, tags, icon)
11804
+ * @returns {Promise<Object>} Result with success, filePath, and config
11805
+ */
11806
+ exportDashboardConfig: (appId, workspaceId, options = {}) =>
11807
+ ipcRenderer$1.invoke(DASHBOARD_CONFIG_EXPORT, {
11808
+ appId,
11809
+ workspaceId,
11810
+ options,
11811
+ }),
11812
+
11813
+ /**
11814
+ * Import a dashboard config from a ZIP file.
11815
+ * Shows a file picker, validates the config, installs missing widgets,
11816
+ * creates the workspace, and applies event wiring.
11817
+ *
11818
+ * @param {string} appId - Application identifier
11819
+ * @returns {Promise<Object>} Result with success, workspace, and summary
11820
+ */
11821
+ importDashboardConfig: (appId) =>
11822
+ ipcRenderer$1.invoke(DASHBOARD_CONFIG_IMPORT, { appId }),
11823
+
11824
+ /**
11825
+ * Install a dashboard from the registry by package name.
11826
+ * Fetches the dashboard ZIP, validates config, installs widgets,
11827
+ * creates workspace, and applies event wiring.
11828
+ *
11829
+ * @param {string} appId - Application identifier
11830
+ * @param {string} packageName - Registry package name
11831
+ * @returns {Promise<Object>} Result with success, workspace, and summary
11832
+ */
11833
+ installDashboardFromRegistry: (appId, packageName) =>
11834
+ ipcRenderer$1.invoke(DASHBOARD_CONFIG_INSTALL, {
11835
+ appId,
11836
+ packageName,
11837
+ }),
11659
11838
  };
11660
11839
 
11661
11840
  var dashboardConfigApi_1 = dashboardConfigApi$2;