@startinblox/components-ds4go 3.3.5 โ 3.3.7
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/index.js +1614 -2204
- package/package.json +1 -1
- package/src/component.d.ts +2 -1
- package/src/components/modal/customer-modal/ds4go-contract-modal-part.ts +7 -7
- package/src/components/modal/ds4go-catalog-modal.ts +1 -848
- package/src/components/modal/ds4go-customer-modal.ts +5 -5
- package/src/components/modal/ds4go-fact-bundle-modal.ts +5 -6
- package/src/components/solid-customer-list.ts +6 -3
- package/src/components/solid-fact-bundle-creation.ts +18 -15
- package/src/components/solid-fact-bundle.ts +9 -6
- package/src/ds4go.d.ts +45 -0
|
@@ -24,15 +24,9 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
24
24
|
@property({ attribute: false })
|
|
25
25
|
dspStore?: any;
|
|
26
26
|
|
|
27
|
-
@property({ attribute: false })
|
|
28
|
-
apiGatewayConfig?: any;
|
|
29
|
-
|
|
30
27
|
@property({ attribute: false })
|
|
31
28
|
participantId?: string;
|
|
32
29
|
|
|
33
|
-
@property({ attribute: false, type: Boolean })
|
|
34
|
-
displayServiceTest = true;
|
|
35
|
-
|
|
36
30
|
@state()
|
|
37
31
|
negotiationStatus:
|
|
38
32
|
| "idle"
|
|
@@ -60,33 +54,9 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
60
54
|
@state()
|
|
61
55
|
maxAttempts?: number;
|
|
62
56
|
|
|
63
|
-
@state()
|
|
64
|
-
apiGatewayToken?: string;
|
|
65
|
-
|
|
66
|
-
@state()
|
|
67
|
-
apiGatewayError?: string;
|
|
68
|
-
|
|
69
|
-
@state()
|
|
70
|
-
gettingToken = false;
|
|
71
|
-
|
|
72
|
-
@state()
|
|
73
|
-
testingService = false;
|
|
74
|
-
|
|
75
|
-
@state()
|
|
76
|
-
testResult?: any;
|
|
77
|
-
|
|
78
57
|
@state()
|
|
79
58
|
existingAgreementChecked = false;
|
|
80
59
|
|
|
81
|
-
@state()
|
|
82
|
-
transferId?: string;
|
|
83
|
-
|
|
84
|
-
@state()
|
|
85
|
-
edrToken?: string;
|
|
86
|
-
|
|
87
|
-
@state()
|
|
88
|
-
edrEndpoint?: string;
|
|
89
|
-
|
|
90
60
|
@state()
|
|
91
61
|
showPolicySelection = false;
|
|
92
62
|
|
|
@@ -96,29 +66,6 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
96
66
|
@state()
|
|
97
67
|
availablePolicies?: any[];
|
|
98
68
|
|
|
99
|
-
@state()
|
|
100
|
-
transferError?: string;
|
|
101
|
-
|
|
102
|
-
@state()
|
|
103
|
-
gettingEDR = false;
|
|
104
|
-
|
|
105
|
-
@state()
|
|
106
|
-
accessingData = false;
|
|
107
|
-
|
|
108
|
-
@state()
|
|
109
|
-
dataAccessAttempt?: number;
|
|
110
|
-
|
|
111
|
-
@state()
|
|
112
|
-
dataAccessMaxAttempts?: number;
|
|
113
|
-
|
|
114
|
-
@state()
|
|
115
|
-
countdown?: number;
|
|
116
|
-
|
|
117
|
-
@state()
|
|
118
|
-
dataResult?: any;
|
|
119
|
-
|
|
120
|
-
@state()
|
|
121
|
-
dataAccessError?: string;
|
|
122
69
|
|
|
123
70
|
/**
|
|
124
71
|
* Check for existing agreement when component connects
|
|
@@ -435,11 +382,6 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
435
382
|
this.contractId = undefined;
|
|
436
383
|
this.negotiationId = undefined;
|
|
437
384
|
this.negotiationError = undefined;
|
|
438
|
-
this.apiGatewayToken = undefined;
|
|
439
|
-
this.apiGatewayError = undefined;
|
|
440
|
-
this.gettingToken = false;
|
|
441
|
-
this.testingService = false;
|
|
442
|
-
this.testResult = undefined;
|
|
443
385
|
this.existingAgreementChecked = false;
|
|
444
386
|
this.requestUpdate();
|
|
445
387
|
}
|
|
@@ -833,415 +775,6 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
833
775
|
this.requestUpdate();
|
|
834
776
|
}
|
|
835
777
|
|
|
836
|
-
/**
|
|
837
|
-
* Get the current OIDC access token from the session.
|
|
838
|
-
* This retrieves the token from localStorage where oidc-client stores it.
|
|
839
|
-
*/
|
|
840
|
-
_getOidcAccessToken(apiGatewayConfig: any): string {
|
|
841
|
-
const { oidcAuthority, oidcClientId } = apiGatewayConfig;
|
|
842
|
-
|
|
843
|
-
if (!oidcAuthority || !oidcClientId) {
|
|
844
|
-
throw new Error(
|
|
845
|
-
"OIDC configuration (oidcAuthority, oidcClientId) required for API Gateway",
|
|
846
|
-
);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
// The OIDC library stores the user session in localStorage
|
|
850
|
-
const storageKey = `oidc.user:${oidcAuthority}:${oidcClientId}`;
|
|
851
|
-
const stored = localStorage.getItem(storageKey);
|
|
852
|
-
|
|
853
|
-
if (!stored) {
|
|
854
|
-
throw new Error("No OIDC session found. Please log in first.");
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
try {
|
|
858
|
-
const oidcUser = JSON.parse(stored);
|
|
859
|
-
|
|
860
|
-
// Check if token is expired
|
|
861
|
-
if (oidcUser.expires_at && oidcUser.expires_at * 1000 < Date.now()) {
|
|
862
|
-
throw new Error("OIDC token has expired. Please log in again.");
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
if (!oidcUser.access_token) {
|
|
866
|
-
throw new Error("No access token in OIDC session");
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
return oidcUser.access_token;
|
|
870
|
-
} catch (e) {
|
|
871
|
-
if (e instanceof SyntaxError) {
|
|
872
|
-
throw new Error("Failed to parse OIDC session data");
|
|
873
|
-
}
|
|
874
|
-
throw e;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
async _getApiGatewayToken(
|
|
879
|
-
apiGatewayConfig: any,
|
|
880
|
-
accessToken: string,
|
|
881
|
-
contractAgreementId: string,
|
|
882
|
-
): Promise<string> {
|
|
883
|
-
const { apiGatewayBaseUrl } = apiGatewayConfig;
|
|
884
|
-
|
|
885
|
-
if (!apiGatewayBaseUrl) {
|
|
886
|
-
throw new Error("API Gateway base URL not configured");
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Use the TEMS API Gateway token endpoint
|
|
890
|
-
const tokenUrl = `${apiGatewayBaseUrl}/temsapigateway/token`;
|
|
891
|
-
|
|
892
|
-
const response = await fetch(tokenUrl, {
|
|
893
|
-
method: "POST",
|
|
894
|
-
headers: {
|
|
895
|
-
"Content-Type": "application/json",
|
|
896
|
-
Authorization: `Bearer ${accessToken}`,
|
|
897
|
-
},
|
|
898
|
-
body: JSON.stringify({
|
|
899
|
-
agreementId: contractAgreementId,
|
|
900
|
-
}),
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
if (!response.ok) {
|
|
904
|
-
const errorText = await response.text();
|
|
905
|
-
console.error("โ Failed to get API Gateway token:", {
|
|
906
|
-
status: response.status,
|
|
907
|
-
statusText: response.statusText,
|
|
908
|
-
errorText,
|
|
909
|
-
});
|
|
910
|
-
throw new Error(
|
|
911
|
-
`Failed to get API Gateway token: ${response.status} - ${errorText}`,
|
|
912
|
-
);
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
const data = await response.json();
|
|
916
|
-
// Support multiple possible field names for the token
|
|
917
|
-
const token = data.apiGatewayToken || data.token || data.access_token;
|
|
918
|
-
|
|
919
|
-
if (!token) {
|
|
920
|
-
console.error("โ No token found in response:", data);
|
|
921
|
-
throw new Error(
|
|
922
|
-
"API Gateway response did not contain a token. Response keys: " +
|
|
923
|
-
Object.keys(data).join(", "),
|
|
924
|
-
);
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
return token;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
async _getGatewayToken() {
|
|
931
|
-
try {
|
|
932
|
-
this.gettingToken = true;
|
|
933
|
-
this.apiGatewayError = undefined;
|
|
934
|
-
this.requestUpdate();
|
|
935
|
-
|
|
936
|
-
// Use API Gateway configuration passed as property
|
|
937
|
-
if (!this.apiGatewayConfig) {
|
|
938
|
-
throw new Error(
|
|
939
|
-
"API Gateway not configured. Please provide api-gateway-config attribute.",
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
const apiGatewayConfig = this.apiGatewayConfig;
|
|
944
|
-
|
|
945
|
-
if (!this.contractId) {
|
|
946
|
-
throw new Error(
|
|
947
|
-
"No contract ID available. Please complete contract negotiation first.",
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// Step 1: Get access token from current OIDC session
|
|
952
|
-
const oidcAccessToken = this._getOidcAccessToken(apiGatewayConfig);
|
|
953
|
-
|
|
954
|
-
// Step 2: Get API Gateway token using the contract agreement ID
|
|
955
|
-
const apiGatewayToken = await this._getApiGatewayToken(
|
|
956
|
-
apiGatewayConfig,
|
|
957
|
-
oidcAccessToken,
|
|
958
|
-
this.contractId,
|
|
959
|
-
);
|
|
960
|
-
|
|
961
|
-
this.apiGatewayToken = apiGatewayToken;
|
|
962
|
-
this.requestUpdate();
|
|
963
|
-
} catch (error) {
|
|
964
|
-
console.error("Failed to get API Gateway token:", error);
|
|
965
|
-
this.apiGatewayError = (error as Error).message;
|
|
966
|
-
} finally {
|
|
967
|
-
this.gettingToken = false;
|
|
968
|
-
this.requestUpdate();
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
async _testService() {
|
|
973
|
-
try {
|
|
974
|
-
this.testingService = true;
|
|
975
|
-
this.apiGatewayError = undefined;
|
|
976
|
-
this.testResult = undefined;
|
|
977
|
-
this.requestUpdate();
|
|
978
|
-
|
|
979
|
-
// Check if we have the API Gateway token
|
|
980
|
-
if (!this.apiGatewayToken) {
|
|
981
|
-
throw new Error(
|
|
982
|
-
'No API Gateway token available. Please click "Get gateway token" first.',
|
|
983
|
-
);
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
const apiGatewayToken = this.apiGatewayToken;
|
|
987
|
-
|
|
988
|
-
// Step 3: Access the service via API Gateway
|
|
989
|
-
// The service endpoint URL from dcat:endpointURL already includes the full API Gateway path
|
|
990
|
-
const serviceEndpointUrl = (this.object as any).url;
|
|
991
|
-
|
|
992
|
-
if (!serviceEndpointUrl) {
|
|
993
|
-
throw new Error(
|
|
994
|
-
"No service endpoint URL found in service object. " +
|
|
995
|
-
"The dcat:service in the self-description must include a dcat:endpointURL field. " +
|
|
996
|
-
'Example: "dcat:endpointURL": "https://participant-a.tems-dataspace.eu/apigateway/v2/store/inventory"',
|
|
997
|
-
);
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
// The dcat:endpointURL already points to the correct URL with API Gateway included
|
|
1001
|
-
// No need to reconstruct - use it directly
|
|
1002
|
-
|
|
1003
|
-
const response = await fetch(serviceEndpointUrl, {
|
|
1004
|
-
method: "GET",
|
|
1005
|
-
headers: {
|
|
1006
|
-
"X-API-Gateway-Token": apiGatewayToken,
|
|
1007
|
-
},
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
if (!response.ok) {
|
|
1011
|
-
const errorText = await response.text();
|
|
1012
|
-
throw new Error(
|
|
1013
|
-
`Failed to fetch data via API Gateway: ${response.status} - ${errorText}`,
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
const data = await response.json();
|
|
1018
|
-
this.testResult = data;
|
|
1019
|
-
} catch (error) {
|
|
1020
|
-
console.error("โ Service test failed:", error);
|
|
1021
|
-
this.apiGatewayError = (error as Error).message;
|
|
1022
|
-
} finally {
|
|
1023
|
-
this.testingService = false;
|
|
1024
|
-
this.requestUpdate();
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// ============================================================================
|
|
1029
|
-
// EDR (Endpoint Data Reference) Data Access Methods
|
|
1030
|
-
// ============================================================================
|
|
1031
|
-
// These methods provide UI coordination for EDR-based data access when no
|
|
1032
|
-
// API Gateway is configured. They delegate business logic to the
|
|
1033
|
-
// DataspaceConnectorStore (sib-core) and focus on UI state management.
|
|
1034
|
-
//
|
|
1035
|
-
// Architecture:
|
|
1036
|
-
// - tems-modal: UI layer (state, progress, errors, user interaction)
|
|
1037
|
-
// - dspStore (DataspaceConnectorStore): Business logic (HTTP calls, polling, auth)
|
|
1038
|
-
//
|
|
1039
|
-
// Flow:
|
|
1040
|
-
// 1. _initiateEDRTransfer() โ dspStore.initiateEDRTransfer() + getEDRToken()
|
|
1041
|
-
// 2. _accessData() โ _fetchDataWithLongPolling() โ dspStore.fetchWithEDRToken()
|
|
1042
|
-
// ============================================================================
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* Initiate EDR transfer for HTTP Pull data access
|
|
1046
|
-
* Delegates to the DataspaceConnectorStore
|
|
1047
|
-
*/
|
|
1048
|
-
async _initiateEDRTransfer() {
|
|
1049
|
-
try {
|
|
1050
|
-
this.gettingEDR = true;
|
|
1051
|
-
this.transferError = undefined;
|
|
1052
|
-
this.requestUpdate();
|
|
1053
|
-
|
|
1054
|
-
// Use the DSP store passed as property
|
|
1055
|
-
if (!this.dspStore) {
|
|
1056
|
-
throw new Error("DSP connector not configured.");
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
const obj = this.object as any;
|
|
1060
|
-
const assetId = obj.datasetId || obj.assetId;
|
|
1061
|
-
const counterPartyAddress = obj.counterPartyAddress;
|
|
1062
|
-
|
|
1063
|
-
if (!assetId) {
|
|
1064
|
-
throw new Error("No asset ID found in service object");
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
if (!counterPartyAddress) {
|
|
1068
|
-
throw new Error("No provider endpoint address found");
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
if (!this.contractId) {
|
|
1072
|
-
throw new Error(
|
|
1073
|
-
"No contract agreement available. Please complete contract negotiation first.",
|
|
1074
|
-
);
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
// Get providerParticipantId to properly key the agreement mapping
|
|
1078
|
-
const providerId =
|
|
1079
|
-
obj.counterPartyId || obj._providerParticipantId || obj._provider || "";
|
|
1080
|
-
|
|
1081
|
-
// Store delegates to DataspaceConnectorStore.initiateEDRTransfer()
|
|
1082
|
-
// which handles the transfer process creation
|
|
1083
|
-
const transferId = await this.dspStore.initiateEDRTransfer(
|
|
1084
|
-
assetId,
|
|
1085
|
-
counterPartyAddress,
|
|
1086
|
-
this.contractId,
|
|
1087
|
-
providerId,
|
|
1088
|
-
);
|
|
1089
|
-
|
|
1090
|
-
// Store delegates to DataspaceConnectorStore.getEDRToken()
|
|
1091
|
-
// which handles polling (10 attempts ร 2s) and returns EDR data address
|
|
1092
|
-
const edrDataAddress = await this.dspStore.getEDRToken(transferId);
|
|
1093
|
-
|
|
1094
|
-
if (!edrDataAddress) {
|
|
1095
|
-
throw new Error("Failed to retrieve EDR token");
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
// Transform localhost endpoint to public provider address if needed
|
|
1099
|
-
let transformedEndpoint = edrDataAddress.endpoint;
|
|
1100
|
-
if (
|
|
1101
|
-
transformedEndpoint.includes("localhost") ||
|
|
1102
|
-
transformedEndpoint.includes("127.0.0.1")
|
|
1103
|
-
) {
|
|
1104
|
-
const providerBase = counterPartyAddress.replace("/protocol", "");
|
|
1105
|
-
const localUrl = new URL(transformedEndpoint);
|
|
1106
|
-
transformedEndpoint = `${providerBase}${localUrl.pathname}${localUrl.search}`;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// Store the EDR information for data access
|
|
1110
|
-
this.transferId = transferId;
|
|
1111
|
-
this.edrToken = edrDataAddress.authorization;
|
|
1112
|
-
this.edrEndpoint = transformedEndpoint;
|
|
1113
|
-
} catch (error) {
|
|
1114
|
-
console.error("โ EDR transfer failed:", error);
|
|
1115
|
-
this.transferError = (error as Error).message;
|
|
1116
|
-
} finally {
|
|
1117
|
-
this.gettingEDR = false;
|
|
1118
|
-
this.requestUpdate();
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
* Access data using EDR token with long-polling strategy
|
|
1124
|
-
* Delegates to the DataspaceConnectorStore for actual data fetching
|
|
1125
|
-
*/
|
|
1126
|
-
async _accessData() {
|
|
1127
|
-
try {
|
|
1128
|
-
this.accessingData = true;
|
|
1129
|
-
this.dataAccessError = undefined;
|
|
1130
|
-
this.dataResult = undefined;
|
|
1131
|
-
this.requestUpdate();
|
|
1132
|
-
|
|
1133
|
-
if (!this.dspStore) {
|
|
1134
|
-
throw new Error("DSP connector not configured.");
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
if (!this.edrToken || !this.edrEndpoint) {
|
|
1138
|
-
throw new Error(
|
|
1139
|
-
'No EDR token available. Please click "Get EDR Token" first.',
|
|
1140
|
-
);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
const obj = this.object as any;
|
|
1144
|
-
const counterPartyAddress = obj.counterPartyAddress;
|
|
1145
|
-
|
|
1146
|
-
// Transform localhost endpoint to public provider address if needed
|
|
1147
|
-
let transformedEndpoint = this.edrEndpoint;
|
|
1148
|
-
if (
|
|
1149
|
-
transformedEndpoint.includes("localhost") ||
|
|
1150
|
-
transformedEndpoint.includes("127.0.0.1")
|
|
1151
|
-
) {
|
|
1152
|
-
const providerBase = counterPartyAddress.replace("/protocol", "");
|
|
1153
|
-
const localUrl = new URL(transformedEndpoint);
|
|
1154
|
-
transformedEndpoint = `${providerBase}${localUrl.pathname}${localUrl.search}`;
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
// Build EDR data address object for the store
|
|
1158
|
-
const edrDataAddress = {
|
|
1159
|
-
endpoint: transformedEndpoint,
|
|
1160
|
-
authorization: this.edrToken,
|
|
1161
|
-
authType: "bearer",
|
|
1162
|
-
type: "https://w3id.org/idsa/v4.1/HTTP",
|
|
1163
|
-
endpointType: "https://w3id.org/idsa/v4.1/HTTP",
|
|
1164
|
-
};
|
|
1165
|
-
|
|
1166
|
-
// Implement UI-level long-polling with progress feedback
|
|
1167
|
-
// The store's fetchWithEDRToken handles the actual HTTP request
|
|
1168
|
-
const data = await this._fetchDataWithLongPolling(edrDataAddress);
|
|
1169
|
-
|
|
1170
|
-
this.dataResult = data;
|
|
1171
|
-
} catch (error) {
|
|
1172
|
-
console.error("โ Data access failed:", error);
|
|
1173
|
-
this.dataAccessError = (error as Error).message;
|
|
1174
|
-
} finally {
|
|
1175
|
-
this.accessingData = false;
|
|
1176
|
-
this.dataAccessAttempt = undefined;
|
|
1177
|
-
this.dataAccessMaxAttempts = undefined;
|
|
1178
|
-
this.countdown = undefined;
|
|
1179
|
-
this.requestUpdate();
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
/**
|
|
1184
|
-
* Fetch data with long-polling retry logic and UI progress updates
|
|
1185
|
-
* Delegates actual fetching to DataspaceConnectorStore.fetchWithEDRToken()
|
|
1186
|
-
*/
|
|
1187
|
-
async _fetchDataWithLongPolling(
|
|
1188
|
-
edrDataAddress: any,
|
|
1189
|
-
maxAttempts = 12, // 12 attempts over 1 minute
|
|
1190
|
-
pollInterval = 5000, // 5 seconds between attempts
|
|
1191
|
-
): Promise<any> {
|
|
1192
|
-
this.dataAccessMaxAttempts = maxAttempts;
|
|
1193
|
-
|
|
1194
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1195
|
-
this.dataAccessAttempt = attempt;
|
|
1196
|
-
this.requestUpdate();
|
|
1197
|
-
|
|
1198
|
-
try {
|
|
1199
|
-
// Delegate to store's fetchWithEDRToken method
|
|
1200
|
-
// Store handles the actual HTTP request with proper headers
|
|
1201
|
-
const data = await this.dspStore.fetchWithEDRToken(edrDataAddress);
|
|
1202
|
-
|
|
1203
|
-
if (data) {
|
|
1204
|
-
return data;
|
|
1205
|
-
}
|
|
1206
|
-
} catch (error) {
|
|
1207
|
-
const errorMessage = (error as Error).message;
|
|
1208
|
-
console.warn(
|
|
1209
|
-
`โ ๏ธ Data access attempt ${attempt}/${maxAttempts} failed:`,
|
|
1210
|
-
errorMessage,
|
|
1211
|
-
);
|
|
1212
|
-
|
|
1213
|
-
// Check for specific error types that might resolve with waiting
|
|
1214
|
-
const isRetryableError =
|
|
1215
|
-
errorMessage.includes("404") ||
|
|
1216
|
-
errorMessage.includes("503") ||
|
|
1217
|
-
errorMessage.includes("502") ||
|
|
1218
|
-
errorMessage.includes("timeout") ||
|
|
1219
|
-
errorMessage.includes("not ready") ||
|
|
1220
|
-
errorMessage.includes("processing");
|
|
1221
|
-
|
|
1222
|
-
// If this is the last attempt or not a retryable error, throw
|
|
1223
|
-
if (attempt === maxAttempts || !isRetryableError) {
|
|
1224
|
-
console.error(`โ Final data access attempt failed: ${errorMessage}`);
|
|
1225
|
-
throw error;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
// Wait before next attempt (except on the last iteration)
|
|
1230
|
-
if (attempt < maxAttempts) {
|
|
1231
|
-
// Show countdown in UI during wait
|
|
1232
|
-
for (let countdown = pollInterval / 1000; countdown > 0; countdown--) {
|
|
1233
|
-
this.countdown = countdown;
|
|
1234
|
-
this.requestUpdate();
|
|
1235
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1236
|
-
}
|
|
1237
|
-
this.countdown = undefined;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
throw new Error(
|
|
1242
|
-
`Data access failed after ${maxAttempts} attempts over ${(maxAttempts * pollInterval) / 1000} seconds`,
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
778
|
|
|
1246
779
|
_renderBoolean(field: boolean): TemplateResultOrSymbol {
|
|
1247
780
|
if (field) {
|
|
@@ -1545,88 +1078,6 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
1545
1078
|
</div>`;
|
|
1546
1079
|
}
|
|
1547
1080
|
|
|
1548
|
-
_renderApiAccessGuide(): TemplateResultOrSymbol {
|
|
1549
|
-
// Only show for services with API Gateway configuration
|
|
1550
|
-
if (!this.apiGatewayConfig) {
|
|
1551
|
-
return nothing;
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
const obj = this.object as any;
|
|
1555
|
-
const serviceUrl = obj.url;
|
|
1556
|
-
const agreementId = this.contractId || "<your-agreement-id>";
|
|
1557
|
-
|
|
1558
|
-
const { keycloakUrl, realm, clientId, apiGatewayBaseUrl } =
|
|
1559
|
-
this.apiGatewayConfig;
|
|
1560
|
-
|
|
1561
|
-
return html`
|
|
1562
|
-
<div style="margin-top: 16px; padding: 16px; background: #f5f5f5; border-radius: 8px; font-family: monospace; font-size: 0.9em;">
|
|
1563
|
-
${this._renderDivision("h4", msg("API Access Guide"))}
|
|
1564
|
-
|
|
1565
|
-
<div style="margin-bottom: 16px;">
|
|
1566
|
-
<strong>Step 1: Get Keycloak Access Token</strong>
|
|
1567
|
-
<pre style="background: #fff; padding: 12px; border-radius: 4px; overflow-x: auto; margin-top: 8px;"><code>curl -X POST '${keycloakUrl}/realms/${realm}/protocol/openid-connect/token' \\
|
|
1568
|
-
-H 'Content-Type: application/x-www-form-urlencoded' \\
|
|
1569
|
-
-d 'grant_type=password' \\
|
|
1570
|
-
-d 'client_id=${clientId}' \\
|
|
1571
|
-
-d 'client_secret=<your-client-secret>' \\
|
|
1572
|
-
-d 'username=<your-username>' \\
|
|
1573
|
-
-d 'password=<your-password>' \\
|
|
1574
|
-
-d 'scope=openid'</code></pre>
|
|
1575
|
-
<div style="margin-top: 8px; color: #666; font-size: 0.9em;">
|
|
1576
|
-
Response: <code>{ "access_token": "eyJhbGc...", ... }</code>
|
|
1577
|
-
</div>
|
|
1578
|
-
</div>
|
|
1579
|
-
|
|
1580
|
-
<div style="margin-bottom: 16px;">
|
|
1581
|
-
<strong>Step 2: Get API Gateway Token</strong>
|
|
1582
|
-
<pre style="background: #fff; padding: 12px; border-radius: 4px; overflow-x: auto; margin-top: 8px;"><code>curl -X POST '${apiGatewayBaseUrl}/token' \\
|
|
1583
|
-
-H 'Content-Type: application/json' \\
|
|
1584
|
-
-H 'Authorization: Bearer <access_token_from_step_1>' \\
|
|
1585
|
-
-d '{
|
|
1586
|
-
"agreementId": "${agreementId}"
|
|
1587
|
-
}'</code></pre>
|
|
1588
|
-
<div style="margin-top: 8px; color: #666; font-size: 0.9em;">
|
|
1589
|
-
Response: <code>{ "apiGatewayToken": "xxx...", ... }</code>
|
|
1590
|
-
</div>
|
|
1591
|
-
</div>
|
|
1592
|
-
|
|
1593
|
-
<div style="margin-bottom: 16px;">
|
|
1594
|
-
<strong>Step 3: Call the Service</strong>
|
|
1595
|
-
<pre style="background: #fff; padding: 12px; border-radius: 4px; overflow-x: auto; margin-top: 8px;"><code>curl -X GET '${serviceUrl || "<service-endpoint>"}' \\
|
|
1596
|
-
-H 'X-API-Gateway-Token: <apiGatewayToken_from_step_2>'</code></pre>
|
|
1597
|
-
<div style="margin-top: 8px; color: #666; font-size: 0.9em;">
|
|
1598
|
-
${
|
|
1599
|
-
this.negotiationStatus === "granted" && this.contractId
|
|
1600
|
-
? html`โ
<strong>Agreement ID:</strong>
|
|
1601
|
-
<code>${this.contractId}</code>`
|
|
1602
|
-
: html`โ ๏ธ You need to complete contract negotiation first to get
|
|
1603
|
-
an agreement ID.`
|
|
1604
|
-
}
|
|
1605
|
-
</div>
|
|
1606
|
-
</div>
|
|
1607
|
-
|
|
1608
|
-
${
|
|
1609
|
-
this.apiGatewayToken
|
|
1610
|
-
? html`
|
|
1611
|
-
<div
|
|
1612
|
-
style="margin-top: 16px; padding: 12px; background: #e8f5e9; border-radius: 4px;"
|
|
1613
|
-
>
|
|
1614
|
-
<strong>๐ Your Current Session:</strong>
|
|
1615
|
-
<div style="margin-top: 8px;">
|
|
1616
|
-
<code
|
|
1617
|
-
style="word-break: break-all; display: block; background: #fff; padding: 8px; border-radius: 4px;"
|
|
1618
|
-
>
|
|
1619
|
-
X-API-Gateway-Token:
|
|
1620
|
-
${this.apiGatewayToken.substring(0, 40)}...
|
|
1621
|
-
</code>
|
|
1622
|
-
</div>
|
|
1623
|
-
</div>
|
|
1624
|
-
`
|
|
1625
|
-
: nothing
|
|
1626
|
-
}
|
|
1627
|
-
</div>
|
|
1628
|
-
`;
|
|
1629
|
-
}
|
|
1630
1081
|
|
|
1631
1082
|
_renderServiceSpecificModal(): TemplateResultOrSymbol {
|
|
1632
1083
|
return html` ${this._renderColumns(
|
|
@@ -1674,14 +1125,6 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
1674
1125
|
msg("Last Update"),
|
|
1675
1126
|
formatDate(this.object.last_update),
|
|
1676
1127
|
),
|
|
1677
|
-
)}${this.renderTemplateWhenWith(["url"], () =>
|
|
1678
|
-
this._renderButton(
|
|
1679
|
-
undefined,
|
|
1680
|
-
"sm",
|
|
1681
|
-
"Access the service",
|
|
1682
|
-
"outline-gray",
|
|
1683
|
-
this.object.url,
|
|
1684
|
-
),
|
|
1685
1128
|
)}${this.renderTemplateWhenWith(["documentation_url"], () =>
|
|
1686
1129
|
this._renderButton(
|
|
1687
1130
|
undefined,
|
|
@@ -1691,9 +1134,7 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
1691
1134
|
this.object.documentation_url,
|
|
1692
1135
|
),
|
|
1693
1136
|
)}
|
|
1694
|
-
${this._renderPolicyDescription()} ${this._renderAgreementInfo()}
|
|
1695
|
-
${this._renderApiAccessGuide()} ${this._renderApiTestingSection()}
|
|
1696
|
-
${this._renderEDRDataAccessSection()}`,
|
|
1137
|
+
${this._renderPolicyDescription()} ${this._renderAgreementInfo()}`,
|
|
1697
1138
|
)}`;
|
|
1698
1139
|
}
|
|
1699
1140
|
|
|
@@ -1871,294 +1312,6 @@ export class Ds4goCatalogModal extends TemsObjectHandler {
|
|
|
1871
1312
|
`;
|
|
1872
1313
|
}
|
|
1873
1314
|
|
|
1874
|
-
_renderEDRDataAccessSection(): TemplateResultOrSymbol {
|
|
1875
|
-
// Only show for services with negotiated access and NO API Gateway config
|
|
1876
|
-
if (this.negotiationStatus !== "granted" || this.apiGatewayConfig) {
|
|
1877
|
-
return nothing;
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
const storedInfo = this._loadAgreementInfo();
|
|
1881
|
-
const agreementDate = storedInfo?.timestamp
|
|
1882
|
-
? new Date(storedInfo.timestamp).toLocaleString()
|
|
1883
|
-
: null;
|
|
1884
|
-
|
|
1885
|
-
return html`
|
|
1886
|
-
<div
|
|
1887
|
-
style="margin-top: 24px; padding: 16px; background: #f5f5f5; border-radius: 8px;"
|
|
1888
|
-
>
|
|
1889
|
-
${this._renderDivision("h4", msg("EDR Data Access (HTTP Pull)"))}
|
|
1890
|
-
${this.contractId
|
|
1891
|
-
? html`
|
|
1892
|
-
<div
|
|
1893
|
-
style="font-size: 0.85em; opacity: 0.8; padding: 8px; background: rgba(0,128,0,0.05); border-radius: 4px; margin-bottom: 12px;"
|
|
1894
|
-
>
|
|
1895
|
-
<div><strong>Agreement ID:</strong> ${this.contractId}</div>
|
|
1896
|
-
${agreementDate
|
|
1897
|
-
? html`<div style="margin-top: 4px;">
|
|
1898
|
-
<strong>Agreed on:</strong> ${agreementDate}
|
|
1899
|
-
</div>`
|
|
1900
|
-
: nothing}
|
|
1901
|
-
${this.transferId
|
|
1902
|
-
? html`<div style="margin-top: 4px;">
|
|
1903
|
-
<strong>Transfer ID:</strong> ${this.transferId}
|
|
1904
|
-
</div>`
|
|
1905
|
-
: nothing}
|
|
1906
|
-
</div>
|
|
1907
|
-
`
|
|
1908
|
-
: nothing}
|
|
1909
|
-
|
|
1910
|
-
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
1911
|
-
<!-- Step 1: Get EDR Token -->
|
|
1912
|
-
${this.gettingEDR
|
|
1913
|
-
? html`<tems-button disabled="">
|
|
1914
|
-
<span
|
|
1915
|
-
style="display: inline-block; animation: spin 1s linear infinite;"
|
|
1916
|
-
>โณ</span
|
|
1917
|
-
>
|
|
1918
|
-
${msg("Getting EDR token...")}
|
|
1919
|
-
</tems-button>`
|
|
1920
|
-
: !this.edrToken
|
|
1921
|
-
? html`<tems-button
|
|
1922
|
-
@click=${this._initiateEDRTransfer}
|
|
1923
|
-
type="outline-gray"
|
|
1924
|
-
>
|
|
1925
|
-
๐ ${msg("Get EDR Token")}
|
|
1926
|
-
</tems-button>`
|
|
1927
|
-
: html`
|
|
1928
|
-
<div
|
|
1929
|
-
style="color: green; font-size: 0.85em; padding: 8px; background: rgba(0,128,0,0.05); border-radius: 4px;"
|
|
1930
|
-
>
|
|
1931
|
-
<strong>โ
${msg("EDR Token Ready")}</strong>
|
|
1932
|
-
${this.edrEndpoint
|
|
1933
|
-
? html`<div
|
|
1934
|
-
style="margin-top: 4px; font-family: monospace; font-size: 0.8em; word-break: break-all;"
|
|
1935
|
-
>
|
|
1936
|
-
<strong>Endpoint:</strong> ${this.edrEndpoint}
|
|
1937
|
-
</div>`
|
|
1938
|
-
: nothing}
|
|
1939
|
-
</div>
|
|
1940
|
-
`}
|
|
1941
|
-
${this.transferError
|
|
1942
|
-
? html`
|
|
1943
|
-
<div
|
|
1944
|
-
style="color: red; font-size: 0.85em; padding: 8px; background: rgba(255,0,0,0.05); border-radius: 4px;"
|
|
1945
|
-
>
|
|
1946
|
-
โ ๏ธ ${this.transferError}
|
|
1947
|
-
<tems-button
|
|
1948
|
-
@click=${this._initiateEDRTransfer}
|
|
1949
|
-
type="outline-gray"
|
|
1950
|
-
style="margin-top: 8px;"
|
|
1951
|
-
>
|
|
1952
|
-
๐ ${msg("Retry")}
|
|
1953
|
-
</tems-button>
|
|
1954
|
-
</div>
|
|
1955
|
-
`
|
|
1956
|
-
: nothing}
|
|
1957
|
-
|
|
1958
|
-
<!-- Step 2: Access Data -->
|
|
1959
|
-
${this.edrToken
|
|
1960
|
-
? this.accessingData
|
|
1961
|
-
? html`<tems-button disabled="">
|
|
1962
|
-
<span
|
|
1963
|
-
style="display: inline-block; animation: spin 1s linear infinite;"
|
|
1964
|
-
>๐ก</span
|
|
1965
|
-
>
|
|
1966
|
-
${msg("Accessing data")}
|
|
1967
|
-
${this.dataAccessAttempt
|
|
1968
|
-
? ` (${this.dataAccessAttempt}/${this.dataAccessMaxAttempts})`
|
|
1969
|
-
: "..."}
|
|
1970
|
-
${this.countdown
|
|
1971
|
-
? html`<br /><span style="font-size: 0.8em;"
|
|
1972
|
-
>โณ Next retry in ${this.countdown}s</span
|
|
1973
|
-
>`
|
|
1974
|
-
: nothing}
|
|
1975
|
-
</tems-button>`
|
|
1976
|
-
: html`<tems-button @click=${this._accessData} type="primary">
|
|
1977
|
-
๐ ${msg("Access Data")}
|
|
1978
|
-
</tems-button>`
|
|
1979
|
-
: nothing}
|
|
1980
|
-
${this.dataAccessError
|
|
1981
|
-
? html`
|
|
1982
|
-
<div
|
|
1983
|
-
style="color: red; font-size: 0.85em; padding: 8px; background: rgba(255,0,0,0.05); border-radius: 4px;"
|
|
1984
|
-
>
|
|
1985
|
-
โ ๏ธ ${this.dataAccessError}
|
|
1986
|
-
</div>
|
|
1987
|
-
`
|
|
1988
|
-
: nothing}
|
|
1989
|
-
${this.dataResult
|
|
1990
|
-
? html`
|
|
1991
|
-
<div
|
|
1992
|
-
style="color: green; font-size: 0.85em; padding: 8px; background: rgba(0,128,0,0.05); border-radius: 4px; max-height: 300px; overflow-y: auto;"
|
|
1993
|
-
>
|
|
1994
|
-
<strong>โ
${msg("Data retrieved successfully:")}</strong>
|
|
1995
|
-
<pre
|
|
1996
|
-
style="margin-top: 8px; font-size: 0.9em; white-space: pre-wrap; word-wrap: break-word; background: white; padding: 8px; border-radius: 4px;"
|
|
1997
|
-
>
|
|
1998
|
-
${JSON.stringify(this.dataResult, null, 2)}</pre
|
|
1999
|
-
>
|
|
2000
|
-
</div>
|
|
2001
|
-
`
|
|
2002
|
-
: nothing}
|
|
2003
|
-
</div>
|
|
2004
|
-
|
|
2005
|
-
<div
|
|
2006
|
-
style="margin-top: 16px; padding: 12px; background: rgba(0,0,0,0.05); border-radius: 4px; font-size: 0.85em;"
|
|
2007
|
-
>
|
|
2008
|
-
<div style="margin-bottom: 4px;">
|
|
2009
|
-
<strong>โน๏ธ ${msg("About EDR (Endpoint Data Reference)")}</strong>
|
|
2010
|
-
</div>
|
|
2011
|
-
<div>
|
|
2012
|
-
${msg(
|
|
2013
|
-
"EDR is the Eclipse Dataspace Protocol's HTTP Pull mechanism for accessing data. The EDR token provides temporary, authorized access to the provider's data endpoint.",
|
|
2014
|
-
)}
|
|
2015
|
-
</div>
|
|
2016
|
-
</div>
|
|
2017
|
-
|
|
2018
|
-
${storedInfo
|
|
2019
|
-
? html`
|
|
2020
|
-
<div
|
|
2021
|
-
style="margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0,0,0,0.1);"
|
|
2022
|
-
>
|
|
2023
|
-
<button
|
|
2024
|
-
@click=${this._renewContract}
|
|
2025
|
-
style="font-size: 0.85em; color: #666; background: none; border: none; cursor: pointer; text-decoration: underline; padding: 0;"
|
|
2026
|
-
>
|
|
2027
|
-
๐ ${msg("Renegotiate contract")}
|
|
2028
|
-
</button>
|
|
2029
|
-
</div>
|
|
2030
|
-
`
|
|
2031
|
-
: nothing}
|
|
2032
|
-
</div>
|
|
2033
|
-
|
|
2034
|
-
<style>
|
|
2035
|
-
@keyframes spin {
|
|
2036
|
-
0% {
|
|
2037
|
-
transform: rotate(0deg);
|
|
2038
|
-
}
|
|
2039
|
-
100% {
|
|
2040
|
-
transform: rotate(360deg);
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
</style>
|
|
2044
|
-
`;
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
_renderApiTestingSection(): TemplateResultOrSymbol {
|
|
2048
|
-
// Only show for services with negotiated access AND API Gateway config
|
|
2049
|
-
if (this.negotiationStatus !== "granted" || !this.apiGatewayConfig) {
|
|
2050
|
-
return nothing;
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
const storedInfo = this._loadAgreementInfo();
|
|
2054
|
-
const agreementDate = storedInfo?.timestamp
|
|
2055
|
-
? new Date(storedInfo.timestamp).toLocaleString()
|
|
2056
|
-
: null;
|
|
2057
|
-
|
|
2058
|
-
return html`
|
|
2059
|
-
<div
|
|
2060
|
-
style="margin-top: 24px; padding: 16px; background: #f5f5f5; border-radius: 8px;"
|
|
2061
|
-
>
|
|
2062
|
-
${this._renderDivision("h4", msg("API Testing"))}
|
|
2063
|
-
${this.contractId
|
|
2064
|
-
? html`
|
|
2065
|
-
<div
|
|
2066
|
-
style="font-size: 0.85em; opacity: 0.8; padding: 8px; background: rgba(0,128,0,0.05); border-radius: 4px; margin-bottom: 12px;"
|
|
2067
|
-
>
|
|
2068
|
-
<div><strong>Agreement ID:</strong> ${this.contractId}</div>
|
|
2069
|
-
${agreementDate
|
|
2070
|
-
? html`<div style="margin-top: 4px;">
|
|
2071
|
-
<strong>Agreed on:</strong> ${agreementDate}
|
|
2072
|
-
</div>`
|
|
2073
|
-
: nothing}
|
|
2074
|
-
</div>
|
|
2075
|
-
`
|
|
2076
|
-
: nothing}
|
|
2077
|
-
|
|
2078
|
-
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
2079
|
-
<!-- Button 1: Get Gateway Token -->
|
|
2080
|
-
${this.gettingToken
|
|
2081
|
-
? html`<tems-button disabled="">
|
|
2082
|
-
${msg("Getting token...")}
|
|
2083
|
-
</tems-button>`
|
|
2084
|
-
: html`<tems-button
|
|
2085
|
-
@click=${this._getGatewayToken}
|
|
2086
|
-
type="outline-gray"
|
|
2087
|
-
?disabled=${!!this.apiGatewayToken}
|
|
2088
|
-
>
|
|
2089
|
-
๐ ${msg("Get gateway token")}
|
|
2090
|
-
</tems-button>`}
|
|
2091
|
-
|
|
2092
|
-
<!-- Button 2: Test Service (only if displayServiceTest is enabled) -->
|
|
2093
|
-
${this.displayServiceTest
|
|
2094
|
-
? this.testingService
|
|
2095
|
-
? html`<tems-button disabled="">
|
|
2096
|
-
${msg("Testing service...")}
|
|
2097
|
-
</tems-button>`
|
|
2098
|
-
: html`<tems-button
|
|
2099
|
-
@click=${this._testService}
|
|
2100
|
-
type="primary"
|
|
2101
|
-
?disabled=${!this.apiGatewayToken}
|
|
2102
|
-
>
|
|
2103
|
-
๐งช ${msg("Test service")}
|
|
2104
|
-
</tems-button>`
|
|
2105
|
-
: nothing}
|
|
2106
|
-
${this.apiGatewayError
|
|
2107
|
-
? html`
|
|
2108
|
-
<div style="color: red; font-size: 0.85em;">
|
|
2109
|
-
โ ๏ธ ${this.apiGatewayError}
|
|
2110
|
-
</div>
|
|
2111
|
-
`
|
|
2112
|
-
: nothing}
|
|
2113
|
-
${this.apiGatewayToken
|
|
2114
|
-
? html`
|
|
2115
|
-
<div
|
|
2116
|
-
style="color: green; font-size: 0.85em; padding: 8px; background: rgba(0,128,0,0.05); border-radius: 4px;"
|
|
2117
|
-
>
|
|
2118
|
-
<div style="margin-bottom: 4px;">
|
|
2119
|
-
<strong>โ
${msg("API Gateway token obtained")}</strong>
|
|
2120
|
-
</div>
|
|
2121
|
-
<div
|
|
2122
|
-
style="word-break: break-all; font-family: monospace; font-size: 0.9em;"
|
|
2123
|
-
>
|
|
2124
|
-
<strong>X-API-Gateway-Token:</strong><br />
|
|
2125
|
-
${this.apiGatewayToken}
|
|
2126
|
-
</div>
|
|
2127
|
-
</div>
|
|
2128
|
-
`
|
|
2129
|
-
: nothing}
|
|
2130
|
-
${this.displayServiceTest && this.testResult
|
|
2131
|
-
? html`
|
|
2132
|
-
<div
|
|
2133
|
-
style="color: green; font-size: 0.85em; padding: 8px; background: rgba(0,128,0,0.05); border-radius: 4px; max-height: 200px; overflow-y: auto;"
|
|
2134
|
-
>
|
|
2135
|
-
<strong>โ
${msg("Service response:")}</strong>
|
|
2136
|
-
<pre
|
|
2137
|
-
style="margin-top: 4px; font-size: 0.9em; white-space: pre-wrap; word-wrap: break-word;"
|
|
2138
|
-
>
|
|
2139
|
-
${JSON.stringify(this.testResult, null, 2)}</pre
|
|
2140
|
-
>
|
|
2141
|
-
</div>
|
|
2142
|
-
`
|
|
2143
|
-
: nothing}
|
|
2144
|
-
${storedInfo
|
|
2145
|
-
? html`
|
|
2146
|
-
<div
|
|
2147
|
-
style="margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(0,0,0,0.1);"
|
|
2148
|
-
>
|
|
2149
|
-
<button
|
|
2150
|
-
@click=${this._renewContract}
|
|
2151
|
-
style="font-size: 0.85em; color: #666; background: none; border: none; cursor: pointer; text-decoration: underline; padding: 0;"
|
|
2152
|
-
>
|
|
2153
|
-
๐ ${msg("Renegotiate contract")}
|
|
2154
|
-
</button>
|
|
2155
|
-
</div>
|
|
2156
|
-
`
|
|
2157
|
-
: nothing}
|
|
2158
|
-
</div>
|
|
2159
|
-
</div>
|
|
2160
|
-
`;
|
|
2161
|
-
}
|
|
2162
1315
|
|
|
2163
1316
|
_closeModal() {
|
|
2164
1317
|
this.dispatchEvent(new CustomEvent("close"));
|