snow-flow 2.0.2 → 2.0.3

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.
Files changed (173) hide show
  1. package/{.claude/mcp-config.json → .mcp.json} +13 -12
  2. package/.releaserc.json +47 -0
  3. package/README.md +6 -0
  4. package/package.json +1 -1
  5. package/.claude/claude-settings.json +0 -15
  6. package/.claude/commands/analysis/README.md +0 -9
  7. package/.claude/commands/automation/README.md +0 -9
  8. package/.claude/commands/github/README.md +0 -11
  9. package/.claude/commands/helpers/standard-checkpoint-hooks.sh +0 -179
  10. package/.claude/commands/hooks/README.md +0 -11
  11. package/.claude/commands/monitoring/README.md +0 -9
  12. package/.claude/commands/optimization/README.md +0 -9
  13. package/.claude/commands/training/README.md +0 -9
  14. package/.claude/commands/workflows/README.md +0 -9
  15. package/.claude/config.json +0 -36
  16. package/.claude/helpers/github-setup.sh +0 -28
  17. package/.claude/helpers/quick-start.sh +0 -19
  18. package/.claude/helpers/setup-mcp.sh +0 -18
  19. package/.claude/settings.json +0 -114
  20. package/.claude/settings.local.json +0 -33
  21. package/.claude-flow/metrics/agent-metrics.json +0 -1
  22. package/.claude-flow/metrics/performance.json +0 -9
  23. package/.claude-flow/metrics/system-metrics.json +0 -15986
  24. package/.claude-flow/metrics/task-metrics.json +0 -10
  25. package/.roo/README.md +0 -402
  26. package/.roo/mcp-list.txt +0 -257
  27. package/.roo/mcp.json +0 -32
  28. package/.roo/workflows/basic-tdd.json +0 -32
  29. package/.roomodes +0 -201
  30. package/CLAUDE.md +0 -393
  31. package/bin/snow-flow.js +0 -3
  32. package/dist/cli.js.bak +0 -2052
  33. package/dist/config/snow-flow-config.d.ts +0 -1492
  34. package/dist/config/snow-flow-config.js +0 -938
  35. package/scripts/create-env.js +0 -91
  36. package/scripts/postinstall.js +0 -30
  37. package/scripts/register-mcp-servers.js +0 -210
  38. package/scripts/reset-mcp-servers.js +0 -246
  39. package/scripts/setup-mcp.js +0 -106
  40. package/scripts/update-version.js +0 -31
  41. package/src/agents/README.md +0 -192
  42. package/src/agents/base-agent.ts +0 -251
  43. package/src/agents/coordinator.ts +0 -971
  44. package/src/agents/index.ts +0 -18
  45. package/src/agents/queen-403-handler.ts +0 -230
  46. package/src/agents/queen-agent.ts +0 -1399
  47. package/src/agents/script-writer-agent.ts +0 -769
  48. package/src/agents/security-agent.ts +0 -726
  49. package/src/agents/widget-creator-agent.ts +0 -568
  50. package/src/api/error-handling.ts +0 -1758
  51. package/src/api/natural-language-mapper.ts +0 -630
  52. package/src/api/performance-optimizer.ts +0 -891
  53. package/src/api/transaction-manager.ts +0 -754
  54. package/src/cli/deploy-artifact.ts +0 -304
  55. package/src/cli/snow-flow-cli-integration.ts +0 -434
  56. package/src/cli.ts +0 -4455
  57. package/src/cli.ts.bak +0 -5616
  58. package/src/compliance/advanced-compliance-system.ts +0 -1268
  59. package/src/compliance/index.ts +0 -19
  60. package/src/config/snow-flow-config.ts +0 -1019
  61. package/src/documentation/index.ts +0 -16
  62. package/src/documentation/self-documenting-system.ts +0 -1421
  63. package/src/dynamic-version.ts +0 -44
  64. package/src/healing/index.ts +0 -20
  65. package/src/healing/self-healing-system.ts +0 -1435
  66. package/src/health/README.md +0 -161
  67. package/src/health/system-health.ts +0 -995
  68. package/src/index.ts +0 -60
  69. package/src/intelligence/acl-analyzer.ts +0 -377
  70. package/src/intelligence/auto-resolution-engine.ts +0 -643
  71. package/src/intelligence/gap-analysis-engine.ts +0 -488
  72. package/src/intelligence/manual-instructions-generator.ts +0 -744
  73. package/src/intelligence/mcp-coverage-analyzer.ts +0 -708
  74. package/src/intelligence/multi-pass-requirements-analyzer.ts +0 -603
  75. package/src/intelligence/performance-recommendations-engine.ts +0 -1223
  76. package/src/intelligence/requirements-analyzer.ts +0 -1148
  77. package/src/intelligence/task-analyzer.ts +0 -628
  78. package/src/managers/scope-manager.ts +0 -555
  79. package/src/mcp/advanced/servicenow-advanced-features-mcp.ts +0 -16110
  80. package/src/mcp/base-mcp-server.ts +0 -660
  81. package/src/mcp/http-transport-wrapper.ts +0 -426
  82. package/src/mcp/service-discovery-client.ts +0 -312
  83. package/src/mcp/servicenow-automation-mcp-refactored.ts +0 -678
  84. package/src/mcp/servicenow-automation-mcp.ts +0 -838
  85. package/src/mcp/servicenow-deployment-mcp-refactored.ts +0 -1292
  86. package/src/mcp/servicenow-deployment-mcp.ts +0 -7244
  87. package/src/mcp/servicenow-graph-memory-mcp-refactored.ts +0 -721
  88. package/src/mcp/servicenow-graph-memory-mcp.ts +0 -832
  89. package/src/mcp/servicenow-integration-mcp-refactored.ts +0 -655
  90. package/src/mcp/servicenow-integration-mcp.ts +0 -819
  91. package/src/mcp/servicenow-intelligent-mcp-refactored.ts +0 -977
  92. package/src/mcp/servicenow-intelligent-mcp.ts +0 -4638
  93. package/src/mcp/servicenow-mcp-server.ts +0 -728
  94. package/src/mcp/servicenow-memory-mcp.ts +0 -474
  95. package/src/mcp/servicenow-operations-mcp-refactored.ts +0 -1995
  96. package/src/mcp/servicenow-operations-mcp.ts +0 -3384
  97. package/src/mcp/servicenow-platform-development-mcp-refactored.ts +0 -611
  98. package/src/mcp/servicenow-platform-development-mcp.ts +0 -970
  99. package/src/mcp/servicenow-progressive-indexer.ts +0 -370
  100. package/src/mcp/servicenow-reporting-analytics-mcp-refactored.ts +0 -695
  101. package/src/mcp/servicenow-reporting-analytics-mcp.ts +0 -1123
  102. package/src/mcp/servicenow-security-compliance-mcp-refactored.ts +0 -642
  103. package/src/mcp/servicenow-security-compliance-mcp.ts +0 -1157
  104. package/src/mcp/servicenow-update-set-mcp-refactored.ts +0 -751
  105. package/src/mcp/servicenow-update-set-mcp.ts +0 -811
  106. package/src/mcp/shared/agent-context-provider.ts +0 -365
  107. package/src/mcp/shared/base-mcp-server.ts +0 -2463
  108. package/src/mcp/shared/mcp-memory-manager.ts +0 -540
  109. package/src/mcp/shared/mcp-resource-manager.ts +0 -367
  110. package/src/mcp/snow-flow-mcp.ts +0 -911
  111. package/src/mcp/start-all-mcp-servers.ts +0 -102
  112. package/src/mcp/start-servicenow-mcp.ts +0 -67
  113. package/src/monitoring/enhanced-monitoring-system.ts +0 -1345
  114. package/src/monitoring/performance-tracker.ts +0 -777
  115. package/src/optimization/cost-optimization-engine.ts +0 -1125
  116. package/src/optimization/index.ts +0 -10
  117. package/src/queen/README.md +0 -403
  118. package/src/queen/agent-factory.ts +0 -574
  119. package/src/queen/index.ts +0 -94
  120. package/src/queen/mcp-execution-bridge.ts +0 -681
  121. package/src/queen/neural-learning.ts +0 -319
  122. package/src/queen/parallel-agent-engine.ts +0 -848
  123. package/src/queen/queen-memory-system.ts +0 -490
  124. package/src/queen/queen-memory.ts +0 -456
  125. package/src/queen/servicenow-queen.ts +0 -1029
  126. package/src/queen/types.ts +0 -87
  127. package/src/rollback/smart-rollback-system.ts +0 -866
  128. package/src/schemas/deployment.schema.json +0 -58
  129. package/src/schemas/flow.schema.json +0 -79
  130. package/src/schemas/widget.schema.json +0 -72
  131. package/src/snow-flow-system.ts +0 -541
  132. package/src/sparc/sparc-help.ts +0 -49
  133. package/src/sparc/team-sparc.ts +0 -88
  134. package/src/strategies/global-scope-strategy.ts +0 -736
  135. package/src/templates/base/application.template.json +0 -45
  136. package/src/templates/base/business_rule.template.json +0 -33
  137. package/src/templates/base/script_include.template.json +0 -18
  138. package/src/templates/base/table.template.json +0 -64
  139. package/src/templates/base/widget.template.json +0 -25
  140. package/src/templates/patterns/composite.incident-management.template.json +0 -140
  141. package/src/templates/patterns/widget.dashboard.template.json +0 -238
  142. package/src/templates/patterns/widget.datatable.template.json +0 -292
  143. package/src/testing/integration-test-suite.ts +0 -1276
  144. package/src/types/index.ts +0 -183
  145. package/src/types/neo4j-driver.d.ts +0 -23
  146. package/src/types/servicenow.types.ts +0 -307
  147. package/src/types/snow-flow.types.ts +0 -186
  148. package/src/types/todo.types.ts +0 -70
  149. package/src/utils/action-type-cache.ts +0 -286
  150. package/src/utils/agent-detector.ts +0 -573
  151. package/src/utils/artifact-tracker.ts +0 -379
  152. package/src/utils/dependency-detector.ts +0 -233
  153. package/src/utils/deployment-metadata-handler.ts +0 -518
  154. package/src/utils/error-recovery.ts +0 -680
  155. package/src/utils/logger.ts +0 -60
  156. package/src/utils/mcp-auth-middleware.ts +0 -262
  157. package/src/utils/mcp-config-manager.ts +0 -270
  158. package/src/utils/mcp-server-manager.ts +0 -446
  159. package/src/utils/mcp-tool-registry.ts +0 -298
  160. package/src/utils/migrate-snow-flow.ts +0 -98
  161. package/src/utils/oauth-html-templates.ts +0 -188
  162. package/src/utils/scope-utils.ts +0 -443
  163. package/src/utils/servicenow-client.ts +0 -3321
  164. package/src/utils/servicenow-id-generator.ts +0 -181
  165. package/src/utils/snow-memory-manager.ts +0 -605
  166. package/src/utils/snow-oauth.ts +0 -797
  167. package/src/utils/template-engine.ts +0 -463
  168. package/src/utils/theme-manager.ts +0 -295
  169. package/src/utils/unified-auth-store.ts +0 -222
  170. package/src/utils/update-set-importer.ts +0 -422
  171. package/src/utils/update-set-xml-packager.ts +0 -371
  172. package/src/utils/widget-template-generator.ts +0 -1744
  173. package/src/version.ts +0 -1950
@@ -1,1744 +0,0 @@
1
- /**
2
- * ServiceNow Widget Template Generator
3
- * Generates functional ServiceNow Service Portal widget templates based on requirements
4
- */
5
-
6
- export interface WidgetTemplateOptions {
7
- title?: string;
8
- instruction?: string;
9
- type?: 'dashboard' | 'chart' | 'table' | 'form' | 'info' | 'auto';
10
- theme?: 'default' | 'dark' | 'minimal';
11
- responsive?: boolean;
12
- }
13
-
14
- export interface WidgetComponents {
15
- template: string;
16
- css: string;
17
- serverScript: string;
18
- clientScript: string;
19
- optionSchema: string;
20
- }
21
-
22
- export class ServiceNowWidgetTemplateGenerator {
23
-
24
- /**
25
- * Generate complete widget components based on instruction
26
- */
27
- generateWidget(options: WidgetTemplateOptions): WidgetComponents {
28
- const { instruction = '', title = 'Widget', type = 'auto' } = options;
29
- const detectedType = type === 'auto' ? this.detectWidgetType(instruction) : type;
30
-
31
- const widgetTitle = title || this.extractTitleFromInstruction(instruction);
32
-
33
- return {
34
- template: this.generateTemplate(detectedType, widgetTitle, instruction),
35
- css: this.generateCss(detectedType, options),
36
- serverScript: this.generateServerScript(detectedType, instruction),
37
- clientScript: this.generateClientScript(detectedType, instruction),
38
- optionSchema: this.generateOptionSchema(detectedType)
39
- };
40
- }
41
-
42
- /**
43
- * Detect widget type from instruction
44
- */
45
- private detectWidgetType(instruction: string): string {
46
- const lower = instruction.toLowerCase();
47
-
48
- if (lower.includes('chart') || lower.includes('graph') || lower.includes('analytics')) {
49
- return 'chart';
50
- }
51
- if (lower.includes('dashboard') || lower.includes('metrics') || lower.includes('kpi')) {
52
- return 'dashboard';
53
- }
54
- if (lower.includes('table') || lower.includes('list') || lower.includes('records')) {
55
- return 'table';
56
- }
57
- if (lower.includes('form') || lower.includes('input') || lower.includes('create') || lower.includes('submit')) {
58
- return 'form';
59
- }
60
-
61
- return 'info'; // Default to info card widget
62
- }
63
-
64
- /**
65
- * Extract widget title from instruction
66
- */
67
- private extractTitleFromInstruction(instruction: string): string {
68
- // Simple extraction - look for patterns like "create X widget" or "X dashboard"
69
- const patterns = [
70
- /create\s+(.+?)\s+widget/i,
71
- /(.+?)\s+dashboard/i,
72
- /(.+?)\s+chart/i,
73
- /(.+?)\s+table/i,
74
- /(.+?)\s+form/i
75
- ];
76
-
77
- for (const pattern of patterns) {
78
- const match = instruction.match(pattern);
79
- if (match) {
80
- return match[1].replace(/\b\w/g, l => l.toUpperCase());
81
- }
82
- }
83
-
84
- // Fallback: use first few words
85
- const words = instruction.split(' ').slice(0, 3);
86
- return words.map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
87
- }
88
-
89
- /**
90
- * Generate HTML template based on widget type
91
- */
92
- private generateTemplate(type: string, title: string, instruction: string): string {
93
- switch (type) {
94
- case 'chart':
95
- return this.generateChartTemplate(title);
96
- case 'dashboard':
97
- return this.generateDashboardTemplate(title);
98
- case 'table':
99
- return this.generateTableTemplate(title);
100
- case 'form':
101
- return this.generateFormTemplate(title);
102
- case 'info':
103
- default:
104
- return this.generateInfoTemplate(title, instruction);
105
- }
106
- }
107
-
108
- /**
109
- * Chart widget template with Chart.js integration
110
- */
111
- private generateChartTemplate(title: string): string {
112
- return `
113
- <div class="panel panel-default widget-chart">
114
- <div class="panel-heading">
115
- <h3 class="panel-title">{{data.title || '${title}'}}</h3>
116
- <div class="panel-actions" ng-if="options.show_refresh">
117
- <button class="btn btn-sm btn-default" ng-click="c.refreshData()" title="Refresh Chart">
118
- <i class="fa fa-refresh" ng-class="{'fa-spin': data.loading}"></i>
119
- </button>
120
- </div>
121
- </div>
122
- <div class="panel-body">
123
- <div ng-if="data.loading" class="text-center loading-state">
124
- <i class="fa fa-spinner fa-spin fa-2x"></i>
125
- <p class="text-muted">Loading chart data...</p>
126
- </div>
127
-
128
- <div ng-if="!data.loading && !data.error" class="chart-container">
129
- <canvas id="chart-{{::widget.id}}" width="400" height="300"></canvas>
130
-
131
- <!-- Chart Legend -->
132
- <div ng-if="data.chart_legend" class="chart-legend">
133
- <div class="legend-item" ng-repeat="item in data.chart_legend">
134
- <span class="legend-color" style="background-color: {{item.color}}"></span>
135
- <span class="legend-label">{{item.label}}: {{item.value}}</span>
136
- </div>
137
- </div>
138
- </div>
139
-
140
- <div ng-if="data.error" class="alert alert-danger">
141
- <i class="fa fa-exclamation-triangle"></i>
142
- <strong>Chart Error:</strong> {{data.error}}
143
- </div>
144
-
145
- <div ng-if="!data.loading && !data.error && (!data.chart_data || data.chart_data.length === 0)" class="empty-state text-center">
146
- <i class="fa fa-bar-chart fa-3x text-muted"></i>
147
- <h4>No Data Available</h4>
148
- <p class="text-muted">{{data.empty_message || 'No chart data to display'}}</p>
149
- </div>
150
- </div>
151
- </div>`.trim();
152
- }
153
-
154
- /**
155
- * Dashboard widget template with metrics
156
- */
157
- private generateDashboardTemplate(title: string): string {
158
- return `
159
- <div class="widget-dashboard">
160
- <div class="panel panel-default">
161
- <div class="panel-heading">
162
- <h3 class="panel-title">{{data.title || '${title}'}}</h3>
163
- <span class="panel-subtitle" ng-if="data.last_updated">
164
- Last updated: {{data.last_updated | date:'medium'}}
165
- </span>
166
- </div>
167
- <div class="panel-body">
168
- <div ng-if="data.loading" class="text-center loading-state">
169
- <i class="fa fa-spinner fa-spin fa-2x"></i>
170
- <p class="text-muted">Loading dashboard data...</p>
171
- </div>
172
-
173
- <div ng-if="!data.loading && data.metrics && data.metrics.length > 0" class="metrics-grid">
174
- <div class="metric-item" ng-repeat="metric in data.metrics track by $index"
175
- ng-class="'metric-col-' + (12/data.metrics.length)">
176
- <div class="metric-card" ng-class="{
177
- 'metric-critical': metric.status === 'critical',
178
- 'metric-warning': metric.status === 'warning',
179
- 'metric-success': metric.status === 'success',
180
- 'metric-info': metric.status === 'info'
181
- }">
182
- <div class="metric-icon" ng-if="metric.icon">
183
- <i class="fa fa-{{metric.icon}}"></i>
184
- </div>
185
- <h4 class="metric-label">{{metric.label}}</h4>
186
- <div class="metric-value-container">
187
- <h2 class="metric-value">
188
- {{metric.value}}
189
- <small ng-if="metric.unit" class="metric-unit">{{metric.unit}}</small>
190
- </h2>
191
- <div class="metric-trend" ng-if="metric.trend !== undefined">
192
- <i class="fa" ng-class="{
193
- 'fa-arrow-up text-success': metric.trend > 0,
194
- 'fa-arrow-down text-danger': metric.trend < 0,
195
- 'fa-minus text-muted': metric.trend === 0
196
- }"></i>
197
- <span class="trend-text">{{metric.trend_text || (metric.trend > 0 ? '+' + metric.trend + '%' : metric.trend + '%')}}</span>
198
- </div>
199
- </div>
200
- <div class="metric-description" ng-if="metric.description">
201
- {{metric.description}}
202
- </div>
203
- </div>
204
- </div>
205
- </div>
206
-
207
- <div ng-if="!data.loading && (!data.metrics || data.metrics.length === 0)" class="empty-state text-center">
208
- <i class="fa fa-dashboard fa-3x text-muted"></i>
209
- <h4>No Metrics Available</h4>
210
- <p class="text-muted">{{data.empty_message || 'Dashboard metrics will appear here'}}</p>
211
- </div>
212
- </div>
213
- </div>
214
- </div>`.trim();
215
- }
216
-
217
- /**
218
- * Table widget template with sorting and filtering
219
- */
220
- private generateTableTemplate(title: string): string {
221
- return `
222
- <div class="panel panel-default widget-table">
223
- <div class="panel-heading">
224
- <h3 class="panel-title">{{data.title || '${title}'}}</h3>
225
- <div class="panel-actions">
226
- <div class="input-group input-group-sm search-container" ng-if="options.enable_search">
227
- <input type="text" class="form-control" placeholder="Search records..."
228
- ng-model="c.searchTerm" ng-change="c.onSearchChange()">
229
- <span class="input-group-btn">
230
- <button class="btn btn-default" ng-click="c.clearSearch()" ng-if="c.searchTerm">
231
- <i class="fa fa-times"></i>
232
- </button>
233
- </span>
234
- </div>
235
- <button class="btn btn-sm btn-primary" ng-click="c.refreshData()" title="Refresh Data">
236
- <i class="fa fa-refresh" ng-class="{'fa-spin': data.loading}"></i>
237
- </button>
238
- </div>
239
- </div>
240
- <div class="panel-body">
241
- <div ng-if="data.loading" class="text-center loading-state">
242
- <i class="fa fa-spinner fa-spin fa-2x"></i>
243
- <p class="text-muted">Loading records...</p>
244
- </div>
245
-
246
- <div ng-if="!data.loading && data.records && data.records.length > 0" class="table-container">
247
- <table class="table table-striped table-hover table-responsive">
248
- <thead>
249
- <tr>
250
- <th ng-repeat="column in data.columns"
251
- ng-click="c.sortBy(column.field)"
252
- class="sortable-header"
253
- ng-class="{'sorted': c.sortField === column.field}">
254
- {{column.label}}
255
- <span class="sort-indicator">
256
- <i class="fa fa-sort" ng-if="c.sortField !== column.field"></i>
257
- <i class="fa fa-sort-up" ng-if="c.sortField === column.field && !c.sortReverse"></i>
258
- <i class="fa fa-sort-down" ng-if="c.sortField === column.field && c.sortReverse"></i>
259
- </span>
260
- </th>
261
- <th ng-if="options.show_actions" class="actions-column">Actions</th>
262
- </tr>
263
- </thead>
264
- <tbody>
265
- <tr ng-repeat="record in data.records | filter:c.searchTerm | orderBy:c.sortField:c.sortReverse | limitTo:options.page_size track by record.sys_id"
266
- ng-class="{'row-highlighted': record.highlighted}">
267
- <td ng-repeat="column in data.columns" ng-class="'column-' + column.type">
268
- <!-- Link fields -->
269
- <a ng-if="column.type === 'link'"
270
- href="{{record[column.field + '_link'] || '#'}}"
271
- target="_blank"
272
- class="record-link">
273
- {{record[column.field] || '-'}}
274
- </a>
275
-
276
- <!-- Date fields -->
277
- <span ng-if="column.type === 'date'">
278
- {{record[column.field] | date:'short'}}
279
- </span>
280
-
281
- <!-- Status/State fields -->
282
- <span ng-if="column.type === 'status'"
283
- class="label"
284
- ng-class="'label-' + (record[column.field + '_class'] || 'default')">
285
- {{record[column.field] || '-'}}
286
- </span>
287
-
288
- <!-- Default text fields -->
289
- <span ng-if="!column.type || (column.type !== 'link' && column.type !== 'date' && column.type !== 'status')">
290
- {{record[column.field] || '-'}}
291
- </span>
292
- </td>
293
-
294
- <td ng-if="options.show_actions" class="actions-column">
295
- <button class="btn btn-xs btn-default" ng-click="c.viewRecord(record)" title="View">
296
- <i class="fa fa-eye"></i>
297
- </button>
298
- <button class="btn btn-xs btn-primary" ng-click="c.editRecord(record)" title="Edit" ng-if="options.allow_edit">
299
- <i class="fa fa-edit"></i>
300
- </button>
301
- </td>
302
- </tr>
303
- </tbody>
304
- </table>
305
-
306
- <!-- Pagination -->
307
- <div ng-if="data.total_records > options.page_size" class="table-pagination text-center">
308
- <button class="btn btn-sm btn-default" ng-click="c.previousPage()" ng-disabled="c.currentPage <= 1">
309
- <i class="fa fa-chevron-left"></i> Previous
310
- </button>
311
- <span class="pagination-info">
312
- Page {{c.currentPage}} of {{c.totalPages}} ({{data.total_records}} total)
313
- </span>
314
- <button class="btn btn-sm btn-default" ng-click="c.nextPage()" ng-disabled="c.currentPage >= c.totalPages">
315
- Next <i class="fa fa-chevron-right"></i>
316
- </button>
317
- </div>
318
- </div>
319
-
320
- <div ng-if="!data.loading && (!data.records || data.records.length === 0)" class="empty-state text-center">
321
- <i class="fa fa-table fa-3x text-muted"></i>
322
- <h4>No Records Found</h4>
323
- <p class="text-muted">{{data.empty_message || 'No records match your criteria'}}</p>
324
- </div>
325
- </div>
326
- </div>`.trim();
327
- }
328
-
329
- /**
330
- * Form widget template with validation
331
- */
332
- private generateFormTemplate(title: string): string {
333
- return `
334
- <div class="panel panel-default widget-form">
335
- <div class="panel-heading">
336
- <h3 class="panel-title">{{data.title || '${title}'}}</h3>
337
- </div>
338
- <div class="panel-body">
339
- <div ng-if="data.success_message" class="alert alert-success alert-dismissible">
340
- <button type="button" class="close" ng-click="data.success_message = null">
341
- <span>&times;</span>
342
- </button>
343
- <i class="fa fa-check-circle"></i> {{data.success_message}}
344
- </div>
345
-
346
- <div ng-if="data.error_message" class="alert alert-danger alert-dismissible">
347
- <button type="button" class="close" ng-click="data.error_message = null">
348
- <span>&times;</span>
349
- </button>
350
- <i class="fa fa-exclamation-triangle"></i> {{data.error_message}}
351
- </div>
352
-
353
- <form name="widgetForm" ng-submit="c.submitForm()" novalidate class="widget-form">
354
- <div class="form-group" ng-repeat="field in data.form_fields" ng-class="{'has-error': widgetForm[field.name].$invalid && widgetForm[field.name].$touched}">
355
- <label class="control-label" for="{{field.name}}">
356
- {{field.label}}
357
- <span class="text-danger" ng-if="field.required">*</span>
358
- <span class="help-icon" ng-if="field.help_text" title="{{field.help_text}}">
359
- <i class="fa fa-question-circle"></i>
360
- </span>
361
- </label>
362
-
363
- <!-- Text Input -->
364
- <input ng-if="field.type === 'text' || !field.type"
365
- type="text"
366
- class="form-control"
367
- id="{{field.name}}"
368
- name="{{field.name}}"
369
- ng-model="c.formData[field.name]"
370
- ng-required="field.required"
371
- placeholder="{{field.placeholder}}"
372
- maxlength="{{field.max_length}}">
373
-
374
- <!-- Email Input -->
375
- <input ng-if="field.type === 'email'"
376
- type="email"
377
- class="form-control"
378
- id="{{field.name}}"
379
- name="{{field.name}}"
380
- ng-model="c.formData[field.name]"
381
- ng-required="field.required"
382
- placeholder="{{field.placeholder}}">
383
-
384
- <!-- Textarea -->
385
- <textarea ng-if="field.type === 'textarea'"
386
- class="form-control"
387
- id="{{field.name}}"
388
- name="{{field.name}}"
389
- ng-model="c.formData[field.name]"
390
- ng-required="field.required"
391
- placeholder="{{field.placeholder}}"
392
- rows="{{field.rows || 3}}"
393
- maxlength="{{field.max_length}}"></textarea>
394
-
395
- <!-- Select Dropdown -->
396
- <select ng-if="field.type === 'select'"
397
- class="form-control"
398
- id="{{field.name}}"
399
- name="{{field.name}}"
400
- ng-model="c.formData[field.name]"
401
- ng-required="field.required">
402
- <option value="">{{field.placeholder || 'Select ' + field.label}}</option>
403
- <option ng-repeat="option in field.options" value="{{option.value}}">{{option.label}}</option>
404
- </select>
405
-
406
- <!-- Checkbox -->
407
- <div ng-if="field.type === 'checkbox'" class="checkbox">
408
- <label>
409
- <input type="checkbox"
410
- id="{{field.name}}"
411
- name="{{field.name}}"
412
- ng-model="c.formData[field.name]"
413
- ng-required="field.required">
414
- {{field.checkbox_label || field.label}}
415
- </label>
416
- </div>
417
-
418
- <!-- Date Input -->
419
- <input ng-if="field.type === 'date'"
420
- type="date"
421
- class="form-control"
422
- id="{{field.name}}"
423
- name="{{field.name}}"
424
- ng-model="c.formData[field.name]"
425
- ng-required="field.required">
426
-
427
- <!-- Validation Messages -->
428
- <div class="form-field-errors" ng-if="widgetForm[field.name].$invalid && widgetForm[field.name].$touched">
429
- <small class="text-danger" ng-if="widgetForm[field.name].$error.required">
430
- {{field.label}} is required
431
- </small>
432
- <small class="text-danger" ng-if="widgetForm[field.name].$error.email">
433
- Please enter a valid email address
434
- </small>
435
- <small class="text-danger" ng-if="widgetForm[field.name].$error.maxlength">
436
- {{field.label}} is too long (max {{field.max_length}} characters)
437
- </small>
438
- </div>
439
-
440
- <div class="field-help-text" ng-if="field.help_text">
441
- <small class="text-muted">{{field.help_text}}</small>
442
- </div>
443
- </div>
444
-
445
- <div class="form-actions">
446
- <button type="submit"
447
- class="btn btn-primary"
448
- ng-disabled="widgetForm.$invalid || c.submitting">
449
- <i class="fa fa-spinner fa-spin" ng-if="c.submitting"></i>
450
- <i class="fa fa-check" ng-if="!c.submitting"></i>
451
- {{c.submitting ? (options.submit_text_loading || 'Submitting...') : (options.submit_text || 'Submit')}}
452
- </button>
453
- <button type="button"
454
- class="btn btn-default"
455
- ng-click="c.resetForm()"
456
- ng-if="options.show_reset">
457
- <i class="fa fa-undo"></i> Reset
458
- </button>
459
- <button type="button"
460
- class="btn btn-link"
461
- ng-click="c.cancelForm()"
462
- ng-if="options.show_cancel">
463
- Cancel
464
- </button>
465
- </div>
466
- </form>
467
- </div>
468
- </div>`.trim();
469
- }
470
-
471
- /**
472
- * Info card widget template (default/generic)
473
- */
474
- private generateInfoTemplate(title: string, instruction: string): string {
475
- return `
476
- <div class="panel panel-default widget-info">
477
- <div class="panel-heading">
478
- <h3 class="panel-title">{{data.title || '${title}'}}</h3>
479
- <div class="panel-actions" ng-if="options.show_refresh">
480
- <button class="btn btn-sm btn-default" ng-click="c.refreshData()" title="Refresh">
481
- <i class="fa fa-refresh" ng-class="{'fa-spin': data.loading}"></i>
482
- </button>
483
- </div>
484
- </div>
485
- <div class="panel-body">
486
- <div ng-if="data.loading" class="text-center loading-state">
487
- <i class="fa fa-spinner fa-spin fa-2x"></i>
488
- <p class="text-muted">Loading information...</p>
489
- </div>
490
-
491
- <div ng-if="!data.loading">
492
- <!-- Primary Content -->
493
- <div ng-if="data.content" class="widget-content">
494
- <div ng-if="data.content.icon" class="content-icon text-center">
495
- <i class="fa fa-{{data.content.icon}} fa-3x" ng-class="'text-' + (data.content.icon_color || 'primary')"></i>
496
- </div>
497
-
498
- <div ng-if="data.content.title" class="content-title">
499
- <h4>{{data.content.title}}</h4>
500
- </div>
501
-
502
- <div ng-if="data.content.description" class="content-description">
503
- <p ng-bind-html="data.content.description"></p>
504
- </div>
505
-
506
- <div ng-if="data.content.value" class="content-value text-center">
507
- <h2 class="value-display" ng-class="'text-' + (data.content.value_color || 'info')">
508
- {{data.content.value}}
509
- <small ng-if="data.content.unit">{{data.content.unit}}</small>
510
- </h2>
511
- </div>
512
- </div>
513
-
514
- <!-- List Items -->
515
- <div ng-if="data.items && data.items.length > 0" class="info-items">
516
- <div class="list-group">
517
- <div class="list-group-item info-item" ng-repeat="item in data.items track by $index"
518
- ng-class="'item-' + (item.type || 'default')">
519
- <div class="item-header" ng-if="item.title || item.icon">
520
- <div class="item-icon" ng-if="item.icon">
521
- <i class="fa fa-{{item.icon}}" ng-class="'text-' + (item.icon_color || 'default')"></i>
522
- </div>
523
- <div class="item-title" ng-if="item.title">
524
- <strong>{{item.title}}</strong>
525
- </div>
526
- </div>
527
-
528
- <div class="item-content">
529
- <div class="item-description" ng-if="item.description">
530
- {{item.description}}
531
- </div>
532
-
533
- <div class="item-value" ng-if="item.value">
534
- <span class="value" ng-class="'text-' + (item.value_color || 'default')">
535
- {{item.value}}
536
- <small ng-if="item.unit">{{item.unit}}</small>
537
- </span>
538
- </div>
539
- </div>
540
-
541
- <div class="item-meta" ng-if="item.timestamp || item.meta">
542
- <small class="text-muted">
543
- <span ng-if="item.timestamp">{{item.timestamp | date:'medium'}}</span>
544
- <span ng-if="item.meta">{{item.meta}}</span>
545
- </small>
546
- </div>
547
-
548
- <div class="item-actions" ng-if="item.actions && item.actions.length > 0">
549
- <button ng-repeat="action in item.actions"
550
- class="btn btn-xs"
551
- ng-class="'btn-' + (action.type || 'default')"
552
- ng-click="c.executeAction(action, item)">
553
- <i class="fa fa-{{action.icon}}" ng-if="action.icon"></i>
554
- {{action.label}}
555
- </button>
556
- </div>
557
- </div>
558
- </div>
559
- </div>
560
-
561
- <!-- Empty State -->
562
- <div ng-if="!data.content && (!data.items || data.items.length === 0)" class="empty-state text-center">
563
- <i class="fa fa-info-circle fa-3x text-muted"></i>
564
- <h4>{{data.empty_title || 'Information Widget'}}</h4>
565
- <p class="text-muted">{{data.empty_message || 'Widget data will appear here when available'}}</p>
566
- </div>
567
- </div>
568
- </div>
569
- </div>`.trim();
570
- }
571
-
572
- /**
573
- * Generate CSS styles based on widget type and options
574
- */
575
- private generateCss(type: string, options: WidgetTemplateOptions): string {
576
- const { theme = 'default', responsive = true } = options;
577
-
578
- let css = `
579
- /* Base Panel Styles */
580
- .panel {
581
- margin-bottom: 20px;
582
- border-radius: 6px;
583
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
584
- border: 1px solid #ddd;
585
- }
586
-
587
- .panel-heading {
588
- background: linear-gradient(to bottom, #f8f9fa 0%, #e9ecef 100%);
589
- border-bottom: 1px solid #dee2e6;
590
- position: relative;
591
- padding: 15px;
592
- border-radius: 5px 5px 0 0;
593
- }
594
-
595
- .panel-title {
596
- margin: 0;
597
- font-size: 16px;
598
- font-weight: 600;
599
- color: #495057;
600
- }
601
-
602
- .panel-subtitle {
603
- font-size: 12px;
604
- color: #6c757d;
605
- display: block;
606
- margin-top: 4px;
607
- }
608
-
609
- .panel-actions {
610
- position: absolute;
611
- right: 15px;
612
- top: 50%;
613
- transform: translateY(-50%);
614
- display: flex;
615
- gap: 8px;
616
- align-items: center;
617
- }
618
-
619
- .panel-body {
620
- padding: 15px;
621
- }
622
-
623
- /* Loading States */
624
- .loading-state {
625
- padding: 40px 20px;
626
- }
627
-
628
- .loading-state .fa-spinner {
629
- color: #007bff;
630
- margin-bottom: 10px;
631
- }
632
-
633
- /* Empty States */
634
- .empty-state {
635
- padding: 40px 20px;
636
- }
637
-
638
- .empty-state .fa {
639
- margin-bottom: 15px;
640
- opacity: 0.5;
641
- }
642
- `;
643
-
644
- // Add type-specific styles
645
- switch (type) {
646
- case 'chart':
647
- css += this.getChartCss();
648
- break;
649
- case 'dashboard':
650
- css += this.getDashboardCss();
651
- break;
652
- case 'table':
653
- css += this.getTableCss();
654
- break;
655
- case 'form':
656
- css += this.getFormCss();
657
- break;
658
- case 'info':
659
- css += this.getInfoCss();
660
- break;
661
- }
662
-
663
- // Add theme styles
664
- if (theme === 'dark') {
665
- css += this.getDarkThemeCss();
666
- } else if (theme === 'minimal') {
667
- css += this.getMinimalThemeCss();
668
- }
669
-
670
- // Add responsive styles
671
- if (responsive) {
672
- css += this.getResponsiveCss();
673
- }
674
-
675
- return css;
676
- }
677
-
678
- private getChartCss(): string {
679
- return `
680
- /* Chart Widget Styles */
681
- .widget-chart canvas {
682
- max-width: 100%;
683
- height: auto;
684
- }
685
-
686
- .chart-container {
687
- position: relative;
688
- }
689
-
690
- .chart-legend {
691
- margin-top: 15px;
692
- display: flex;
693
- flex-wrap: wrap;
694
- gap: 15px;
695
- justify-content: center;
696
- }
697
-
698
- .legend-item {
699
- display: flex;
700
- align-items: center;
701
- gap: 5px;
702
- }
703
-
704
- .legend-color {
705
- width: 12px;
706
- height: 12px;
707
- border-radius: 2px;
708
- }
709
-
710
- .legend-label {
711
- font-size: 12px;
712
- color: #495057;
713
- }
714
- `;
715
- }
716
-
717
- private getDashboardCss(): string {
718
- return `
719
- /* Dashboard Widget Styles */
720
- .metrics-grid {
721
- display: flex;
722
- flex-wrap: wrap;
723
- gap: 15px;
724
- }
725
-
726
- .metric-item {
727
- flex: 1;
728
- min-width: 200px;
729
- }
730
-
731
- .metric-card {
732
- background: #fff;
733
- border: 1px solid #e9ecef;
734
- border-radius: 6px;
735
- padding: 20px;
736
- text-align: center;
737
- transition: all 0.3s ease;
738
- height: 100%;
739
- }
740
-
741
- .metric-card:hover {
742
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
743
- transform: translateY(-2px);
744
- }
745
-
746
- .metric-card.metric-critical {
747
- border-left: 4px solid #dc3545;
748
- }
749
-
750
- .metric-card.metric-warning {
751
- border-left: 4px solid #ffc107;
752
- }
753
-
754
- .metric-card.metric-success {
755
- border-left: 4px solid #28a745;
756
- }
757
-
758
- .metric-card.metric-info {
759
- border-left: 4px solid #17a2b8;
760
- }
761
-
762
- .metric-icon {
763
- font-size: 24px;
764
- color: #6c757d;
765
- margin-bottom: 10px;
766
- }
767
-
768
- .metric-label {
769
- font-size: 14px;
770
- color: #6c757d;
771
- margin-bottom: 8px;
772
- font-weight: 500;
773
- }
774
-
775
- .metric-value {
776
- font-size: 28px;
777
- font-weight: 700;
778
- margin-bottom: 8px;
779
- line-height: 1;
780
- }
781
-
782
- .metric-unit {
783
- font-size: 14px;
784
- font-weight: 400;
785
- color: #6c757d;
786
- }
787
-
788
- .metric-trend {
789
- font-size: 12px;
790
- display: flex;
791
- align-items: center;
792
- justify-content: center;
793
- gap: 4px;
794
- }
795
-
796
- .trend-text {
797
- font-weight: 500;
798
- }
799
-
800
- .metric-description {
801
- font-size: 12px;
802
- color: #6c757d;
803
- margin-top: 8px;
804
- }
805
- `;
806
- }
807
-
808
- private getTableCss(): string {
809
- return `
810
- /* Table Widget Styles */
811
- .search-container {
812
- max-width: 200px;
813
- margin-right: 8px;
814
- }
815
-
816
- .table-container {
817
- overflow-x: auto;
818
- }
819
-
820
- .table {
821
- margin-bottom: 0;
822
- }
823
-
824
- .sortable-header {
825
- cursor: pointer;
826
- user-select: none;
827
- position: relative;
828
- }
829
-
830
- .sortable-header:hover {
831
- background-color: #f8f9fa;
832
- }
833
-
834
- .sort-indicator {
835
- margin-left: 5px;
836
- opacity: 0.5;
837
- }
838
-
839
- .sorted .sort-indicator {
840
- opacity: 1;
841
- }
842
-
843
- .actions-column {
844
- width: 100px;
845
- text-align: center;
846
- }
847
-
848
- .record-link {
849
- color: #007bff;
850
- text-decoration: none;
851
- }
852
-
853
- .record-link:hover {
854
- text-decoration: underline;
855
- }
856
-
857
- .row-highlighted {
858
- background-color: #fff3cd !important;
859
- }
860
-
861
- .table-pagination {
862
- margin-top: 15px;
863
- padding-top: 15px;
864
- border-top: 1px solid #dee2e6;
865
- }
866
-
867
- .pagination-info {
868
- margin: 0 15px;
869
- color: #6c757d;
870
- font-size: 14px;
871
- }
872
-
873
- .column-status .label {
874
- font-size: 11px;
875
- padding: 3px 8px;
876
- }
877
-
878
- .label-critical { background-color: #dc3545; }
879
- .label-warning { background-color: #ffc107; color: #212529; }
880
- .label-success { background-color: #28a745; }
881
- .label-info { background-color: #17a2b8; }
882
- .label-default { background-color: #6c757d; }
883
- `;
884
- }
885
-
886
- private getFormCss(): string {
887
- return `
888
- /* Form Widget Styles */
889
- .widget-form {
890
- max-width: 600px;
891
- }
892
-
893
- .form-group {
894
- margin-bottom: 20px;
895
- }
896
-
897
- .control-label {
898
- font-weight: 500;
899
- margin-bottom: 5px;
900
- display: block;
901
- }
902
-
903
- .help-icon {
904
- color: #6c757d;
905
- cursor: help;
906
- margin-left: 4px;
907
- }
908
-
909
- .form-control {
910
- border-radius: 4px;
911
- border: 1px solid #ced4da;
912
- padding: 8px 12px;
913
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
914
- }
915
-
916
- .form-control:focus {
917
- border-color: #80bdff;
918
- box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
919
- }
920
-
921
- .has-error .form-control {
922
- border-color: #dc3545;
923
- }
924
-
925
- .has-error .control-label {
926
- color: #dc3545;
927
- }
928
-
929
- .form-field-errors {
930
- margin-top: 5px;
931
- }
932
-
933
- .field-help-text {
934
- margin-top: 5px;
935
- }
936
-
937
- .form-actions {
938
- margin-top: 30px;
939
- padding-top: 20px;
940
- border-top: 1px solid #dee2e6;
941
- display: flex;
942
- gap: 10px;
943
- align-items: center;
944
- }
945
-
946
- .checkbox label {
947
- font-weight: normal;
948
- margin-bottom: 0;
949
- cursor: pointer;
950
- }
951
- `;
952
- }
953
-
954
- private getInfoCss(): string {
955
- return `
956
- /* Info Widget Styles */
957
- .widget-content {
958
- text-align: center;
959
- margin-bottom: 20px;
960
- }
961
-
962
- .content-icon {
963
- margin-bottom: 15px;
964
- }
965
-
966
- .content-title h4 {
967
- margin-bottom: 10px;
968
- color: #495057;
969
- }
970
-
971
- .content-description {
972
- color: #6c757d;
973
- margin-bottom: 15px;
974
- }
975
-
976
- .content-value {
977
- margin-bottom: 15px;
978
- }
979
-
980
- .value-display {
981
- font-weight: 700;
982
- margin-bottom: 0;
983
- }
984
-
985
- .info-items {
986
- margin-top: 20px;
987
- }
988
-
989
- .info-item {
990
- border: 1px solid #e9ecef;
991
- margin-bottom: 8px;
992
- padding: 12px;
993
- border-radius: 4px;
994
- }
995
-
996
- .item-header {
997
- display: flex;
998
- align-items: center;
999
- margin-bottom: 8px;
1000
- gap: 8px;
1001
- }
1002
-
1003
- .item-icon {
1004
- font-size: 16px;
1005
- }
1006
-
1007
- .item-title {
1008
- flex: 1;
1009
- }
1010
-
1011
- .item-content {
1012
- margin-bottom: 8px;
1013
- }
1014
-
1015
- .item-description {
1016
- color: #6c757d;
1017
- margin-bottom: 5px;
1018
- }
1019
-
1020
- .item-value .value {
1021
- font-weight: 600;
1022
- }
1023
-
1024
- .item-meta {
1025
- margin-top: 8px;
1026
- }
1027
-
1028
- .item-actions {
1029
- margin-top: 8px;
1030
- display: flex;
1031
- gap: 5px;
1032
- }
1033
- `;
1034
- }
1035
-
1036
- private getDarkThemeCss(): string {
1037
- return `
1038
- /* Dark Theme */
1039
- .panel {
1040
- background-color: #343a40;
1041
- border-color: #495057;
1042
- }
1043
-
1044
- .panel-heading {
1045
- background: linear-gradient(to bottom, #495057 0%, #343a40 100%);
1046
- border-bottom-color: #495057;
1047
- }
1048
-
1049
- .panel-title {
1050
- color: #fff;
1051
- }
1052
-
1053
- .panel-subtitle {
1054
- color: #adb5bd;
1055
- }
1056
-
1057
- .panel-body {
1058
- background-color: #343a40;
1059
- color: #fff;
1060
- }
1061
-
1062
- .metric-card {
1063
- background-color: #495057;
1064
- border-color: #6c757d;
1065
- color: #fff;
1066
- }
1067
-
1068
- .table {
1069
- color: #fff;
1070
- }
1071
-
1072
- .table-striped tbody tr:nth-of-type(odd) {
1073
- background-color: rgba(255,255,255,.05);
1074
- }
1075
- `;
1076
- }
1077
-
1078
- private getMinimalThemeCss(): string {
1079
- return `
1080
- /* Minimal Theme */
1081
- .panel {
1082
- border: none;
1083
- box-shadow: none;
1084
- background: transparent;
1085
- }
1086
-
1087
- .panel-heading {
1088
- background: transparent;
1089
- border-bottom: 1px solid #dee2e6;
1090
- padding: 10px 0;
1091
- }
1092
-
1093
- .panel-title {
1094
- font-size: 18px;
1095
- font-weight: 300;
1096
- }
1097
-
1098
- .panel-body {
1099
- padding: 20px 0;
1100
- }
1101
-
1102
- .metric-card {
1103
- background: transparent;
1104
- border: 1px solid #dee2e6;
1105
- box-shadow: none;
1106
- }
1107
- `;
1108
- }
1109
-
1110
- private getResponsiveCss(): string {
1111
- return `
1112
- /* Responsive Styles */
1113
- @media (max-width: 768px) {
1114
- .panel-actions {
1115
- position: static;
1116
- transform: none;
1117
- margin-top: 10px;
1118
- text-align: right;
1119
- }
1120
-
1121
- .metrics-grid {
1122
- flex-direction: column;
1123
- }
1124
-
1125
- .metric-item {
1126
- min-width: auto;
1127
- }
1128
-
1129
- .search-container {
1130
- max-width: none;
1131
- margin-bottom: 10px;
1132
- margin-right: 0;
1133
- }
1134
-
1135
- .table-pagination {
1136
- text-align: center;
1137
- }
1138
-
1139
- .pagination-info {
1140
- display: block;
1141
- margin: 10px 0;
1142
- }
1143
-
1144
- .form-actions {
1145
- flex-direction: column;
1146
- align-items: stretch;
1147
- }
1148
-
1149
- .form-actions .btn {
1150
- margin-bottom: 5px;
1151
- }
1152
- }
1153
-
1154
- @media (max-width: 480px) {
1155
- .panel-body {
1156
- padding: 10px;
1157
- }
1158
-
1159
- .metric-value {
1160
- font-size: 24px;
1161
- }
1162
-
1163
- .table-container {
1164
- font-size: 14px;
1165
- }
1166
-
1167
- .actions-column {
1168
- width: 80px;
1169
- }
1170
-
1171
- .btn-xs {
1172
- padding: 2px 5px;
1173
- font-size: 10px;
1174
- }
1175
- }
1176
- `;
1177
- }
1178
-
1179
- /**
1180
- * Generate server script based on widget type
1181
- */
1182
- private generateServerScript(type: string, instruction: string): string {
1183
- const baseScript = `
1184
- (function() {
1185
- // Widget server script - runs server-side to fetch data
1186
- var widgetType = '${type}';
1187
- var instruction = '${instruction.replace(/'/g, "\\'")}';
1188
-
1189
- // Set widget title
1190
- data.title = options.title || '${this.extractTitleFromInstruction(instruction)}';
1191
- data.loading = false;
1192
- data.error = null;
1193
-
1194
- try {
1195
- // Widget-specific data loading
1196
- switch(widgetType) {`;
1197
-
1198
- switch (type) {
1199
- case 'chart':
1200
- return baseScript + `
1201
- case 'chart':
1202
- loadChartData();
1203
- break;
1204
- case 'dashboard':
1205
- loadDashboardData();
1206
- break;
1207
- case 'table':
1208
- loadTableData();
1209
- break;
1210
- case 'form':
1211
- loadFormData();
1212
- break;
1213
- default:
1214
- loadInfoData();
1215
- }
1216
- } catch (e) {
1217
- data.error = 'Server error: ' + e.message;
1218
- gs.error('Widget server script error: ' + e.message);
1219
- }
1220
-
1221
- function loadChartData() {
1222
- // Sample chart data - replace with actual ServiceNow queries
1223
- data.chart_data = [
1224
- { label: 'Open', value: 25, color: '#dc3545' },
1225
- { label: 'In Progress', value: 45, color: '#ffc107' },
1226
- { label: 'Resolved', value: 30, color: '#28a745' }
1227
- ];
1228
-
1229
- data.chart_legend = data.chart_data;
1230
- data.chart_type = options.chart_type || 'doughnut';
1231
-
1232
- // Example: Load incident data
1233
- // var gr = new GlideRecord('incident');
1234
- // gr.addQuery('state', 'IN', '1,2,3');
1235
- // gr.query();
1236
- // data.chart_data = processIncidentData(gr);
1237
- }
1238
-
1239
- function loadDashboardData() {
1240
- data.metrics = [
1241
- {
1242
- label: 'Total Items',
1243
- value: 150,
1244
- unit: '',
1245
- status: 'info',
1246
- icon: 'list',
1247
- trend: 5,
1248
- trend_text: '+5%'
1249
- },
1250
- {
1251
- label: 'Critical Issues',
1252
- value: 3,
1253
- unit: '',
1254
- status: 'critical',
1255
- icon: 'exclamation-triangle',
1256
- trend: -2,
1257
- trend_text: '-2 from yesterday'
1258
- },
1259
- {
1260
- label: 'Success Rate',
1261
- value: 98.5,
1262
- unit: '%',
1263
- status: 'success',
1264
- icon: 'check-circle',
1265
- trend: 1,
1266
- trend_text: '+1%'
1267
- }
1268
- ];
1269
-
1270
- data.last_updated = new GlideDateTime().getDisplayValue();
1271
- }
1272
-
1273
- function loadTableData() {
1274
- data.columns = [
1275
- { field: 'number', label: 'Number', type: 'link' },
1276
- { field: 'short_description', label: 'Description', type: 'text' },
1277
- { field: 'state', label: 'State', type: 'status' },
1278
- { field: 'opened_at', label: 'Opened', type: 'date' }
1279
- ];
1280
-
1281
- data.records = [];
1282
- data.total_records = 0;
1283
-
1284
- // Example: Load incident records
1285
- // var gr = new GlideRecord('incident');
1286
- // gr.orderByDesc('opened_at');
1287
- // gr.setLimit(options.page_size || 10);
1288
- // gr.query();
1289
- //
1290
- // while (gr.next()) {
1291
- // data.records.push({
1292
- // sys_id: gr.getUniqueValue(),
1293
- // number: gr.getDisplayValue('number'),
1294
- // number_link: '/' + gr.getTableName() + '.do?sys_id=' + gr.getUniqueValue(),
1295
- // short_description: gr.getDisplayValue('short_description'),
1296
- // state: gr.getDisplayValue('state'),
1297
- // state_class: getStateClass(gr.getValue('state')),
1298
- // opened_at: gr.getDisplayValue('opened_at')
1299
- // });
1300
- // }
1301
- //
1302
- // data.total_records = gr.getRowCount();
1303
- }
1304
-
1305
- function loadFormData() {
1306
- data.form_fields = [
1307
- {
1308
- name: 'title',
1309
- label: 'Title',
1310
- type: 'text',
1311
- required: true,
1312
- placeholder: 'Enter title...'
1313
- },
1314
- {
1315
- name: 'description',
1316
- label: 'Description',
1317
- type: 'textarea',
1318
- required: false,
1319
- placeholder: 'Enter description...',
1320
- rows: 3
1321
- },
1322
- {
1323
- name: 'priority',
1324
- label: 'Priority',
1325
- type: 'select',
1326
- required: true,
1327
- options: [
1328
- { value: '1', label: 'Critical' },
1329
- { value: '2', label: 'High' },
1330
- { value: '3', label: 'Medium' },
1331
- { value: '4', label: 'Low' }
1332
- ]
1333
- }
1334
- ];
1335
- }
1336
-
1337
- function loadInfoData() {
1338
- data.content = {
1339
- icon: 'info-circle',
1340
- icon_color: 'primary',
1341
- title: 'Information Widget',
1342
- description: 'This widget displays important information and updates.',
1343
- value: null
1344
- };
1345
-
1346
- data.items = [
1347
- {
1348
- title: 'Sample Information',
1349
- description: 'This is sample information displayed in the widget.',
1350
- icon: 'info',
1351
- icon_color: 'info',
1352
- timestamp: new GlideDateTime().getDisplayValue()
1353
- }
1354
- ];
1355
- }
1356
- })();`.trim();
1357
-
1358
- default:
1359
- return baseScript + `
1360
- default:
1361
- loadInfoData();
1362
- }
1363
- } catch (e) {
1364
- data.error = 'Server error: ' + e.message;
1365
- gs.error('Widget server script error: ' + e.message);
1366
- }
1367
-
1368
- function loadInfoData() {
1369
- data.content = {
1370
- icon: 'info-circle',
1371
- icon_color: 'primary',
1372
- title: 'Information Widget',
1373
- description: 'This widget displays important information and updates.',
1374
- value: null
1375
- };
1376
-
1377
- data.items = [
1378
- {
1379
- title: 'Sample Information',
1380
- description: 'This is sample information displayed in the widget.',
1381
- icon: 'info',
1382
- icon_color: 'info',
1383
- timestamp: new GlideDateTime().getDisplayValue()
1384
- }
1385
- ];
1386
- }
1387
- })();`.trim();
1388
- }
1389
- }
1390
-
1391
- /**
1392
- * Generate client script based on widget type
1393
- */
1394
- private generateClientScript(type: string, instruction: string): string {
1395
- const baseScript = `
1396
- function($scope, $http, spUtil, $timeout) {
1397
- var c = this;
1398
- var widgetType = '${type}';
1399
-
1400
- // Initialize controller
1401
- c.init = function() {
1402
- c.widgetType = widgetType;
1403
- c.loading = false;
1404
-
1405
- // Type-specific initialization
1406
- switch(widgetType) {`;
1407
-
1408
- switch (type) {
1409
- case 'chart':
1410
- return baseScript + `
1411
- case 'chart':
1412
- c.initChart();
1413
- break;
1414
- case 'dashboard':
1415
- c.initDashboard();
1416
- break;
1417
- case 'table':
1418
- c.initTable();
1419
- break;
1420
- case 'form':
1421
- c.initForm();
1422
- break;
1423
- default:
1424
- c.initInfo();
1425
- }
1426
- };
1427
-
1428
- // Chart-specific functions
1429
- c.initChart = function() {
1430
- $timeout(function() {
1431
- if (c.data.chart_data && c.data.chart_data.length > 0) {
1432
- c.renderChart();
1433
- }
1434
- }, 100);
1435
- };
1436
-
1437
- c.renderChart = function() {
1438
- var canvas = document.getElementById('chart-' + c.widget.id);
1439
- if (!canvas) return;
1440
-
1441
- var ctx = canvas.getContext('2d');
1442
- var chartType = c.data.chart_type || 'doughnut';
1443
-
1444
- // Simple chart rendering (requires Chart.js)
1445
- if (typeof Chart !== 'undefined') {
1446
- new Chart(ctx, {
1447
- type: chartType,
1448
- data: {
1449
- labels: c.data.chart_data.map(function(item) { return item.label; }),
1450
- datasets: [{
1451
- data: c.data.chart_data.map(function(item) { return item.value; }),
1452
- backgroundColor: c.data.chart_data.map(function(item) { return item.color; })
1453
- }]
1454
- },
1455
- options: {
1456
- responsive: true,
1457
- maintainAspectRatio: false,
1458
- legend: {
1459
- display: false
1460
- }
1461
- }
1462
- });
1463
- }
1464
- };
1465
-
1466
- c.refreshData = function() {
1467
- c.loading = true;
1468
- c.server.refresh().then(function() {
1469
- c.loading = false;
1470
- if (widgetType === 'chart') {
1471
- $timeout(function() {
1472
- c.renderChart();
1473
- }, 100);
1474
- }
1475
- });
1476
- };
1477
-
1478
- // Dashboard functions
1479
- c.initDashboard = function() {
1480
- // Dashboard initialization
1481
- };
1482
-
1483
- // Table functions
1484
- c.initTable = function() {
1485
- c.currentPage = 1;
1486
- c.sortField = null;
1487
- c.sortReverse = false;
1488
- c.searchTerm = '';
1489
- c.calculatePagination();
1490
- };
1491
-
1492
- c.sortBy = function(field) {
1493
- if (c.sortField === field) {
1494
- c.sortReverse = !c.sortReverse;
1495
- } else {
1496
- c.sortField = field;
1497
- c.sortReverse = false;
1498
- }
1499
- };
1500
-
1501
- c.onSearchChange = function() {
1502
- c.currentPage = 1;
1503
- c.calculatePagination();
1504
- };
1505
-
1506
- c.clearSearch = function() {
1507
- c.searchTerm = '';
1508
- c.onSearchChange();
1509
- };
1510
-
1511
- c.calculatePagination = function() {
1512
- var pageSize = c.options.page_size || 10;
1513
- c.totalPages = Math.ceil((c.data.total_records || 0) / pageSize);
1514
- };
1515
-
1516
- c.previousPage = function() {
1517
- if (c.currentPage > 1) {
1518
- c.currentPage--;
1519
- }
1520
- };
1521
-
1522
- c.nextPage = function() {
1523
- if (c.currentPage < c.totalPages) {
1524
- c.currentPage++;
1525
- }
1526
- };
1527
-
1528
- c.viewRecord = function(record) {
1529
- // Navigate to record
1530
- window.open('/' + record.sys_table + '.do?sys_id=' + record.sys_id, '_blank');
1531
- };
1532
-
1533
- c.editRecord = function(record) {
1534
- // Navigate to record form
1535
- window.open('/' + record.sys_table + '.do?sys_id=' + record.sys_id, '_blank');
1536
- };
1537
-
1538
- // Form functions
1539
- c.initForm = function() {
1540
- c.formData = {};
1541
- c.submitting = false;
1542
- c.resetForm();
1543
- };
1544
-
1545
- c.submitForm = function() {
1546
- if ($scope.widgetForm.$valid) {
1547
- c.submitting = true;
1548
-
1549
- // Submit form data to server
1550
- c.server.update().then(function(response) {
1551
- c.submitting = false;
1552
- if (response && response.success) {
1553
- c.data.success_message = response.message || 'Form submitted successfully!';
1554
- c.data.error_message = null;
1555
- c.resetForm();
1556
- } else {
1557
- c.data.error_message = response.error || 'Form submission failed. Please try again.';
1558
- c.data.success_message = null;
1559
- }
1560
- }).catch(function(error) {
1561
- c.submitting = false;
1562
- c.data.error_message = 'Network error. Please try again.';
1563
- c.data.success_message = null;
1564
- });
1565
- }
1566
- };
1567
-
1568
- c.resetForm = function() {
1569
- c.formData = {};
1570
- if ($scope.widgetForm) {
1571
- $scope.widgetForm.$setPristine();
1572
- $scope.widgetForm.$setUntouched();
1573
- }
1574
- };
1575
-
1576
- c.cancelForm = function() {
1577
- c.resetForm();
1578
- // Additional cancel logic
1579
- };
1580
-
1581
- // Info widget functions
1582
- c.initInfo = function() {
1583
- // Info widget initialization
1584
- };
1585
-
1586
- c.executeAction = function(action, item) {
1587
- switch(action.type) {
1588
- case 'link':
1589
- window.open(action.url, '_blank');
1590
- break;
1591
- case 'refresh':
1592
- c.refreshData();
1593
- break;
1594
- default:
1595
- console.log('Action executed:', action, item);
1596
- }
1597
- };
1598
-
1599
- // Initialize widget
1600
- c.init();
1601
- }`.trim();
1602
-
1603
- default:
1604
- return baseScript + `
1605
- default:
1606
- c.initInfo();
1607
- }
1608
- };
1609
-
1610
- c.initInfo = function() {
1611
- // Info widget initialization
1612
- };
1613
-
1614
- c.refreshData = function() {
1615
- c.loading = true;
1616
- c.server.refresh().then(function() {
1617
- c.loading = false;
1618
- });
1619
- };
1620
-
1621
- c.executeAction = function(action, item) {
1622
- switch(action.type) {
1623
- case 'link':
1624
- window.open(action.url, '_blank');
1625
- break;
1626
- case 'refresh':
1627
- c.refreshData();
1628
- break;
1629
- default:
1630
- console.log('Action executed:', action, item);
1631
- }
1632
- };
1633
-
1634
- // Initialize widget
1635
- c.init();
1636
- }`.trim();
1637
- }
1638
- }
1639
-
1640
- /**
1641
- * Generate option schema based on widget type
1642
- */
1643
- private generateOptionSchema(type: string): string {
1644
- const baseOptions = [
1645
- {
1646
- "name": "title",
1647
- "label": "Widget Title",
1648
- "type": "string",
1649
- "value": ""
1650
- },
1651
- {
1652
- "name": "show_refresh",
1653
- "label": "Show Refresh Button",
1654
- "type": "boolean",
1655
- "value": true
1656
- }
1657
- ];
1658
-
1659
- let typeSpecificOptions: any[] = [];
1660
-
1661
- switch (type) {
1662
- case 'chart':
1663
- typeSpecificOptions = [
1664
- {
1665
- "name": "chart_type",
1666
- "label": "Chart Type",
1667
- "type": "choice",
1668
- "choices": [
1669
- {"label": "Doughnut", "value": "doughnut"},
1670
- {"label": "Bar", "value": "bar"},
1671
- {"label": "Line", "value": "line"},
1672
- {"label": "Pie", "value": "pie"}
1673
- ],
1674
- "value": "doughnut"
1675
- }
1676
- ];
1677
- break;
1678
-
1679
- case 'table':
1680
- typeSpecificOptions = [
1681
- {
1682
- "name": "enable_search",
1683
- "label": "Enable Search",
1684
- "type": "boolean",
1685
- "value": true
1686
- },
1687
- {
1688
- "name": "page_size",
1689
- "label": "Records Per Page",
1690
- "type": "integer",
1691
- "value": 10
1692
- },
1693
- {
1694
- "name": "show_actions",
1695
- "label": "Show Action Buttons",
1696
- "type": "boolean",
1697
- "value": true
1698
- },
1699
- {
1700
- "name": "allow_edit",
1701
- "label": "Allow Edit Action",
1702
- "type": "boolean",
1703
- "value": false
1704
- }
1705
- ];
1706
- break;
1707
-
1708
- case 'form':
1709
- typeSpecificOptions = [
1710
- {
1711
- "name": "submit_text",
1712
- "label": "Submit Button Text",
1713
- "type": "string",
1714
- "value": "Submit"
1715
- },
1716
- {
1717
- "name": "submit_text_loading",
1718
- "label": "Submit Button Loading Text",
1719
- "type": "string",
1720
- "value": "Submitting..."
1721
- },
1722
- {
1723
- "name": "show_reset",
1724
- "label": "Show Reset Button",
1725
- "type": "boolean",
1726
- "value": true
1727
- },
1728
- {
1729
- "name": "show_cancel",
1730
- "label": "Show Cancel Button",
1731
- "type": "boolean",
1732
- "value": false
1733
- }
1734
- ];
1735
- break;
1736
- }
1737
-
1738
- const allOptions = [...baseOptions, ...typeSpecificOptions];
1739
- return JSON.stringify(allOptions, null, 2);
1740
- }
1741
- }
1742
-
1743
- // Export singleton instance
1744
- export const widgetTemplateGenerator = new ServiceNowWidgetTemplateGenerator();