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.
- package/{.claude/mcp-config.json → .mcp.json} +13 -12
- package/.releaserc.json +47 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/.claude/claude-settings.json +0 -15
- package/.claude/commands/analysis/README.md +0 -9
- package/.claude/commands/automation/README.md +0 -9
- package/.claude/commands/github/README.md +0 -11
- package/.claude/commands/helpers/standard-checkpoint-hooks.sh +0 -179
- package/.claude/commands/hooks/README.md +0 -11
- package/.claude/commands/monitoring/README.md +0 -9
- package/.claude/commands/optimization/README.md +0 -9
- package/.claude/commands/training/README.md +0 -9
- package/.claude/commands/workflows/README.md +0 -9
- package/.claude/config.json +0 -36
- package/.claude/helpers/github-setup.sh +0 -28
- package/.claude/helpers/quick-start.sh +0 -19
- package/.claude/helpers/setup-mcp.sh +0 -18
- package/.claude/settings.json +0 -114
- package/.claude/settings.local.json +0 -33
- package/.claude-flow/metrics/agent-metrics.json +0 -1
- package/.claude-flow/metrics/performance.json +0 -9
- package/.claude-flow/metrics/system-metrics.json +0 -15986
- package/.claude-flow/metrics/task-metrics.json +0 -10
- package/.roo/README.md +0 -402
- package/.roo/mcp-list.txt +0 -257
- package/.roo/mcp.json +0 -32
- package/.roo/workflows/basic-tdd.json +0 -32
- package/.roomodes +0 -201
- package/CLAUDE.md +0 -393
- package/bin/snow-flow.js +0 -3
- package/dist/cli.js.bak +0 -2052
- package/dist/config/snow-flow-config.d.ts +0 -1492
- package/dist/config/snow-flow-config.js +0 -938
- package/scripts/create-env.js +0 -91
- package/scripts/postinstall.js +0 -30
- package/scripts/register-mcp-servers.js +0 -210
- package/scripts/reset-mcp-servers.js +0 -246
- package/scripts/setup-mcp.js +0 -106
- package/scripts/update-version.js +0 -31
- package/src/agents/README.md +0 -192
- package/src/agents/base-agent.ts +0 -251
- package/src/agents/coordinator.ts +0 -971
- package/src/agents/index.ts +0 -18
- package/src/agents/queen-403-handler.ts +0 -230
- package/src/agents/queen-agent.ts +0 -1399
- package/src/agents/script-writer-agent.ts +0 -769
- package/src/agents/security-agent.ts +0 -726
- package/src/agents/widget-creator-agent.ts +0 -568
- package/src/api/error-handling.ts +0 -1758
- package/src/api/natural-language-mapper.ts +0 -630
- package/src/api/performance-optimizer.ts +0 -891
- package/src/api/transaction-manager.ts +0 -754
- package/src/cli/deploy-artifact.ts +0 -304
- package/src/cli/snow-flow-cli-integration.ts +0 -434
- package/src/cli.ts +0 -4455
- package/src/cli.ts.bak +0 -5616
- package/src/compliance/advanced-compliance-system.ts +0 -1268
- package/src/compliance/index.ts +0 -19
- package/src/config/snow-flow-config.ts +0 -1019
- package/src/documentation/index.ts +0 -16
- package/src/documentation/self-documenting-system.ts +0 -1421
- package/src/dynamic-version.ts +0 -44
- package/src/healing/index.ts +0 -20
- package/src/healing/self-healing-system.ts +0 -1435
- package/src/health/README.md +0 -161
- package/src/health/system-health.ts +0 -995
- package/src/index.ts +0 -60
- package/src/intelligence/acl-analyzer.ts +0 -377
- package/src/intelligence/auto-resolution-engine.ts +0 -643
- package/src/intelligence/gap-analysis-engine.ts +0 -488
- package/src/intelligence/manual-instructions-generator.ts +0 -744
- package/src/intelligence/mcp-coverage-analyzer.ts +0 -708
- package/src/intelligence/multi-pass-requirements-analyzer.ts +0 -603
- package/src/intelligence/performance-recommendations-engine.ts +0 -1223
- package/src/intelligence/requirements-analyzer.ts +0 -1148
- package/src/intelligence/task-analyzer.ts +0 -628
- package/src/managers/scope-manager.ts +0 -555
- package/src/mcp/advanced/servicenow-advanced-features-mcp.ts +0 -16110
- package/src/mcp/base-mcp-server.ts +0 -660
- package/src/mcp/http-transport-wrapper.ts +0 -426
- package/src/mcp/service-discovery-client.ts +0 -312
- package/src/mcp/servicenow-automation-mcp-refactored.ts +0 -678
- package/src/mcp/servicenow-automation-mcp.ts +0 -838
- package/src/mcp/servicenow-deployment-mcp-refactored.ts +0 -1292
- package/src/mcp/servicenow-deployment-mcp.ts +0 -7244
- package/src/mcp/servicenow-graph-memory-mcp-refactored.ts +0 -721
- package/src/mcp/servicenow-graph-memory-mcp.ts +0 -832
- package/src/mcp/servicenow-integration-mcp-refactored.ts +0 -655
- package/src/mcp/servicenow-integration-mcp.ts +0 -819
- package/src/mcp/servicenow-intelligent-mcp-refactored.ts +0 -977
- package/src/mcp/servicenow-intelligent-mcp.ts +0 -4638
- package/src/mcp/servicenow-mcp-server.ts +0 -728
- package/src/mcp/servicenow-memory-mcp.ts +0 -474
- package/src/mcp/servicenow-operations-mcp-refactored.ts +0 -1995
- package/src/mcp/servicenow-operations-mcp.ts +0 -3384
- package/src/mcp/servicenow-platform-development-mcp-refactored.ts +0 -611
- package/src/mcp/servicenow-platform-development-mcp.ts +0 -970
- package/src/mcp/servicenow-progressive-indexer.ts +0 -370
- package/src/mcp/servicenow-reporting-analytics-mcp-refactored.ts +0 -695
- package/src/mcp/servicenow-reporting-analytics-mcp.ts +0 -1123
- package/src/mcp/servicenow-security-compliance-mcp-refactored.ts +0 -642
- package/src/mcp/servicenow-security-compliance-mcp.ts +0 -1157
- package/src/mcp/servicenow-update-set-mcp-refactored.ts +0 -751
- package/src/mcp/servicenow-update-set-mcp.ts +0 -811
- package/src/mcp/shared/agent-context-provider.ts +0 -365
- package/src/mcp/shared/base-mcp-server.ts +0 -2463
- package/src/mcp/shared/mcp-memory-manager.ts +0 -540
- package/src/mcp/shared/mcp-resource-manager.ts +0 -367
- package/src/mcp/snow-flow-mcp.ts +0 -911
- package/src/mcp/start-all-mcp-servers.ts +0 -102
- package/src/mcp/start-servicenow-mcp.ts +0 -67
- package/src/monitoring/enhanced-monitoring-system.ts +0 -1345
- package/src/monitoring/performance-tracker.ts +0 -777
- package/src/optimization/cost-optimization-engine.ts +0 -1125
- package/src/optimization/index.ts +0 -10
- package/src/queen/README.md +0 -403
- package/src/queen/agent-factory.ts +0 -574
- package/src/queen/index.ts +0 -94
- package/src/queen/mcp-execution-bridge.ts +0 -681
- package/src/queen/neural-learning.ts +0 -319
- package/src/queen/parallel-agent-engine.ts +0 -848
- package/src/queen/queen-memory-system.ts +0 -490
- package/src/queen/queen-memory.ts +0 -456
- package/src/queen/servicenow-queen.ts +0 -1029
- package/src/queen/types.ts +0 -87
- package/src/rollback/smart-rollback-system.ts +0 -866
- package/src/schemas/deployment.schema.json +0 -58
- package/src/schemas/flow.schema.json +0 -79
- package/src/schemas/widget.schema.json +0 -72
- package/src/snow-flow-system.ts +0 -541
- package/src/sparc/sparc-help.ts +0 -49
- package/src/sparc/team-sparc.ts +0 -88
- package/src/strategies/global-scope-strategy.ts +0 -736
- package/src/templates/base/application.template.json +0 -45
- package/src/templates/base/business_rule.template.json +0 -33
- package/src/templates/base/script_include.template.json +0 -18
- package/src/templates/base/table.template.json +0 -64
- package/src/templates/base/widget.template.json +0 -25
- package/src/templates/patterns/composite.incident-management.template.json +0 -140
- package/src/templates/patterns/widget.dashboard.template.json +0 -238
- package/src/templates/patterns/widget.datatable.template.json +0 -292
- package/src/testing/integration-test-suite.ts +0 -1276
- package/src/types/index.ts +0 -183
- package/src/types/neo4j-driver.d.ts +0 -23
- package/src/types/servicenow.types.ts +0 -307
- package/src/types/snow-flow.types.ts +0 -186
- package/src/types/todo.types.ts +0 -70
- package/src/utils/action-type-cache.ts +0 -286
- package/src/utils/agent-detector.ts +0 -573
- package/src/utils/artifact-tracker.ts +0 -379
- package/src/utils/dependency-detector.ts +0 -233
- package/src/utils/deployment-metadata-handler.ts +0 -518
- package/src/utils/error-recovery.ts +0 -680
- package/src/utils/logger.ts +0 -60
- package/src/utils/mcp-auth-middleware.ts +0 -262
- package/src/utils/mcp-config-manager.ts +0 -270
- package/src/utils/mcp-server-manager.ts +0 -446
- package/src/utils/mcp-tool-registry.ts +0 -298
- package/src/utils/migrate-snow-flow.ts +0 -98
- package/src/utils/oauth-html-templates.ts +0 -188
- package/src/utils/scope-utils.ts +0 -443
- package/src/utils/servicenow-client.ts +0 -3321
- package/src/utils/servicenow-id-generator.ts +0 -181
- package/src/utils/snow-memory-manager.ts +0 -605
- package/src/utils/snow-oauth.ts +0 -797
- package/src/utils/template-engine.ts +0 -463
- package/src/utils/theme-manager.ts +0 -295
- package/src/utils/unified-auth-store.ts +0 -222
- package/src/utils/update-set-importer.ts +0 -422
- package/src/utils/update-set-xml-packager.ts +0 -371
- package/src/utils/widget-template-generator.ts +0 -1744
- 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>×</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>×</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();
|