From b80e2550192058c96fe1f49a0c807906e7cefa7f Mon Sep 17 00:00:00 2001 From: Web3NL Date: Mon, 9 Oct 2023 19:27:29 +0200 Subject: [PATCH 1/9] initial setup --- tools/ui/Cargo.lock | 2 +- tools/ui/canister_ids.json | 5 +- tools/ui/dfx.json | 5 + tools/ui/package-lock.json | 270 ++++++++++++++++++++++++-- tools/ui/package.json | 2 + tools/ui/src/auth.ts | 58 ++++++ tools/ui/src/candid.css | 4 + tools/ui/src/candid.html | 5 +- tools/ui/src/candid.ts | 9 + tools/ui/src/index.ts | 2 + tools/ui/target_test_canister/main.mo | 49 +++++ 11 files changed, 396 insertions(+), 15 deletions(-) create mode 100644 tools/ui/src/auth.ts create mode 100644 tools/ui/target_test_canister/main.mo diff --git a/tools/ui/Cargo.lock b/tools/ui/Cargo.lock index 411743aa..7e687c39 100644 --- a/tools/ui/Cargo.lock +++ b/tools/ui/Cargo.lock @@ -111,7 +111,7 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "candid" -version = "0.9.8" +version = "0.9.9" dependencies = [ "anyhow", "binread", diff --git a/tools/ui/canister_ids.json b/tools/ui/canister_ids.json index 016284e1..f25ce9fa 100644 --- a/tools/ui/canister_ids.json +++ b/tools/ui/canister_ids.json @@ -1,5 +1,8 @@ { "didjs": { - "ic": "a4gq6-oaaaa-aaaab-qaa4q-cai" + "ic": "m4ul7-aqaaa-aaaal-qcewq-cai" + }, + "target": { + "ic": "mvxad-wyaaa-aaaal-qcexa-cai" } } \ No newline at end of file diff --git a/tools/ui/dfx.json b/tools/ui/dfx.json index 5fa1ceef..c7478820 100644 --- a/tools/ui/dfx.json +++ b/tools/ui/dfx.json @@ -14,6 +14,11 @@ "name": "candid:service" } ] + }, + "target": { + "type": "motoko", + "main": "target_test_canister/main.mo", + "frontend": {} } }, "version": 1 diff --git a/tools/ui/package-lock.json b/tools/ui/package-lock.json index acc77dce..b3412d41 100644 --- a/tools/ui/package-lock.json +++ b/tools/ui/package-lock.json @@ -9,7 +9,9 @@ "version": "0.1.0", "devDependencies": { "@dfinity/agent": "0.19.2", + "@dfinity/auth-client": "0.19.2", "@dfinity/candid": "0.19.2", + "@dfinity/identity": "0.19.2", "@dfinity/principal": "0.19.2", "buffer": "6.0.3", "copy-webpack-plugin": "^9.0.1", @@ -40,6 +42,20 @@ "@dfinity/principal": "^0.19.2" } }, + "node_modules/@dfinity/auth-client": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-0.19.2.tgz", + "integrity": "sha512-aQQ60Y6fuV8849ZzXDwSfJlHO5mWEnzscYVEqveCSDTbRCMw0RV/PKGmbNuM2mIes3ep+LWpq3IQRR56lYZWUA==", + "dev": true, + "dependencies": { + "idb": "^7.0.2" + }, + "peerDependencies": { + "@dfinity/agent": "^0.19.2", + "@dfinity/identity": "^0.19.2", + "@dfinity/principal": "^0.19.2" + } + }, "node_modules/@dfinity/candid": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.19.2.tgz", @@ -49,6 +65,22 @@ "@dfinity/principal": "^0.19.2" } }, + "node_modules/@dfinity/identity": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-0.19.2.tgz", + "integrity": "sha512-0EwZd8lVfXzHLl5flRysRbjV181b2Xh9loV7QUrt5fE+lP5hgF4f+odFB1YajHQaSyCY1t+v7DA6R9+ICZK9AQ==", + "dev": true, + "dependencies": { + "@noble/hashes": "^1.3.1", + "borc": "^2.1.1", + "tweetnacl": "^1.0.1" + }, + "peerDependencies": { + "@dfinity/agent": "^0.19.2", + "@dfinity/principal": "^0.19.2", + "@peculiar/webcrypto": "^1.4.0" + } + }, "node_modules/@dfinity/principal": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.19.2.tgz", @@ -145,6 +177,48 @@ "node": ">= 8" } }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz", + "integrity": "sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA==", + "dev": true, + "peer": true, + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.3.tgz", + "integrity": "sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A==", + "dev": true, + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.2", + "tslib": "^2.5.0", + "webcrypto-core": "^1.7.7" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/@types/eslint": { "version": "8.21.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.2.tgz", @@ -435,6 +509,21 @@ "node": ">=8" } }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dev": true, + "peer": true, + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/base64-arraybuffer": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", @@ -1321,6 +1410,12 @@ "postcss": "^8.1.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1850,9 +1945,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -1974,6 +2069,26 @@ "node": ">=6" } }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2524,9 +2639,15 @@ } }, "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true }, "node_modules/typescript": { @@ -2582,6 +2703,20 @@ "node": ">=10.13.0" } }, + "node_modules/webcrypto-core": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.7.7.tgz", + "integrity": "sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g==", + "dev": true, + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.1", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } + }, "node_modules/webpack": { "version": "5.76.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", @@ -2797,6 +2932,15 @@ "simple-cbor": "^0.4.1" } }, + "@dfinity/auth-client": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-0.19.2.tgz", + "integrity": "sha512-aQQ60Y6fuV8849ZzXDwSfJlHO5mWEnzscYVEqveCSDTbRCMw0RV/PKGmbNuM2mIes3ep+LWpq3IQRR56lYZWUA==", + "dev": true, + "requires": { + "idb": "^7.0.2" + } + }, "@dfinity/candid": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.19.2.tgz", @@ -2804,6 +2948,17 @@ "dev": true, "requires": {} }, + "@dfinity/identity": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-0.19.2.tgz", + "integrity": "sha512-0EwZd8lVfXzHLl5flRysRbjV181b2Xh9loV7QUrt5fE+lP5hgF4f+odFB1YajHQaSyCY1t+v7DA6R9+ICZK9AQ==", + "dev": true, + "requires": { + "@noble/hashes": "^1.3.1", + "borc": "^2.1.1", + "tweetnacl": "^1.0.1" + } + }, "@dfinity/principal": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.19.2.tgz", @@ -2879,6 +3034,42 @@ "fastq": "^1.6.0" } }, + "@peculiar/asn1-schema": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz", + "integrity": "sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA==", + "dev": true, + "peer": true, + "requires": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } + }, + "@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.0.0" + } + }, + "@peculiar/webcrypto": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.3.tgz", + "integrity": "sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A==", + "dev": true, + "peer": true, + "requires": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.2", + "tslib": "^2.5.0", + "webcrypto-core": "^1.7.7" + } + }, "@types/eslint": { "version": "8.21.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.2.tgz", @@ -3140,6 +3331,18 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dev": true, + "peer": true, + "requires": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + } + }, "base64-arraybuffer": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", @@ -3773,6 +3976,12 @@ "dev": true, "requires": {} }, + "idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4158,9 +4367,9 @@ } }, "postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "requires": { "nanoid": "^3.3.6", @@ -4236,6 +4445,23 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.6.1" + } + }, + "pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "dev": true, + "peer": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4604,9 +4830,15 @@ } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true }, "typescript": { @@ -4652,6 +4884,20 @@ "graceful-fs": "^4.1.2" } }, + "webcrypto-core": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.7.7.tgz", + "integrity": "sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g==", + "dev": true, + "peer": true, + "requires": { + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.1", + "pvtsutils": "^1.3.2", + "tslib": "^2.4.0" + } + }, "webpack": { "version": "5.76.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", diff --git a/tools/ui/package.json b/tools/ui/package.json index 43dcc4be..3c86ce2b 100644 --- a/tools/ui/package.json +++ b/tools/ui/package.json @@ -9,6 +9,8 @@ "devDependencies": { "@dfinity/agent": "0.19.2", "@dfinity/candid": "0.19.2", + "@dfinity/auth-client": "0.19.2", + "@dfinity/identity": "0.19.2", "@dfinity/principal": "0.19.2", "buffer": "6.0.3", "copy-webpack-plugin": "^9.0.1", diff --git a/tools/ui/src/auth.ts b/tools/ui/src/auth.ts new file mode 100644 index 00000000..2ce52320 --- /dev/null +++ b/tools/ui/src/auth.ts @@ -0,0 +1,58 @@ +import { Principal } from "@dfinity/principal" +import { authClient } from "./candid"; + +export async function renderAuth(canisterId: Principal) { + ;(await authClient?.isAuthenticated()) + ? insertLogoutButton() + : insertLoginButton(canisterId) +} + +function insertLoginButton(canisterId: Principal) { + const auth = document.getElementById("authentication") + + const buttonLogin = document.createElement("button") + buttonLogin.className = "btn" + buttonLogin.innerText = "Login with Internet Identity" + + buttonLogin.addEventListener("click", async () => { + await login(canisterId) + }) + + auth!.innerHTML = ''; + auth!.appendChild(buttonLogin) +} + +function insertLogoutButton() { + const auth = document.getElementById("authentication") + + const buttonLogout = document.createElement("button") + buttonLogout.className = "btn random" + buttonLogout.innerText = "Logout" + + buttonLogout.addEventListener("click", async () => { + await logout() + }) + + auth!.innerHTML = ''; + auth!.appendChild(buttonLogout) +} + +async function login(canisterId: Principal) { + const identityProvider = "https://identity.ic0.app" + const derivationOrigin = "https://" + canisterId.toString() + ".raw.icp0.io" + + authClient?.login({ + identityProvider, + derivationOrigin, + onSuccess: async() => { + window.location.reload() + console.log(authClient?.getIdentity()) + }, + onError: (err) => console.error(err), + }) +} + +async function logout() { + authClient?.logout() + window.location.reload() +} diff --git a/tools/ui/src/candid.css b/tools/ui/src/candid.css index 4aac24eb..7aa0de55 100644 --- a/tools/ui/src/candid.css +++ b/tools/ui/src/candid.css @@ -50,6 +50,10 @@ line-height: var(--header-height); color: var(--darkest); border-bottom: 1px solid var(--dark); + + display: flex; + justify-content: space-between; + align-items: center; } #title-card { diff --git a/tools/ui/src/candid.html b/tools/ui/src/candid.html index 451c9514..7161ecbb 100644 --- a/tools/ui/src/candid.html +++ b/tools/ui/src/candid.html @@ -16,7 +16,10 @@ Loading Candid UI...