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