@pb33f/cowboy-components 0.3.3 → 0.3.4

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.
@@ -44,6 +44,11 @@ import { ModelTree } from "../model-tree/tree.js";
44
44
  import { ExplorerComponent } from "../visualizer/explorer.js";
45
45
  import { RenderedNodeComponent } from "../model-renderer/rendered-node.js";
46
46
  import tabsCss from "../../css/tabs.css.js";
47
+ import { CreateBus } from "@pb33f/ranch";
48
+ import { Command, DoctorServiceChannel, isBrokerResponse, QueuePrefix, SpecStreamChannel } from "../../model/channels";
49
+ import formsCss from "../../css/forms.css";
50
+ import { PixelSparks } from "./sparks.js";
51
+ import spinnerCss from "../../css/spinner.css";
47
52
  export const GraphBag = "pb33f-doctor-graph";
48
53
  export const DoctorDocumentBag = "pb33f-doctor-editor";
49
54
  export const HowToFixBag = "pb33f-doctor-howtofix";
@@ -67,6 +72,15 @@ let TheDoctor = class TheDoctor extends LitElement {
67
72
  this.debounceTime = 400;
68
73
  this.debounceTimeRuleset = 900;
69
74
  this.bounceId = 0;
75
+ this.useTLS = false;
76
+ // bus it up
77
+ this.bus = CreateBus();
78
+ this.doctorServiceChannel = this.bus.createChannel(DoctorServiceChannel);
79
+ this.specStreamChannel = this.bus.createChannel(SpecStreamChannel);
80
+ this.bus.mapChannelToBrokerDestination(QueuePrefix + DoctorServiceChannel, DoctorServiceChannel);
81
+ this.bus.mapChannelToBrokerDestination(QueuePrefix + SpecStreamChannel, SpecStreamChannel);
82
+ this.doctorChannelSubscription = this.doctorServiceChannel.subscribe(this.doctorServiceHandler());
83
+ this.specChannelSubscription = this.specStreamChannel.subscribe(this.specStreamHandler());
70
84
  // create a stateful bag manager
71
85
  this.bagManager = CreateBagManager(true);
72
86
  this.bagManager.loadStatefulBags().then(this.loadState.bind(this));
@@ -140,6 +154,19 @@ let TheDoctor = class TheDoctor extends LitElement {
140
154
  this.explorer.equalizer.addEventListener(ExplorerEqualizerChanged, this.filterTreeModel.bind(this));
141
155
  //@ts-ignore
142
156
  this.explorer.equalizer.addEventListener(ExplorerEqualizerFiltered, this.filterTreeModel.bind(this));
157
+ // extract port from session storage.
158
+ this.busPort = localStorage.getItem("pb33f-doctor-port");
159
+ this.busHost = localStorage.getItem("pb33f-doctor-host");
160
+ if (!this.busPort) {
161
+ this.busPort = "9090"; // default port
162
+ }
163
+ if (!this.busHost) {
164
+ this.busHost = "localhost"; // default host
165
+ }
166
+ const useTLS = localStorage.getItem("pb33f-doctor-tls");
167
+ if (useTLS && useTLS == 'true') {
168
+ this.useTLS = true;
169
+ }
143
170
  // hijack navigation buttons.
144
171
  window.addEventListener('popstate', (e) => {
145
172
  const state = e.state;
@@ -155,9 +182,57 @@ let TheDoctor = class TheDoctor extends LitElement {
155
182
  }
156
183
  });
157
184
  }
185
+ whoAmI() {
186
+ this.bus.publish({
187
+ destination: "/p/q/" + DoctorServiceChannel,
188
+ body: JSON.stringify({ request: Command.WhoAmI }),
189
+ });
190
+ }
191
+ connectToBroker() {
192
+ let protocol = "ws://";
193
+ if (this.useTLS) {
194
+ protocol = "wss://";
195
+ }
196
+ // configure wiretap broker.
197
+ const config = {
198
+ brokerURL: protocol + this.busHost + ':' + this.busPort + '/ranch',
199
+ heartbeatIncoming: 0,
200
+ heartbeatOutgoing: 0,
201
+ onConnect: () => {
202
+ console.log("💊 Connected to the %cOpenAPI Doctor%c, we are ready to communicate.", 'background: #0d1117; color: #62C4FFFF; font-weight: bold', 'color: default');
203
+ this.whoAmI();
204
+ }
205
+ };
206
+ this.bus.connectToBroker(config);
207
+ }
158
208
  addClickTrack(node) {
159
209
  history.pushState({ activeNode: node.idHash }, "", `?view=explore&node=${node.idHash}`);
160
210
  }
211
+ doctorServiceHandler() {
212
+ return (msg) => {
213
+ if (msg.payload?.payload != null) {
214
+ if (isBrokerResponse(msg.payload.payload)) {
215
+ this.brokerConnectionId = msg.payload.payload.broker;
216
+ console.log("💊 Welcome patient %c" + this.brokerConnectionId, 'color: #62C4FFFF; font-weight: bold');
217
+ this.boostrap();
218
+ this.loadingOverlay.hide();
219
+ }
220
+ }
221
+ };
222
+ }
223
+ specStreamHandler() {
224
+ return (msg) => {
225
+ if (msg.payload?.payload != null) {
226
+ // base64 decode the payload and update the editor!
227
+ const decoded = atob(msg.payload.payload);
228
+ if (this.docBag) {
229
+ this.docBag.set(DefaultDocument, decoded);
230
+ }
231
+ this.editor?.setValue(decoded, true);
232
+ this.requestUpdate();
233
+ }
234
+ };
235
+ }
161
236
  filterTreeModel(event) {
162
237
  this.filteredNodes = new Map();
163
238
  event.detail.graph.nodes.forEach((node) => {
@@ -362,9 +437,19 @@ let TheDoctor = class TheDoctor extends LitElement {
362
437
  }
363
438
  }
364
439
  }
365
- lintSpec(value) {
440
+ lintSpec(value, url) {
366
441
  this.activitySpinner.show();
367
- LintingService.lintFile(value).then((result) => {
442
+ if (url) {
443
+ this.urlProblem.style.display = 'none';
444
+ this.urlOverlay.style.display = "block";
445
+ this.urlSpinner.style.display = "block";
446
+ }
447
+ LintingService.lintFile(value, this.brokerConnectionId, url).then((result) => {
448
+ if (url) {
449
+ this.urlOverlay.style.display = "none";
450
+ this.urlSpinner.style.display = "none";
451
+ this.urlProblem.style.display = 'none';
452
+ }
368
453
  if (result && !Array.isArray(result)) {
369
454
  this.editor.setMarkers([result]);
370
455
  this.problemBag?.set(DocumentProblems, [result]);
@@ -424,21 +509,26 @@ let TheDoctor = class TheDoctor extends LitElement {
424
509
  this.platformUnavailable(e);
425
510
  });
426
511
  }).catch((e) => {
427
- this.platformUnavailable(e);
428
- console.error("so sorry, the doctor cannot see you right now, the clinic is closed.");
429
- if (e) {
430
- console.error(e.detail);
431
- if (e.instance === 'https://pb33f.io/errors/no-credit-remaining') {
432
- this.statusBar.callsRemaining = 0;
433
- this.statusBar.visible = true;
434
- this.sendToast({
435
- id: crypto.randomUUID(),
436
- type: ToastType.ERROR,
437
- body: "Run out of credit, please authenticate for more or wait 24 hours.",
438
- title: "Credit exhausted!",
439
- });
512
+ if (!url) {
513
+ this.platformUnavailable(e);
514
+ console.error("so sorry, the doctor cannot see you right now, the clinic is closed.");
515
+ if (e) {
516
+ console.error(e.detail);
517
+ if (e.instance === 'https://pb33f.io/errors/no-credit-remaining') {
518
+ this.statusBar.callsRemaining = 0;
519
+ this.statusBar.visible = true;
520
+ this.sendToast({
521
+ id: crypto.randomUUID(),
522
+ type: ToastType.ERROR,
523
+ body: "Run out of credit, please authenticate for more or wait 24 hours.",
524
+ title: "Credit exhausted!",
525
+ });
526
+ }
440
527
  }
441
528
  }
529
+ else {
530
+ this.showUrlError(e);
531
+ }
442
532
  });
443
533
  }
444
534
  platformUnavailable(error) {
@@ -536,6 +626,7 @@ let TheDoctor = class TheDoctor extends LitElement {
536
626
  FeedbackService.doctorEndpoint = this.doctorEndpoint;
537
627
  RulesetService.doctorEndpoint = this.doctorEndpoint;
538
628
  ModelService.doctorEndpoint = this.doctorEndpoint;
629
+ this.connectToBroker();
539
630
  this.graphBag = this.bagManager.getBag(GraphBag);
540
631
  this.docBag = this.bagManager.getBag(DoctorDocumentBag);
541
632
  if (this.docBag) {
@@ -644,7 +735,6 @@ let TheDoctor = class TheDoctor extends LitElement {
644
735
  else {
645
736
  this.manageRuleset.rulesetConfig = { ruleMapping: new Map(), allRulesSwitch: true };
646
737
  }
647
- this.loadingOverlay.hide();
648
738
  setTimeout(() => {
649
739
  this.manageRuleset.buildRulesets();
650
740
  if (this.CustomRuleset && this.CustomRuleset.rules.size > 0) {
@@ -826,9 +916,9 @@ let TheDoctor = class TheDoctor extends LitElement {
826
916
  LintingService.startSession().then((session) => {
827
917
  this.session = session;
828
918
  // bootstrap async
829
- setTimeout(() => {
830
- this.boostrap();
831
- });
919
+ // setTimeout(() => {
920
+ // this.boostrap();
921
+ // });
832
922
  LintingService.fetchAllHowToFix().then((result) => {
833
923
  if (result) {
834
924
  result.forEach((howToFix) => {
@@ -982,6 +1072,14 @@ let TheDoctor = class TheDoctor extends LitElement {
982
1072
  }, this.debounceTime);
983
1073
  }
984
1074
  boostrap() {
1075
+ // if the url is set in the query string, fetch it and run a lint with the URL
1076
+ const url = new URL(window.location.href);
1077
+ const urlParam = url.searchParams.get('url');
1078
+ if (urlParam) {
1079
+ this.urlInput.value = urlParam;
1080
+ this.lintSpec('', urlParam);
1081
+ return;
1082
+ }
985
1083
  if (this.editor.getValue() === '') {
986
1084
  LintingService.bootstrapEditor().then((result) => {
987
1085
  this.editor.setValue(result, true);
@@ -1146,6 +1244,7 @@ let TheDoctor = class TheDoctor extends LitElement {
1146
1244
  <sl-icon-button class="collapse-side" name="chevron-bar-right"
1147
1245
  @click="${this.toggleSidebar}"></sl-icon-button>
1148
1246
  </div>`;
1247
+ let sparks = new PixelSparks();
1149
1248
  return html `
1150
1249
  ${welcomeBox}
1151
1250
  ${this.toastManager}
@@ -1201,13 +1300,45 @@ let TheDoctor = class TheDoctor extends LitElement {
1201
1300
  @click="${this.closeExplorer}">
1202
1301
  Ruleset ${rulesetPulsePill}
1203
1302
  </sl-tab>
1204
-
1205
- <sl-tab-panel name="spec" class="tab-panel">${this.editor}</sl-tab-panel>
1303
+
1304
+ <sl-tab-panel name="spec" class="tab-panel">
1305
+
1306
+ <div class="controls">
1307
+ <div style="margin-top: 8px; margin-right: 5px;">URL:</div>
1308
+ <sl-input id="url-input" class="url-input"
1309
+ placeholder="https://api.pb33f.io/train-travel.yaml" size="small"
1310
+ @keydown="${this.validateUrl}"
1311
+ @keyup="${this.validateUrl}"></sl-input>
1312
+ <sl-button id="url-input-button" class="url-input-button close-button"
1313
+ size="small" variant="neutral" disabled
1314
+ @click="${this.fetchUrl}">Fetch
1315
+ </sl-button>
1316
+ </div>
1317
+ <div class="main-view">
1318
+ <div id="editor-url-overlay" class="editor-url-overlay">
1319
+ <div id="url-spinner">
1320
+ <div class="pb33f-loader">
1321
+ <div class="spin"></div>
1322
+ Fetching from URL '<strong>${this.activeURL}</strong>'...
1323
+ </div>
1324
+ </div>
1325
+ <div id="url-problem" class="url-problem">
1326
+ <pb33f-attention-box type="warning" headerText="Problem with URL">
1327
+ <h4>Error: ${this.urlErrorCode}</h4>
1328
+ <p>${this.urlErrorMessage}</p>
1329
+ <sl-button @click="${this.hideUrlError}">OK</sl-button>
1330
+ <p></p>
1331
+ </pb33f-attention-box>
1332
+ </div>
1333
+ </div>
1334
+ ${this.editor}
1335
+ </div>
1336
+ </sl-tab-panel>
1206
1337
  <sl-tab-panel name="ruleset" class="tab-panel">${this.rulesetEditor}</sl-tab-panel>
1207
1338
  <sl-tab-panel name="explorer" class="tab-panel"
1208
1339
  @mouseleave="${this.ungrabExplorer}">${this.explorer}
1209
1340
  </sl-tab-panel>
1210
-
1341
+
1211
1342
  </sl-tab-group>
1212
1343
  </div>
1213
1344
  ${mainPanelView}
@@ -1224,11 +1355,42 @@ let TheDoctor = class TheDoctor extends LitElement {
1224
1355
  // <sl-tab-panel name="docs" class="tab-panel">
1225
1356
  // </sl-tab-panel>
1226
1357
  }
1358
+ fetchUrl() {
1359
+ this.activeURL = this.urlInput.value;
1360
+ this.lintSpec('', this.urlInput.value);
1361
+ }
1362
+ hideUrlError() {
1363
+ this.urlProblem.style.display = 'none';
1364
+ this.urlOverlay.style.display = 'none';
1365
+ this.urlSpinner.style.display = 'none';
1366
+ }
1367
+ showUrlError(e) {
1368
+ this.urlErrorCode = e.status;
1369
+ this.urlErrorMessage = e.detail;
1370
+ this.urlSpinner.style.display = 'none';
1371
+ this.urlOverlay.style.display = 'block';
1372
+ this.urlProblem.style.display = 'block';
1373
+ this.requestUpdate();
1374
+ }
1375
+ validateUrl(evt) {
1376
+ if (evt.key === "Enter") {
1377
+ this.fetchUrl();
1378
+ }
1379
+ const urlValue = this.urlInput.value;
1380
+ if (urlValue.match(urlRegex)) {
1381
+ this.urlInputButton.disabled = false;
1382
+ this.urlInputButton.classList.remove('close-button');
1383
+ }
1384
+ else {
1385
+ this.urlInputButton.disabled = true;
1386
+ this.urlInputButton.classList.add('close-button');
1387
+ }
1388
+ }
1227
1389
  ungrabExplorer() {
1228
1390
  this.explorer.grabbed = false;
1229
1391
  }
1230
1392
  };
1231
- TheDoctor.styles = [theDoctorCss, linksCss, dialogCss, buttonCss, radioGroupsCss, tabsCss];
1393
+ TheDoctor.styles = [theDoctorCss, linksCss, dialogCss, buttonCss, radioGroupsCss, tabsCss, formsCss, spinnerCss];
1232
1394
  __decorate([
1233
1395
  query('#overviewPanel')
1234
1396
  ], TheDoctor.prototype, "overviewPanel", void 0);
@@ -1262,6 +1424,12 @@ __decorate([
1262
1424
  __decorate([
1263
1425
  query('div.problems-data')
1264
1426
  ], TheDoctor.prototype, "problemsDataDiv", void 0);
1427
+ __decorate([
1428
+ query('#url-input-button')
1429
+ ], TheDoctor.prototype, "urlInputButton", void 0);
1430
+ __decorate([
1431
+ query('#url-input')
1432
+ ], TheDoctor.prototype, "urlInput", void 0);
1265
1433
  __decorate([
1266
1434
  property({ type: Boolean })
1267
1435
  ], TheDoctor.prototype, "unavailable", void 0);
@@ -1286,7 +1454,20 @@ __decorate([
1286
1454
  __decorate([
1287
1455
  state()
1288
1456
  ], TheDoctor.prototype, "explorerVisible", void 0);
1457
+ __decorate([
1458
+ state()
1459
+ ], TheDoctor.prototype, "activeURL", void 0);
1460
+ __decorate([
1461
+ query('#editor-url-overlay')
1462
+ ], TheDoctor.prototype, "urlOverlay", void 0);
1463
+ __decorate([
1464
+ query('#url-problem')
1465
+ ], TheDoctor.prototype, "urlProblem", void 0);
1466
+ __decorate([
1467
+ query('#url-spinner')
1468
+ ], TheDoctor.prototype, "urlSpinner", void 0);
1289
1469
  TheDoctor = __decorate([
1290
1470
  customElement("pb33f-doctor")
1291
1471
  ], TheDoctor);
1292
1472
  export { TheDoctor };
1473
+ const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;