@matdata/yasgui 5.5.0 → 5.7.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/build/ts/src/ConfigExportImport.js +3 -0
- package/build/ts/src/ConfigExportImport.js.map +1 -1
- package/build/ts/src/OAuth2Utils.d.ts +18 -0
- package/build/ts/src/OAuth2Utils.js +214 -0
- package/build/ts/src/OAuth2Utils.js.map +1 -0
- package/build/ts/src/PersistentConfig.d.ts +3 -0
- package/build/ts/src/PersistentConfig.js +7 -0
- package/build/ts/src/PersistentConfig.js.map +1 -1
- package/build/ts/src/Tab.d.ts +1 -1
- package/build/ts/src/Tab.js +116 -85
- package/build/ts/src/Tab.js.map +1 -1
- package/build/ts/src/TabSettingsModal.d.ts +1 -0
- package/build/ts/src/TabSettingsModal.js +330 -27
- package/build/ts/src/TabSettingsModal.js.map +1 -1
- package/build/ts/src/defaults.js +1 -1
- package/build/ts/src/defaults.js.map +1 -1
- package/build/ts/src/index.d.ts +21 -6
- package/build/ts/src/index.js +7 -1
- package/build/ts/src/index.js.map +1 -1
- package/build/ts/src/version.d.ts +1 -1
- package/build/ts/src/version.js +1 -1
- package/build/yasgui.min.css +1 -1
- package/build/yasgui.min.css.map +3 -3
- package/build/yasgui.min.js +185 -157
- package/build/yasgui.min.js.map +4 -4
- package/package.json +3 -2
- package/src/ConfigExportImport.ts +3 -0
- package/src/OAuth2Utils.ts +315 -0
- package/src/PersistentConfig.ts +10 -0
- package/src/Tab.ts +191 -111
- package/src/TabSettingsModal.scss +70 -3
- package/src/TabSettingsModal.ts +400 -30
- package/src/defaults.ts +1 -1
- package/src/endpointSelect.scss +12 -0
- package/src/index.ts +42 -10
- package/src/tab.scss +1 -0
- package/src/themes.scss +1 -0
- package/src/version.ts +1 -1
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
background: var(--yasgui-bg-primary, white);
|
|
64
64
|
border-radius: 8px;
|
|
65
65
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
66
|
-
max-width:
|
|
66
|
+
max-width: 1000px;
|
|
67
67
|
width: 90%;
|
|
68
68
|
max-height: 90vh;
|
|
69
69
|
display: flex;
|
|
@@ -160,6 +160,13 @@
|
|
|
160
160
|
|
|
161
161
|
.settingsSection {
|
|
162
162
|
margin-bottom: 20px;
|
|
163
|
+
|
|
164
|
+
h3 {
|
|
165
|
+
margin: 0 0 12px 0;
|
|
166
|
+
font-size: 16px;
|
|
167
|
+
font-weight: 600;
|
|
168
|
+
color: var(--yasgui-text-primary, #000);
|
|
169
|
+
}
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
.settingsLabel {
|
|
@@ -297,7 +304,8 @@
|
|
|
297
304
|
}
|
|
298
305
|
|
|
299
306
|
.primaryButton,
|
|
300
|
-
.secondaryButton
|
|
307
|
+
.secondaryButton,
|
|
308
|
+
.dangerButton {
|
|
301
309
|
padding: 8px 20px;
|
|
302
310
|
border: none;
|
|
303
311
|
border-radius: 4px;
|
|
@@ -320,7 +328,21 @@
|
|
|
320
328
|
color: var(--yasgui-text-primary, #333);
|
|
321
329
|
|
|
322
330
|
&:hover {
|
|
323
|
-
background: var(--yasgui-
|
|
331
|
+
background: var(--yasgui-bg-quaternary, #d0d0d0);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.dangerButton {
|
|
336
|
+
background: var(--yasgui-danger-bg, #dc3545);
|
|
337
|
+
color: white;
|
|
338
|
+
margin-top: 10px;
|
|
339
|
+
|
|
340
|
+
&:hover {
|
|
341
|
+
background: var(--yasgui-danger-hover, #c82333);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
&:active {
|
|
345
|
+
background: var(--yasgui-danger-active, #bd2130);
|
|
324
346
|
}
|
|
325
347
|
}
|
|
326
348
|
|
|
@@ -849,6 +871,24 @@
|
|
|
849
871
|
}
|
|
850
872
|
}
|
|
851
873
|
|
|
874
|
+
.authHelpLink {
|
|
875
|
+
padding: 10px 12px;
|
|
876
|
+
background: var(--yasgui-bg-secondary, #f5f5f5);
|
|
877
|
+
border: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
878
|
+
border-radius: 4px;
|
|
879
|
+
font-size: 13px;
|
|
880
|
+
margin-bottom: 12px;
|
|
881
|
+
|
|
882
|
+
a {
|
|
883
|
+
color: var(--yasgui-accent-color, #337ab7);
|
|
884
|
+
text-decoration: none;
|
|
885
|
+
|
|
886
|
+
&:hover {
|
|
887
|
+
text-decoration: underline;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
852
892
|
.authSecurityNotice {
|
|
853
893
|
padding: 12px;
|
|
854
894
|
background: var(--yasgui-warning-bg, #fff3cd);
|
|
@@ -925,6 +965,33 @@
|
|
|
925
965
|
}
|
|
926
966
|
}
|
|
927
967
|
|
|
968
|
+
// OAuth 2.0 specific styling
|
|
969
|
+
.authInputHelp {
|
|
970
|
+
font-size: 12px;
|
|
971
|
+
color: var(--yasgui-text-secondary, #666);
|
|
972
|
+
margin-top: 4px;
|
|
973
|
+
line-height: 1.4;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.oauth2TokenStatus {
|
|
977
|
+
padding: 8px 12px;
|
|
978
|
+
border-radius: 4px;
|
|
979
|
+
font-size: 14px;
|
|
980
|
+
font-weight: 500;
|
|
981
|
+
|
|
982
|
+
&.authenticated {
|
|
983
|
+
background: var(--yasgui-success-bg, #d4edda);
|
|
984
|
+
color: var(--yasgui-success-text, #155724);
|
|
985
|
+
border: 1px solid var(--yasgui-success-border, #c3e6cb);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
&.expired {
|
|
989
|
+
background: var(--yasgui-warning-bg, #fff3cd);
|
|
990
|
+
color: var(--yasgui-warning-text, #856404);
|
|
991
|
+
border: 1px solid var(--yasgui-warning-border, #ffeaa7);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
928
995
|
// Add endpoint form styling
|
|
929
996
|
.addEndpointForm {
|
|
930
997
|
margin-top: 20px;
|
package/src/TabSettingsModal.ts
CHANGED
|
@@ -3,6 +3,8 @@ import "./TabSettingsModal.scss";
|
|
|
3
3
|
import Tab from "./Tab";
|
|
4
4
|
import * as ConfigExportImport from "./ConfigExportImport";
|
|
5
5
|
import { VERSION } from "./version";
|
|
6
|
+
import * as OAuth2Utils from "./OAuth2Utils";
|
|
7
|
+
import PersistentConfig from "./PersistentConfig";
|
|
6
8
|
|
|
7
9
|
// Theme toggle icons
|
|
8
10
|
const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
|
|
@@ -34,6 +36,9 @@ const AcceptHeaderGraphMap: { key: string; value: string }[] = [
|
|
|
34
36
|
{ key: "TSV", value: "text/tab-separated-values,*/*;q=0.9" },
|
|
35
37
|
];
|
|
36
38
|
|
|
39
|
+
// Default API Key header name
|
|
40
|
+
const DEFAULT_API_KEY_HEADER = "X-API-Key";
|
|
41
|
+
|
|
37
42
|
export default class TabSettingsModal {
|
|
38
43
|
private tab: Tab;
|
|
39
44
|
private modalOverlay!: HTMLElement;
|
|
@@ -89,7 +94,8 @@ export default class TabSettingsModal {
|
|
|
89
94
|
// Modal overlay
|
|
90
95
|
this.modalOverlay = document.createElement("div");
|
|
91
96
|
addClass(this.modalOverlay, "tabSettingsModalOverlay");
|
|
92
|
-
this.modalOverlay.onclick = () => this.close();
|
|
97
|
+
// Removed: this.modalOverlay.onclick = () => this.close();
|
|
98
|
+
// Users must explicitly click 'Save' or 'Cancel' to close the modal
|
|
93
99
|
|
|
94
100
|
// Modal content
|
|
95
101
|
this.modalContent = document.createElement("div");
|
|
@@ -423,7 +429,10 @@ export default class TabSettingsModal {
|
|
|
423
429
|
const snippetsBarCheckbox = document.createElement("input");
|
|
424
430
|
snippetsBarCheckbox.type = "checkbox";
|
|
425
431
|
snippetsBarCheckbox.id = "showSnippetsBar";
|
|
426
|
-
|
|
432
|
+
// Read from global config
|
|
433
|
+
const persistedValue = this.tab.yasgui.persistentConfig.getShowSnippetsBar();
|
|
434
|
+
snippetsBarCheckbox.checked =
|
|
435
|
+
persistedValue !== undefined ? persistedValue : this.tab.yasgui.config.showSnippetsBar !== false;
|
|
427
436
|
|
|
428
437
|
const snippetsBarLabel = document.createElement("label");
|
|
429
438
|
snippetsBarLabel.htmlFor = "showSnippetsBar";
|
|
@@ -689,22 +698,47 @@ export default class TabSettingsModal {
|
|
|
689
698
|
const body = document.createElement("div");
|
|
690
699
|
addClass(body, "authModalBody");
|
|
691
700
|
|
|
692
|
-
// Auth type
|
|
701
|
+
// Auth type
|
|
693
702
|
const typeSection = document.createElement("div");
|
|
694
703
|
addClass(typeSection, "authModalSection");
|
|
695
704
|
const typeLabel = document.createElement("label");
|
|
696
705
|
typeLabel.textContent = "Authentication Type";
|
|
697
706
|
const typeSelect = document.createElement("select");
|
|
707
|
+
|
|
698
708
|
const basicOption = document.createElement("option");
|
|
699
709
|
basicOption.value = "basic";
|
|
700
710
|
basicOption.textContent = "HTTP Basic Authentication";
|
|
701
711
|
typeSelect.appendChild(basicOption);
|
|
702
|
-
|
|
712
|
+
|
|
713
|
+
const bearerOption = document.createElement("option");
|
|
714
|
+
bearerOption.value = "bearer";
|
|
715
|
+
bearerOption.textContent = "Bearer Token";
|
|
716
|
+
typeSelect.appendChild(bearerOption);
|
|
717
|
+
|
|
718
|
+
const apiKeyOption = document.createElement("option");
|
|
719
|
+
apiKeyOption.value = "apiKey";
|
|
720
|
+
apiKeyOption.textContent = "API Key (Custom Header)";
|
|
721
|
+
typeSelect.appendChild(apiKeyOption);
|
|
722
|
+
|
|
723
|
+
const oauth2Option = document.createElement("option");
|
|
724
|
+
oauth2Option.value = "oauth2";
|
|
725
|
+
oauth2Option.textContent = "OAuth 2.0";
|
|
726
|
+
typeSelect.appendChild(oauth2Option);
|
|
727
|
+
|
|
728
|
+
// Set the current auth type
|
|
729
|
+
if (existingAuth) {
|
|
730
|
+
typeSelect.value = existingAuth.type;
|
|
731
|
+
}
|
|
732
|
+
|
|
703
733
|
typeSection.appendChild(typeLabel);
|
|
704
734
|
typeSection.appendChild(typeSelect);
|
|
705
735
|
body.appendChild(typeSection);
|
|
706
736
|
|
|
707
|
-
//
|
|
737
|
+
// Basic Auth Fields
|
|
738
|
+
const basicAuthFields = document.createElement("div");
|
|
739
|
+
basicAuthFields.id = "basicAuthFields";
|
|
740
|
+
addClass(basicAuthFields, "authFieldsContainer");
|
|
741
|
+
|
|
708
742
|
const usernameSection = document.createElement("div");
|
|
709
743
|
addClass(usernameSection, "authModalSection");
|
|
710
744
|
const usernameLabel = document.createElement("label");
|
|
@@ -712,13 +746,12 @@ export default class TabSettingsModal {
|
|
|
712
746
|
const usernameInput = document.createElement("input");
|
|
713
747
|
usernameInput.type = "text";
|
|
714
748
|
usernameInput.placeholder = "Enter username";
|
|
715
|
-
usernameInput.value = existingAuth?.username
|
|
749
|
+
usernameInput.value = existingAuth?.type === "basic" ? existingAuth.username : "";
|
|
716
750
|
usernameInput.autocomplete = "username";
|
|
717
751
|
usernameSection.appendChild(usernameLabel);
|
|
718
752
|
usernameSection.appendChild(usernameInput);
|
|
719
|
-
|
|
753
|
+
basicAuthFields.appendChild(usernameSection);
|
|
720
754
|
|
|
721
|
-
// Password
|
|
722
755
|
const passwordSection = document.createElement("div");
|
|
723
756
|
addClass(passwordSection, "authModalSection");
|
|
724
757
|
const passwordLabel = document.createElement("label");
|
|
@@ -726,11 +759,181 @@ export default class TabSettingsModal {
|
|
|
726
759
|
const passwordInput = document.createElement("input");
|
|
727
760
|
passwordInput.type = "password";
|
|
728
761
|
passwordInput.placeholder = "Enter password";
|
|
729
|
-
passwordInput.value = existingAuth?.password
|
|
762
|
+
passwordInput.value = existingAuth?.type === "basic" ? existingAuth.password : "";
|
|
730
763
|
passwordInput.autocomplete = "current-password";
|
|
731
764
|
passwordSection.appendChild(passwordLabel);
|
|
732
765
|
passwordSection.appendChild(passwordInput);
|
|
733
|
-
|
|
766
|
+
basicAuthFields.appendChild(passwordSection);
|
|
767
|
+
|
|
768
|
+
body.appendChild(basicAuthFields);
|
|
769
|
+
|
|
770
|
+
// Bearer Token Fields
|
|
771
|
+
const bearerAuthFields = document.createElement("div");
|
|
772
|
+
bearerAuthFields.id = "bearerAuthFields";
|
|
773
|
+
addClass(bearerAuthFields, "authFieldsContainer");
|
|
774
|
+
bearerAuthFields.style.display = "none";
|
|
775
|
+
|
|
776
|
+
const tokenSection = document.createElement("div");
|
|
777
|
+
addClass(tokenSection, "authModalSection");
|
|
778
|
+
const tokenLabel = document.createElement("label");
|
|
779
|
+
tokenLabel.textContent = "Bearer Token";
|
|
780
|
+
const tokenInput = document.createElement("input");
|
|
781
|
+
tokenInput.type = "password";
|
|
782
|
+
tokenInput.placeholder = "Enter bearer token";
|
|
783
|
+
tokenInput.autocomplete = "off";
|
|
784
|
+
tokenInput.value = existingAuth?.type === "bearer" ? existingAuth.token : "";
|
|
785
|
+
tokenSection.appendChild(tokenLabel);
|
|
786
|
+
tokenSection.appendChild(tokenInput);
|
|
787
|
+
bearerAuthFields.appendChild(tokenSection);
|
|
788
|
+
|
|
789
|
+
body.appendChild(bearerAuthFields);
|
|
790
|
+
|
|
791
|
+
// API Key Fields
|
|
792
|
+
const apiKeyAuthFields = document.createElement("div");
|
|
793
|
+
apiKeyAuthFields.id = "apiKeyAuthFields";
|
|
794
|
+
addClass(apiKeyAuthFields, "authFieldsContainer");
|
|
795
|
+
apiKeyAuthFields.style.display = "none";
|
|
796
|
+
|
|
797
|
+
const headerNameSection = document.createElement("div");
|
|
798
|
+
addClass(headerNameSection, "authModalSection");
|
|
799
|
+
const headerNameLabel = document.createElement("label");
|
|
800
|
+
headerNameLabel.textContent = "Header Name";
|
|
801
|
+
const headerNameInput = document.createElement("input");
|
|
802
|
+
headerNameInput.type = "text";
|
|
803
|
+
headerNameInput.placeholder = `e.g., ${DEFAULT_API_KEY_HEADER}`;
|
|
804
|
+
headerNameInput.value = existingAuth?.type === "apiKey" ? existingAuth.headerName : DEFAULT_API_KEY_HEADER;
|
|
805
|
+
headerNameSection.appendChild(headerNameLabel);
|
|
806
|
+
headerNameSection.appendChild(headerNameInput);
|
|
807
|
+
apiKeyAuthFields.appendChild(headerNameSection);
|
|
808
|
+
|
|
809
|
+
const apiKeySection = document.createElement("div");
|
|
810
|
+
addClass(apiKeySection, "authModalSection");
|
|
811
|
+
const apiKeyLabel = document.createElement("label");
|
|
812
|
+
apiKeyLabel.textContent = "API Key";
|
|
813
|
+
const apiKeyInput = document.createElement("input");
|
|
814
|
+
apiKeyInput.type = "password";
|
|
815
|
+
apiKeyInput.placeholder = "Enter API key";
|
|
816
|
+
apiKeyInput.autocomplete = "off";
|
|
817
|
+
apiKeyInput.value = existingAuth?.type === "apiKey" ? existingAuth.apiKey : "";
|
|
818
|
+
apiKeySection.appendChild(apiKeyLabel);
|
|
819
|
+
apiKeySection.appendChild(apiKeyInput);
|
|
820
|
+
apiKeyAuthFields.appendChild(apiKeySection);
|
|
821
|
+
|
|
822
|
+
body.appendChild(apiKeyAuthFields);
|
|
823
|
+
|
|
824
|
+
// OAuth 2.0 Fields
|
|
825
|
+
const oauth2AuthFields = document.createElement("div");
|
|
826
|
+
oauth2AuthFields.id = "oauth2AuthFields";
|
|
827
|
+
addClass(oauth2AuthFields, "authFieldsContainer");
|
|
828
|
+
oauth2AuthFields.style.display = "none";
|
|
829
|
+
|
|
830
|
+
const clientIdSection = document.createElement("div");
|
|
831
|
+
addClass(clientIdSection, "authModalSection");
|
|
832
|
+
const clientIdLabel = document.createElement("label");
|
|
833
|
+
clientIdLabel.textContent = "Client ID";
|
|
834
|
+
const clientIdInput = document.createElement("input");
|
|
835
|
+
clientIdInput.type = "text";
|
|
836
|
+
clientIdInput.placeholder = "Enter OAuth 2.0 client ID";
|
|
837
|
+
clientIdInput.value = existingAuth?.type === "oauth2" ? existingAuth.clientId : "";
|
|
838
|
+
clientIdSection.appendChild(clientIdLabel);
|
|
839
|
+
clientIdSection.appendChild(clientIdInput);
|
|
840
|
+
oauth2AuthFields.appendChild(clientIdSection);
|
|
841
|
+
|
|
842
|
+
const authEndpointSection = document.createElement("div");
|
|
843
|
+
addClass(authEndpointSection, "authModalSection");
|
|
844
|
+
const authEndpointLabel = document.createElement("label");
|
|
845
|
+
authEndpointLabel.textContent = "Authorization Endpoint";
|
|
846
|
+
const authEndpointInput = document.createElement("input");
|
|
847
|
+
authEndpointInput.type = "url";
|
|
848
|
+
authEndpointInput.placeholder = "https://provider.com/oauth/authorize";
|
|
849
|
+
authEndpointInput.value = existingAuth?.type === "oauth2" ? existingAuth.authorizationEndpoint : "";
|
|
850
|
+
authEndpointSection.appendChild(authEndpointLabel);
|
|
851
|
+
authEndpointSection.appendChild(authEndpointInput);
|
|
852
|
+
oauth2AuthFields.appendChild(authEndpointSection);
|
|
853
|
+
|
|
854
|
+
const tokenEndpointSection = document.createElement("div");
|
|
855
|
+
addClass(tokenEndpointSection, "authModalSection");
|
|
856
|
+
const tokenEndpointLabel = document.createElement("label");
|
|
857
|
+
tokenEndpointLabel.textContent = "Token Endpoint";
|
|
858
|
+
const tokenEndpointInput = document.createElement("input");
|
|
859
|
+
tokenEndpointInput.type = "url";
|
|
860
|
+
tokenEndpointInput.placeholder = "https://provider.com/oauth/token";
|
|
861
|
+
tokenEndpointInput.value = existingAuth?.type === "oauth2" ? existingAuth.tokenEndpoint : "";
|
|
862
|
+
tokenEndpointSection.appendChild(tokenEndpointLabel);
|
|
863
|
+
tokenEndpointSection.appendChild(tokenEndpointInput);
|
|
864
|
+
oauth2AuthFields.appendChild(tokenEndpointSection);
|
|
865
|
+
|
|
866
|
+
const redirectUriSection = document.createElement("div");
|
|
867
|
+
addClass(redirectUriSection, "authModalSection");
|
|
868
|
+
const redirectUriLabel = document.createElement("label");
|
|
869
|
+
redirectUriLabel.textContent = "Redirect URI (Optional)";
|
|
870
|
+
const redirectUriInput = document.createElement("input");
|
|
871
|
+
redirectUriInput.type = "url";
|
|
872
|
+
redirectUriInput.placeholder = window.location.origin + window.location.pathname;
|
|
873
|
+
redirectUriInput.value = existingAuth?.type === "oauth2" ? existingAuth.redirectUri || "" : "";
|
|
874
|
+
const redirectUriHelp = document.createElement("div");
|
|
875
|
+
redirectUriHelp.textContent = "Leave empty to use current page URL. Must be registered with OAuth provider.";
|
|
876
|
+
addClass(redirectUriHelp, "authInputHelp");
|
|
877
|
+
redirectUriSection.appendChild(redirectUriLabel);
|
|
878
|
+
redirectUriSection.appendChild(redirectUriInput);
|
|
879
|
+
redirectUriSection.appendChild(redirectUriHelp);
|
|
880
|
+
oauth2AuthFields.appendChild(redirectUriSection);
|
|
881
|
+
|
|
882
|
+
const scopeSection = document.createElement("div");
|
|
883
|
+
addClass(scopeSection, "authModalSection");
|
|
884
|
+
const scopeLabel = document.createElement("label");
|
|
885
|
+
scopeLabel.textContent = "Scope (Optional)";
|
|
886
|
+
const scopeInput = document.createElement("input");
|
|
887
|
+
scopeInput.type = "text";
|
|
888
|
+
scopeInput.placeholder = "e.g., read write";
|
|
889
|
+
scopeInput.value = existingAuth?.type === "oauth2" ? existingAuth.scope || "" : "";
|
|
890
|
+
const scopeHelp = document.createElement("div");
|
|
891
|
+
scopeHelp.textContent = "Space-separated list of OAuth 2.0 scopes";
|
|
892
|
+
addClass(scopeHelp, "authInputHelp");
|
|
893
|
+
scopeSection.appendChild(scopeLabel);
|
|
894
|
+
scopeSection.appendChild(scopeInput);
|
|
895
|
+
scopeSection.appendChild(scopeHelp);
|
|
896
|
+
oauth2AuthFields.appendChild(scopeSection);
|
|
897
|
+
|
|
898
|
+
// Show token status if already authenticated
|
|
899
|
+
if (existingAuth?.type === "oauth2" && existingAuth.accessToken) {
|
|
900
|
+
const tokenStatusSection = document.createElement("div");
|
|
901
|
+
addClass(tokenStatusSection, "authModalSection");
|
|
902
|
+
const tokenStatusLabel = document.createElement("label");
|
|
903
|
+
tokenStatusLabel.textContent = "Authentication Status";
|
|
904
|
+
const tokenStatus = document.createElement("div");
|
|
905
|
+
addClass(tokenStatus, "oauth2TokenStatus");
|
|
906
|
+
tokenStatus.innerHTML = "✓ Authenticated";
|
|
907
|
+
addClass(tokenStatus, "authenticated");
|
|
908
|
+
tokenStatusSection.appendChild(tokenStatusLabel);
|
|
909
|
+
tokenStatusSection.appendChild(tokenStatus);
|
|
910
|
+
oauth2AuthFields.appendChild(tokenStatusSection);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
body.appendChild(oauth2AuthFields);
|
|
914
|
+
|
|
915
|
+
// Function to toggle fields based on auth type
|
|
916
|
+
const toggleAuthFields = () => {
|
|
917
|
+
const authType = typeSelect.value;
|
|
918
|
+
basicAuthFields.style.display = authType === "basic" ? "block" : "none";
|
|
919
|
+
bearerAuthFields.style.display = authType === "bearer" ? "block" : "none";
|
|
920
|
+
apiKeyAuthFields.style.display = authType === "apiKey" ? "block" : "none";
|
|
921
|
+
oauth2AuthFields.style.display = authType === "oauth2" ? "block" : "none";
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
// Set initial visibility
|
|
925
|
+
toggleAuthFields();
|
|
926
|
+
|
|
927
|
+
// Update visibility when auth type changes
|
|
928
|
+
typeSelect.onchange = toggleAuthFields;
|
|
929
|
+
|
|
930
|
+
// Help link
|
|
931
|
+
const helpLink = document.createElement("div");
|
|
932
|
+
addClass(helpLink, "authHelpLink");
|
|
933
|
+
helpLink.innerHTML = `
|
|
934
|
+
📚 <a href="https://triply.cc/docs/yasgui#authentication" target="_blank" rel="noopener noreferrer">View authentication documentation</a>
|
|
935
|
+
`;
|
|
936
|
+
body.appendChild(helpLink);
|
|
734
937
|
|
|
735
938
|
// Security notice
|
|
736
939
|
const securityNotice = document.createElement("div");
|
|
@@ -755,13 +958,18 @@ export default class TabSettingsModal {
|
|
|
755
958
|
removeButton.textContent = "Remove Authentication";
|
|
756
959
|
removeButton.type = "button";
|
|
757
960
|
addClass(removeButton, "authRemoveButton");
|
|
961
|
+
// Helper function to close modal and refresh endpoints list
|
|
962
|
+
const closeModalAndRefresh = () => {
|
|
963
|
+
authModalOverlay.remove();
|
|
964
|
+
const endpointsList = this.modalContent.querySelector(".endpointsTable");
|
|
965
|
+
if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
|
|
966
|
+
};
|
|
967
|
+
|
|
758
968
|
removeButton.onclick = () => {
|
|
759
969
|
this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
|
|
760
970
|
authentication: undefined,
|
|
761
971
|
});
|
|
762
|
-
|
|
763
|
-
const endpointsList = this.modalContent.querySelector(".endpointsTable");
|
|
764
|
-
if (endpointsList) this.renderEndpointsList(endpointsList as HTMLElement);
|
|
972
|
+
closeModalAndRefresh();
|
|
765
973
|
};
|
|
766
974
|
if (!existingAuth) {
|
|
767
975
|
removeButton.disabled = true;
|
|
@@ -778,22 +986,125 @@ export default class TabSettingsModal {
|
|
|
778
986
|
saveButton.type = "button";
|
|
779
987
|
addClass(saveButton, "authSaveButton");
|
|
780
988
|
saveButton.onclick = () => {
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
989
|
+
const authType = typeSelect.value;
|
|
990
|
+
|
|
991
|
+
if (authType === "basic") {
|
|
992
|
+
const username = usernameInput.value.trim();
|
|
993
|
+
const password = passwordInput.value;
|
|
994
|
+
|
|
995
|
+
if (username && password) {
|
|
996
|
+
this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
|
|
997
|
+
authentication: {
|
|
998
|
+
type: "basic",
|
|
999
|
+
username,
|
|
1000
|
+
password,
|
|
1001
|
+
},
|
|
1002
|
+
});
|
|
1003
|
+
closeModalAndRefresh();
|
|
1004
|
+
} else {
|
|
1005
|
+
alert("Please enter both username and password.");
|
|
1006
|
+
}
|
|
1007
|
+
} else if (authType === "bearer") {
|
|
1008
|
+
const token = tokenInput.value.trim();
|
|
1009
|
+
|
|
1010
|
+
if (token) {
|
|
1011
|
+
this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
|
|
1012
|
+
authentication: {
|
|
1013
|
+
type: "bearer",
|
|
1014
|
+
token,
|
|
1015
|
+
},
|
|
1016
|
+
});
|
|
1017
|
+
closeModalAndRefresh();
|
|
1018
|
+
} else {
|
|
1019
|
+
alert("Please enter a bearer token.");
|
|
1020
|
+
}
|
|
1021
|
+
} else if (authType === "apiKey") {
|
|
1022
|
+
const headerName = headerNameInput.value.trim();
|
|
1023
|
+
const apiKey = apiKeyInput.value.trim();
|
|
1024
|
+
|
|
1025
|
+
if (headerName && apiKey) {
|
|
1026
|
+
this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
|
|
1027
|
+
authentication: {
|
|
1028
|
+
type: "apiKey",
|
|
1029
|
+
headerName,
|
|
1030
|
+
apiKey,
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
1033
|
+
closeModalAndRefresh();
|
|
1034
|
+
} else {
|
|
1035
|
+
alert("Please enter both header name and API key.");
|
|
1036
|
+
}
|
|
1037
|
+
} else if (authType === "oauth2") {
|
|
1038
|
+
const clientId = clientIdInput.value.trim();
|
|
1039
|
+
const authorizationEndpoint = authEndpointInput.value.trim();
|
|
1040
|
+
const tokenEndpoint = tokenEndpointInput.value.trim();
|
|
1041
|
+
const redirectUri = redirectUriInput.value.trim() || window.location.origin + window.location.pathname;
|
|
1042
|
+
const scope = scopeInput.value.trim();
|
|
1043
|
+
|
|
1044
|
+
if (!clientId || !authorizationEndpoint || !tokenEndpoint) {
|
|
1045
|
+
alert("Please enter Client ID, Authorization Endpoint, and Token Endpoint.");
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Check if already authenticated (has access token)
|
|
1050
|
+
if (existingAuth?.type === "oauth2" && existingAuth.accessToken) {
|
|
1051
|
+
// Already has token, just save the configuration updates
|
|
1052
|
+
this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
|
|
1053
|
+
authentication: {
|
|
1054
|
+
type: "oauth2",
|
|
1055
|
+
clientId,
|
|
1056
|
+
authorizationEndpoint,
|
|
1057
|
+
tokenEndpoint,
|
|
1058
|
+
redirectUri,
|
|
1059
|
+
scope,
|
|
1060
|
+
accessToken: existingAuth.accessToken,
|
|
1061
|
+
idToken: existingAuth.idToken,
|
|
1062
|
+
refreshToken: existingAuth.refreshToken,
|
|
1063
|
+
tokenExpiry: existingAuth.tokenExpiry,
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
closeModalAndRefresh();
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Start OAuth 2.0 flow
|
|
1071
|
+
saveButton.disabled = true;
|
|
1072
|
+
saveButton.textContent = "Authenticating...";
|
|
1073
|
+
|
|
1074
|
+
OAuth2Utils.startOAuth2Flow({
|
|
1075
|
+
clientId,
|
|
1076
|
+
authorizationEndpoint,
|
|
1077
|
+
tokenEndpoint,
|
|
1078
|
+
redirectUri,
|
|
1079
|
+
scope,
|
|
1080
|
+
})
|
|
1081
|
+
.then((tokenResponse) => {
|
|
1082
|
+
const tokenExpiry = OAuth2Utils.calculateTokenExpiry(tokenResponse.expires_in);
|
|
1083
|
+
|
|
1084
|
+
this.tab.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {
|
|
1085
|
+
authentication: {
|
|
1086
|
+
type: "oauth2",
|
|
1087
|
+
clientId,
|
|
1088
|
+
authorizationEndpoint,
|
|
1089
|
+
tokenEndpoint,
|
|
1090
|
+
redirectUri,
|
|
1091
|
+
scope,
|
|
1092
|
+
accessToken: tokenResponse.access_token,
|
|
1093
|
+
idToken: tokenResponse.id_token,
|
|
1094
|
+
refreshToken: tokenResponse.refresh_token,
|
|
1095
|
+
tokenExpiry,
|
|
1096
|
+
},
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
closeModalAndRefresh();
|
|
1100
|
+
alert("OAuth 2.0 authentication successful!");
|
|
1101
|
+
})
|
|
1102
|
+
.catch((error) => {
|
|
1103
|
+
console.error("OAuth 2.0 authentication failed:", error);
|
|
1104
|
+
alert("OAuth 2.0 authentication failed: " + error.message);
|
|
1105
|
+
saveButton.disabled = false;
|
|
1106
|
+
saveButton.textContent = "Save & Authenticate";
|
|
1107
|
+
});
|
|
797
1108
|
}
|
|
798
1109
|
};
|
|
799
1110
|
|
|
@@ -927,7 +1238,23 @@ export default class TabSettingsModal {
|
|
|
927
1238
|
}
|
|
928
1239
|
const snippetsBarCheckbox = document.getElementById("showSnippetsBar") as HTMLInputElement;
|
|
929
1240
|
if (snippetsBarCheckbox) {
|
|
930
|
-
|
|
1241
|
+
// Save globally to config and persistent storage
|
|
1242
|
+
this.tab.yasgui.config.showSnippetsBar = snippetsBarCheckbox.checked;
|
|
1243
|
+
this.tab.yasgui.persistentConfig.setShowSnippetsBar(snippetsBarCheckbox.checked);
|
|
1244
|
+
|
|
1245
|
+
// Apply to all tabs by updating each Yasqe instance's config and refreshing
|
|
1246
|
+
this.tab.yasgui.persistentConfig.getTabs().forEach((tabId: string) => {
|
|
1247
|
+
const tab = this.tab.yasgui.getTab(tabId);
|
|
1248
|
+
if (tab) {
|
|
1249
|
+
const tabYasqe = tab.getYasqe();
|
|
1250
|
+
if (tabYasqe) {
|
|
1251
|
+
// Update the individual Yasqe instance's config
|
|
1252
|
+
tabYasqe.config.showSnippetsBar = snippetsBarCheckbox.checked;
|
|
1253
|
+
// Refresh the snippets bar to reflect the change
|
|
1254
|
+
tabYasqe.refreshSnippetsBar();
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
931
1258
|
}
|
|
932
1259
|
yasqe.saveQuery();
|
|
933
1260
|
}
|
|
@@ -1450,6 +1777,28 @@ export default class TabSettingsModal {
|
|
|
1450
1777
|
aboutSection.appendChild(footerInfo);
|
|
1451
1778
|
|
|
1452
1779
|
container.appendChild(aboutSection);
|
|
1780
|
+
|
|
1781
|
+
// Storage Management Section
|
|
1782
|
+
const storageSection = document.createElement("div");
|
|
1783
|
+
addClass(storageSection, "settingsSection");
|
|
1784
|
+
|
|
1785
|
+
const storageTitle = document.createElement("h3");
|
|
1786
|
+
storageTitle.textContent = "Storage Management";
|
|
1787
|
+
storageSection.appendChild(storageTitle);
|
|
1788
|
+
|
|
1789
|
+
const storageDescription = document.createElement("p");
|
|
1790
|
+
storageDescription.textContent =
|
|
1791
|
+
"Clear all locally stored data including tabs, queries, endpoint configurations, and preferences. This action cannot be undone.";
|
|
1792
|
+
addClass(storageDescription, "settingsHelp");
|
|
1793
|
+
storageSection.appendChild(storageDescription);
|
|
1794
|
+
|
|
1795
|
+
const clearStorageButton = document.createElement("button");
|
|
1796
|
+
clearStorageButton.textContent = "Clear Persistent Storage";
|
|
1797
|
+
addClass(clearStorageButton, "dangerButton");
|
|
1798
|
+
clearStorageButton.onclick = () => this.clearPersistentStorage();
|
|
1799
|
+
storageSection.appendChild(clearStorageButton);
|
|
1800
|
+
|
|
1801
|
+
container.appendChild(storageSection);
|
|
1453
1802
|
}
|
|
1454
1803
|
|
|
1455
1804
|
private createAboutLink(label: string, url: string, description: string): HTMLElement {
|
|
@@ -1473,6 +1822,27 @@ export default class TabSettingsModal {
|
|
|
1473
1822
|
return linkContainer;
|
|
1474
1823
|
}
|
|
1475
1824
|
|
|
1825
|
+
private clearPersistentStorage() {
|
|
1826
|
+
const confirmed = confirm(
|
|
1827
|
+
"Are you sure you want to clear all persistent storage?\n\n" +
|
|
1828
|
+
"This will delete:\n" +
|
|
1829
|
+
"- All saved tabs and queries\n" +
|
|
1830
|
+
"- Endpoint configurations and history\n" +
|
|
1831
|
+
"- Authentication settings\n" +
|
|
1832
|
+
"- Editor preferences\n" +
|
|
1833
|
+
"- Prefixes\n\n" +
|
|
1834
|
+
"This action cannot be undone and the page will reload.",
|
|
1835
|
+
);
|
|
1836
|
+
|
|
1837
|
+
if (confirmed) {
|
|
1838
|
+
// Clear persistent storage
|
|
1839
|
+
PersistentConfig.clear();
|
|
1840
|
+
|
|
1841
|
+
// Reload the page to reinitialize with empty storage
|
|
1842
|
+
window.location.reload();
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1476
1846
|
public destroy() {
|
|
1477
1847
|
if (this.modalOverlay && this.modalOverlay.parentNode) {
|
|
1478
1848
|
this.modalOverlay.parentNode.removeChild(this.modalOverlay);
|
package/src/defaults.ts
CHANGED
|
@@ -22,7 +22,6 @@ export default function initialize(): Config<CatalogueItem> {
|
|
|
22
22
|
return "yagui_" + id;
|
|
23
23
|
},
|
|
24
24
|
tabName: "Query",
|
|
25
|
-
corsProxy: undefined,
|
|
26
25
|
persistencyExpire: 60 * 60 * 24 * 30,
|
|
27
26
|
persistenceLabelResponse: "response",
|
|
28
27
|
persistenceLabelConfig: "config",
|
|
@@ -31,6 +30,7 @@ export default function initialize(): Config<CatalogueItem> {
|
|
|
31
30
|
theme: undefined,
|
|
32
31
|
showThemeToggle: true,
|
|
33
32
|
orientation: "vertical",
|
|
33
|
+
showSnippetsBar: true,
|
|
34
34
|
endpointButtons: undefined,
|
|
35
35
|
endpointCatalogueOptions: {
|
|
36
36
|
getData: () => {
|
package/src/endpointSelect.scss
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
margin: 4px 0px;
|
|
5
5
|
border: 2px solid #ccc;
|
|
6
6
|
width: 100%;
|
|
7
|
+
min-width: 200px;
|
|
7
8
|
&:hover {
|
|
8
9
|
border-color: #bbb;
|
|
9
10
|
}
|
|
@@ -125,6 +126,11 @@
|
|
|
125
126
|
align-items: center;
|
|
126
127
|
gap: 4px;
|
|
127
128
|
margin-left: 4px;
|
|
129
|
+
|
|
130
|
+
// Hide on small screens (mobile)
|
|
131
|
+
@media (max-width: 768px) {
|
|
132
|
+
display: none;
|
|
133
|
+
}
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
.endpointButton {
|
|
@@ -152,5 +158,11 @@
|
|
|
152
158
|
outline: 2px solid var(--yasgui-endpoint-button-focus, #5cb3fd);
|
|
153
159
|
outline-offset: 2px;
|
|
154
160
|
}
|
|
161
|
+
|
|
162
|
+
// Reduce padding on medium screens
|
|
163
|
+
@media (max-width: 1024px) {
|
|
164
|
+
padding: 4px 8px;
|
|
165
|
+
font-size: 12px;
|
|
166
|
+
}
|
|
155
167
|
}
|
|
156
168
|
}
|