@pb33f/cowboy-components 0.1.16 → 0.2.0
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/dist/assets/rule-documentation.worker-D39NS8Lx.js +1 -0
- package/dist/components/editor/editor.d.ts +2 -0
- package/dist/components/editor/editor.js +6 -4
- package/dist/components/error-banner/error-banner.css.js +1 -1
- package/dist/components/manage-ruleset/function-option.css.d.ts +2 -0
- package/dist/components/manage-ruleset/function-option.css.js +38 -0
- package/dist/components/manage-ruleset/function-option.d.ts +19 -0
- package/dist/components/manage-ruleset/function-option.js +117 -0
- package/dist/components/manage-ruleset/manage-ruleset.css.d.ts +2 -0
- package/dist/components/manage-ruleset/manage-ruleset.css.js +75 -0
- package/dist/components/manage-ruleset/manage-ruleset.d.ts +62 -0
- package/dist/components/manage-ruleset/manage-ruleset.js +575 -0
- package/dist/components/manage-ruleset/rule-action.css.d.ts +2 -0
- package/dist/components/manage-ruleset/rule-action.css.js +56 -0
- package/dist/components/manage-ruleset/rule-action.d.ts +37 -0
- package/dist/components/manage-ruleset/rule-action.js +351 -0
- package/dist/components/manage-ruleset/rule-input.d.ts +38 -0
- package/dist/components/manage-ruleset/rule-input.js +296 -0
- package/dist/components/manage-ruleset/rule.css.d.ts +2 -0
- package/dist/components/manage-ruleset/rule.css.js +117 -0
- package/dist/components/manage-ruleset/rule.d.ts +31 -0
- package/dist/components/manage-ruleset/rule.js +153 -0
- package/dist/components/problem-list/details-drawer.d.ts +2 -1
- package/dist/components/problem-list/details-drawer.js +7 -0
- package/dist/components/problem-list/filter.css.js +2 -3
- package/dist/components/problem-list/problem-item.css.js +1 -3
- package/dist/components/problem-list/problem-item.js +1 -1
- package/dist/components/problem-list/problem-list.css.js +0 -10
- package/dist/components/problem-list/problem-list.js +0 -1
- package/dist/components/problems-overview/document-statistic.css.js +0 -1
- package/dist/components/problems-overview/problem-overview-group.css.js +1 -3
- package/dist/components/problems-overview/problem-overview-group.js +2 -2
- package/dist/components/problems-overview/problem-statistics.css.js +0 -5
- package/dist/components/problems-overview/problems-overview.css.js +0 -4
- package/dist/components/the-doctor/the-doctor.css.js +99 -27
- package/dist/components/the-doctor/the-doctor.d.ts +65 -8
- package/dist/components/the-doctor/the-doctor.js +663 -63
- package/dist/components/toast/toast-component.css.d.ts +2 -0
- package/dist/components/toast/toast-component.css.js +151 -0
- package/dist/components/toast/toast-component.d.ts +19 -0
- package/dist/components/toast/toast-component.js +116 -0
- package/dist/components/toast/toast-manager.d.ts +13 -0
- package/dist/components/toast/toast-manager.js +54 -0
- package/dist/cowboy-components.umd.cjs +1375 -372
- package/dist/css/button.css.js +46 -0
- package/dist/css/dialog.css.d.ts +2 -0
- package/dist/css/dialog.css.js +11 -0
- package/dist/css/forms.css.d.ts +2 -0
- package/dist/css/forms.css.js +123 -0
- package/dist/css/modal.css.d.ts +2 -0
- package/dist/css/modal.css.js +15 -0
- package/dist/css/pb33f-theme.css +1 -0
- package/dist/css/radiogroups.css.d.ts +2 -0
- package/dist/css/radiogroups.css.js +26 -0
- package/dist/css/spinner.css.d.ts +2 -0
- package/dist/css/spinner.css.js +42 -0
- package/dist/events/doctor.d.ts +57 -3
- package/dist/events/doctor.js +13 -1
- package/dist/model/errors.d.ts +10 -0
- package/dist/model/rule_documentation.d.ts +8 -2
- package/dist/model/rule_documentation.js +5 -1
- package/dist/model/toast.d.ts +15 -0
- package/dist/model/toast.js +9 -0
- package/dist/model/vacuum_rule.d.ts +58 -0
- package/dist/model/vacuum_rule.js +1 -0
- package/dist/services/linting-service.d.ts +1 -1
- package/dist/services/linting-service.js +2 -6
- package/dist/services/ruleset-service.d.ts +17 -0
- package/dist/services/ruleset-service.js +316 -0
- package/dist/style.css +1 -1
- package/dist/workers/rule-documentation.worker.d.ts +7 -4
- package/dist/workers/rule-documentation.worker.js +93 -2
- package/package.json +1 -1
- package/dist/assets/rule-documentation.worker-BFIxMBU8.js +0 -1
|
@@ -11,10 +11,11 @@ import '@shoelace-style/shoelace/dist/components/tab/tab.js';
|
|
|
11
11
|
import '@shoelace-style/shoelace/dist/components/switch/switch.js';
|
|
12
12
|
import '@shoelace-style/shoelace/dist/components/dialog/dialog.js';
|
|
13
13
|
import '@shoelace-style/shoelace/dist/components/alert/alert.js';
|
|
14
|
-
import
|
|
14
|
+
import '@shoelace-style/shoelace/dist/components/badge/badge.js';
|
|
15
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
15
16
|
import { html, LitElement } from "lit";
|
|
16
17
|
import { SpecEditor } from "../editor/editor.js";
|
|
17
|
-
import { ActiveView, EditorClicked, EditorUpdated, OpenProblemDrawer, ProblemClicked, ProblemRuleFilterChangedManual, RuleClicked } from "../../events/doctor.js";
|
|
18
|
+
import { ActiveView, AddToast, CustomRulesetEnabled, EditorClicked, EditorUpdated, ExportRuleset, OpenProblemDrawer, ProblemClicked, ProblemRuleFilterChangedManual, RuleViolationClicked, RulesetSaved, RuleClicked } from "../../events/doctor.js";
|
|
18
19
|
import { ProblemDetailsDrawer, ProblemDrawerEventType } from "../problem-list/details-drawer.js";
|
|
19
20
|
import { CreateBagManager } from "@pb33f/saddlebag";
|
|
20
21
|
import { LintingService } from "../../services/linting-service.js";
|
|
@@ -26,26 +27,44 @@ import { StatusBar } from "./status-bar.js";
|
|
|
26
27
|
import { FeedbackComponent } from "./feedback.js";
|
|
27
28
|
import { FeedbackService } from "../../services/feedback-service.js";
|
|
28
29
|
import { ActivitySpinner } from "./activity-spinner.js";
|
|
30
|
+
import { ManageRuleset } from "../manage-ruleset/manage-ruleset.js";
|
|
29
31
|
import RuleDocumentationWorker from "../../workers/rule-documentation.worker.js?worker";
|
|
30
32
|
import theDoctorCss from "./the-doctor.css.js";
|
|
31
33
|
import linksCss from "../../css/links.css.js";
|
|
34
|
+
import { RulesetService } from "../../services/ruleset-service.js";
|
|
35
|
+
import { ToastManager } from "../toast/toast-manager.js";
|
|
36
|
+
import { ToastType } from "../../model/toast.js";
|
|
37
|
+
import dialogCss from "../../css/dialog.css.js";
|
|
38
|
+
import buttonCss from "../../css/button.css.js";
|
|
39
|
+
import radioGroupsCss from "../../css/radiogroups.css.js";
|
|
40
|
+
import { MarkerSeverity } from "monaco-editor";
|
|
32
41
|
export const DoctorDocumentBag = "pb33f-doctor-editor";
|
|
33
42
|
export const HowToFixBag = "pb33f-doctor-howtofix";
|
|
43
|
+
export const FunctionDocumentationBag = "pb33f-doctor-funcdocs";
|
|
34
44
|
export const RuleDocumentationBag = "pb33f-doctor-ruledocs";
|
|
35
45
|
export const DiagnosticBag = "pb33f-doctor-diagnostic";
|
|
36
|
-
export const
|
|
46
|
+
export const DefaultRulesetBag = "pb33f-doctor-default-ruleset";
|
|
47
|
+
export const OWASPRulesetBag = "pb33f-doctor-owasp-ruleset";
|
|
48
|
+
export const AllRulesetBag = "pb33f-doctor-all-ruleset";
|
|
49
|
+
export const FunctionsBag = "pb33f-doctor-functions";
|
|
50
|
+
export const FunctionsSchemaBag = "pb33f-doctor-function-schema";
|
|
51
|
+
export const CustomRulesetBag = "pb33f-doctor-custom-ruleset";
|
|
52
|
+
export const RuleConfigurationBag = "pb33f-doctor-rule-configuration";
|
|
53
|
+
export const SessionRulesetMapBag = "pb33f-doctor-session-rulesetmap";
|
|
37
54
|
export const DefaultDocument = "document";
|
|
38
55
|
export const DocumentProblems = "problems";
|
|
39
56
|
export const DoctorEndpoint = "doctor-endpoint";
|
|
40
57
|
let TheDoctor = class TheDoctor extends LitElement {
|
|
41
58
|
constructor(doctorEndpoint = "https://doctor.pb33f.io") {
|
|
42
59
|
super();
|
|
43
|
-
this.debounceTime =
|
|
60
|
+
this.debounceTime = 400;
|
|
61
|
+
this.debounceTimeRuleset = 900;
|
|
44
62
|
this.bounceId = 0;
|
|
45
63
|
// create a stateful bag manager
|
|
46
64
|
this.bagManager = CreateBagManager(true);
|
|
47
65
|
this.bagManager.loadStatefulBags().then(this.loadState.bind(this));
|
|
48
66
|
this.editor = new SpecEditor();
|
|
67
|
+
this.rulesetEditor = new SpecEditor('ruleset');
|
|
49
68
|
this.problemList = new ProblemList();
|
|
50
69
|
this.detailsDrawer = new ProblemDetailsDrawer();
|
|
51
70
|
this.problemsOverview = new ProblemsOverview();
|
|
@@ -55,6 +74,13 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
55
74
|
this.unavailable = false;
|
|
56
75
|
this.feedback = new FeedbackComponent();
|
|
57
76
|
this.activitySpinner = new ActivitySpinner();
|
|
77
|
+
this.manageRuleset = new ManageRuleset();
|
|
78
|
+
this.toastManager = new ToastManager();
|
|
79
|
+
this.editorMap = new Map();
|
|
80
|
+
this.editorMap.set("spec", this.editor);
|
|
81
|
+
this.editorMap.set("ruleset", this.rulesetEditor);
|
|
82
|
+
this.selectedEditorTab = "spec";
|
|
83
|
+
this.sidebarClosed = false;
|
|
58
84
|
// extract the doctor endpoint from session storage.
|
|
59
85
|
const sessionEndpoint = sessionStorage.getItem(DoctorEndpoint);
|
|
60
86
|
if (sessionEndpoint) {
|
|
@@ -74,23 +100,41 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
74
100
|
// @ts-ignore
|
|
75
101
|
this.addEventListener(OpenProblemDrawer, this.ruleDocsClicked);
|
|
76
102
|
// @ts-ignore
|
|
77
|
-
this.addEventListener(
|
|
103
|
+
this.addEventListener(RuleViolationClicked, this.ruleGroupClicked);
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
this.addEventListener(CustomRulesetEnabled, this.customRulesetEnabled);
|
|
106
|
+
// @ts-ignore
|
|
107
|
+
this.addEventListener(RulesetSaved, this.rulesetSaved);
|
|
108
|
+
// @ts-ignore
|
|
109
|
+
this.addEventListener(AddToast, this.addToastEvent);
|
|
110
|
+
this.addEventListener(ExportRuleset, this.exportRuleset);
|
|
111
|
+
// @ts-ignore
|
|
112
|
+
this.addEventListener(RuleClicked, this.ruleClicked);
|
|
78
113
|
// hijack navigation buttons.
|
|
79
114
|
window.addEventListener('popstate', (e) => {
|
|
80
115
|
const state = e.state;
|
|
81
116
|
if (state) {
|
|
82
117
|
switch (state.view) {
|
|
83
118
|
case ActiveView.Problems:
|
|
84
|
-
this.
|
|
119
|
+
this.controlTabGroup.show(ActiveView.Problems);
|
|
85
120
|
break;
|
|
86
121
|
case ActiveView.Overview:
|
|
87
|
-
this.
|
|
122
|
+
this.controlTabGroup.show(ActiveView.Overview);
|
|
88
123
|
break;
|
|
89
124
|
}
|
|
90
125
|
}
|
|
91
126
|
});
|
|
92
127
|
//history.pushState({view: ActiveView.Overview}, "", ActiveView.Overview);
|
|
93
128
|
}
|
|
129
|
+
exportRuleset() {
|
|
130
|
+
this.exportRulesetDialog.show();
|
|
131
|
+
}
|
|
132
|
+
addToastEvent(event) {
|
|
133
|
+
this.sendToast(event.detail.toast);
|
|
134
|
+
}
|
|
135
|
+
sendToast(toast) {
|
|
136
|
+
this.toastManager.addToastManually(toast);
|
|
137
|
+
}
|
|
94
138
|
ruleGroupClicked(event) {
|
|
95
139
|
this.problemsPanel.focus();
|
|
96
140
|
const simEvent = new CustomEvent(ProblemRuleFilterChangedManual, {
|
|
@@ -103,7 +147,7 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
103
147
|
this.problemsPanel.focus();
|
|
104
148
|
this.problemList.dispatchEvent(simEvent);
|
|
105
149
|
history.pushState({ rule: event.detail.rule }, "", `/${ActiveView.Problems}?rule=${event.detail.rule}`);
|
|
106
|
-
this.
|
|
150
|
+
this.controlTabGroup.show(ActiveView.Problems);
|
|
107
151
|
}
|
|
108
152
|
ruleDocsClicked(event) {
|
|
109
153
|
const ruleId = event.detail.rule;
|
|
@@ -115,6 +159,14 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
115
159
|
event.detail.body = ruleDoc.body;
|
|
116
160
|
this.detailsDrawer.open(event.detail);
|
|
117
161
|
}
|
|
162
|
+
else {
|
|
163
|
+
this.sendToast({
|
|
164
|
+
id: crypto.randomUUID(),
|
|
165
|
+
type: ToastType.INFO,
|
|
166
|
+
title: "Rule documentation unavailable",
|
|
167
|
+
body: `Documentation for '${ruleId}' not available`
|
|
168
|
+
});
|
|
169
|
+
}
|
|
118
170
|
break;
|
|
119
171
|
case ProblemDrawerEventType.HOW_TO_FIX:
|
|
120
172
|
const howToFix = this.howToFixBag?.get(ruleId);
|
|
@@ -122,12 +174,64 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
122
174
|
event.detail.body = howToFix.howToFix;
|
|
123
175
|
this.detailsDrawer.open(event.detail);
|
|
124
176
|
}
|
|
177
|
+
else {
|
|
178
|
+
this.sendToast({
|
|
179
|
+
id: crypto.randomUUID(),
|
|
180
|
+
type: ToastType.INFO,
|
|
181
|
+
title: "How to fix unavailable",
|
|
182
|
+
body: `Information on how to fix '${ruleId}' not available`
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
case ProblemDrawerEventType.FUNCTION_DOCS:
|
|
187
|
+
const funcDocs = this.functionDocsBag?.get(ruleId);
|
|
188
|
+
if (funcDocs) {
|
|
189
|
+
event.detail.body = funcDocs.body;
|
|
190
|
+
this.detailsDrawer.open(event.detail);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.sendToast({
|
|
194
|
+
id: crypto.randomUUID(),
|
|
195
|
+
type: ToastType.INFO,
|
|
196
|
+
title: "Function documentation unavailable",
|
|
197
|
+
body: `Documentation for '${ruleId}' not available`
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
rulesetSaved(evt) {
|
|
204
|
+
// lint spec
|
|
205
|
+
if (this.docBag) {
|
|
206
|
+
const doc = this.docBag.get(DefaultDocument);
|
|
207
|
+
const config = this.RuleConfigBag?.get(RuleConfigurationBag);
|
|
208
|
+
if (evt.detail.rules) {
|
|
209
|
+
this.CustomRulesetBag?.set(CustomRulesetBag, evt.detail.rules);
|
|
210
|
+
// get ruleset config and disable everything,
|
|
211
|
+
// then enable the custom rules.
|
|
212
|
+
if (config) {
|
|
213
|
+
// extract config from the event
|
|
214
|
+
const newConfig = evt.detail.config;
|
|
215
|
+
if (newConfig && this.RuleConfigBag) {
|
|
216
|
+
newConfig.returnedRuleset = evt.detail.returnedRules;
|
|
217
|
+
this.RuleConfigBag?.set(RuleConfigurationBag, newConfig);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
RulesetService.getSessionRulesetAsYAML().then((yaml) => {
|
|
221
|
+
if (this.selectedEditorTab == "spec") {
|
|
222
|
+
this.rulesetPulse = true;
|
|
223
|
+
}
|
|
224
|
+
this.rulesetEditor.setValue(yaml, true);
|
|
225
|
+
this.fetchRulesetMap();
|
|
226
|
+
});
|
|
227
|
+
// lint the spec
|
|
228
|
+
this.lintSpec(doc);
|
|
125
229
|
}
|
|
126
230
|
}
|
|
127
231
|
}
|
|
128
232
|
lintSpec(value) {
|
|
129
233
|
this.activitySpinner.show();
|
|
130
|
-
LintingService.lintFile(value
|
|
234
|
+
LintingService.lintFile(value).then((result) => {
|
|
131
235
|
if (result && !Array.isArray(result)) {
|
|
132
236
|
this.editor.setMarkers([result]);
|
|
133
237
|
this.problemBag?.set(DocumentProblems, [result]);
|
|
@@ -144,7 +248,10 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
144
248
|
}
|
|
145
249
|
// update the overview statistics
|
|
146
250
|
LintingService.fetchStatistics().then((result) => {
|
|
147
|
-
|
|
251
|
+
let oldScore = 0;
|
|
252
|
+
if (this.problemsOverview.statistics) {
|
|
253
|
+
oldScore = this.problemsOverview.statistics.statistics.overallScore;
|
|
254
|
+
}
|
|
148
255
|
this.diagnosticBag?.set(DiagnosticBag, result);
|
|
149
256
|
this.activitySpinner.hide();
|
|
150
257
|
if (result?.remainingCredit <= 10) {
|
|
@@ -153,8 +260,29 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
153
260
|
console.warn("You are running low on credit, you will need to authenticate soon. " +
|
|
154
261
|
"" + result.remainingCredit + " credits remaining.");
|
|
155
262
|
}
|
|
263
|
+
// determine if the score went up or down and toast it!
|
|
264
|
+
this.problemsOverview.statistics = result;
|
|
265
|
+
if (this.problemsOverview.statistics) {
|
|
266
|
+
const newScore = result.statistics.overallScore;
|
|
267
|
+
if (oldScore > newScore) {
|
|
268
|
+
this.sendToast({
|
|
269
|
+
id: crypto.randomUUID(),
|
|
270
|
+
type: ToastType.SCOREDOWN,
|
|
271
|
+
body: "Your score has decreased. It is now " + newScore + "%",
|
|
272
|
+
title: "Score went down by " + (oldScore - newScore) + "%"
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (oldScore < newScore) {
|
|
276
|
+
this.sendToast({
|
|
277
|
+
id: crypto.randomUUID(),
|
|
278
|
+
type: ToastType.SCOREUP,
|
|
279
|
+
body: "Your score has increased to " + newScore + "%",
|
|
280
|
+
title: "Score went up by " + (newScore - oldScore) + "%"
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
156
284
|
}).catch((e) => {
|
|
157
|
-
console.error("statistics service is down");
|
|
285
|
+
console.error("statistics service is down", e);
|
|
158
286
|
this.platformUnavailable(e);
|
|
159
287
|
});
|
|
160
288
|
}).catch((e) => {
|
|
@@ -165,6 +293,12 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
165
293
|
if (e.instance === 'https://pb33f.io/errors/no-credit-remaining') {
|
|
166
294
|
this.statusBar.callsRemaining = 0;
|
|
167
295
|
this.statusBar.visible = true;
|
|
296
|
+
this.sendToast({
|
|
297
|
+
id: crypto.randomUUID(),
|
|
298
|
+
type: ToastType.ERROR,
|
|
299
|
+
body: "Run out of credit, please authenticate for more or wait 24 hours.",
|
|
300
|
+
title: "Credit exhausted!",
|
|
301
|
+
});
|
|
168
302
|
}
|
|
169
303
|
}
|
|
170
304
|
});
|
|
@@ -184,20 +318,45 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
184
318
|
this.errorBanner.visible = true;
|
|
185
319
|
this.unavailable = true;
|
|
186
320
|
this.problemsOverview.unavailable = true;
|
|
187
|
-
this.
|
|
321
|
+
this.controlTabGroup.show(ActiveView.Overview);
|
|
188
322
|
}
|
|
189
323
|
}
|
|
190
324
|
specClicked(event) {
|
|
191
325
|
this.detailsDrawer.close();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this.
|
|
195
|
-
|
|
196
|
-
|
|
326
|
+
if (this.selectedEditorTab == "spec") {
|
|
327
|
+
for (let i = 0; i < this.problems?.length; i++) {
|
|
328
|
+
if (this.problems[i].startLineNumber === event.detail.line) {
|
|
329
|
+
this.problemList.lineClicked(event.detail.line, true);
|
|
330
|
+
this.controlTabGroup.show(ActiveView.Problems);
|
|
331
|
+
if (this.sidebarClosed) {
|
|
332
|
+
this.toggleSidebar();
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (this.selectedEditorTab == ActiveView.Ruleset) {
|
|
339
|
+
// check ruleset map for the line number
|
|
340
|
+
const rulesetMap = this.sessionRulesetMapBag?.get(SessionRulesetMapBag);
|
|
341
|
+
if (rulesetMap) {
|
|
342
|
+
for (let i = 0; i < rulesetMap.length; i++) {
|
|
343
|
+
if (rulesetMap[i].line === event.detail.line) {
|
|
344
|
+
this.controlTabGroup.show(ActiveView.Ruleset);
|
|
345
|
+
if (this.sidebarClosed) {
|
|
346
|
+
this.toggleSidebar();
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
197
351
|
}
|
|
198
352
|
}
|
|
199
353
|
}
|
|
200
354
|
problemClicked(event) {
|
|
355
|
+
if (this.selectedEditorTab != "spec") {
|
|
356
|
+
this.editorTabGroup.show("spec");
|
|
357
|
+
this.selectedEditorTab = "spec";
|
|
358
|
+
}
|
|
359
|
+
this.controlTabGroup.show(ActiveView.Problems);
|
|
201
360
|
this.editor.editor?.revealLineInCenter(event.detail.problem.line);
|
|
202
361
|
this.editor.editor?.setPosition({
|
|
203
362
|
lineNumber: event.detail.problem.line,
|
|
@@ -207,11 +366,11 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
207
366
|
if (!event.detail.launchedFromProblems) {
|
|
208
367
|
this.problemList.lineClicked(event.detail.problem.problemObject.startLineNumber);
|
|
209
368
|
}
|
|
210
|
-
this.tabGroup.show(ActiveView.Problems);
|
|
211
369
|
}
|
|
212
370
|
loadState() {
|
|
213
371
|
LintingService.doctorEndpoint = this.doctorEndpoint;
|
|
214
372
|
FeedbackService.doctorEndpoint = this.doctorEndpoint;
|
|
373
|
+
RulesetService.doctorEndpoint = this.doctorEndpoint;
|
|
215
374
|
this.docBag = this.bagManager.getBag(DoctorDocumentBag);
|
|
216
375
|
if (this.docBag) {
|
|
217
376
|
const doc = this.docBag.get(DefaultDocument);
|
|
@@ -240,14 +399,207 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
240
399
|
this.problemsOverview.statistics = stats;
|
|
241
400
|
}
|
|
242
401
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
402
|
+
// extract rulesets from bags
|
|
403
|
+
const promises = [];
|
|
404
|
+
this.DefaultRulesetBag = this.bagManager.getBag(DefaultRulesetBag);
|
|
405
|
+
if (this.DefaultRulesetBag) {
|
|
406
|
+
const ruleset = this.DefaultRulesetBag.get(DefaultRulesetBag);
|
|
407
|
+
if (ruleset) {
|
|
408
|
+
this.defaultRuleset = ruleset;
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
promises.push(this.fetchDefaultRuleset());
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// OWASP ruleset
|
|
415
|
+
this.OWASPRulesetBag = this.bagManager.getBag(OWASPRulesetBag);
|
|
416
|
+
if (this.OWASPRulesetBag) {
|
|
417
|
+
const ruleset = this.OWASPRulesetBag.get(OWASPRulesetBag);
|
|
418
|
+
if (ruleset) {
|
|
419
|
+
this.OWASPRuleset = ruleset;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
promises.push(this.fetchOWASPRuleset());
|
|
423
|
+
}
|
|
247
424
|
}
|
|
425
|
+
// All ruleset
|
|
426
|
+
this.AllRulesetBag = this.bagManager.getBag(AllRulesetBag);
|
|
427
|
+
if (this.AllRulesetBag) {
|
|
428
|
+
const ruleset = this.AllRulesetBag.get(AllRulesetBag);
|
|
429
|
+
if (ruleset) {
|
|
430
|
+
this.AllRuleset = ruleset;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
promises.push(this.fetchAllRuleset());
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// functions
|
|
437
|
+
this.FunctionsBag = this.bagManager.getBag(FunctionsBag);
|
|
438
|
+
this.FunctionSchemaBag = this.bagManager.getBag(FunctionsSchemaBag);
|
|
439
|
+
if (this.FunctionsBag) {
|
|
440
|
+
const functions = this.FunctionsBag.get(FunctionsBag);
|
|
441
|
+
if (functions) {
|
|
442
|
+
this.functions = functions;
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
promises.push(this.fetchFunctions());
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// custom ruleset
|
|
449
|
+
this.CustomRulesetBag = this.bagManager.getBag(CustomRulesetBag);
|
|
450
|
+
if (this.CustomRulesetBag) {
|
|
451
|
+
const ruleset = this.CustomRulesetBag.get(CustomRulesetBag);
|
|
452
|
+
if (ruleset) {
|
|
453
|
+
this.CustomRuleset = ruleset;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// create rule configuration bag
|
|
457
|
+
this.RuleConfigBag = this.bagManager.getBag(RuleConfigurationBag);
|
|
458
|
+
// create rule configuration bag
|
|
459
|
+
this.sessionRulesetMapBag = this.bagManager.getBag(SessionRulesetMapBag);
|
|
460
|
+
// fire off all network requests, then configure the ruleset management.
|
|
461
|
+
Promise.all(promises).then(() => {
|
|
462
|
+
// configure rule management
|
|
463
|
+
this.manageRuleset.defaultRuleset = this.defaultRuleset;
|
|
464
|
+
this.manageRuleset.owaspRuleset = this.OWASPRuleset;
|
|
465
|
+
this.manageRuleset.allRuleset = this.AllRuleset;
|
|
466
|
+
this.manageRuleset.functions = this.functions;
|
|
467
|
+
const config = this.RuleConfigBag?.get(RuleConfigurationBag);
|
|
468
|
+
if (config) {
|
|
469
|
+
this.manageRuleset.rulesetConfig = config;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
this.manageRuleset.rulesetConfig = { ruleMapping: new Map(), allRulesSwitch: true };
|
|
473
|
+
}
|
|
474
|
+
this.manageRuleset.buildRulesets();
|
|
475
|
+
if (this.CustomRuleset && this.CustomRuleset.rules.size > 0) {
|
|
476
|
+
this.manageRuleset.customRuleset = this.CustomRuleset;
|
|
477
|
+
}
|
|
478
|
+
// do nothing with this for now. we just need to make sure the default ruleset
|
|
479
|
+
// has a session.
|
|
480
|
+
this.fetchSessionRulesetAsYaml().then((result) => {
|
|
481
|
+
this.loadingOverlay.hide();
|
|
482
|
+
this.rulesetEditor?.setValue(result, true);
|
|
483
|
+
this.fetchRulesetMap();
|
|
484
|
+
});
|
|
485
|
+
});
|
|
248
486
|
// refresh state for how to fix.
|
|
249
487
|
this.fetchDocs();
|
|
250
488
|
}
|
|
489
|
+
customRulesetEnabled(event) {
|
|
490
|
+
const customRS = { rules: new Map() };
|
|
491
|
+
customRS.id = crypto.randomUUID();
|
|
492
|
+
customRS.owner = this.session.sessionId;
|
|
493
|
+
event.detail.rules.forEach((rule) => {
|
|
494
|
+
if (rule.rule.id != rule.ruleId) {
|
|
495
|
+
rule.rule.id = rule.ruleId;
|
|
496
|
+
}
|
|
497
|
+
customRS.rules.set(rule.ruleId, rule.rule);
|
|
498
|
+
});
|
|
499
|
+
// write state
|
|
500
|
+
this.CustomRulesetBag?.set(CustomRulesetBag, customRS);
|
|
501
|
+
if (event.detail.ruleConfig) {
|
|
502
|
+
this.RuleConfigBag?.set(RuleConfigurationBag, event.detail.ruleConfig);
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
this.RuleConfigBag?.reset();
|
|
506
|
+
// reset session ruleset
|
|
507
|
+
RulesetService.resetSessionRuleset().then(() => {
|
|
508
|
+
this.sendToast({
|
|
509
|
+
id: crypto.randomUUID(),
|
|
510
|
+
type: ToastType.INFO,
|
|
511
|
+
body: "Ruleset has been reset to the default",
|
|
512
|
+
title: "Ruleset reset",
|
|
513
|
+
});
|
|
514
|
+
if (this.docBag) {
|
|
515
|
+
const doc = this.docBag.get(DefaultDocument);
|
|
516
|
+
this.lintSpec(doc);
|
|
517
|
+
// extract bootstrap ruleset
|
|
518
|
+
// LintingService.bootstrapEditor().then((result) => {
|
|
519
|
+
// this.docBag?.set(DefaultDocument, result);
|
|
520
|
+
// this.editor.setValue(result, true);
|
|
521
|
+
// this.lintSpec(doc!);
|
|
522
|
+
// })
|
|
523
|
+
}
|
|
524
|
+
this.fetchSessionRulesetAsYaml().then((result) => {
|
|
525
|
+
this.rulesetEditor?.setValue(result, true);
|
|
526
|
+
});
|
|
527
|
+
}).catch((e) => {
|
|
528
|
+
this.sendToast({
|
|
529
|
+
id: crypto.randomUUID(),
|
|
530
|
+
type: ToastType.ERROR,
|
|
531
|
+
body: e.detail,
|
|
532
|
+
title: "Cannot reset ruleset",
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async fetchSessionRulesetAsYaml() {
|
|
538
|
+
return new Promise(async (resolve) => {
|
|
539
|
+
RulesetService.getSessionRulesetAsYAML().then((result) => {
|
|
540
|
+
resolve(result);
|
|
541
|
+
}).catch((e) => {
|
|
542
|
+
console.error("cannot fetch session ruleset: ", e.title);
|
|
543
|
+
resolve("");
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
async fetchFunctions() {
|
|
548
|
+
return new Promise(async (resolve) => {
|
|
549
|
+
RulesetService.getFunctions().then((result) => {
|
|
550
|
+
this.functions = result;
|
|
551
|
+
this.FunctionsBag?.set(FunctionsBag, result);
|
|
552
|
+
// extract schemas for each function
|
|
553
|
+
const promises = [];
|
|
554
|
+
result.forEach((functionId) => {
|
|
555
|
+
promises.push(this.fetchFunctionSchema(functionId));
|
|
556
|
+
});
|
|
557
|
+
Promise.all(promises).then(() => {
|
|
558
|
+
resolve(result);
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
async fetchFunctionSchema(functionId) {
|
|
564
|
+
return new Promise(async (resolve) => {
|
|
565
|
+
RulesetService.getFunctionSchema(functionId).then((result) => {
|
|
566
|
+
let m = this.FunctionSchemaBag?.get(FunctionsSchemaBag);
|
|
567
|
+
if (!m) {
|
|
568
|
+
m = new Map();
|
|
569
|
+
}
|
|
570
|
+
m.set(functionId, result);
|
|
571
|
+
this.FunctionSchemaBag?.set(FunctionsSchemaBag, m);
|
|
572
|
+
resolve(result);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
async fetchDefaultRuleset() {
|
|
577
|
+
return new Promise(async (resolve) => {
|
|
578
|
+
RulesetService.getDefaultRuleset().then((result) => {
|
|
579
|
+
this.defaultRuleset = result;
|
|
580
|
+
this.DefaultRulesetBag?.set(DefaultRulesetBag, result);
|
|
581
|
+
resolve(result);
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
async fetchOWASPRuleset() {
|
|
586
|
+
return new Promise(async (resolve) => {
|
|
587
|
+
RulesetService.getOWASPRuleset().then((result) => {
|
|
588
|
+
this.OWASPRuleset = result;
|
|
589
|
+
this.OWASPRulesetBag?.set(OWASPRulesetBag, result);
|
|
590
|
+
resolve(result);
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
async fetchAllRuleset() {
|
|
595
|
+
return new Promise(async (resolve) => {
|
|
596
|
+
RulesetService.getAllRuleset().then((result) => {
|
|
597
|
+
this.AllRuleset = result;
|
|
598
|
+
this.AllRulesetBag?.set(AllRulesetBag, result);
|
|
599
|
+
resolve(result);
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
}
|
|
251
603
|
fetchDocs() {
|
|
252
604
|
this.activitySpinner.show();
|
|
253
605
|
// the first this we need to do is start a session
|
|
@@ -268,14 +620,20 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
268
620
|
console.error("documentation service is down");
|
|
269
621
|
});
|
|
270
622
|
this.ruleDocsBag = this.bagManager.getBag(RuleDocumentationBag);
|
|
623
|
+
this.functionDocsBag = this.bagManager.getBag(FunctionDocumentationBag);
|
|
271
624
|
// populate docs via worker.
|
|
272
625
|
this.ruleDocsWorker.addEventListener("message", (event) => {
|
|
273
626
|
const data = event.data;
|
|
274
627
|
if (data) {
|
|
275
628
|
data.forEach((doc) => {
|
|
276
|
-
|
|
277
|
-
|
|
629
|
+
if (doc.ruleId) {
|
|
630
|
+
this.ruleDocsBag?.set(doc.ruleId, doc);
|
|
631
|
+
}
|
|
632
|
+
if (doc.functionId) {
|
|
633
|
+
this.functionDocsBag?.set(doc.functionId, doc);
|
|
634
|
+
}
|
|
278
635
|
});
|
|
636
|
+
this.activitySpinner.hide();
|
|
279
637
|
}
|
|
280
638
|
});
|
|
281
639
|
this.ruleDocsWorker.postMessage({ start: true, endpoint: this.doctorEndpoint });
|
|
@@ -284,7 +642,114 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
284
642
|
console.error("cannot start session");
|
|
285
643
|
});
|
|
286
644
|
}
|
|
645
|
+
fetchRulesetMap() {
|
|
646
|
+
RulesetService.getSessionRulesetMap().then((result) => {
|
|
647
|
+
this.sessionRulesetMapBag?.set(SessionRulesetMapBag, result);
|
|
648
|
+
}).catch(() => {
|
|
649
|
+
console.warn("map is empty, or unavailable, Did you submit an empty ruleset?");
|
|
650
|
+
this.sendToast({
|
|
651
|
+
id: crypto.randomUUID(),
|
|
652
|
+
type: ToastType.WARNING,
|
|
653
|
+
body: "map is empty, or unavailable, Did you submit an empty ruleset?",
|
|
654
|
+
title: "Issue fetching ruleset map"
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
ruleClicked(evt) {
|
|
659
|
+
// get rule map from bag
|
|
660
|
+
const map = this.sessionRulesetMapBag?.get(SessionRulesetMapBag);
|
|
661
|
+
if (map) {
|
|
662
|
+
const rule = map.find((r) => r.ruleId === evt.detail.ruleId);
|
|
663
|
+
if (rule) {
|
|
664
|
+
this.selectedEditorTab = "ruleset";
|
|
665
|
+
this.editorTabGroup.show(ActiveView.Ruleset);
|
|
666
|
+
this.rulesetEditor.editor?.setPosition({ lineNumber: rule.line, column: rule.start });
|
|
667
|
+
this.rulesetEditor.editor?.revealLineInCenter(rule.line);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
rulesetChanged() {
|
|
672
|
+
clearTimeout(this.bounceId);
|
|
673
|
+
this.bounceId = window.setTimeout(() => {
|
|
674
|
+
const ruleset = this.rulesetEditor.editor?.getValue();
|
|
675
|
+
RulesetService.validateRuleset(ruleset).then((result) => {
|
|
676
|
+
// clear any problems
|
|
677
|
+
this.manageRuleset.clearRuleProblems();
|
|
678
|
+
// create a new custom ruleset with a map of rules
|
|
679
|
+
let customRS = { rules: new Map() };
|
|
680
|
+
customRS.id = crypto.randomUUID();
|
|
681
|
+
customRS.owner = this.session.sessionId;
|
|
682
|
+
customRS.rules = new Map(Object.entries(result.rules));
|
|
683
|
+
customRS.rules.forEach((rule, id) => {
|
|
684
|
+
rule.id = id; // set the id to the key
|
|
685
|
+
});
|
|
686
|
+
// write state
|
|
687
|
+
this.CustomRulesetBag?.set(CustomRulesetBag, customRS);
|
|
688
|
+
this.CustomRuleset = customRS;
|
|
689
|
+
if (customRS.rules.size > 0) {
|
|
690
|
+
// rebuild the ruleset management
|
|
691
|
+
this.manageRuleset.customRulesetManual = customRS;
|
|
692
|
+
}
|
|
693
|
+
this.selectedEditorTab = ActiveView.Ruleset;
|
|
694
|
+
this.controlTabGroup.show(ActiveView.Ruleset);
|
|
695
|
+
this.rulesetEditor.setMarkers([]);
|
|
696
|
+
this.fetchRulesetMap();
|
|
697
|
+
}).catch((e) => {
|
|
698
|
+
this.sendToast({
|
|
699
|
+
id: crypto.randomUUID(),
|
|
700
|
+
type: ToastType.ERROR,
|
|
701
|
+
body: e.detail,
|
|
702
|
+
title: e.title
|
|
703
|
+
});
|
|
704
|
+
console.error('unable to validate ruleset', e);
|
|
705
|
+
// process bad rules
|
|
706
|
+
const ruleErrors = e.body;
|
|
707
|
+
const markers = [];
|
|
708
|
+
ruleErrors?.forEach((err) => {
|
|
709
|
+
markers.push({
|
|
710
|
+
severity: MarkerSeverity.Error,
|
|
711
|
+
message: err.ruleError,
|
|
712
|
+
startLineNumber: err.line,
|
|
713
|
+
startColumn: err.startCol,
|
|
714
|
+
endLineNumber: err.line,
|
|
715
|
+
endColumn: err.endCol
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
// convert to JSON
|
|
719
|
+
RulesetService.convertToJSON(ruleset).then((json) => {
|
|
720
|
+
// create a new custom ruleset with a map of rules
|
|
721
|
+
let customRS = { rules: new Map() };
|
|
722
|
+
customRS.id = crypto.randomUUID();
|
|
723
|
+
customRS.owner = this.session.sessionId;
|
|
724
|
+
customRS.rules = new Map(Object.entries(json.rules));
|
|
725
|
+
customRS.rules.forEach((rule, id) => {
|
|
726
|
+
if (rule.id != id)
|
|
727
|
+
rule.id = id; // set the id to the key
|
|
728
|
+
});
|
|
729
|
+
this.CustomRuleset = customRS;
|
|
730
|
+
if (customRS.rules.size > 0) {
|
|
731
|
+
// rebuild the ruleset management
|
|
732
|
+
this.manageRuleset.customRulesetManual = customRS;
|
|
733
|
+
}
|
|
734
|
+
this.rulesetEditor.setMarkers([]);
|
|
735
|
+
// clear any problems
|
|
736
|
+
this.manageRuleset.clearRuleProblems();
|
|
737
|
+
// set problems.
|
|
738
|
+
this.rulesetEditor.setMarkers(markers);
|
|
739
|
+
this.manageRuleset.processBadRules(ruleErrors);
|
|
740
|
+
this.controlTabGroup.show(ActiveView.Ruleset);
|
|
741
|
+
}).catch((e) => {
|
|
742
|
+
console.error("unable to convert ruleset to JSON", e);
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
}, this.debounceTimeRuleset);
|
|
746
|
+
}
|
|
287
747
|
specChanged(event) {
|
|
748
|
+
const editor = this.editorMap.get(event.detail.id);
|
|
749
|
+
if (editor && event.detail.id === 'ruleset') {
|
|
750
|
+
this.rulesetChanged();
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
288
753
|
if (this.docBag) {
|
|
289
754
|
this.docBag.set(DefaultDocument, event.detail.content);
|
|
290
755
|
}
|
|
@@ -293,30 +758,114 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
293
758
|
this.lintSpec(event.detail.content);
|
|
294
759
|
}, this.debounceTime);
|
|
295
760
|
}
|
|
296
|
-
toggleOWASP() {
|
|
297
|
-
this.OWASPEnabled = this.owaspSwitch.checked;
|
|
298
|
-
this.OWASPBag?.set(OWASPBag, this.OWASPEnabled);
|
|
299
|
-
if (this.docBag) {
|
|
300
|
-
const doc = this.docBag.get(DefaultDocument);
|
|
301
|
-
this.lintSpec(doc);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
761
|
boostrap() {
|
|
305
762
|
if (this.editor.getValue() === '') {
|
|
306
763
|
LintingService.bootstrapEditor().then((result) => {
|
|
307
764
|
this.editor.setValue(result, true);
|
|
308
765
|
this.specChanged(new CustomEvent(EditorUpdated, {
|
|
309
766
|
detail: {
|
|
310
|
-
content: result
|
|
767
|
+
content: result,
|
|
768
|
+
id: "spec"
|
|
311
769
|
}
|
|
312
770
|
}));
|
|
313
771
|
});
|
|
314
772
|
}
|
|
315
773
|
}
|
|
774
|
+
exportJSON() {
|
|
775
|
+
let jsonRS;
|
|
776
|
+
if (this.RuleConfigBag) {
|
|
777
|
+
const config = this.RuleConfigBag.get(RuleConfigurationBag);
|
|
778
|
+
if (config && config.returnedRuleset) {
|
|
779
|
+
jsonRS = config.returnedRuleset;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (jsonRS) {
|
|
783
|
+
let reportData = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonRS, null, 2));
|
|
784
|
+
this.downloadRulesetLink.download = "ruleset.json";
|
|
785
|
+
this.downloadRulesetLink.href = reportData;
|
|
786
|
+
this.downloadRulesetLink.click();
|
|
787
|
+
this.exportRulesetDialog.hide();
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
exportYAML() {
|
|
791
|
+
RulesetService.getSessionRulesetAsYAML().then((yaml) => {
|
|
792
|
+
let reportData = "data:text/yaml;charset=utf-8," + encodeURIComponent(yaml);
|
|
793
|
+
this.downloadRulesetLink.download = "ruleset.yaml";
|
|
794
|
+
this.downloadRulesetLink.href = reportData;
|
|
795
|
+
this.downloadRulesetLink.click();
|
|
796
|
+
this.exportRulesetDialog.hide();
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
confirmExport() {
|
|
800
|
+
switch (this.exportSelection.value) {
|
|
801
|
+
case "json":
|
|
802
|
+
this.exportJSON();
|
|
803
|
+
break;
|
|
804
|
+
case "yaml":
|
|
805
|
+
this.exportYAML();
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
316
809
|
closeWelcome() {
|
|
317
810
|
this.welcomeBox.hide();
|
|
318
811
|
localStorage.setItem("pb33f-doctor-welcome", "closed");
|
|
319
812
|
}
|
|
813
|
+
selectEditorTab(event) {
|
|
814
|
+
this.selectedEditorTab = event.detail.name;
|
|
815
|
+
switch (event.detail.name) {
|
|
816
|
+
case ActiveView.Ruleset:
|
|
817
|
+
if (this.rulesetPulse) {
|
|
818
|
+
this.rulesetPulse = false;
|
|
819
|
+
}
|
|
820
|
+
this.controlTabGroup.show(ActiveView.Ruleset);
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
selectControlTab(event) {
|
|
825
|
+
switch (event.detail.name) {
|
|
826
|
+
case ActiveView.Problems:
|
|
827
|
+
case ActiveView.Overview:
|
|
828
|
+
if (this.selectedEditorTab != "spec") {
|
|
829
|
+
this.selectedEditorTab = "spec";
|
|
830
|
+
this.editorTabGroup.show("spec");
|
|
831
|
+
}
|
|
832
|
+
break;
|
|
833
|
+
case ActiveView.Ruleset:
|
|
834
|
+
if (this.selectedEditorTab != ActiveView.Ruleset) {
|
|
835
|
+
this.editorTabGroup.show(ActiveView.Ruleset);
|
|
836
|
+
this.selectedEditorTab = ActiveView.Ruleset;
|
|
837
|
+
}
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
toggleSidebar() {
|
|
842
|
+
const splitPanel = this.shadowRoot?.querySelector('sl-split-panel');
|
|
843
|
+
if (!this.sidebarClosed) {
|
|
844
|
+
if (splitPanel) {
|
|
845
|
+
this.collapseButton.name = "chevron-bar-left";
|
|
846
|
+
this.problemsDataDiv.style.display = "none";
|
|
847
|
+
splitPanel.style.setProperty('--min', '50px');
|
|
848
|
+
splitPanel.style.setProperty('--max', 'calc(100vw - 50px)');
|
|
849
|
+
splitPanel.position = 100;
|
|
850
|
+
splitPanel.disabled = true;
|
|
851
|
+
this.splitDivider.style.display = "none";
|
|
852
|
+
this.activitySpinner.classList.add('spinner-draw-closed');
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
if (splitPanel) {
|
|
857
|
+
this.collapseButton.name = "chevron-bar-right";
|
|
858
|
+
splitPanel.style.setProperty('--min', '600px');
|
|
859
|
+
splitPanel.style.setProperty('--max', 'calc(100vw - 600px)');
|
|
860
|
+
splitPanel.position = 60;
|
|
861
|
+
splitPanel.disabled = false;
|
|
862
|
+
this.problemsDataDiv.style.display = "block";
|
|
863
|
+
this.splitDivider.style.display = "block";
|
|
864
|
+
this.activitySpinner.classList.remove('spinner-draw-closed');
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
this.sidebarClosed = !this.sidebarClosed;
|
|
868
|
+
}
|
|
320
869
|
render() {
|
|
321
870
|
let overlay = html ``;
|
|
322
871
|
if (this.unavailable) {
|
|
@@ -335,48 +884,75 @@ let TheDoctor = class TheDoctor extends LitElement {
|
|
|
335
884
|
if (welcomeClosed) {
|
|
336
885
|
welcomeBox = html ``;
|
|
337
886
|
}
|
|
887
|
+
let rulesetPulsePill = html ``;
|
|
888
|
+
if (this.rulesetPulse) {
|
|
889
|
+
rulesetPulsePill = html `
|
|
890
|
+
<sl-badge pulse></sl-badge>`;
|
|
891
|
+
}
|
|
338
892
|
return html `
|
|
339
893
|
${welcomeBox}
|
|
894
|
+
${this.toastManager}
|
|
895
|
+
<sl-dialog open id="loading-overlay" class="dialog-overview"
|
|
896
|
+
style="--width: 30vw" no-header>
|
|
897
|
+
<h3 class="loading">OpenAPI Doctor is booting...</h3>
|
|
898
|
+
</sl-dialog>
|
|
899
|
+
<sl-dialog id="export-ruleset" label="Export Ruleset" class="dialog-overview"
|
|
900
|
+
style="--width: 50vw">
|
|
901
|
+
<div class="export right">
|
|
902
|
+
<div class="export-block">
|
|
903
|
+
<sl-radio-group name="ruleset" value="json" id="export-ruleset-radio">
|
|
904
|
+
<sl-radio-button value="json">JSON
|
|
905
|
+
</sl-radio-button>
|
|
906
|
+
<sl-radio-button value="yaml">YAML
|
|
907
|
+
</sl-radio-button>
|
|
908
|
+
</sl-radio-group>
|
|
909
|
+
</div>
|
|
910
|
+
<div class="export-block">
|
|
911
|
+
<sl-button @click=${this.confirmExport} variant="primary" size="small" style="float: left">
|
|
912
|
+
Export
|
|
913
|
+
</sl-button>
|
|
914
|
+
<a id="download-ruleset" style="display:none"></a>
|
|
915
|
+
</div>
|
|
916
|
+
</div>
|
|
917
|
+
</sl-dialog>
|
|
340
918
|
<div class="doctor">
|
|
341
919
|
${this.activitySpinner}
|
|
342
920
|
${this.errorBanner}
|
|
343
921
|
${overlay}
|
|
344
|
-
<sl-split-panel class="split-panel ${this.unavailable ? 'unavailable' : ''}">
|
|
345
|
-
<sl-icon slot="divider" name="grip-vertical"></sl-icon>
|
|
922
|
+
<sl-split-panel class="split-panel ${this.unavailable ? 'unavailable' : ''}" position="60">
|
|
923
|
+
<sl-icon id="split-divider" slot="divider" name="grip-vertical"></sl-icon>
|
|
346
924
|
<div class="editor" slot="start">
|
|
347
925
|
${this.detailsDrawer}
|
|
348
|
-
${this.editor
|
|
926
|
+
<sl-tab-group class="tab-group" @sl-tab-show="${this.selectEditorTab}" id="editor-controls">
|
|
927
|
+
<sl-tab slot="nav" panel="spec" class="tab" id="spec">OpenAPI Spec</sl-tab>
|
|
928
|
+
<sl-tab slot="nav" panel="ruleset" class="tab" id="ruleset">Ruleset ${rulesetPulsePill}
|
|
929
|
+
</sl-tab>
|
|
930
|
+
<sl-tab-panel name="spec" class="tab-panel">${this.editor}</sl-tab-panel>
|
|
931
|
+
<sl-tab-panel name="ruleset" class="tab-panel">${this.rulesetEditor}</sl-tab-panel>
|
|
932
|
+
</sl-tab-group>
|
|
349
933
|
</div>
|
|
350
934
|
<div class="problems" slot="end">
|
|
351
|
-
<
|
|
352
|
-
<sl-tab
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
<
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
Rules</a>?
|
|
366
|
-
</sl-switch>
|
|
367
|
-
</div>
|
|
368
|
-
</sl-tab-panel>
|
|
369
|
-
<sl-tab-panel name="feedback" class="tab-panel">
|
|
370
|
-
${this.feedback}
|
|
371
|
-
</sl-tab-panel>
|
|
372
|
-
</sl-tab-group>
|
|
935
|
+
<div class="problems-data">
|
|
936
|
+
<sl-tab-group class="tab-group" id="manager-controls" @sl-tab-show="${this.selectControlTab}">
|
|
937
|
+
<sl-tab slot="nav" panel="overview" class="tab" id="overviewPanel" active>Overview
|
|
938
|
+
</sl-tab>
|
|
939
|
+
<sl-tab slot="nav" panel="problems" class="tab" id="problemsPanel">Problems</sl-tab>
|
|
940
|
+
<sl-tab slot="nav" panel="ruleset" class="tab" id="rulesetPanel">Rules</sl-tab>
|
|
941
|
+
<sl-tab slot="nav" panel="feedback" class="tab" id="feedbackPanel">Feedback</sl-tab>
|
|
942
|
+
<sl-tab-panel name="overview" class="tab-panel">${this.problemsOverview}</sl-tab-panel>
|
|
943
|
+
<sl-tab-panel name="problems" class="tab-panel">${this.problemList}</sl-tab-panel>
|
|
944
|
+
<sl-tab-panel name="ruleset" class="tab-panel">${this.manageRuleset}</sl-tab-panel>
|
|
945
|
+
<sl-tab-panel name="feedback" class="tab-panel">${this.feedback}</sl-tab-panel>
|
|
946
|
+
</sl-tab-group>
|
|
947
|
+
</div>
|
|
948
|
+
<sl-icon-button class="collapse-side" name="chevron-bar-right" @click="${this.toggleSidebar}"></sl-icon-button>
|
|
373
949
|
</div>
|
|
374
950
|
</sl-split-panel>
|
|
375
951
|
${this.statusBar}
|
|
376
952
|
</div>`;
|
|
377
953
|
}
|
|
378
954
|
};
|
|
379
|
-
TheDoctor.styles = [theDoctorCss, linksCss];
|
|
955
|
+
TheDoctor.styles = [theDoctorCss, linksCss, dialogCss, buttonCss, radioGroupsCss];
|
|
380
956
|
__decorate([
|
|
381
957
|
query('#overviewPanel')
|
|
382
958
|
], TheDoctor.prototype, "overviewPanel", void 0);
|
|
@@ -384,20 +960,44 @@ __decorate([
|
|
|
384
960
|
query('#problemsPanel')
|
|
385
961
|
], TheDoctor.prototype, "problemsPanel", void 0);
|
|
386
962
|
__decorate([
|
|
387
|
-
query('sl-tab-group')
|
|
388
|
-
], TheDoctor.prototype, "
|
|
963
|
+
query('sl-tab-group#manager-controls')
|
|
964
|
+
], TheDoctor.prototype, "controlTabGroup", void 0);
|
|
389
965
|
__decorate([
|
|
390
|
-
query('sl-
|
|
391
|
-
], TheDoctor.prototype, "
|
|
966
|
+
query('sl-tab-group#editor-controls')
|
|
967
|
+
], TheDoctor.prototype, "editorTabGroup", void 0);
|
|
392
968
|
__decorate([
|
|
393
969
|
query('pb33f-attention-box#welcome')
|
|
394
970
|
], TheDoctor.prototype, "welcomeBox", void 0);
|
|
971
|
+
__decorate([
|
|
972
|
+
query('sl-radio-group#export-ruleset-radio')
|
|
973
|
+
], TheDoctor.prototype, "exportSelection", void 0);
|
|
974
|
+
__decorate([
|
|
975
|
+
query('a#download-ruleset')
|
|
976
|
+
], TheDoctor.prototype, "downloadRulesetLink", void 0);
|
|
977
|
+
__decorate([
|
|
978
|
+
query('sl-dialog#loading-overlay')
|
|
979
|
+
], TheDoctor.prototype, "loadingOverlay", void 0);
|
|
980
|
+
__decorate([
|
|
981
|
+
query('div.problems-data')
|
|
982
|
+
], TheDoctor.prototype, "problemsDataDiv", void 0);
|
|
395
983
|
__decorate([
|
|
396
984
|
property({ type: Boolean })
|
|
397
985
|
], TheDoctor.prototype, "unavailable", void 0);
|
|
398
986
|
__decorate([
|
|
399
987
|
property()
|
|
400
988
|
], TheDoctor.prototype, "doctorEndpoint", void 0);
|
|
989
|
+
__decorate([
|
|
990
|
+
query('sl-dialog#export-ruleset')
|
|
991
|
+
], TheDoctor.prototype, "exportRulesetDialog", void 0);
|
|
992
|
+
__decorate([
|
|
993
|
+
query('sl-icon-button.collapse-side')
|
|
994
|
+
], TheDoctor.prototype, "collapseButton", void 0);
|
|
995
|
+
__decorate([
|
|
996
|
+
query('sl-icon#split-divider')
|
|
997
|
+
], TheDoctor.prototype, "splitDivider", void 0);
|
|
998
|
+
__decorate([
|
|
999
|
+
state()
|
|
1000
|
+
], TheDoctor.prototype, "rulesetPulse", void 0);
|
|
401
1001
|
TheDoctor = __decorate([
|
|
402
1002
|
customElement("pb33f-doctor")
|
|
403
1003
|
], TheDoctor);
|