mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 04:27:02 +01:00
Javascript linting (#3329)
* feat(ci/dev): adding npm/eslint config for javascript linting * feat(ci/dev): adding js-prettier config for consistency in formatting * fix(css): linting * fix(js): linting the js things
This commit is contained in:
parent
3430a1c31d
commit
b74883ae0d
|
@ -2,9 +2,21 @@
|
||||||
|
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
max_line_length = 100
|
max_line_length = 100
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
tab_width = 4
|
||||||
|
max_line_length = 120
|
||||||
|
print_width = 120
|
||||||
|
|
||||||
|
[*.mjs]
|
||||||
|
tab_width = 4
|
||||||
|
max_line_length = 120
|
||||||
|
print_width = 120
|
21
.github/workflows/javascript_lint.yml
vendored
Normal file
21
.github/workflows/javascript_lint.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
name: Javascript Linting
|
||||||
|
"on":
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
jobs:
|
||||||
|
javascript_lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Run ESLint to check Javascript files
|
||||||
|
run:
|
||||||
|
make eslint
|
||||||
|
javascript_fmt:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Run Prettier to check Javascript files
|
||||||
|
run:
|
||||||
|
make prettier
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -45,3 +45,6 @@ scripts/oauth_proxy/envfile
|
||||||
# IDEs
|
# IDEs
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# javascript test things
|
||||||
|
node_modules/
|
29
Makefile
29
Makefile
|
@ -326,3 +326,32 @@ coveralls: ## Run cargo tarpaulin and upload to coveralls
|
||||||
coveralls:
|
coveralls:
|
||||||
cargo tarpaulin --coveralls $(COVERALLS_REPO_TOKEN)
|
cargo tarpaulin --coveralls $(COVERALLS_REPO_TOKEN)
|
||||||
@echo "Coveralls repo information is at https://coveralls.io/github/kanidm/kanidm"
|
@echo "Coveralls repo information is at https://coveralls.io/github/kanidm/kanidm"
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: eslint
|
||||||
|
eslint: ## Run eslint on the UI javascript things
|
||||||
|
eslint: eslint/setup
|
||||||
|
@echo "################################"
|
||||||
|
@echo " Running eslint..."
|
||||||
|
@echo "################################"
|
||||||
|
cd server/core && find ./static -name '*js' -not -path '*/external/*' -exec eslint "{}" \;
|
||||||
|
@echo "################################"
|
||||||
|
@echo "Done!"
|
||||||
|
|
||||||
|
.PHONY: eslint/setup
|
||||||
|
eslint/setup: ## Install eslint for the UI javascript things
|
||||||
|
cd server/core && npm ci
|
||||||
|
|
||||||
|
.PHONY: prettier
|
||||||
|
prettier: ## Run prettier on the UI javascript things
|
||||||
|
prettier: eslint/setup
|
||||||
|
@echo " Running prettier..."
|
||||||
|
cd server/core && npm run prettier
|
||||||
|
@echo "Done!"
|
||||||
|
|
||||||
|
.PHONY: prettier/fix
|
||||||
|
prettier/fix: ## Run prettier on the UI javascript things and write back changes
|
||||||
|
prettier/fix: eslint/setup
|
||||||
|
@echo " Running prettier..."
|
||||||
|
cd server/core && npm run prettier:fix
|
||||||
|
@echo "Done!"
|
16
server/core/eslint.config.mjs
Normal file
16
server/core/eslint.config.mjs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import globals from "globals";
|
||||||
|
import pluginJs from "@eslint/js";
|
||||||
|
|
||||||
|
|
||||||
|
/** @type {import('eslint').Linter.Config[]} */
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
Base64 : "writeable" // to feed the Base64 class into the global scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
];
|
1104
server/core/package-lock.json
generated
Normal file
1104
server/core/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
server/core/package.json
Normal file
27
server/core/package.json
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "kanidm",
|
||||||
|
"version": "0.0.1-dev",
|
||||||
|
"description": "Kanidm UI Javascript - not a publishable package, just for development",
|
||||||
|
"type": "module",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/kanidm/kanidm.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/kanidm/kanidm/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://kanidm.com",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.17.0",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"globals": "^15.14.0",
|
||||||
|
"prettier": "^3.4.2"
|
||||||
|
},
|
||||||
|
"scripts" : {
|
||||||
|
"prettier": "npx prettier --ignore-path 'prettier-ignore' ./static --check",
|
||||||
|
"prettier:fix": "npx prettier --ignore-path 'prettier-ignore' ./static --write"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
server/core/prettier-ignore
Normal file
1
server/core/prettier-ignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
static/external/
|
|
@ -1,4 +1,4 @@
|
||||||
console.debug('credupdate: loaded');
|
console.debug("credupdate: loaded");
|
||||||
|
|
||||||
// Makes the password form interactive (e.g. shows when passwords don't match)
|
// Makes the password form interactive (e.g. shows when passwords don't match)
|
||||||
function setupInteractivePwdFormListeners() {
|
function setupInteractivePwdFormListeners() {
|
||||||
|
@ -18,10 +18,10 @@ function setupInteractivePwdFormListeners() {
|
||||||
pwd_submit.disabled = true;
|
pwd_submit.disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_pwd.addEventListener("input", (_) => {
|
new_pwd.addEventListener("input", () => {
|
||||||
// Don't mark invalid if user didn't fill in the confirmation box yet
|
// Don't mark invalid if user didn't fill in the confirmation box yet
|
||||||
// Also my password manager (keepassxc with autocomplete)
|
// Also KeepassXC with autocomplete likes to fire off input events when
|
||||||
// likes to fire off input events when both inputs were empty.
|
// both inputs are empty.
|
||||||
if (new_pwd_check.value !== "") {
|
if (new_pwd_check.value !== "") {
|
||||||
if (new_pwd.value === new_pwd_check.value) {
|
if (new_pwd.value === new_pwd_check.value) {
|
||||||
markPwdCheckValid();
|
markPwdCheckValid();
|
||||||
|
@ -32,7 +32,7 @@ function setupInteractivePwdFormListeners() {
|
||||||
new_pwd.classList.remove("is-invalid");
|
new_pwd.classList.remove("is-invalid");
|
||||||
});
|
});
|
||||||
|
|
||||||
new_pwd_check.addEventListener("input", (_) => {
|
new_pwd_check.addEventListener("input", () => {
|
||||||
// No point in updating the status if confirmation box is empty
|
// No point in updating the status if confirmation box is empty
|
||||||
if (new_pwd_check.value === "") return;
|
if (new_pwd_check.value === "") return;
|
||||||
if (new_pwd_check.value === new_pwd.value) {
|
if (new_pwd_check.value === new_pwd.value) {
|
||||||
|
@ -43,38 +43,44 @@ function setupInteractivePwdFormListeners() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.stillSwapFailureResponse = function(event) {
|
window.stillSwapFailureResponse = function (event) {
|
||||||
if (event.detail.xhr.status === 422 || event.detail.xhr.status === 500) {
|
if (event.detail.xhr.status === 422 || event.detail.xhr.status === 500) {
|
||||||
console.log("Still swapping failure response")
|
console.debug(`Got HTTP/${event.detail.xhr.status}, still swapping failure response`);
|
||||||
event.detail.shouldSwap = true;
|
event.detail.shouldSwap = true;
|
||||||
event.detail.isError = false;
|
event.detail.isError = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function onPasskeyCreated(assertion) {
|
function onPasskeyCreated(assertion) {
|
||||||
try {
|
try {
|
||||||
console.log(assertion)
|
console.log(assertion);
|
||||||
let creationData = {};
|
let creationData = {};
|
||||||
|
|
||||||
creationData.id = assertion.id;
|
creationData.id = assertion.id;
|
||||||
creationData.rawId = Base64.fromUint8Array(new Uint8Array(assertion.rawId))
|
creationData.rawId = Base64.fromUint8Array(new Uint8Array(assertion.rawId));
|
||||||
creationData.response = {};
|
creationData.response = {};
|
||||||
creationData.response.attestationObject = Base64.fromUint8Array(new Uint8Array(assertion.response.attestationObject))
|
creationData.response.attestationObject = Base64.fromUint8Array(
|
||||||
creationData.response.clientDataJSON = Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON))
|
new Uint8Array(assertion.response.attestationObject),
|
||||||
creationData.type = assertion.type
|
);
|
||||||
creationData.extensions = assertion.getClientExtensionResults()
|
creationData.response.clientDataJSON = Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON));
|
||||||
creationData.extensions.uvm = undefined
|
creationData.type = assertion.type;
|
||||||
|
creationData.extensions = assertion.getClientExtensionResults();
|
||||||
|
creationData.extensions.uvm = undefined;
|
||||||
|
|
||||||
// Put the passkey creation data into the form for submission
|
// Put the passkey creation data into the form for submission
|
||||||
document.getElementById("passkey-create-data").value = JSON.stringify(creationData)
|
document.getElementById("passkey-create-data").value = JSON.stringify(creationData);
|
||||||
|
|
||||||
// Make the name input visible and hide the "Begin Passkey Enrollment" button
|
// Make the name input visible and hide the "Begin Passkey Enrollment" button
|
||||||
document.getElementById("passkeyNamingSafariPre").classList.add("d-none")
|
document.getElementById("passkeyNamingSafariPre").classList.add("d-none");
|
||||||
document.getElementById("passkeyNamingForm").classList.remove("d-none")
|
document.getElementById("passkeyNamingForm").classList.remove("d-none");
|
||||||
document.getElementById("passkeyNamingSubmitBtn").classList.remove("d-none")
|
document.getElementById("passkeyNamingSubmitBtn").classList.remove("d-none");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e);
|
||||||
if (confirm("Failed to encode your new passkey's data for transmission, confirm to reload this page.\nReport this issue if it keeps occurring.")) {
|
if (
|
||||||
|
confirm(
|
||||||
|
"Failed to encode your new passkey's data for transmission, confirm to reload this page.\nReport this issue if it keeps occurring.",
|
||||||
|
)
|
||||||
|
) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,36 +88,41 @@ function onPasskeyCreated(assertion) {
|
||||||
|
|
||||||
function startPasskeyEnrollment() {
|
function startPasskeyEnrollment() {
|
||||||
try {
|
try {
|
||||||
const data_elem = document.getElementById('data');
|
const data_elem = document.getElementById("data");
|
||||||
const credentialRequestOptions = JSON.parse(data_elem.textContent);
|
const credentialRequestOptions = JSON.parse(data_elem.textContent);
|
||||||
credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(credentialRequestOptions.publicKey.challenge);
|
credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(
|
||||||
|
credentialRequestOptions.publicKey.challenge,
|
||||||
|
);
|
||||||
credentialRequestOptions.publicKey.user.id = Base64.toUint8Array(credentialRequestOptions.publicKey.user.id);
|
credentialRequestOptions.publicKey.user.id = Base64.toUint8Array(credentialRequestOptions.publicKey.user.id);
|
||||||
|
|
||||||
console.log(credentialRequestOptions)
|
console.log(credentialRequestOptions);
|
||||||
navigator.credentials
|
navigator.credentials.create({ publicKey: credentialRequestOptions.publicKey }).then(
|
||||||
.create({publicKey: credentialRequestOptions.publicKey})
|
(assertion) => {
|
||||||
.then((assertion) => {
|
|
||||||
onPasskeyCreated(assertion);
|
onPasskeyCreated(assertion);
|
||||||
}, (reason) => {
|
},
|
||||||
alert("Passkey creation failed " + reason.toString())
|
(reason) => {
|
||||||
console.log("Passkey creation failed: " + reason.toString())
|
alert(`Passkey creation failed ${reason.toString()}`);
|
||||||
});
|
console.log(`Passkey creation failed: ${reason.toString()}`);
|
||||||
|
},
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(`Failed to initialize passkey creation: ${e}`);
|
||||||
if (confirm("Failed to initialize passkey creation, confirm to reload this page.\nReport this issue if it keeps occurring.")) {
|
if (
|
||||||
|
confirm(
|
||||||
|
"Failed to initialize passkey creation, confirm to reload this page.\nReport this issue if it keeps occurring.",
|
||||||
|
)
|
||||||
|
) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupPasskeyNamingSafariButton() {
|
function setupPasskeyNamingSafariButton() {
|
||||||
document.getElementById("passkeyNamingSafariBtn")
|
document.getElementById("passkeyNamingSafariBtn").addEventListener("click", startPasskeyEnrollment);
|
||||||
.addEventListener("click", startPasskeyEnrollment)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupSubmitBtnVisibility() {
|
function setupSubmitBtnVisibility() {
|
||||||
document.getElementById("passkey-label")
|
document.getElementById("passkey-label")?.addEventListener("input", updateSubmitButtonVisibility);
|
||||||
?.addEventListener("input", updateSubmitButtonVisibility)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSubmitButtonVisibility(event) {
|
function updateSubmitButtonVisibility(event) {
|
||||||
|
@ -119,12 +130,14 @@ function updateSubmitButtonVisibility(event) {
|
||||||
submitButton.disabled = event.value === "";
|
submitButton.disabled = event.value === "";
|
||||||
}
|
}
|
||||||
|
|
||||||
(function() {
|
(function () {
|
||||||
console.debug('credupdate: init');
|
console.debug("credupdate: init");
|
||||||
document.body.addEventListener("addPasswordSwapped", () => { setupInteractivePwdFormListeners() });
|
document.body.addEventListener("addPasswordSwapped", () => {
|
||||||
|
setupInteractivePwdFormListeners();
|
||||||
|
});
|
||||||
document.body.addEventListener("addPasskeySwapped", () => {
|
document.body.addEventListener("addPasskeySwapped", () => {
|
||||||
setupPasskeyNamingSafariButton();
|
setupPasskeyNamingSafariButton();
|
||||||
startPasskeyEnrollment();
|
startPasskeyEnrollment();
|
||||||
setupSubmitBtnVisibility();
|
setupSubmitBtnVisibility();
|
||||||
});
|
});
|
||||||
})()
|
})();
|
||||||
|
|
|
@ -1,26 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Initiates the passkey login process by requesting credentials from the user.
|
||||||
|
*
|
||||||
|
* This function retrieves the credential request options from the DOM, converts
|
||||||
|
* necessary fields from Base64 to Uint8Array, and then uses the Web Authentication API
|
||||||
|
* to get the user's credentials. Upon successful retrieval, it encodes the assertion
|
||||||
|
* response back to Base64 and submits the form with the credential data.
|
||||||
|
*
|
||||||
|
* @function asskey_login
|
||||||
|
* @throws {Error} If the passkey authentication process fails.
|
||||||
|
*/
|
||||||
|
|
||||||
function asskey_login() {
|
function asskey_login() {
|
||||||
let credentialRequestOptions = JSON.parse(document.getElementById('data').textContent);
|
let credentialRequestOptions = JSON.parse(document.getElementById("data").textContent);
|
||||||
credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(credentialRequestOptions.publicKey.challenge);
|
credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(credentialRequestOptions.publicKey.challenge);
|
||||||
credentialRequestOptions.publicKey.allowCredentials?.forEach(function (listItem) {
|
credentialRequestOptions.publicKey.allowCredentials?.forEach(function (listItem) {
|
||||||
listItem.id = Base64.toUint8Array(listItem.id)
|
listItem.id = Base64.toUint8Array(listItem.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
navigator.credentials.get({ publicKey: credentialRequestOptions.publicKey })
|
navigator.credentials
|
||||||
|
.get({ publicKey: credentialRequestOptions.publicKey })
|
||||||
.then((assertion) => {
|
.then((assertion) => {
|
||||||
document.getElementById("cred").value = JSON.stringify({
|
document.getElementById("cred").value = JSON.stringify({
|
||||||
id: assertion.id,
|
id: assertion.id,
|
||||||
rawId: Base64.fromUint8Array(new Uint8Array(assertion.rawId), true),
|
rawId: Base64.fromUint8Array(new Uint8Array(assertion.rawId), true),
|
||||||
type: assertion.type,
|
type: assertion.type,
|
||||||
response: {
|
response: {
|
||||||
authenticatorData: Base64.fromUint8Array(new Uint8Array(assertion.response.authenticatorData), true),
|
authenticatorData: Base64.fromUint8Array(
|
||||||
|
new Uint8Array(assertion.response.authenticatorData),
|
||||||
|
true,
|
||||||
|
),
|
||||||
clientDataJSON: Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON), true),
|
clientDataJSON: Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON), true),
|
||||||
signature: Base64.fromUint8Array(new Uint8Array(assertion.response.signature), true),
|
signature: Base64.fromUint8Array(new Uint8Array(assertion.response.signature), true),
|
||||||
userHandle: Base64.fromUint8Array(new Uint8Array(assertion.response.userHandle), true)
|
userHandle: Base64.fromUint8Array(new Uint8Array(assertion.response.userHandle), true),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
document.getElementById("cred-form").submit();
|
document.getElementById("cred-form").submit();
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
console.error(`Failed to complete passkey authentication: ${error}`);
|
console.error(`Failed to complete passkey authentication: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
@ -31,17 +47,23 @@ try {
|
||||||
myButton.addEventListener("click", () => {
|
myButton.addEventListener("click", () => {
|
||||||
asskey_login();
|
asskey_login();
|
||||||
});
|
});
|
||||||
} catch (_error) {};
|
} catch (error) {
|
||||||
|
console.error(`Failed to add button event listener for passkey authentication: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const myButton = document.getElementById("start-seckey-button");
|
const myButton = document.getElementById("start-seckey-button");
|
||||||
myButton.addEventListener("click", () => {
|
myButton.addEventListener("click", () => {
|
||||||
asskey_login();
|
asskey_login();
|
||||||
});
|
});
|
||||||
} catch (_error) {};
|
} catch (error) {
|
||||||
|
console.error(`Failed to add button event listener for security key authentication: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.addEventListener("load", (event) => {
|
addEventListener("load", () => {
|
||||||
asskey_login()
|
asskey_login();
|
||||||
});
|
});
|
||||||
} catch (_error) {};
|
} catch (error) {
|
||||||
|
console.error(`Failed to add load-time event listener for passkey authentication: ${error}`);
|
||||||
|
}
|
||||||
|
|
|
@ -30,14 +30,15 @@ body {
|
||||||
|
|
||||||
.side-menu-item {
|
.side-menu-item {
|
||||||
--icon-size: 24px;
|
--icon-size: 24px;
|
||||||
padding: .4rem .7rem;
|
padding: 0.4rem 0.7rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &.active {
|
&:hover,
|
||||||
|
&.active {
|
||||||
background-color: var(--bs-gray-300);
|
background-color: var(--bs-gray-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +54,6 @@ body {
|
||||||
* Personal Settings sidemenu
|
* Personal Settings sidemenu
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Navbar
|
* Navbar
|
||||||
*/
|
*/
|
||||||
|
@ -80,7 +80,9 @@ body {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
background: #21252915;
|
background: #21252915;
|
||||||
box-shadow: -5px -5px 11px #ededed, 5px 5px 11px #ffffff;
|
box-shadow:
|
||||||
|
-5px -5px 11px #ededed,
|
||||||
|
5px 5px 11px #ffffff;
|
||||||
margin: 15px;
|
margin: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,9 +152,9 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-tiny {
|
.btn-tiny {
|
||||||
--bs-btn-padding-y: .05rem;
|
--bs-btn-padding-y: 0.05rem;
|
||||||
--bs-btn-padding-x: .4rem;
|
--bs-btn-padding-x: 0.4rem;
|
||||||
--bs-btn-font-size: .75rem;
|
--bs-btn-font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cred-update-commit-bar {
|
#cred-update-commit-bar {
|
||||||
|
|
Loading…
Reference in a new issue