diff --git a/Makefile b/Makefile index 22407170..f8e87850 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # WIP -default: build-frontend +default: frontend cargo build -run: build-frontend-dev +run: frontend-dev cargo run dev-env: @@ -12,10 +12,10 @@ dev-env: docs: cargo doc --no-deps --workspace --all-features -build-frontend-dev: +frontend-dev: yarn start -build-frontend: +frontend: yarn build test: migrate @@ -27,7 +27,7 @@ xml-test-coverage: migrate coverage: migrate cargo tarpaulin -t 1200 --out Html -release: build-frontend +release: frontend cargo build --release clean: @@ -38,12 +38,14 @@ migrate: cargo run --bin tests-migrate help: - @echo ' docs - build documentation' - @echo ' run - run developer instance' - @echo ' test - run unit and integration tests' - @echo ' migrate - run database migrations' - @echo ' dev-env - download dependencies' - @echo ' clean - drop builds and environments' - @echo ' coverage - build test coverage in HTML format' - @echo ' xml-coverage - build test coverage in XML for upload to codecov' + @echo ' run - run developer instance' + @echo ' test - run unit and integration tests' + @echo ' frontend-dev - build static assets in dev mode' + @echo ' frontend - build static assets in prod mode' + @echo ' migrate - run database migrations' + @echo ' dev-env - download dependencies' + @echo ' docs - build documentation' + @echo ' clean - drop builds and environments' + @echo ' coverage - build test coverage in HTML format' + @echo ' xml-coverage - build test coverage in XML for upload to codecov' @echo '' diff --git a/src/api/v1/auth.rs b/src/api/v1/auth.rs index fa01ea12..81387dcf 100644 --- a/src/api/v1/auth.rs +++ b/src/api/v1/auth.rs @@ -177,7 +177,8 @@ async fn signout(id: Identity) -> impl Responder { if let Some(_) = id.identity() { id.forget(); } - HttpResponse::Ok() - .set_header(header::LOCATION, "/login") - .body("") + HttpResponse::Found() + .header(header::LOCATION, "/login") + .finish() + .into_body() } diff --git a/src/api/v1/mcaptcha/levels.rs b/src/api/v1/mcaptcha/levels.rs index 0c869fba..3810083b 100644 --- a/src/api/v1/mcaptcha/levels.rs +++ b/src/api/v1/mcaptcha/levels.rs @@ -20,6 +20,7 @@ use actix_web::{web, HttpResponse, Responder}; use m_captcha::{defense::Level, DefenseBuilder}; use serde::{Deserialize, Serialize}; +use super::mcaptcha::add_mcaptcha_util; use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails; use crate::errors::*; use crate::Data; @@ -52,8 +53,6 @@ pub mod routes { #[derive(Serialize, Deserialize)] pub struct AddLevels { pub levels: Vec, - /// name is config_name - pub key: String, } pub fn services(cfg: &mut web::ServiceConfig) { @@ -104,6 +103,8 @@ async fn add_levels( defense.build()?; + let mcaptcha_config = add_mcaptcha_util(&data, &id).await?; + for level in payload.levels.iter() { let difficulty_factor = level.difficulty_factor as i32; let visitor_threshold = level.visitor_threshold as i32; @@ -119,18 +120,25 @@ async fn add_levels( )));", difficulty_factor, visitor_threshold, - &payload.key, + &mcaptcha_config.key, &username, ) .execute(&data.db) .await?; } - Ok(HttpResponse::Ok()) + Ok(HttpResponse::Ok().json(mcaptcha_config)) +} + +#[derive(Serialize, Deserialize)] +pub struct UpdateLevels { + pub levels: Vec, + /// name is config_name + pub key: String, } async fn update_levels( - payload: web::Json, + payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { @@ -187,7 +195,7 @@ async fn update_levels( } async fn delete_levels( - payload: web::Json, + payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { @@ -304,7 +312,7 @@ mod tests { visitor_threshold: 5000, }; let levels = vec![l1, l2]; - let add_level = AddLevels { + let add_level = UpdateLevels { levels: levels.clone(), key: key.key.clone(), }; @@ -337,7 +345,7 @@ mod tests { visitor_threshold: 5000, }; let levels = vec![l1, l2]; - let add_level = AddLevels { + let add_level = UpdateLevels { levels: levels.clone(), key: key.key.clone(), }; diff --git a/src/api/v1/mcaptcha/mcaptcha.rs b/src/api/v1/mcaptcha/mcaptcha.rs index 0b207db1..36126085 100644 --- a/src/api/v1/mcaptcha/mcaptcha.rs +++ b/src/api/v1/mcaptcha/mcaptcha.rs @@ -88,7 +88,7 @@ pub struct MCaptchaDetails { } // this should be called from within add levels -async fn add_mcaptcha(data: web::Data, id: Identity) -> ServiceResult { +pub async fn add_mcaptcha_util(data: &Data, id: &Identity) -> ServiceResult { let username = id.identity().unwrap(); let mut key; @@ -125,7 +125,12 @@ async fn add_mcaptcha(data: web::Data, id: Identity) -> ServiceResult, id: Identity) -> ServiceResult { + let resp = add_mcaptcha_util(&data, &id).await?; Ok(HttpResponse::Ok().json(resp)) } diff --git a/src/api/v1/tests/auth.rs b/src/api/v1/tests/auth.rs index a6e668fa..9ec79a1a 100644 --- a/src/api/v1/tests/auth.rs +++ b/src/api/v1/tests/auth.rs @@ -123,7 +123,7 @@ async fn auth_works() { .to_request(), ) .await; - assert_eq!(signout_resp.status(), StatusCode::OK); + assert_eq!(signout_resp.status(), StatusCode::FOUND); let headers = signout_resp.headers(); assert_eq!(headers.get(header::LOCATION).unwrap(), PAGES.auth.login); } diff --git a/src/api/v1/tests/protected.rs b/src/api/v1/tests/protected.rs index e04e994a..7175af9d 100644 --- a/src/api/v1/tests/protected.rs +++ b/src/api/v1/tests/protected.rs @@ -70,6 +70,10 @@ async fn protected_routes_work() { ) .await; - assert_eq!(authenticated_resp.status(), StatusCode::OK); + if url == &V1_API_ROUTES.auth.logout { + assert_eq!(authenticated_resp.status(), StatusCode::FOUND); + } else { + assert_eq!(authenticated_resp.status(), StatusCode::OK); + } } } diff --git a/src/pages/auth/login.rs b/src/pages/auth/login.rs index 21a3c1fc..3ce4a97a 100644 --- a/src/pages/auth/login.rs +++ b/src/pages/auth/login.rs @@ -18,6 +18,8 @@ use actix_web::{HttpResponse, Responder}; use sailfish::TemplateOnce; +use crate::pages::TITLE; + #[derive(Clone, TemplateOnce)] #[template(path = "auth/login/index.html")] struct IndexPage<'a> { @@ -28,7 +30,7 @@ struct IndexPage<'a> { impl<'a> Default for IndexPage<'a> { fn default() -> Self { IndexPage { - name: "mCaptcha", + name: TITLE, title: "Login", } } diff --git a/src/pages/auth/register.rs b/src/pages/auth/register.rs index 349f0258..74722abe 100644 --- a/src/pages/auth/register.rs +++ b/src/pages/auth/register.rs @@ -18,6 +18,8 @@ use actix_web::{HttpResponse, Responder}; use sailfish::TemplateOnce; +use crate::pages::TITLE; + #[derive(TemplateOnce, Clone)] #[template(path = "auth/register/index.html")] struct IndexPage<'a> { @@ -28,7 +30,7 @@ struct IndexPage<'a> { impl<'a> Default for IndexPage<'a> { fn default() -> Self { IndexPage { - name: "mCaptcha", + name: TITLE, title: "Join", } } diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 6e524cdf..3396ce40 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -21,6 +21,8 @@ mod auth; mod panel; pub mod routes; +pub const TITLE: &str = "mCaptcha"; + pub fn services(cfg: &mut ServiceConfig) { auth::services(cfg); panel::services(cfg); @@ -59,7 +61,11 @@ mod tests { ) .await; - let urls = vec![PAGES.home, PAGES.panel.sitekey.add]; + let urls = vec![ + PAGES.home, + PAGES.panel.sitekey.add, + PAGES.panel.sitekey.list, + ]; for url in urls.iter() { let resp = diff --git a/src/pages/panel/mod.rs b/src/pages/panel/mod.rs index 44d04455..dffde348 100644 --- a/src/pages/panel/mod.rs +++ b/src/pages/panel/mod.rs @@ -18,6 +18,8 @@ use actix_web::{HttpResponse, Responder}; use sailfish::TemplateOnce; +use crate::pages::TITLE; + pub mod sitekey; #[derive(TemplateOnce, Clone)] @@ -27,13 +29,13 @@ pub struct IndexPage<'a> { pub title: &'a str, } -const TITLE: &str = "Dashboard"; +const COMPONENT: &str = "Dashboard"; impl<'a> Default for IndexPage<'a> { fn default() -> Self { IndexPage { - name: "mCaptcha", - title: TITLE, + name: TITLE, + title: COMPONENT, } } } diff --git a/src/pages/panel/sitekey/add.rs b/src/pages/panel/sitekey/add.rs index 1c3558fa..f7abf022 100644 --- a/src/pages/panel/sitekey/add.rs +++ b/src/pages/panel/sitekey/add.rs @@ -18,6 +18,8 @@ use actix_web::{HttpResponse, Responder}; use sailfish::TemplateOnce; +use crate::pages::TITLE; + #[derive(TemplateOnce, Clone)] #[template(path = "panel/add-site-key/index.html")] pub struct IndexPage<'a> { @@ -28,13 +30,13 @@ pub struct IndexPage<'a> { pub form_description: &'a str, } -const TITLE: &str = "Add Site Key"; +const COMPONENT: &str = "Add Site Key"; impl<'a> Default for IndexPage<'a> { fn default() -> Self { IndexPage { - name: "mCaptcha", - title: TITLE, + name: TITLE, + title: COMPONENT, levels: 1, form_description: "", form_title: "Add Site Key", diff --git a/src/pages/panel/sitekey/list.rs b/src/pages/panel/sitekey/list.rs new file mode 100644 index 00000000..d03a806d --- /dev/null +++ b/src/pages/panel/sitekey/list.rs @@ -0,0 +1,44 @@ +/* +* Copyright (C) 2021 Aravinth Manivannan +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +use actix_web::{HttpResponse, Responder}; +use sailfish::TemplateOnce; + +#[derive(TemplateOnce, Clone)] +#[template(path = "panel/site-keys/index.html")] +pub struct IndexPage<'a> { + pub name: &'a str, + pub title: &'a str, +} + +const TITLE: &str = "Add Site Key"; + +impl<'a> Default for IndexPage<'a> { + fn default() -> Self { + IndexPage { + name: "mCaptcha", + title: TITLE, + } + } +} + +pub async fn list_sitekeys() -> impl Responder { + let body = IndexPage::default().render_once().unwrap(); + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(body) +} diff --git a/src/pages/panel/sitekey/mod.rs b/src/pages/panel/sitekey/mod.rs index db1b2623..c6e0d8a1 100644 --- a/src/pages/panel/sitekey/mod.rs +++ b/src/pages/panel/sitekey/mod.rs @@ -16,6 +16,7 @@ */ mod add; +mod list; pub mod routes { pub struct Sitekey { @@ -43,4 +44,11 @@ pub fn services(cfg: &mut actix_web::web::ServiceConfig) { Methods::ProtectGet, add::add_sitekey ); + + define_resource!( + cfg, + PAGES.panel.sitekey.list, + Methods::ProtectGet, + list::list_sitekeys + ); } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 2a546ab8..39104298 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -180,7 +180,7 @@ pub async fn add_levels_util( name: &str, password: &str, ) -> (data::Data, Login, ServiceResponse, MCaptchaDetails) { - let (data, creds, signin_resp, token_key) = add_token_util(name, password).await; + let (data, creds, signin_resp) = signin(name, password).await; let cookies = get_cookie!(signin_resp); let mut app = get_app!(data).await; @@ -188,7 +188,6 @@ pub async fn add_levels_util( let add_level = AddLevels { levels: levels.clone(), - key: token_key.key.clone(), }; // 1. add level @@ -200,6 +199,7 @@ pub async fn add_levels_util( ) .await; assert_eq!(add_token_resp.status(), StatusCode::OK); + let token_key: MCaptchaDetails = test::read_body_json(add_token_resp).await; (data, creds, signin_resp, token_key) } diff --git a/templates/_reset.scss b/templates/_reset.scss index d1fb7149..a2539ca8 100644 --- a/templates/_reset.scss +++ b/templates/_reset.scss @@ -1,6 +1,24 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + * { margin: 0; padding: 0; + font-family: Arial, Helvetica, sans-serif; } a { @@ -12,9 +30,9 @@ li { } html { - height: 100%; + height: 100%; } body { - height: 100%; + height: 100%; } diff --git a/templates/_vars.scss b/templates/_vars.scss index 4e52cb1f..d8d2a25b 100644 --- a/templates/_vars.scss +++ b/templates/_vars.scss @@ -1,6 +1,26 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + $green: #5cad66; $violet: #800080; +$light-violet: #993299; $backdrop: #f0f0f0; $light-text: rgba(255, 255, 255, 0.87); $secondary-backdrop: #2b2c30; $light-grey: rgba(0, 0, 0, 0.125); +$white: #fff; +$form-content-width: 90%; diff --git a/templates/auth/forms.scss b/templates/auth/forms.scss index 004f6906..1929006d 100644 --- a/templates/auth/forms.scss +++ b/templates/auth/forms.scss @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + @import '../reset'; @import '../vars'; diff --git a/templates/auth/login/index.html b/templates/auth/login/index.html index 4b729ab9..d373257f 100644 --- a/templates/auth/login/index.html +++ b/templates/auth/login/index.html @@ -12,6 +12,7 @@ type="text" name="username" required="" + autofocus="true" /> @@ -30,9 +31,7 @@ > --> - +

diff --git a/templates/auth/login/index.ts b/templates/auth/login/index.ts index a70867ce..cc0de71d 100644 --- a/templates/auth/login/index.ts +++ b/templates/auth/login/index.ts @@ -15,51 +15,51 @@ * along with this program. If not, see . */ -import ROUTES from '../../api/v1/routes'; import VIEWS from '../../views/v1/routes'; import isBlankString from '../../utils/isBlankString'; import genJsonPayload from '../../utils/genJsonPayload'; +import getFormUrl from '../../utils/getFormUrl'; //import '../forms.scss'; -const login = (e: Event) => { +const login = async (e: Event) => { e.preventDefault(); - InputEvent - const usernameElement: HTMLInputElement = document.getElementById('username'); + const usernameElement = document.getElementById('username'); if (usernameElement === null) { - console.debug("Username element is null"); + console.debug('Username element is null'); return; } - let username = usernameElement.value; - isBlankString(e, username, 'username'); -// isBlankString(e);//, username, 'username'); + const username = usernameElement.value; + isBlankString(username, 'username', e); - const passwordElement: HTMLInputElement = document.getElementById('password'); + const passwordElement = document.getElementById('password'); if (passwordElement === null) { - console.debug("Password is null"); + console.debug('Password is null'); return; } - let password = passwordElement.value; + const password = passwordElement.value; - let payload = { + const payload = { username, password, }; - fetch(ROUTES.loginUser, genJsonPayload(payload)).then(res => { - if (res.ok) { - alert('success'); - window.location.assign(VIEWS.panelHome); - } else { - res.json().then(err => alert(`error: ${err.error}`)); - } - }); + const formUrl = getFormUrl(null); + + const res = await fetch(formUrl, genJsonPayload(payload)); + if (res.ok) { + alert('success'); + window.location.assign(VIEWS.panelHome); + } else { + const err = await res.json(); + alert(`error: ${err.error}`); + } }; export const index = () => { - let form = document.getElementById('form'); + const form = document.getElementById('form'); form.addEventListener('submit', login, true); }; diff --git a/templates/auth/register/index.html b/templates/auth/register/index.html index 634c73c6..1e5bc9f2 100644 --- a/templates/auth/register/index.html +++ b/templates/auth/register/index.html @@ -3,7 +3,7 @@ " class="form__logo" alt="" />

Join mCaptcha

-
+ @@ -50,9 +51,7 @@ required /> - +

diff --git a/templates/auth/register/index.ts b/templates/auth/register/index.ts index f3e8f69a..4f1eb39b 100644 --- a/templates/auth/register/index.ts +++ b/templates/auth/register/index.ts @@ -15,7 +15,6 @@ * along with this program. If not, see . */ -import ROUTES from '../../api/v1/routes'; import VIEWS from '../../views/v1/routes'; import isBlankString from '../../utils/isBlankString'; @@ -23,6 +22,7 @@ import genJsonPayload from '../../utils/genJsonPayload'; import userExists from './userExists'; import {checkEmailExists} from './emailExists'; +import getFormUrl from '../../utils/getFormUrl'; //import '../forms.scss'; @@ -33,15 +33,15 @@ const passwordElement = document.getElementById('password'); const registerUser = async (e: Event) => { e.preventDefault(); - let username = usernameElement.value; - isBlankString(e, username, 'username'); + const username = usernameElement.value; + isBlankString(username, 'username', e); //isBlankString(e);//, username, 'username'); - let password = passwordElement.value; + const password = passwordElement.value; const passwordCheckElement = ( document.getElementById('password-check') ); - let passwordCheck = passwordCheckElement.value; + const passwordCheck = passwordCheckElement.value; if (password != passwordCheck) { return alert("passwords don't match, check again!"); } @@ -61,25 +61,26 @@ const registerUser = async (e: Event) => { } } - let payload = { + const payload = { username, password, confirm_password: passwordCheck, email, }; + const formUrl = getFormUrl(null); - let res = await fetch(ROUTES.registerUser, genJsonPayload(payload)); + const res = await fetch(formUrl, genJsonPayload(payload)); if (res.ok) { alert('success'); window.location.assign(VIEWS.loginUser); } else { - let err = await res.json(); + const err = await res.json(); alert(`error: ${err.error}`); } }; export const index = () => { - let form = document.getElementById('form'); + const form = document.getElementById('form'); form.addEventListener('submit', registerUser, true); usernameElement.addEventListener('input', userExists, false); }; diff --git a/templates/components/_button.scss b/templates/components/_button.scss new file mode 100644 index 00000000..cbb8510a --- /dev/null +++ b/templates/components/_button.scss @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@mixin violet-button-hover { + background-color: $light-violet; + cursor: grab; + transform: translateY(-5px); +} + +@mixin violet-button { + background-color: $violet; + color: $light-text; + border-radius: 5px; + border: 1px $light-grey solid; + padding: 5px; + font-size: 20px; + min-height: 45px; + width: 125px; +} diff --git a/templates/components/_forms.scss b/templates/components/_forms.scss new file mode 100644 index 00000000..bd7aa4e8 --- /dev/null +++ b/templates/components/_forms.scss @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@import '../vars'; + +@mixin form-label { + margin: 10px 0; + box-sizing: border-box; + width: $form-content-width; + justify-self: left; +} + +@mixin form-input { + position: relative; + margin-top: 5px; + box-sizing: border-box; + height: 40px; + width: 90%; +} diff --git a/templates/index.ts b/templates/index.ts index 76cecef9..346ab079 100644 --- a/templates/index.ts +++ b/templates/index.ts @@ -21,9 +21,17 @@ import * as login from './auth/login'; import * as register from './auth/register'; import * as panel from './panel/index'; import * as addSiteKey from './panel/add-site-key/'; + +import VIEWS from './views/v1/routes'; + + import './auth/forms.scss'; import './panel/main.scss'; -import VIEWS from './views/v1/routes'; +import './panel/header/sidebar/main.scss'; +import './panel/taskbar/main.scss'; +import './panel/help-banner/main.scss'; +import './panel/add-site-key/main.scss'; + const router = new Router(); diff --git a/templates/panel/add-site-key/add-level.html b/templates/panel/add-site-key/add-level.html index 599e2beb..4c8840ab 100644 --- a/templates/panel/add-site-key/add-level.html +++ b/templates/panel/add-site-key/add-level.html @@ -1,17 +1,31 @@ -

- -
+
+ Level <.= level .> + -
+ - <.= "required" .> - <. } .> - value="" + class="sitekey-form__add-level-button" + type="button" + name="add" + id="add" + value="Add" /> - -
+
diff --git a/templates/panel/add-site-key/addLevelButton.ts b/templates/panel/add-site-key/addLevelButton.ts index 2b613b2b..db3e3ad9 100644 --- a/templates/panel/add-site-key/addLevelButton.ts +++ b/templates/panel/add-site-key/addLevelButton.ts @@ -14,124 +14,91 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import VALIDATE_LEVELS from './levels'; -import isBlankString from '../../utils/isBlankString'; -import isNumber from '../../utils/isNumber'; +import getNumLevels from './levels/getNumLevels'; +import validateLevel from './levels/validateLevel'; +import * as UpdateLevel from './levels/updateLevel'; -const LABEL_CONTAINER_CLASS = 'sitekey-form__add-level-flex-container'; -const LABEL_CLASS = 'sitekey-form__label'; -export const LABEL_INNER_TEXT_WITHOUT_LEVEL = 'Level '; +const ADD_LEVEL_BUTTON = 'sitekey-form__level-add-level-button'; -export const INPUT_ID_WITHOUT_LEVEL = 'level'; -const INPUT_CLASS = 'sitekey-form__input--add-level'; +/** + * Gets executed when 'Add' Button is clicked to add levels + * Used to validate levels per m_captcha::defense::Defense's + * specifications + */ +const addLevel = (e: Event) => { + const eventTarget = e.target; + const PARENT = eventTarget.parentElement; + const FIELDSET = PARENT.parentElement; + const numLevels = getNumLevels(); -const ADD_LEVEL_BUTTON_INNER_TEXT = 'Add Level'; -const ADD_LEVEL_BUTTON = 'sitekey-form__add-level-button'; - -export const getNumLevels = () => { - let numLevels = 0; - document.querySelectorAll(`.${LABEL_CLASS}`).forEach(_ => numLevels++); - return numLevels; -}; - -const validateLevel = (numLevels: number) => { - numLevels = numLevels - 1; - let inputID = INPUT_ID_WITHOUT_LEVEL + numLevels.toString(); - let filed = LABEL_INNER_TEXT_WITHOUT_LEVEL + numLevels; - let inputElement = document.getElementById(inputID); - - let val = inputElement.value; - if (!isNumber(val)) { - return false; - } - - let level = parseInt(val); - if (Number.isNaN(level)) { - alert('Level can contain nubers only'); - return false; - } - - let e = null; - console.log(level); - - isBlankString(e, val, filed); - let isValid = VALIDATE_LEVELS.add(level); - return isValid; -}; - -const addLevelButtonEventHandler = (e: Event) => { - let eventTarget = e.target; - // if (!eventTarget) { - // return; - // } - const PREV_LEVEL_CONTAINER = eventTarget.parentElement; - let numLevels: string | number = getNumLevels(); - let isValid = validateLevel(numLevels); + const isValid = validateLevel(numLevels); console.log(`[addLevelButton] isValid: ${isValid}`); - if (!isValid) { - return console.log('Aborting level addition'); + return console.error('Aborting level addition'); } - eventTarget.remove(); + PARENT.remove(); - numLevels = numLevels.toString(); - - let labelContainer = document.createElement('div'); - labelContainer.className = LABEL_CONTAINER_CLASS; - - let inputID = INPUT_ID_WITHOUT_LEVEL + numLevels; - let label = document.createElement('label'); - label.className = LABEL_CLASS; - label.htmlFor = inputID; - label.innerText = LABEL_INNER_TEXT_WITHOUT_LEVEL + numLevels; - - labelContainer.appendChild(label); - - PREV_LEVEL_CONTAINER.insertAdjacentElement('afterend', labelContainer); - - let inputContainer = document.createElement('div'); - inputContainer.className = LABEL_CONTAINER_CLASS; - - let input = document.createElement('input'); - input.id = inputID; - input.name = inputID; - input.type = 'text'; - input.className = INPUT_CLASS; - - inputContainer.appendChild(input); - - let button = document.createElement('button'); - button.className = ADD_LEVEL_BUTTON; - button.innerText = ADD_LEVEL_BUTTON_INNER_TEXT; - - inputContainer.appendChild(button); - - labelContainer.insertAdjacentElement('afterend', inputContainer); + const newLevelHTML = getHtml(numLevels + 1); + FIELDSET.insertAdjacentHTML('afterend', newLevelHTML); + UpdateLevel.register(numLevels); addLevelButtonAddEventListener(); }; -export const addLevelButtonAddEventListener = () => { +/** adds onclick event listener */ +const addLevelButtonAddEventListener = () => { let addLevelButton = ( document.querySelector(`.${ADD_LEVEL_BUTTON}`) ); - addLevelButton.addEventListener('click', addLevelButtonEventHandler); + addLevelButton.addEventListener('click', addLevel); }; -/* -
- -
+/** + * Generate HTML to be added when 'Add Level' button is clicked + * Check if './add-level.html` to see if this is up to date + */ +const getHtml = (level: number) => { + console.debug(`[generating HTML getHtml]level: ${level}`); + const HTML = ` +
+ + Level ${level} + + -
- - -
-*/ + + +
+`; + return HTML; +}; + +export default addLevelButtonAddEventListener; diff --git a/templates/panel/add-site-key/const.ts b/templates/panel/add-site-key/const.ts new file mode 100644 index 00000000..eb01e72b --- /dev/null +++ b/templates/panel/add-site-key/const.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +const LABEL_INNER_TEXT_WITHOUT_LEVEL = 'Level '; +const INPUT_ID_WITHOUT_LEVEL = 'level'; +const LABEL_CLASS = 'sitekey-form__label'; +const VISITOR_WITHOUT_LEVEL = 'visitor'; +const DIFFICULTY_WITHOUT_LEVEL = 'difficulty'; +const LEVEL_CONTAINER_CLASS = "sitekey__level-container"; + + +const CONST = { + LABEL_CLASS, + INPUT_ID_WITHOUT_LEVEL, + LABEL_INNER_TEXT_WITHOUT_LEVEL, + VISITOR_WITHOUT_LEVEL, + DIFFICULTY_WITHOUT_LEVEL, + LEVEL_CONTAINER_CLASS, +} + +export default CONST; diff --git a/templates/panel/add-site-key/existing-level.html b/templates/panel/add-site-key/existing-level.html index 535c4510..81b68108 100644 --- a/templates/panel/add-site-key/existing-level.html +++ b/templates/panel/add-site-key/existing-level.html @@ -1,6 +1,6 @@
- +
-
- <.= form_title .> -
-
- -
- +
+

+ <.= form_title .> +

+ <. for level in 1..=levels { .> <. if level == levels { .> - <. include!("./add-level.html"); .> + <. include!("./new-add-level.html"); .> <. } else { .> <. include!("./existing-level.html"); .> <. } .> diff --git a/templates/panel/add-site-key/form.ts b/templates/panel/add-site-key/form.ts index 418bcebc..dc923c82 100644 --- a/templates/panel/add-site-key/form.ts +++ b/templates/panel/add-site-key/form.ts @@ -15,43 +15,71 @@ * along with this program. If not, see . */ +import CONST from './const'; +import getNumLevels from './levels/getNumLevels'; +import isBlankString from '../../utils/isBlankString'; +import getFormUrl from '../../utils/getFormUrl'; +import genJsonPayload from '../../utils/genJsonPayload'; +import {LEVELS} from './levels'; + +import VIEWS from '../../views/v1/routes'; + const SITE_KEY_FORM_CLASS = 'sitekey-form'; const FORM = document.querySelector(`.${SITE_KEY_FORM_CLASS}`); +//const FORM_SUBMIT_BUTTON_CLASS = "sitekey-form__submit"; +//const FORM_SUBMIT_BUTTON = document.querySelector(`.${FORM_SUBMIT_BUTTON_CLASS}`); -import * as addLevelButton from './addLevelButton'; -import isBlankString from '../../utils/isBlankString'; - -export const addSubmitEventListener = () => { +const addSubmitEventListener = () => { FORM.addEventListener('submit', submit, true); }; -const validateLevels = (e: Event) => { - let numLevels = addLevelButton.getNumLevels(); - // check if levels are unique and are in increasing order; - // also if they are positive - // also if level input field is accompanied by a "Add Level" button, - // it shouldn't be used for validation - for (let levelNum = 1; levelNum < numLevels; levelNum++) { - let inputID = addLevelButton.INPUT_ID_WITHOUT_LEVEL + levelNum; - let inputElement = document.getElementById(inputID); - let val = inputElement.value; - let filed = addLevelButton.LABEL_INNER_TEXT_WITHOUT_LEVEL + levelNum; - isBlankString(e, val, filed); - } -} +//const validateLevels = (e: Event) => { +// const numLevels = getNumLevels(); +// // check if levels are unique and are in increasing order; +// // also if they are positive +// // also if level input field is accompanied by a "Add Level" button, +// // it shouldn't be used for validation +// for (let levelNum = 1; levelNum < numLevels; levelNum++) { +// const inputID = CONST.INPUT_ID_WITHOUT_LEVEL + levelNum; +// const inputElement = document.getElementById(inputID); +// const val = inputElement.value; +// const filed = CONST.LABEL_INNER_TEXT_WITHOUT_LEVEL + levelNum; +// isBlankString(val, filed, e); +// } +//}; const validateDescription = (e: Event) => { - let inputElement = document.getElementById("description"); - let val = inputElement.value; - let filed = "Description"; - isBlankString(e, val, filed); -} + const inputElement = document.getElementById('description'); + const val = inputElement.value; + const filed = 'Description'; + isBlankString(val, filed, e); +}; const submit = async (e: Event) => { + e.preventDefault(); + validateDescription(e); - validateLevels(e); - // get values - // check validate levels - // submit - // handle erros +// validateLevels(e); + + const formUrl = getFormUrl(FORM); + + const levels = LEVELS.getLevels(); + console.debug(`[form submition]: levels: ${levels}`); + + const payload = { + levels: levels, + }; + + console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`); + + const res = await fetch(formUrl, genJsonPayload(payload)); + if (res.ok) { + alert('success'); + window.location.assign(VIEWS.sitekey); + } else { + const err = await res.json(); + alert(`error: ${err.error}`); + } }; + +export default addSubmitEventListener; diff --git a/templates/panel/add-site-key/index.ts b/templates/panel/add-site-key/index.ts index b4dd3aeb..5e1efc05 100644 --- a/templates/panel/add-site-key/index.ts +++ b/templates/panel/add-site-key/index.ts @@ -15,10 +15,10 @@ * along with this program. If not, see . */ -import * as addLevelButton from './addLevelButton'; -import * as addLevelForm from './form'; +import addLevelButtonAddEventListener from './addLevelButton'; +import addSubmitEventListener from './form'; export const index = () => { - addLevelButton.addLevelButtonAddEventListener(); - addLevelForm.addSubmitEventListener(); + addLevelButtonAddEventListener(); + addSubmitEventListener(); }; diff --git a/templates/panel/add-site-key/levels.ts b/templates/panel/add-site-key/levels.ts deleted file mode 100644 index 4dfc2f2a..00000000 --- a/templates/panel/add-site-key/levels.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2021 Aravinth Manivannan - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -const VALIDATE_LEVELS = (function() { - const levels: Array = []; - - const checkAscendingOrder = (newLevel: number) => { - if (levels.length == 0) { - return true; - } - - let isValid = true; - levels.find(level => { - if (level > newLevel) { - alert( - `Level: ${newLevel} has to greater than previous levels ${level}`, - ); - isValid = false; - return true; - } - return false; - }); - - return isValid; - }; - return { - add: function(newLevel: number) { - console.log(`[levels.js]levels: ${levels} newLevel: ${newLevel}`); - if (levels.find(level => level == newLevel)) { - alert(`Level: ${newLevel} has to be unique`); - return false; - } - - let isValid = checkAscendingOrder(newLevel); - if (isValid) { - levels.push(newLevel); - return true; - } - - console.log( - `Ascending arder failure. Levels: ${levels}, levels length: ${levels.length}`, - ); - return false; - }, - }; })(); - -export default VALIDATE_LEVELS; diff --git a/templates/panel/add-site-key/levels/getLevelFields.ts b/templates/panel/add-site-key/levels/getLevelFields.ts new file mode 100644 index 00000000..21b24ab1 --- /dev/null +++ b/templates/panel/add-site-key/levels/getLevelFields.ts @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {Level} from './index'; +import CONST from '../const'; + +import isNumber from '../../../utils/isNumber'; + +/** Fetches level from DOM using the ID passesd and validates */ +const getLevelFields = (id: number) => { + console.log(`[getLevelFields]: id: ${id}`); + const visitorID = CONST.VISITOR_WITHOUT_LEVEL + id.toString(); + const difficultyID = CONST.DIFFICULTY_WITHOUT_LEVEL + id.toString(); + + const visitorElement = document.getElementById(visitorID); + const difficultyElement = ( + document.getElementById(difficultyID) + ); + + const visitor_threshold = parseInt(visitorElement.value); + const difficulty_factor = parseInt(difficultyElement.value); + + if (!isNumber(visitor_threshold) || Number.isNaN(visitor_threshold)) { + throw new Error('visitor can contain nubers only'); + } + + if (!isNumber(difficulty_factor) || Number.isNaN(difficulty_factor)) { + throw new Error('difficulty can contain nubers only'); + } + + const level: Level = { + difficulty_factor, + visitor_threshold, + }; + + console.debug( + `[getLevelFields.ts] visitor: ${visitor_threshold} difficulty: ${difficulty_factor}`, + ); + + return level; +}; + +export default getLevelFields; diff --git a/templates/panel/add-site-key/levels/getNumLevels.ts b/templates/panel/add-site-key/levels/getNumLevels.ts new file mode 100644 index 00000000..b42d2170 --- /dev/null +++ b/templates/panel/add-site-key/levels/getNumLevels.ts @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import CONST from '../const'; + + +/** returns number of level input fields currently in DOM */ +const getNumLevels = () => { + let numLevels = 0; + document.querySelectorAll(`.${CONST.LEVEL_CONTAINER_CLASS}`).forEach(_ => numLevels++); + console.debug(`[getNumLevels]: numLevels: ${numLevels}`); + return numLevels; +}; + +export default getNumLevels; diff --git a/templates/panel/add-site-key/levels/index.ts b/templates/panel/add-site-key/levels/index.ts new file mode 100644 index 00000000..c886efee --- /dev/null +++ b/templates/panel/add-site-key/levels/index.ts @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** Datatype represenging an mCaptcha level */ +export type Level = { + difficulty_factor: number; + visitor_threshold: number; +}; + +/** Datatype representing a collection of mCaptcha levels */ +class Levels { + levels: Array; + + constructor() { + this.levels = []; + } + + add = (newLevel: Level) => { + console.debug(`[levels/index.ts] levels lenght: ${this.levels.length}`); + if (newLevel.difficulty_factor <= 0) { + throw new Error('Difficulty must be greater than zero'); + } + + if (newLevel.visitor_threshold <= 0) { + throw new Error('Visitors must be graeter than zero'); + } + + if (this.levels.length == 0) { + this.levels.push(newLevel); + return true; + } + + let msg; + let count = 1; + + const validate = (level: Level, newLevel: Level) => { + if (level.visitor_threshold >= newLevel.visitor_threshold) { + msg = `Level: ${newLevel} visitor count has to greater than previous levels. See ${count}`; + return true; + } + + if (level.difficulty_factor >= newLevel.difficulty_factor) { + msg = `Level ${this.levels.length} difficulty has to greater than previous levels See ${count}`; + return true; + } + count++; + return false; + }; + + if (this.levels.find(level => validate(level, newLevel))) { + alert(msg); + throw new Error(msg); + } else { + this.levels.push(newLevel); + } + }; + + get = () => this.levels; +} + +/** Singleton that does manipulations on Levels object */ +export const LEVELS = (function() { + const levels = new Levels(); + + return { + /** get levels */ + getLevels: () => levels.get(), + + /** add new level */ + add: (newLevel: Level) => levels.add(newLevel), + + /** update levels */ + update: (updateLevel: Level, id: number) => { + const tmpLevel = new Levels(); + + id -= 1; + try { + for (let i = 0; i < levels.levels.length; i++) { + if (id == i) { + tmpLevel.add(updateLevel); + } else { + tmpLevel.add(levels.levels[i]); + } + } + return true; + } catch (e) { + return false; + } + }, + }; +})(); diff --git a/templates/panel/add-site-key/levels/updateLevel.ts b/templates/panel/add-site-key/levels/updateLevel.ts new file mode 100644 index 00000000..9424a1de --- /dev/null +++ b/templates/panel/add-site-key/levels/updateLevel.ts @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import CONST from '../const'; +import getLevelFields from './getLevelFields'; +import {LEVELS} from './index'; + +/** on-change event handler to update level */ +const updateLevel = (e: Event) => { + const target = e.target; + + const id = target.id; + + let level; + if (id.includes(CONST.VISITOR_WITHOUT_LEVEL)) { + level = id.slice(CONST.VISITOR_WITHOUT_LEVEL.length); + } else if (id.includes(CONST.DIFFICULTY_WITHOUT_LEVEL)) { + level = id.slice(CONST.DIFFICULTY_WITHOUT_LEVEL.length); + } else { + throw new Error( + 'update event was triggered by some element other than difficulty or visitor', + ); + } + + level = parseInt(level); + if (Number.isNaN(level)) { + console.error(`[updateLevel.ts] level # computed is not correct, got NaN`); + } + + try { + const updatedLevel = getLevelFields(level); + LEVELS.update(updatedLevel, level); + } catch (e) { + alert(e); + } +}; + +/** registers on-change event handlers to update levels */ +export const register = (id: number) => { + const visitorID = CONST.VISITOR_WITHOUT_LEVEL + id.toString(); + const difficultyID = CONST.DIFFICULTY_WITHOUT_LEVEL + id.toString(); + + const visitorElement = document.getElementById(visitorID); + const difficultyElement = ( + document.getElementById(difficultyID) + ); + + visitorElement.addEventListener('input', updateLevel, false); + difficultyElement.addEventListener('input', updateLevel, false); +}; diff --git a/templates/panel/add-site-key/levels/validateLevel.ts b/templates/panel/add-site-key/levels/validateLevel.ts new file mode 100644 index 00000000..c7c4b9b5 --- /dev/null +++ b/templates/panel/add-site-key/levels/validateLevel.ts @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {LEVELS} from './index'; +import getLevelFields from './getLevelFields'; + +/** + * Fetches level from DOM using the ID passesd and validates + * its contents + * */ +const validateLevel = (id: number) => { + const level = getLevelFields(id); + + if (level === null) { + return false; + } + + try { + LEVELS.add(level); + return true; + } catch (e) { + return false; + } +}; + +export default validateLevel; diff --git a/templates/panel/add-site-key/main.scss b/templates/panel/add-site-key/main.scss new file mode 100644 index 00000000..e8465820 --- /dev/null +++ b/templates/panel/add-site-key/main.scss @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@import '../../reset'; +@import '../../vars'; +@import '../../components/button'; +@import '../../components/forms'; + +.sitekey-form { + display: flex; + flex-direction: column; + width: 90%; + justify-content: center; + align-items: center; + box-sizing: content-box; + background-color: $white; + margin: auto; + padding-bottom: 30px; +} + +.form__title-flex-container { + display: flex; + width: 100%; + border-bottom: 0.1px solid $light-grey; +} + +.form__title { + padding-left: 10px; + font-size: 1rem; + padding: 0.75rem 1.25rem; + box-sizing: border-box; + text-align: left; + width: 100%; + border-bottom: 0.1px solid $light-grey; +} + +.sitekey-form__label { + @include form-label; +} + +.sitekey-form__input { + @include form-input; + width: 100%; +} + +// level styling +.sitekey__level-container { + width: $form-content-width; + box-sizing: border-box; + display: flex; +} + +.sitekey__level-title { + margin-bottom: 10px; + margin-top: 5px; +} + +.sitekey-form__level-label { + @include form-label; + font-size: 0.9rem; +} + +.sitekey-form__level-name { + @include form-label; + display: block; +} + +.sitekey-form__level-input { + @include form-input; + flex: 2; +} + +.sitekey-form__level-add-level-button { + @include violet-button; +} + +.sitekey-form__level-add-level-button:hover { + @include violet-button-hover; +} + +.sitekey-form__level-label--hidden { + @include form-label; + color: $white; + flex: 1; +} + +// level styling ends + +.sitekey-form__submit { + @include violet-button; + display: block; + margin-top: 50px; + width: $form-content-width; + width: 90%; +} + +.sitekey-form__submit:hover { + @include violet-button-hover; +} diff --git a/templates/panel/add-site-key/new-add-level.html b/templates/panel/add-site-key/new-add-level.html new file mode 100644 index 00000000..d8d1526f --- /dev/null +++ b/templates/panel/add-site-key/new-add-level.html @@ -0,0 +1,36 @@ +
+ + Level <.= level .> + + + + + +
diff --git a/templates/panel/header/sidebar/index.ts b/templates/panel/header/sidebar/index.ts new file mode 100644 index 00000000..ec88d740 --- /dev/null +++ b/templates/panel/header/sidebar/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import './main.scss'; diff --git a/templates/panel/header/sidebar/main.scss b/templates/panel/header/sidebar/main.scss new file mode 100644 index 00000000..f8fc3825 --- /dev/null +++ b/templates/panel/header/sidebar/main.scss @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@import '../../../reset'; +@import '../../../vars'; + +.secondary-menu { + position: fixed; + width: 14%; + left: 0; + bottom: 0; + top: 0; + right: 0; + height: 100%; + overflow: auto; + + background-color: $secondary-backdrop; + color: $light-text; +} + +.secondary-menu__heading { + margin: auto; + padding: 20px 5px; + display: flex; +} +.secondary-menu__heading:hover { + color: $green; + cursor: grab; +} + +.secondary-menu__logo { + width: 70px; + display: inline-block; +} + +.secondary-menu__brand-name { + display: inline-block; + margin: auto; + font-size: 1.5rem; +} + +.secondary-menu__item { + margin: auto; + padding: 20px 25px; + display: flex; +} + +.secondary-menu__icon { + filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(93deg) brightness(103%) + contrast(103%); + opacity: 0.8; + width: 1rem; + margin: auto; + margin-right: 10px; +} + +.secondary-menu__item-name { + display: inline-block; + margin: auto; + font-size: 1rem; +} + +.secondary-menu__item-link:hover { + color: $green; + cursor: grab; + filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg) + brightness(91%) contrast(92%); +} + +.secondary-menu__item-link { + color: inherit; + width: 100%; + height: 100%; +} diff --git a/templates/panel/help-banner/main.scss b/templates/panel/help-banner/main.scss new file mode 100644 index 00000000..c94cb5e2 --- /dev/null +++ b/templates/panel/help-banner/main.scss @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@import '../../reset'; +@import '../../vars'; + +.help-text { + border-radius: 4px; + box-shadow: $secondary-backdrop 0px 2px 6px 0px; + min-width: 70%; + max-width: 80%; + min-height: 70px; + display: flex; + margin-left: 15px; + margin-top: 40px; +} + +.help-text__serial-num { + display: inline-flex; + background-color: $violet; + color: $light-text; + width: 30px; + height: 30px; + border-radius: 50%; + align-items: center; + justify-content: center; +} + +.help-text__instructions { + display: inline-block; + list-style: none; + font-size: 19px; + font-weight: 500; + padding: 10px; + margin: auto; +} diff --git a/templates/panel/main.scss b/templates/panel/main.scss index 96d44dee..a33658a3 100644 --- a/templates/panel/main.scss +++ b/templates/panel/main.scss @@ -1,206 +1,31 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + @import '../reset'; @import '../vars'; - -.secondary-menu { - position: fixed; - width: 14%; - left: 0; - bottom: 0; - top: 0; - right: 0; - height: 100%; - overflow: auto; - - background-color: $secondary-backdrop; - color: $light-text; -} - -.secondary-menu__heading { - margin: auto; - padding: 20px 5px; - display: flex; -} -.secondary-menu__heading:hover { - color: $green; - cursor: grab; -} - -.secondary-menu__logo { - width: 70px; - display: inline-block; -} - -.secondary-menu__brand-name { - display: inline-block; - margin: auto; - font-size: 1.5rem; -} - -.secondary-menu__item { - margin: auto; - padding: 20px 25px; - display: flex; -} - -.secondary-menu__icon { - filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(93deg) brightness(103%) - contrast(103%); - opacity: 0.8; - width: 1rem; - margin: auto; - margin-right: 10px; -} - -.secondary-menu__item-name { - display: inline-block; - margin: auto; - font-size: 1rem; -} - -.secondary-menu__item-link:hover { - color: $green; - cursor: grab; - filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg) - brightness(91%) contrast(92%); -} - -.secondary-menu__item-link { - color: inherit; - width: 100%; - height: 100%; -} +@import '../components/button'; main { - margin-left: 14%; + margin-left: 14%; min-height: 100%; background-color: $backdrop; padding-bottom: 20px; } -.task-bar { - display: flex; - padding: 0px; - margin: 0px; - background-color: #fff; -} - -.task-bar__action { - display: inline-block; - padding: 10px 0px; - margin: auto; -} - -.task-bar__spacer { - min-width: 250px; - flex: 6; -} - -.task-bar__icon { - opacity: 0.8; - width: 1.5rem; - margin: auto 20px; -} - -.task-bar__icon { - color: $light-text; -} - -.task-bar__icon:hover { - cursor: grab; -} - -.main-menu { - display: flex; - flex-grow: 0; - padding-top: 20px; - padding-left: 10px; -} - -.main-menu__item { - list-style: none; - background-color: #c3c3c3; - flex: 2; - text-align: center; - vertical-align: middle; - margin: auto 20px; - padding: 10px 0; -} - -.main-menu__item--spacer { - list-style: none; - flex: 3; - text-align: center; -} - -.main-menu__item:hover { - background-color: grey; - cursor: grab; -} - -.main-menu__item:last-child { - padding: 0; - display: flex; - flex: 2; - border: none; - background-color: unset; -} - -.main-menu__item:last-child:hover { - cursor: unset; - background-color: unset; -} - -.main-menu__add-site { - display: inline-block; - background-color: $violet; - color: white; - font-weight: 500; - font-size: 16px; - padding: 10px 15px; - - border-radius: 5px; - border: 1px grey solid; - min-height: 45px; - margin: auto; -} - -.main-menu__add-site:hover { - background-color: $violet; - cursor: grab; - transform: translateY(-5px); -} - -.help-text { - border-radius: 4px; - box-shadow: $secondary-backdrop 0px 2px 6px 0px; - min-width: 70%; - max-width: 80%; - min-height: 70px; - display: flex; - margin-left: 15px; - margin-top: 40px; -} - -.help-text__serial-num { - display: inline-flex; - background-color: $violet; - color: $light-text; - width: 30px; - height: 30px; - border-radius: 50%; - align-items: center; - justify-content: center; -} - -.help-text__instructions { - display: inline-block; - list-style: none; - font-size: 19px; - font-weight: 500; - padding: 10px; - margin: auto; -} - .inner-container { display: flex; box-sizing: border-box; @@ -209,86 +34,3 @@ main { border-radius: 5px; display: flex; } - -.sitekey-form { - display: flex; - flex-direction: column; - width: 90%; - justify-content: center; - align-items: center; - box-sizing: content-box; - background-color: #fff; - margin: auto; - padding-bottom: 30px; -} - -.sitekey-form__title-flex-container { - display: flex; - width: 100%; - border-bottom: 0.1px solid $light-grey; -} - -.sitekey-form__title { - padding-left: 10px; - font-size: 1rem; - padding: 0.75rem 1.25rem; -} - -.sitekey-form__label { - display: block; - margin: 10px 0; - box-sizing: inherit; - justify-self: left; -} - -.sitekey-form__input { - position: relative; - margin-top: 5px; - box-sizing: border-box; - height: 40px; - width: 90%; -} - -.sitekey-form__input--add-level { - position: relative; - margin-top: 5px; - box-sizing: inherit; - flex: 3; - height: 40px; - margin-right: 20px; -} - -.sitekey-form__add-level-flex-container { - display: flex; - box-sizing: border-box; - width: 90%; - margin-top: 10px; -} - -.sitekey-form__add-level-button { - background-color: $violet; - color: white; - padding: 5px; - font-size: 16px; - - border-radius: 5px; - border: 1px $light-grey solid; - height: 40px; - min-width: 125px; - margin: auto; -} - -.sitekey-form__submit { - margin-top: 50px; - display: block; - background-color: $violet; - color: white; - padding: 5px; - font-size: 20px; - - border-radius: 5px; - border: 1px $light-grey solid; - min-height: 45px; - width: 125px; - width: 90%; -} diff --git a/templates/panel/site-keys/index.html b/templates/panel/site-keys/index.html new file mode 100644 index 00000000..67c3de62 --- /dev/null +++ b/templates/panel/site-keys/index.html @@ -0,0 +1,14 @@ +<. include!("../../components/headers.html"); .> <. include!("../header/index.html"); +.> + +
+ <. include!("../taskbar/index.html"); .> <. + include!("../help-banner/index.html"); .> + +
+ + +
+ +
+<. include!("../../components/footers.html"); .> diff --git a/templates/panel/taskbar/index.html b/templates/panel/taskbar/index.html index 5a1e536e..464af33c 100644 --- a/templates/panel/taskbar/index.html +++ b/templates/panel/taskbar/index.html @@ -1,29 +1,29 @@ -
    +
      -
    • -
    • - -
    • -
    • - + " alt="Profile" />
    • -
    • - + " alt="Notifications" />
    • -
    • +
    • - " alt="Profile" />
    • diff --git a/templates/panel/taskbar/main.scss b/templates/panel/taskbar/main.scss new file mode 100644 index 00000000..f799fd36 --- /dev/null +++ b/templates/panel/taskbar/main.scss @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@import '../../reset'; +@import '../../vars'; +@import '../../components/button'; + +.taskbar { + display: flex; + padding: 0px; + margin: 0px; + background-color: $white; +} + +.taskbar__action { + display: inline-block; + padding: 10px 0px; + margin: auto; +} + +.taskbar__spacer { + min-width: 250px; + flex: 6; +} + +.taskbar__icon { + opacity: 0.8; + width: 1.5rem; + margin: auto 20px; +} + +.taskbar__icon { + color: $light-text; +} + +.taskbar__icon:hover { + cursor: grab; +} + +.taskbar__add-site { + display: inline-block; + @include violet-button; +} + +.taskbar__add-site:hover { + @include violet-button-hover; +} diff --git a/templates/utils/getFormUrl.ts b/templates/utils/getFormUrl.ts new file mode 100644 index 00000000..b0b57a0c --- /dev/null +++ b/templates/utils/getFormUrl.ts @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * querySelector is the the selector that will be + * used to fetch elements. + * So when using class-names, pass in ".whatever-classname" + * and for ID, "#id". + * */ +const getFormUrl = (querySelector: null|string|HTMLFormElement) => { + let form; + if (querySelector === null) { + form = document.querySelector("form"); + } + if (querySelector === "string" || querySelector instanceof String) { + form = document.querySelector(querySelector.toString()); + } + if (querySelector instanceof HTMLFormElement) { + form = querySelector; + } + + if ( form !== undefined) { + return form.action + } else { + throw new Error("Can't find form"); + } +}; + +export default getFormUrl; diff --git a/templates/utils/isBlankString.ts b/templates/utils/isBlankString.ts index df8063b6..2946efab 100644 --- a/templates/utils/isBlankString.ts +++ b/templates/utils/isBlankString.ts @@ -15,10 +15,10 @@ * along with this program. If not, see . */ -const isBlankString = (event: Event|null, value: string|number, field: string) => { +const isBlankString = (value: string|number, field: string, event?: Event) => { value = value.toString(); if (!value.replace(/\s/g, '').length) { - if (event) { + if (event !== undefined) { event.preventDefault(); } alert(`${field} can't be empty`); diff --git a/templates/utils/isNumber.ts b/templates/utils/isNumber.ts index 8018cc64..0088a9f9 100644 --- a/templates/utils/isNumber.ts +++ b/templates/utils/isNumber.ts @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -const isNumber = (value: string) => { +const isNumber = (value: string|number) => { value = value.toString(); return /^\d+$/.test(value); };