zenml-nightly 0.84.2.dev20250826__py3-none-any.whl → 0.84.3.dev20250828__py3-none-any.whl
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.
- zenml/VERSION +1 -1
- zenml/cli/login.py +30 -19
- zenml/cli/server.py +6 -10
- zenml/login/credentials.py +27 -0
- zenml/login/credentials_store.py +80 -80
- zenml/login/pro/client.py +74 -54
- zenml/login/pro/utils.py +1 -3
- zenml/utils/package_utils.py +1 -1
- zenml/zen_server/dashboard/assets/{404-CMbwR10f.js → 404-JcbBcNWG.js} +1 -1
- zenml/zen_server/dashboard/assets/{AlertDialogDropdownItem-BpLj419i.js → AlertDialogDropdownItem-DuTp8JFP.js} +1 -1
- zenml/zen_server/dashboard/assets/{ButtonGroup-DVhzbQxV.js → ButtonGroup-B9_gfcC-.js} +1 -1
- zenml/zen_server/dashboard/assets/{CodeSnippet-B7Wd4R8u.js → CodeSnippet-D7OZMz7I.js} +1 -1
- zenml/zen_server/dashboard/assets/{CollapsibleCard-DSZzbjx5.js → CollapsibleCard-Aqd_AiGn.js} +1 -1
- zenml/zen_server/dashboard/assets/{ComponentBadge-MVT08kBV.js → ComponentBadge-BVRb7wgm.js} +1 -1
- zenml/zen_server/dashboard/assets/{ComponentIcon-C9AY8usJ.js → ComponentIcon-CWDJZO4Q.js} +1 -1
- zenml/zen_server/dashboard/assets/{DeleteAlertDialog-CzhTVQaC.js → DeleteAlertDialog-C5z9RupH.js} +1 -1
- zenml/zen_server/dashboard/assets/{DialogItem-DD_7v3UY.js → DialogItem-Ce3ugDgT.js} +1 -1
- zenml/zen_server/dashboard/assets/{Error-Cakgjexo.js → Error-BGej6F2q.js} +1 -1
- zenml/zen_server/dashboard/assets/{ExecutionStatus-CQbWovhV.js → ExecutionStatus-BhDlfndt.js} +1 -1
- zenml/zen_server/dashboard/assets/{Helpbox-7FZpmsHB.js → Helpbox-CQjyNo0n.js} +1 -1
- zenml/zen_server/dashboard/assets/{Infobox-DnduZjNU.js → Infobox-CXOIY92f.js} +1 -1
- zenml/zen_server/dashboard/assets/{LeftSideMenu-D5UEs4vB.js → LeftSideMenu-DGICatl7.js} +1 -1
- zenml/zen_server/dashboard/assets/{NestedCollapsible-0yviIfaA.js → NestedCollapsible-DmryIMwk.js} +1 -1
- zenml/zen_server/dashboard/assets/{NumberBox-C2Pju4PR.js → NumberBox-7OIA56xe.js} +1 -1
- zenml/zen_server/dashboard/assets/{Pagination-C1sE5Nzn.js → Pagination-CVYIgR9l.js} +1 -1
- zenml/zen_server/dashboard/assets/{Partials-DosysQMU.js → Partials-NicibWrh.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProCta-CUkpV3PJ.js → ProCta-DspRnbtD.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderIcon-aQjTuTRX.js → ProviderIcon-DwKsXziU.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderRadio-C6pLVNxC.js → ProviderRadio-BixXi4GC.js} +1 -1
- zenml/zen_server/dashboard/assets/{RunsBody-CZAiSxYK.js → RunsBody-BSOSIxRG.js} +1 -1
- zenml/zen_server/dashboard/assets/{SearchField-Byv1PtST.js → SearchField-BbwrusMV.js} +1 -1
- zenml/zen_server/dashboard/assets/{SecretTooltip-CErfhVgF.js → SecretTooltip-BsFul9Oe.js} +1 -1
- zenml/zen_server/dashboard/assets/{SetPassword-C4OH-cn1.js → SetPassword-D-V7D0OR.js} +1 -1
- zenml/zen_server/dashboard/assets/{SheetHeader-Bb3v9rha.js → SheetHeader-xumE4haD.js} +1 -1
- zenml/zen_server/dashboard/assets/{StackComponentList-D85oab98.js → StackComponentList-CLnkE6-6.js} +1 -1
- zenml/zen_server/dashboard/assets/{StackList-DSAjbVb5.js → StackList-DhsbaNva.js} +1 -1
- zenml/zen_server/dashboard/assets/{Tabs-2uwqXsdL.js → Tabs-CkY3GwhK.js} +1 -1
- zenml/zen_server/dashboard/assets/{Tick-BQ7_xDBl.js → Tick-Bo7jwBjb.js} +1 -1
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DNkYgzN6.js → UpdatePasswordSchemas-BP4hg95A.js} +1 -1
- zenml/zen_server/dashboard/assets/{Wizard-2FIi8JNY.js → Wizard-0nbnAM1R.js} +1 -1
- zenml/zen_server/dashboard/assets/{WizardFooter-DQEnlDmZ.js → WizardFooter-CLPi4VBo.js} +1 -1
- zenml/zen_server/dashboard/assets/{all-pipeline-runs-query-CdvWtiAY.js → all-pipeline-runs-query-mPYeJsKz.js} +1 -1
- zenml/zen_server/dashboard/assets/{bulk-delete-wdObjfps.js → bulk-delete-Bgf_MsSj.js} +1 -1
- zenml/zen_server/dashboard/assets/{configuration-form-BbcNBQTO.js → configuration-form-Bnyw4-V5.js} +1 -1
- zenml/zen_server/dashboard/assets/{constants-DfvsDtcH.js → constants-Ddhz1mpL.js} +1 -1
- zenml/zen_server/dashboard/assets/{create-stack-D4Sf7QpF.js → create-stack-BpTy7-E4.js} +1 -1
- zenml/zen_server/dashboard/assets/{delete-run-XSre8ru-.js → delete-run-MHYb1wuH.js} +1 -1
- zenml/zen_server/dashboard/assets/form-VRON3-kT.js +1 -0
- zenml/zen_server/dashboard/assets/{form-schemas-BjWDRvGd.js → form-schemas-CpTCEVoG.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-BYHxFvoG.js → index-BYCBKUXk.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-CO6UN3UX.js → index-Ci5z7q6x.js} +2 -2
- zenml/zen_server/dashboard/assets/{index-d_QrPL-8.js → index-D_-iUPTg.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-BYzR7YrM.js → index-rnWXXblA.js} +1 -1
- zenml/zen_server/dashboard/assets/{layout-Cyc-V16c.js → layout-DhFllShI.js} +1 -1
- zenml/zen_server/dashboard/assets/{login-mutation-CDgTlQBD.js → login-mutation-DOxF97JV.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Bncp08FW.js → page-62MHYKGv.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CMvcoaSZ.js → page-6YPAQ0BU.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CEHZzQ1Q.js → page-8ZbMMHB-.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-D654xHWd.js → page-AWVW_lra.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BnZEAhNn.js → page-B1rTgWkQ.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BbsvGfyi.js → page-B5YCjh4t.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DgOEl3Bc.js → page-BDawWKR9.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DOnAInjC.js → page-BFzVhfws.js} +1 -1
- zenml/zen_server/dashboard/assets/page-BaQw51KO.js +1 -0
- zenml/zen_server/dashboard/assets/{page-C4Jnp1qP.js → page-Bh4TFAum.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CGtll3Jk.js → page-BjuIRasU.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-43SWdz4k.js → page-BoQ_Wheb.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Du6bFZTS.js → page-Bs6NTqHo.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-B4QBxV0B.js → page-Bsiqb0ZN.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-AOVMuHDc.js → page-C09_xoEs.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-O5xnz_nW.js → page-CI0U7nE4.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-C9IsnFid.js → page-CId8DuKX.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CzDjN1wE.js → page-CJSSCJ58.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-B8ict_cR.js → page-CJXxkmGx.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DgblJuXC.js → page-CS3tgRFg.js} +1 -1
- zenml/zen_server/dashboard/assets/page-Cd8VCEYG.js +1 -0
- zenml/zen_server/dashboard/assets/{page-DsFXol8x.js → page-CicaAxfg.js} +1 -1
- zenml/zen_server/dashboard/assets/page-CkFIY_Cg.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BnAMyQZb.js → page-CkX3R4er.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DerIbaqa.js → page-Cu-ZYOrj.js} +1 -1
- zenml/zen_server/dashboard/assets/page-CyID6Sp5.js +1 -0
- zenml/zen_server/dashboard/assets/{page-DZ5hcS1o.js → page-D0Z9wEBf.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-hX7NkNdA.js → page-D83H0IPX.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DgSu4pTE.js → page-DAF5ySum.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-ClqRvz_V.js → page-DMiWibIV.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Bi4I23Hs.js → page-DMtUDzEY.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BDre_qCO.js → page-DNLxVFH7.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DLrYW-Dn.js → page-Dfaj9bx6.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DznxxpKA.js → page-DhlhT_XT.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CKPkbOoE.js → page-DqXw1YhM.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-E9Mx9_rn.js → page-DtjangP-.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DQncGJeH.js → page-GJ63jkFi.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DSn1Jcvs.js → page-RtFcujP8.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-HrebZOAM.js → page-T1JEtrlC.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DQzNmXk3.js → page-cS5O9iLC.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Dpw-xP4q.js → page-s1bm6Xid.js} +1 -1
- zenml/zen_server/dashboard/assets/{persist-Dkbp9MFP.js → persist-C98EYq5I.js} +1 -1
- zenml/zen_server/dashboard/assets/{persist-B3Hb8HDW.js → persist-FuiGbvJD.js} +1 -1
- zenml/zen_server/dashboard/assets/{primary-role-CjkpP9fL.js → primary-role-Bsu7s76U.js} +1 -1
- zenml/zen_server/dashboard/assets/{resource-tyes-list-CugyWUAE.js → resource-tyes-list-C7I0VL8e.js} +1 -1
- zenml/zen_server/dashboard/assets/{resource-type-tooltip-V-zdiLKz.js → resource-type-tooltip-2vjL6Wh9.js} +1 -1
- zenml/zen_server/dashboard/assets/{service-FjqYTmBw.js → service-BPdLzxxT.js} +1 -1
- zenml/zen_server/dashboard/assets/{service-connectors-nF4-OXB9.js → service-connectors-BkE8Ol4i.js} +1 -1
- zenml/zen_server/dashboard/assets/{sharedSchema-Br1JPr8d.js → sharedSchema-CKFbjwmg.js} +1 -1
- zenml/zen_server/dashboard/assets/{stack-detail-query-DnyMCdVE.js → stack-detail-query-JsrqFo3U.js} +1 -1
- zenml/zen_server/dashboard/assets/{update-current-user-mutation-B3Dpv3u6.js → update-current-user-mutation-C4CPf7vG.js} +1 -1
- zenml/zen_server/dashboard/assets/{update-server-settings-mutation-s7tlHN8s.js → update-server-settings-mutation-BYfIU-xU.js} +1 -1
- zenml/zen_server/dashboard/index.html +2 -2
- zenml/zen_server/routers/service_accounts_endpoints.py +24 -0
- zenml/zen_stores/migrations/versions/0.84.3_release.py +23 -0
- zenml/zen_stores/rest_zen_store.py +16 -5
- {zenml_nightly-0.84.2.dev20250826.dist-info → zenml_nightly-0.84.3.dev20250828.dist-info}/METADATA +2 -2
- {zenml_nightly-0.84.2.dev20250826.dist-info → zenml_nightly-0.84.3.dev20250828.dist-info}/RECORD +116 -115
- zenml/zen_server/dashboard/assets/form-C1jJgRy1.js +0 -1
- zenml/zen_server/dashboard/assets/page-1DXG7hp8.js +0 -1
- zenml/zen_server/dashboard/assets/page-C7Z2sDHE.js +0 -1
- zenml/zen_server/dashboard/assets/page-C8t9qLdV.js +0 -1
- zenml/zen_server/dashboard/assets/page-CVwTAlzd.js +0 -1
- {zenml_nightly-0.84.2.dev20250826.dist-info → zenml_nightly-0.84.3.dev20250828.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.84.2.dev20250826.dist-info → zenml_nightly-0.84.3.dev20250828.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.84.2.dev20250826.dist-info → zenml_nightly-0.84.3.dev20250828.dist-info}/entry_points.txt +0 -0
zenml/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.84.
|
1
|
+
0.84.3.dev20250828
|
zenml/cli/login.py
CHANGED
@@ -254,7 +254,7 @@ def connect_to_server(
|
|
254
254
|
# flow.
|
255
255
|
cli_utils.declare(f"Authenticating to ZenML server '{url}'...")
|
256
256
|
else:
|
257
|
-
if refresh or not credentials_store.
|
257
|
+
if refresh or not credentials_store.has_valid_credentials(url):
|
258
258
|
cli_utils.declare(
|
259
259
|
f"Authenticating to ZenML server '{url}' using the web "
|
260
260
|
"login..."
|
@@ -331,6 +331,17 @@ def connect_to_pro_server(
|
|
331
331
|
|
332
332
|
server_id, server_url, server_name = None, None, None
|
333
333
|
login = False
|
334
|
+
credentials_store = get_credentials_store()
|
335
|
+
|
336
|
+
if api_key and api_key.startswith("ZENPROKEY_"):
|
337
|
+
if not pro_server:
|
338
|
+
raise ValueError(
|
339
|
+
"You must provide the name of the ZenML Pro server when "
|
340
|
+
"connecting with a ZenML Pro API key."
|
341
|
+
)
|
342
|
+
credentials_store.set_api_key(pro_api_url, api_key, is_zenml_pro=True)
|
343
|
+
api_key = None
|
344
|
+
|
334
345
|
if not pro_server:
|
335
346
|
login = True
|
336
347
|
if api_key:
|
@@ -351,8 +362,7 @@ def connect_to_pro_server(
|
|
351
362
|
else:
|
352
363
|
server_url = pro_server
|
353
364
|
|
354
|
-
|
355
|
-
if not credentials_store.has_valid_pro_authentication(pro_api_url):
|
365
|
+
if not credentials_store.has_valid_pro_credentials(pro_api_url):
|
356
366
|
# Without valid ZenML Pro credentials, we can only connect to a ZenML
|
357
367
|
# Pro server with an API key and we also need to know the URL of the
|
358
368
|
# server to connect to.
|
@@ -375,10 +385,9 @@ def connect_to_pro_server(
|
|
375
385
|
|
376
386
|
if login or refresh:
|
377
387
|
# If we reached this point, then we need to start a new login flow.
|
378
|
-
# We also need to remove all existing
|
379
|
-
#
|
380
|
-
|
381
|
-
credentials_store.clear_all_pro_tokens()
|
388
|
+
# We also need to remove all existing credentials, otherwise they will
|
389
|
+
# continue to be used after the re-login flow.
|
390
|
+
credentials_store.clear_all_credentials()
|
382
391
|
try:
|
383
392
|
token = web_login(
|
384
393
|
pro_api_url=pro_api_url,
|
@@ -858,10 +867,21 @@ def login(
|
|
858
867
|
)
|
859
868
|
return
|
860
869
|
|
870
|
+
api_key_value: Optional[str] = None
|
871
|
+
if api_key:
|
872
|
+
# Read the API key from the user
|
873
|
+
api_key_value = click.prompt(
|
874
|
+
"Please enter the API key for the ZenML server",
|
875
|
+
type=str,
|
876
|
+
hide_input=True,
|
877
|
+
)
|
878
|
+
if api_key_value and api_key_value.startswith("ZENPROKEY_"):
|
879
|
+
pro = True
|
880
|
+
|
861
881
|
if pro:
|
862
882
|
connect_to_pro_server(
|
863
883
|
pro_server=server,
|
864
|
-
|
884
|
+
api_key=api_key_value,
|
865
885
|
pro_api_url=pro_api_url,
|
866
886
|
verify_ssl=ssl_ca_cert
|
867
887
|
if ssl_ca_cert is not None
|
@@ -877,15 +897,6 @@ def login(
|
|
877
897
|
if not connected_to_local_server():
|
878
898
|
current_non_local_server = store_cfg.url
|
879
899
|
|
880
|
-
api_key_value: Optional[str] = None
|
881
|
-
if api_key:
|
882
|
-
# Read the API key from the user
|
883
|
-
api_key_value = click.prompt(
|
884
|
-
"Please enter the API key for the ZenML server",
|
885
|
-
type=str,
|
886
|
-
hide_input=True,
|
887
|
-
)
|
888
|
-
|
889
900
|
verify_ssl: Union[str, bool] = (
|
890
901
|
ssl_ca_cert if ssl_ca_cert is not None else not no_verify_ssl
|
891
902
|
)
|
@@ -1126,7 +1137,7 @@ def logout(
|
|
1126
1137
|
|
1127
1138
|
pro_api_url = pro_api_url or ZENML_PRO_API_URL
|
1128
1139
|
pro_api_url = pro_api_url.rstrip("/")
|
1129
|
-
if credentials_store.
|
1140
|
+
if credentials_store.get_pro_credentials(pro_api_url):
|
1130
1141
|
credentials_store.clear_pro_credentials(pro_api_url)
|
1131
1142
|
cli_utils.declare("Logged out from ZenML Pro.")
|
1132
1143
|
else:
|
@@ -1141,7 +1152,7 @@ def logout(
|
|
1141
1152
|
if credentials and credentials.pro_api_url == pro_api_url:
|
1142
1153
|
gc.set_default_store()
|
1143
1154
|
|
1144
|
-
credentials_store.
|
1155
|
+
credentials_store.clear_all_credentials()
|
1145
1156
|
cli_utils.declare("Logged out from all ZenML Pro servers.")
|
1146
1157
|
return
|
1147
1158
|
|
zenml/cli/server.py
CHANGED
@@ -217,12 +217,12 @@ def status() -> None:
|
|
217
217
|
if server:
|
218
218
|
if server.type == ServerType.PRO:
|
219
219
|
# If connected to a ZenML Pro server, refresh the server info
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
220
|
+
if credentials_store.can_login(
|
221
|
+
server_url=server.pro_api_url or ZENML_PRO_API_URL,
|
222
|
+
):
|
223
|
+
pro_client = ZenMLProClient(
|
224
|
+
server.pro_api_url or ZENML_PRO_API_URL
|
225
|
+
)
|
226
226
|
pro_servers = pro_client.workspace.list(
|
227
227
|
url=store_cfg.url, member_only=True
|
228
228
|
)
|
@@ -240,10 +240,6 @@ def status() -> None:
|
|
240
240
|
cli_utils.declare(
|
241
241
|
f" ZenML Pro Organization: {server.organization_hyperlink}"
|
242
242
|
)
|
243
|
-
if pro_credentials:
|
244
|
-
cli_utils.declare(
|
245
|
-
f" ZenML Pro authentication: {pro_credentials.auth_status}"
|
246
|
-
)
|
247
243
|
else:
|
248
244
|
cli_utils.declare(
|
249
245
|
f"Connected to a remote ZenML server: `{server.dashboard_hyperlink}`"
|
zenml/login/credentials.py
CHANGED
@@ -138,6 +138,33 @@ class ServerCredentials(BaseModel):
|
|
138
138
|
return ServerType.LOCAL
|
139
139
|
return ServerType.REMOTE
|
140
140
|
|
141
|
+
@property
|
142
|
+
def can_refresh_token(self) -> bool:
|
143
|
+
"""Check if the credentials can be used to refresh the API token.
|
144
|
+
|
145
|
+
A token refresh is only possible if long-lived credentials exist that
|
146
|
+
can be used to login and generate a new API token.
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
True if the server credentials can be used to refresh the API token,
|
150
|
+
False otherwise.
|
151
|
+
"""
|
152
|
+
return (
|
153
|
+
self.username is not None
|
154
|
+
and self.password is not None
|
155
|
+
or self.api_key is not None
|
156
|
+
or self.type == ServerType.LOCAL
|
157
|
+
)
|
158
|
+
|
159
|
+
@property
|
160
|
+
def has_valid_token(self) -> bool:
|
161
|
+
"""Check if the API token is valid.
|
162
|
+
|
163
|
+
Returns:
|
164
|
+
True if the API token is valid, False otherwise.
|
165
|
+
"""
|
166
|
+
return self.api_token is not None and not self.api_token.expired
|
167
|
+
|
141
168
|
def update_server_info(
|
142
169
|
self, server_info: Union[ServerModel, WorkspaceRead]
|
143
170
|
) -> None:
|
zenml/login/credentials_store.py
CHANGED
@@ -54,12 +54,15 @@ class CredentialsStore(metaclass=SingletonMetaClass):
|
|
54
54
|
1. when the user runs `zenml login` to authenticate to a ZenML Pro
|
55
55
|
server, the ZenML Pro API token fetched from the web login flow is
|
56
56
|
stored in the Credentials Store.
|
57
|
-
2. when the user runs `zenml login` to authenticate to a
|
57
|
+
2. when the user runs `zenml login` to authenticate to a ZenML Pro
|
58
|
+
server using an API key, the ZenML Pro API key is stored in the
|
59
|
+
Credentials Store.
|
60
|
+
3. when the user runs `zenml login` to authenticate to a regular ZenML
|
58
61
|
server with the web login flow, the ZenML server API token fetched
|
59
62
|
through the web login flow is stored in the Credentials Store
|
60
|
-
|
63
|
+
4. when the user runs `zenml login` to authenticate to any ZenML server
|
61
64
|
using an API key, the API key is stored in the Credentials Store
|
62
|
-
|
65
|
+
5. when the REST zen store is initialized, it starts up not yet
|
63
66
|
authenticated. Then, if/when it needs to authenticate or re-authenticate
|
64
67
|
to the remote server, it will use whatever form of credentials it finds
|
65
68
|
in the Credentials Store, in order of priority:
|
@@ -71,8 +74,12 @@ class CredentialsStore(metaclass=SingletonMetaClass):
|
|
71
74
|
* for ZenML servers that use an API key to authenticate, it will use
|
72
75
|
that to fetch a short-lived ZenML Pro server API token that it also
|
73
76
|
stores in the Credentials Store
|
74
|
-
* for ZenML Pro servers
|
75
|
-
|
77
|
+
* for ZenML Pro servers:
|
78
|
+
* if a valid ZenML Pro API token is found, it exchanges it into
|
79
|
+
a short lived ZenML Pro server API token
|
80
|
+
* otherwise, if a ZenML Pro API key is found, it will use that
|
81
|
+
to fetch a short-lived ZenML Pro server API token that it also
|
82
|
+
stores in the Credentials Store
|
76
83
|
|
77
84
|
Alongside credentials, the Credentials Store is also used to store
|
78
85
|
additional server information:
|
@@ -290,6 +297,37 @@ class CredentialsStore(metaclass=SingletonMetaClass):
|
|
290
297
|
self.check_and_reload_from_file()
|
291
298
|
return self.credentials.get(server_url)
|
292
299
|
|
300
|
+
def has_valid_credentials(self, server_url: str) -> bool:
|
301
|
+
"""Check if valid credentials exist for a specific server URL.
|
302
|
+
|
303
|
+
Args:
|
304
|
+
server_url: The server URL for which to check the authentication.
|
305
|
+
|
306
|
+
Returns:
|
307
|
+
True if the credentials store contains credentials that can be used
|
308
|
+
to login to the given server URL, False otherwise.
|
309
|
+
"""
|
310
|
+
credentials = self.get_credentials(server_url)
|
311
|
+
if credentials and (
|
312
|
+
credentials.has_valid_token or credentials.can_refresh_token
|
313
|
+
):
|
314
|
+
return True
|
315
|
+
return False
|
316
|
+
|
317
|
+
def get_pro_api_key(self, pro_api_url: str) -> Optional[str]:
|
318
|
+
"""Retrieve an API key from the credentials store for a ZenML Pro API server.
|
319
|
+
|
320
|
+
Args:
|
321
|
+
pro_api_url: The URL of the ZenML Pro API server.
|
322
|
+
|
323
|
+
Returns:
|
324
|
+
The stored API key if it exists, None otherwise.
|
325
|
+
"""
|
326
|
+
credential = self.get_pro_credentials(pro_api_url)
|
327
|
+
if credential and credential.api_key:
|
328
|
+
return credential.api_key
|
329
|
+
return None
|
330
|
+
|
293
331
|
def get_pro_token(
|
294
332
|
self, pro_api_url: str, allow_expired: bool = False
|
295
333
|
) -> Optional[APIToken]:
|
@@ -304,98 +342,58 @@ class CredentialsStore(metaclass=SingletonMetaClass):
|
|
304
342
|
Returns:
|
305
343
|
The stored token if it exists and is not expired, None otherwise.
|
306
344
|
"""
|
307
|
-
credential = self.get_pro_credentials(pro_api_url
|
345
|
+
credential = self.get_pro_credentials(pro_api_url)
|
308
346
|
if credential:
|
309
|
-
|
347
|
+
token = credential.api_token
|
348
|
+
if token and (not token.expired or allow_expired):
|
349
|
+
return token
|
310
350
|
return None
|
311
351
|
|
312
352
|
def get_pro_credentials(
|
313
|
-
self, pro_api_url: str
|
353
|
+
self, pro_api_url: str
|
314
354
|
) -> Optional[ServerCredentials]:
|
315
|
-
"""Retrieve
|
355
|
+
"""Retrieve valid credentials from the credentials store for a ZenML Pro API server.
|
316
356
|
|
317
357
|
Args:
|
318
358
|
pro_api_url: The URL of the ZenML Pro API server.
|
319
|
-
allow_expired: Whether to allow expired tokens to be returned. The
|
320
|
-
default behavior is to return None if a token does exist but is
|
321
|
-
expired.
|
322
359
|
|
323
360
|
Returns:
|
324
|
-
The stored credentials if they exist
|
361
|
+
The stored credentials if they exist, None otherwise.
|
325
362
|
"""
|
326
363
|
credential = self.get_credentials(pro_api_url)
|
327
|
-
if
|
328
|
-
credential
|
329
|
-
and credential.type == ServerType.PRO_API
|
330
|
-
and credential.api_token
|
331
|
-
and (not credential.api_token.expired or allow_expired)
|
332
|
-
):
|
364
|
+
if credential and credential.type == ServerType.PRO_API:
|
333
365
|
return credential
|
334
366
|
return None
|
335
367
|
|
336
|
-
def
|
337
|
-
"""
|
338
|
-
|
339
|
-
Args:
|
340
|
-
pro_api_url: The URL of the ZenML Pro API server.
|
341
|
-
"""
|
342
|
-
self.clear_token(pro_api_url)
|
343
|
-
|
344
|
-
def clear_all_pro_tokens(
|
345
|
-
self, pro_api_url: Optional[str] = None
|
346
|
-
) -> List[ServerCredentials]:
|
347
|
-
"""Delete all tokens from the store for ZenML Pro servers connected to a given API server.
|
368
|
+
def has_valid_pro_credentials(self, pro_api_url: str) -> bool:
|
369
|
+
"""Check if valid credentials exist for a ZenML Pro API server.
|
348
370
|
|
349
371
|
Args:
|
350
372
|
pro_api_url: The URL of the ZenML Pro API server.
|
351
373
|
|
352
374
|
Returns:
|
353
|
-
|
354
|
-
|
355
|
-
credentials_to_clear = []
|
356
|
-
for server_url, server in self.credentials.copy().items():
|
357
|
-
if (
|
358
|
-
server.type == ServerType.PRO
|
359
|
-
and server.pro_api_url
|
360
|
-
and (pro_api_url is None or server.pro_api_url == pro_api_url)
|
361
|
-
):
|
362
|
-
if server.api_key:
|
363
|
-
continue
|
364
|
-
self.clear_token(server_url)
|
365
|
-
credentials_to_clear.append(server)
|
366
|
-
return credentials_to_clear
|
367
|
-
|
368
|
-
def has_valid_authentication(self, url: str) -> bool:
|
369
|
-
"""Check if a valid authentication credential for the given server URL is stored.
|
370
|
-
|
371
|
-
Args:
|
372
|
-
url: The server URL for which to check the authentication.
|
373
|
-
|
374
|
-
Returns:
|
375
|
-
bool: True if a valid token or API key is stored, False otherwise.
|
375
|
+
True if the credentials store contains credentials that can be used
|
376
|
+
to login to the given ZenML Pro API server, False otherwise.
|
376
377
|
"""
|
377
|
-
self.
|
378
|
-
|
379
|
-
|
380
|
-
if not credential:
|
381
|
-
return False
|
382
|
-
if credential.api_key or (
|
383
|
-
credential.username and credential.password is not None
|
378
|
+
credentials = self.get_pro_credentials(pro_api_url)
|
379
|
+
if credentials and (
|
380
|
+
credentials.has_valid_token or credentials.can_refresh_token
|
384
381
|
):
|
385
382
|
return True
|
386
|
-
|
387
|
-
return token is not None and not token.expired
|
383
|
+
return False
|
388
384
|
|
389
|
-
def
|
390
|
-
"""
|
385
|
+
def clear_pro_credentials(self, pro_api_url: str) -> None:
|
386
|
+
"""Delete the token from the store for a ZenML Pro API server.
|
391
387
|
|
392
388
|
Args:
|
393
389
|
pro_api_url: The URL of the ZenML Pro API server.
|
394
|
-
|
395
|
-
Returns:
|
396
|
-
bool: True if a valid token is stored, False otherwise.
|
397
390
|
"""
|
398
|
-
|
391
|
+
self.clear_token(pro_api_url)
|
392
|
+
|
393
|
+
def clear_all_credentials(self) -> None:
|
394
|
+
"""Delete all stored credentials."""
|
395
|
+
self.credentials = {}
|
396
|
+
self._save_credentials()
|
399
397
|
|
400
398
|
def can_login(self, server_url: str) -> bool:
|
401
399
|
"""Check if credentials to login to the given server exist.
|
@@ -407,22 +405,20 @@ class CredentialsStore(metaclass=SingletonMetaClass):
|
|
407
405
|
True if the credentials store contains credentials that can be used
|
408
406
|
to login to the given server URL, False otherwise.
|
409
407
|
"""
|
410
|
-
self.check_and_reload_from_file()
|
411
408
|
credentials = self.get_credentials(server_url)
|
412
409
|
if not credentials:
|
413
410
|
return False
|
414
411
|
|
415
|
-
if credentials.
|
416
|
-
return True
|
417
|
-
elif (
|
418
|
-
credentials.username is not None
|
419
|
-
and credentials.password is not None
|
420
|
-
):
|
412
|
+
if credentials.can_refresh_token:
|
421
413
|
return True
|
422
|
-
|
414
|
+
|
415
|
+
if credentials.type == ServerType.PRO:
|
423
416
|
pro_api_url = credentials.pro_api_url or ZENML_PRO_API_URL
|
424
|
-
|
425
|
-
if
|
417
|
+
pro_credentials = self.get_pro_credentials(pro_api_url)
|
418
|
+
if pro_credentials and (
|
419
|
+
pro_credentials.has_valid_token
|
420
|
+
or pro_credentials.can_refresh_token
|
421
|
+
):
|
426
422
|
return True
|
427
423
|
|
428
424
|
return False
|
@@ -431,6 +427,7 @@ class CredentialsStore(metaclass=SingletonMetaClass):
|
|
431
427
|
self,
|
432
428
|
server_url: str,
|
433
429
|
api_key: str,
|
430
|
+
is_zenml_pro: bool = False,
|
434
431
|
) -> None:
|
435
432
|
"""Store an API key in the credentials store for a specific server URL.
|
436
433
|
|
@@ -440,6 +437,7 @@ class CredentialsStore(metaclass=SingletonMetaClass):
|
|
440
437
|
Args:
|
441
438
|
server_url: The server URL for which the token is to be stored.
|
442
439
|
api_key: The API key to store.
|
440
|
+
is_zenml_pro: Whether the API key is for the ZenML Pro API.
|
443
441
|
"""
|
444
442
|
self.check_and_reload_from_file()
|
445
443
|
credential = self.credentials.get(server_url)
|
@@ -450,9 +448,11 @@ class CredentialsStore(metaclass=SingletonMetaClass):
|
|
450
448
|
credential.api_key = api_key
|
451
449
|
credential.username = None
|
452
450
|
credential.password = None
|
451
|
+
if is_zenml_pro:
|
452
|
+
credential.pro_api_url = server_url
|
453
453
|
else:
|
454
454
|
self.credentials[server_url] = ServerCredentials(
|
455
|
-
url=server_url, api_key=api_key
|
455
|
+
url=server_url, api_key=api_key, pro_api_url=server_url
|
456
456
|
)
|
457
457
|
|
458
458
|
self._save_credentials()
|
zenml/login/pro/client.py
CHANGED
@@ -34,6 +34,7 @@ from zenml.logger import get_logger
|
|
34
34
|
from zenml.login.credentials import APIToken
|
35
35
|
from zenml.login.credentials_store import get_credentials_store
|
36
36
|
from zenml.login.pro.models import BaseRestAPIModel
|
37
|
+
from zenml.models.v2.misc.auth_models import OAuthTokenResponse
|
37
38
|
from zenml.utils.singleton import SingletonMetaClass
|
38
39
|
from zenml.zen_server.exceptions import exception_from_response
|
39
40
|
|
@@ -54,39 +55,54 @@ class ZenMLProClient(metaclass=SingletonMetaClass):
|
|
54
55
|
"""ZenML Pro client."""
|
55
56
|
|
56
57
|
_url: str
|
57
|
-
_api_token: APIToken
|
58
|
+
_api_token: Optional[APIToken] = None
|
58
59
|
_session: Optional[requests.Session] = None
|
59
60
|
_workspace: Optional["WorkspaceClient"] = None
|
60
61
|
_organization: Optional["OrganizationClient"] = None
|
61
62
|
|
62
|
-
def __init__(self, url: str
|
63
|
+
def __init__(self, url: str) -> None:
|
63
64
|
"""Initialize the ZenML Pro client.
|
64
65
|
|
65
66
|
Args:
|
66
67
|
url: The URL of the ZenML Pro API server.
|
67
|
-
|
68
|
-
|
68
|
+
"""
|
69
|
+
self._url = url
|
70
|
+
|
71
|
+
def authenticate(self) -> APIToken:
|
72
|
+
"""Authenticate to ZenML Pro and return the API token.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
The API token.
|
69
76
|
|
70
77
|
Raises:
|
71
|
-
AuthorizationException: If
|
72
|
-
is found in the credentials store.
|
78
|
+
AuthorizationException: If the login fails.
|
73
79
|
"""
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
"
|
80
|
+
credentials_store = get_credentials_store()
|
81
|
+
pro_credentials = credentials_store.get_pro_credentials(self._url)
|
82
|
+
if pro_credentials is None:
|
83
|
+
raise AuthorizationException(
|
84
|
+
"No ZenML Pro credentials found. Please run 'zenml login' to "
|
85
|
+
"login to ZenML Pro."
|
79
86
|
)
|
80
|
-
|
81
|
-
|
87
|
+
if pro_credentials.has_valid_token:
|
88
|
+
assert pro_credentials.api_token is not None
|
89
|
+
return pro_credentials.api_token
|
90
|
+
|
91
|
+
if pro_credentials.can_refresh_token:
|
92
|
+
assert pro_credentials.api_key is not None
|
93
|
+
api_token = self.fetch_api_token(
|
94
|
+
self._url, pro_credentials.api_key
|
95
|
+
)
|
96
|
+
return credentials_store.set_token(
|
97
|
+
self._url,
|
98
|
+
api_token,
|
99
|
+
is_zenml_pro=True,
|
100
|
+
)
|
101
|
+
else:
|
102
|
+
raise AuthorizationException(
|
103
|
+
"Your ZenML Pro authentication has expired. Please run "
|
104
|
+
"'zenml login' to login again."
|
82
105
|
)
|
83
|
-
if api_token is None:
|
84
|
-
raise AuthorizationException(
|
85
|
-
"No ZenML Pro API token found. Please run 'zenml login' to "
|
86
|
-
"login to ZenML Pro."
|
87
|
-
)
|
88
|
-
|
89
|
-
self._api_token = api_token
|
90
106
|
|
91
107
|
@property
|
92
108
|
def workspace(self) -> "WorkspaceClient":
|
@@ -121,20 +137,10 @@ class ZenMLProClient(metaclass=SingletonMetaClass):
|
|
121
137
|
Returns:
|
122
138
|
The API token.
|
123
139
|
"""
|
140
|
+
if self._api_token is None or self._api_token.expired:
|
141
|
+
self._api_token = self.authenticate()
|
124
142
|
return self._api_token.access_token
|
125
143
|
|
126
|
-
def raise_on_expired_api_token(self) -> None:
|
127
|
-
"""Raise an exception if the API token has expired.
|
128
|
-
|
129
|
-
Raises:
|
130
|
-
AuthorizationException: If the API token has expired.
|
131
|
-
"""
|
132
|
-
if self._api_token and self._api_token.expired:
|
133
|
-
raise AuthorizationException(
|
134
|
-
"Your ZenML Pro authentication has expired. Please run "
|
135
|
-
"'zenml login' to login again."
|
136
|
-
)
|
137
|
-
|
138
144
|
@property
|
139
145
|
def session(self) -> requests.Session:
|
140
146
|
"""Authenticate to the ZenML Pro API server.
|
@@ -142,10 +148,6 @@ class ZenMLProClient(metaclass=SingletonMetaClass):
|
|
142
148
|
Returns:
|
143
149
|
A requests session with the authentication token.
|
144
150
|
"""
|
145
|
-
# Check if the API token has expired before every call to the server.
|
146
|
-
# This prevents unwanted authorization errors from being raised during
|
147
|
-
# the call itself.
|
148
|
-
self.raise_on_expired_api_token()
|
149
151
|
if self._session is None:
|
150
152
|
self._session = requests.Session()
|
151
153
|
retries = Retry(backoff_factor=0.1, connect=5)
|
@@ -157,6 +159,35 @@ class ZenMLProClient(metaclass=SingletonMetaClass):
|
|
157
159
|
logger.debug("Authenticated to ZenML Pro server.")
|
158
160
|
return self._session
|
159
161
|
|
162
|
+
@classmethod
|
163
|
+
def fetch_api_token(cls, url: str, api_key: str) -> OAuthTokenResponse:
|
164
|
+
"""Fetch the API token for a given ZenML Pro server URL.
|
165
|
+
|
166
|
+
Args:
|
167
|
+
url: ZenML Pro server URL
|
168
|
+
api_key: API key
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
The API token.
|
172
|
+
|
173
|
+
Raises:
|
174
|
+
ValueError: if the response is not in the right format.
|
175
|
+
"""
|
176
|
+
headers = {"Authorization": "Bearer " + api_key}
|
177
|
+
response = requests.post(
|
178
|
+
url + "/auth/login",
|
179
|
+
data={"password": api_key},
|
180
|
+
headers=headers,
|
181
|
+
timeout=10,
|
182
|
+
)
|
183
|
+
json_response = cls._handle_response(response)
|
184
|
+
if not isinstance(json_response, dict):
|
185
|
+
raise ValueError(
|
186
|
+
f"Bad response from API. Expected dict, got\n{json_response}"
|
187
|
+
)
|
188
|
+
|
189
|
+
return OAuthTokenResponse.model_validate(json_response)
|
190
|
+
|
160
191
|
@staticmethod
|
161
192
|
def _handle_response(response: requests.Response) -> Json:
|
162
193
|
"""Handle API response, translating http status codes to Exception.
|
@@ -215,10 +246,6 @@ class ZenMLProClient(metaclass=SingletonMetaClass):
|
|
215
246
|
|
216
247
|
Returns:
|
217
248
|
The parsed response.
|
218
|
-
|
219
|
-
Raises:
|
220
|
-
AuthorizationException: if the request fails due to an expired
|
221
|
-
authentication token.
|
222
249
|
"""
|
223
250
|
params = {k: str(v) for k, v in params.items()} if params else {}
|
224
251
|
|
@@ -226,21 +253,14 @@ class ZenMLProClient(metaclass=SingletonMetaClass):
|
|
226
253
|
{source_context.name: source_context.get().value}
|
227
254
|
)
|
228
255
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
**kwargs,
|
236
|
-
)
|
256
|
+
return self._handle_response(
|
257
|
+
self.session.request(
|
258
|
+
method,
|
259
|
+
url,
|
260
|
+
params=params,
|
261
|
+
**kwargs,
|
237
262
|
)
|
238
|
-
|
239
|
-
# Check if this is caused by an expired API token.
|
240
|
-
self.raise_on_expired_api_token()
|
241
|
-
|
242
|
-
# If not, raise the exception.
|
243
|
-
raise
|
263
|
+
)
|
244
264
|
|
245
265
|
def get(
|
246
266
|
self,
|
zenml/login/pro/utils.py
CHANGED
@@ -39,9 +39,7 @@ def get_troubleshooting_instructions(url: str) -> str:
|
|
39
39
|
if credentials and credentials.type == ServerType.PRO:
|
40
40
|
pro_api_url = credentials.pro_api_url or ZENML_PRO_API_URL
|
41
41
|
|
42
|
-
if pro_api_url and credentials_store.
|
43
|
-
pro_api_url
|
44
|
-
):
|
42
|
+
if pro_api_url and credentials_store.can_login(pro_api_url):
|
45
43
|
client = ZenMLProClient(pro_api_url)
|
46
44
|
|
47
45
|
try:
|
zenml/utils/package_utils.py
CHANGED
@@ -42,7 +42,7 @@ def is_latest_zenml_version() -> bool:
|
|
42
42
|
True in case the current running zenml code is the latest available version on PYPI, otherwise False.
|
43
43
|
|
44
44
|
Raises:
|
45
|
-
RuntimeError: In case something
|
45
|
+
RuntimeError: In case something goes wrong
|
46
46
|
"""
|
47
47
|
from zenml import __version__
|
48
48
|
|
@@ -1 +1 @@
|
|
1
|
-
import{j as e}from"./@radix-B1sy0Lhr.js";import{B as s,r as t}from"./index-
|
1
|
+
import{j as e}from"./@radix-B1sy0Lhr.js";import{B as s,r as t}from"./index-Ci5z7q6x.js";import{E as r}from"./EmptyState-BMA34iVR.js";import{S as a}from"./help-CRPfbPTO.js";import{L as o}from"./@react-router-CHjLNlgw.js";import"./@tanstack-Dwlisomv.js";import"./@reactflow-gbyyU3kq.js";function d(){return e.jsx("div",{className:"flex min-h-screen w-full flex-col",children:e.jsx(r,{icon:e.jsx(a,{className:"h-[120px] w-[120px] fill-neutral-300"}),children:e.jsxs("div",{className:"text-center",children:[e.jsx("h1",{className:"mb-2 text-display-xs font-semibold",children:"We can't find the page you are looking for"}),e.jsx("p",{className:"text-lg text-theme-text-secondary",children:"You can try typing a different URL or we can bring you back to your Homepage."}),e.jsx("div",{className:"mt-5 flex justify-center",children:e.jsx(s,{size:"md",asChild:!0,children:e.jsx(o,{className:"w-min self-center whitespace-nowrap",to:t.projects.overview,children:e.jsx("span",{className:"px-0.5",children:"Go to Home"})})})})]})})})}export{d as default};
|
@@ -1 +1 @@
|
|
1
|
-
import{r as m,j as e}from"./@radix-B1sy0Lhr.js";import{J as g,K as p,aw as d,br as h}from"./index-
|
1
|
+
import{r as m,j as e}from"./@radix-B1sy0Lhr.js";import{J as g,K as p,aw as d,br as h}from"./index-Ci5z7q6x.js";const x=m.forwardRef((r,t)=>{const{triggerChildren:a,children:i,onSelect:o,onOpenChange:l,icon:D,open:n,...s}=r;return e.jsxs(g,{open:n,onOpenChange:l,children:[e.jsx(p,{asChild:!0,children:e.jsx(d,{...s,className:"hover:cursor-pointer",icon:r.icon,ref:t,onSelect:c=>{c.preventDefault(),o&&o()},children:a})}),e.jsx(h,{children:i})]})});x.displayName="AlertDialogItem";export{x as A};
|
@@ -1 +1 @@
|
|
1
|
-
import{r as l,j as e}from"./@radix-B1sy0Lhr.js";import{L as j,M as R,N as w,O as C,J as b,K as y,B as O}from"./index-
|
1
|
+
import{r as l,j as e}from"./@radix-B1sy0Lhr.js";import{L as j,M as R,N as w,O as C,J as b,K as y,B as O}from"./index-Ci5z7q6x.js";import{S as h}from"./trash-D9LA4VFD.js";import{A as v}from"./AlertDialogDropdownItem-DuTp8JFP.js";import{D as f,a as p}from"./DeleteAlertDialog-C5z9RupH.js";import{c as A,u as S}from"./bulk-delete-Bgf_MsSj.js";import{u as T}from"./delete-run-MHYb1wuH.js";const{ContextProvider:F,useContext:c}=A();function m(){const{setRowSelection:n}=c(),{mutateAsync:t}=T();async function s(r){await t({runId:r})}return S({deleteFn:s,queryKeyToInvalidate:["runs"],setRowSelection:n})}function H({id:n}){const[t,s]=l.useState(!1),[r,a]=l.useState(!1),i=l.useRef(null),u=l.useRef(null),{bulkDelete:x}=m();async function D(){await x([n]),d(!1)}function g(){u.current=i.current}function d(o){if(o===!1){a(!1),setTimeout(()=>{s(o)},200);return}s(o)}return e.jsxs(j,{onOpenChange:a,open:r,children:[e.jsx(R,{ref:i,children:e.jsx(w,{className:"h-4 w-4 fill-theme-text-tertiary"})}),e.jsx(C,{hidden:t,onCloseAutoFocus:o=>{u.current&&(u.current.focus(),u.current=null,o.preventDefault())},align:"end",sideOffset:7,children:e.jsx(v,{onSelect:g,open:t,onOpenChange:d,triggerChildren:"Delete",icon:e.jsx(h,{fill:"red"}),children:e.jsx(f,{title:"Delete Run",handleDelete:D,children:e.jsxs(p,{children:[e.jsx("p",{children:"Are you sure?"}),e.jsx("p",{children:"This action cannot be undone."})]})})})})]})}function I(){const[n,t]=l.useState(!1),{selectedRowCount:s,selectedRowIDs:r}=c(),{bulkDelete:a}=m();async function i(){await a(r),t(!1)}return e.jsxs(b,{open:n,onOpenChange:t,children:[e.jsx(y,{asChild:!0,children:e.jsxs(O,{className:"rounded-sharp border-y-0 bg-white",size:"md",emphasis:"subtle",intent:"secondary",children:[e.jsx(h,{className:"h-5 w-5 shrink-0 gap-1 fill-neutral-400"}),"Delete"]})}),e.jsx(f,{title:`Delete Run${s>=2?"s":""}`,handleDelete:i,children:e.jsxs(p,{children:[e.jsx("p",{children:"Are you sure?"}),e.jsx("p",{children:"This action cannot be undone."})]})})]})}function K(){const{selectedRowCount:n}=c();return e.jsxs("div",{className:"flex items-center divide-x divide-theme-border-moderate overflow-hidden rounded-md border border-theme-border-moderate",children:[e.jsx("div",{className:"bg-primary-25 px-2 py-1 font-semibold text-theme-text-brand",children:`${n} Run${n>1?"s":""} selected`}),e.jsx(I,{})]})}export{H as R,K as a,F as b,c as u};
|