list sitekey: copy sitekey

This commit is contained in:
realaravinth 2021-07-15 18:07:12 +05:30
parent 97db774e70
commit 863d22f62c
No known key found for this signature in database
GPG key ID: AD9F0F08E855ED88
25 changed files with 369 additions and 224 deletions

11
Cargo.lock generated
View file

@ -1588,6 +1588,7 @@ dependencies = [
"log",
"mime",
"mime_guess",
"openssl",
"pow_sha256",
"pretty_env_logger",
"rand 0.8.4",
@ -1873,6 +1874,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]]
name = "openssl-src"
version = "111.15.0+1.1.1k"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a5f6ae2ac04393b217ea9f700cd04fa9bf3d93fae2872069f3d15d908af70a"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.65"
@ -1882,6 +1892,7 @@ dependencies = [
"autocfg",
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]

View file

@ -85,6 +85,8 @@ lettre = { version = "0.10.0-rc.3", features = [
"smtp-transport"
]}
openssl = { version = "0.10.29", features = ["vendored"] }
[build-dependencies]
serde_yaml = "0.8.17"

View file

@ -29,8 +29,8 @@ pub mod routes {
impl Sitekey {
pub const fn new() -> Self {
Sitekey {
list: "/sitekey/list",
add: "/sitekey/add",
list: "/sitekeys",
add: "/sitekeys/add",
view: "/sitekey/{key}",
}
}

1
static/cache/img/svg/clipboard.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-clipboard"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>

After

Width:  |  Height:  |  Size: 371 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>

After

Width:  |  Height:  |  Size: 388 B

View file

@ -17,7 +17,7 @@
@mixin violet-button-hover {
background-color: $light-violet;
cursor: grab;
cursor: pointer;
transform: translateY(-5px);
}

View file

@ -45,7 +45,7 @@ $message-bg: #d63f3f;
}
.err__close:hover {
cursor: grab;
cursor: pointer;
width: 20px;
height: 20px;
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
@import '../../vars';
@mixin table {
background-color: $white;
width: 90%;
padding: 0 10px;
}
@mixin table__title-text {
font-size: 1rem;
padding: 0.75rem 0.5rem;
box-sizing: border-box;
text-align: left;
width: 100%;
border-bottom: 0.1px solid $light-grey;
}

View file

@ -1,6 +1,6 @@
.button:hover {
background-color: #993299;
cursor: grab;
cursor: pointer;
transform: translateY(-5px);
}

View file

@ -21,6 +21,7 @@ import * as login from './auth/login/ts/';
import * as register from './auth/register/ts/';
import * as panel from './panel/ts/index';
import * as addSiteKey from './panel/sitekey/add/ts';
import * as listSitekeys from './panel/sitekey/list/ts';
import {MODE} from './logger';
import log from './logger';
@ -48,6 +49,7 @@ const router = new Router();
router.register(VIEWS.panelHome, panel.index);
router.register(VIEWS.registerUser, register.index);
router.register(VIEWS.loginUser, login.index);
router.register(VIEWS.listSitekey, listSitekeys.index);
router.register(VIEWS.addSiteKey, addSiteKey.index);
try {

View file

@ -23,3 +23,4 @@ import './panel/header/taskbar/mobile.scss';
import './panel/navbar/mobile.scss';
import './panel/help-banner/mobile.scss';
import './panel/sitekey/add/css/mobile.scss';
import './panel/sitekey/list/css/mobile.scss';

View file

@ -4,29 +4,31 @@
-->
<li class="taskbar__spacer"></li>
<li class="taskbar__action">
<a class="taskbar__link" href="<.= crate::PAGES.panel.sitekey.add .>">
<button class="taskbar__add-site">
+ New Site
</button>
<a class="taskbar__link" href="<.= crate::PAGES.panel.sitekey.add .>">
<button class="taskbar__add-site">
+ New Site
</button>
</a>
</li>
<li class="taskbar__action">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/moon.svg").unwrap() .>" alt="Profile" />
crate::FILES.get("./static/cache/img/svg/moon.svg").unwrap() .>"
alt="Profile" />
</li>
<li class="taskbar__action">
<a href="<.= crate::PAGES.panel.notifications .>">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/bell.svg").unwrap() .>"
alt="Notifications" />
</a>
<a href="<.= crate::PAGES.panel.notifications .>">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/bell.svg").unwrap() .>"
alt="Notifications" />
</a>
</li>
<li class="taskbar__action">
<a href="<.= crate::V1_API_ROUTES.auth.logout .>">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/log-out.svg").unwrap() .>" alt="Profile"
/></a>
<a href="<.= crate::V1_API_ROUTES.auth.logout .>">
<img class="taskbar__icon" src="<.=
crate::FILES.get("./static/cache/img/svg/log-out.svg").unwrap() .>"
alt="Profile" /></a
>
</li>
</ul>

View file

@ -48,10 +48,9 @@
}
.taskbar__icon:hover {
cursor: grab;
cursor: pointer;
background-color: $light-grey;
color: $green;
background-color: $light-grey;
filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
brightness(91%) contrast(92%);
}

View file

@ -1,40 +1,63 @@
<. include!("../components/headers/index.html"); .>
<. include!("./navbar/index.html"); .>
<. include!("../components/headers/index.html"); .> <.
include!("./navbar/index.html"); .>
<div class="tmp-layout">
<. include!("./header/index.html"); .>
<main class="panel-main">
<. include!("./help-banner/index.html"); .>
<div class="inner-container">
<. include!("./header/index.html"); .>
<main class="panel-main">
<. include!("./help-banner/index.html"); .>
<div class="inner-container">
<. if sitekeys.is_empty() { .>
<ul class="sitekey-list__box">
<p>
It looks like you don't have any sitekeys. Click
<a href="<.= crate::PAGES.panel.sitekey.add .>">here</a> to add new sitekey.
<ul class="sitekey-list__box">
<p>
It looks like you don't have any sitekeys. Click
<a href="<.= crate::PAGES.panel.sitekey.add .>">here</a> to add new
sitekey.
</p>
</ul>
</ul>
<.} else {.>
<ul class="sitekey-list__box">
<h1 class="sitekey-list__title">Your Sitekeys</h1>
<. for sitekey in sitekeys.iter() { .>
<a href="/sitekey/<.= sitekey.key .>/" class="sitekey-list__item-container">
<li class="sitekey-list__item">
<span class="sitekey-list__name">
<.= sitekey.name .>
</span>
<span class="sitekey-list__key">
<.= sitekey.key .>
</span>
</li>
</a>
<. } .>
</ul>
<table class="sitekey__table">
<thead class="sitekey__table-heading">
<tr>
<th colspan="4" class="sitekey__table-title-text">
Your Sitekeys
</th>
</tr>
</thead>
<tbody class="sitekey__body">
<. for sitekey in sitekeys.iter() { .>
<tr class="sitekey__item">
<td class="sitekey-list__name">
<a
href="/sitekey/<.= sitekey.key .>/"
class="sitekey-list__sitekey-link"
>
<.= sitekey.name .>
</a>
</td>
<td class="sitekey-list__key">
<div class="sitekey__key-container">
<img class="sitekey__copy-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/clipboard.svg") .unwrap() .>"
alt="copy sitekey" data-sitekey="<.= sitekey.key .>" /> <img
class="sitekey__copy-done-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/check.svg") .unwrap() .>"
alt="sitekey copied" data-sitekey="<.= sitekey.key .>" />
<a
class="sitekey__widget-link"
href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"
>
<.= &sitekey.key[0..5] .>
</a>
</div>
</td>
</tr>
<. } .>
</tbody>
</table>
<.}.>
</div>
<. include!("../components/footers.html"); .>
</div>
<. include!("../components/footers.html"); .>
</main>
</div>

View file

@ -57,7 +57,7 @@
}
.secondary-menu__heading:hover {
color: $green;
cursor: grab;
cursor: pointer;
}
.secondary-menu__logo {
@ -99,7 +99,7 @@
.secondary-menu__item-link:hover {
color: $green;
cursor: grab;
cursor: pointer;
filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
brightness(91%) contrast(92%);
}

View file

@ -34,7 +34,7 @@
}
.nav__hamburger-menu:hover {
cursor: grab;
cursor: pointer;
}
.nav__hamburger-menu:hover > span {
@ -75,5 +75,5 @@
.secondary-menu__brand-name:hover {
color: $light-text;
cursor: grab;
cursor: pointer;
}

View file

@ -16,20 +16,15 @@
*/
@import '../../vars';
@import '../../components//table/main';
.notification__table {
background-color: $white;
width: 90%;
padding: 0 10px;
@include table;
margin: auto;
}
.notification__title-text {
font-size: 1rem;
padding: 0.75rem 0.5rem;
box-sizing: border-box;
text-align: left;
width: 100%;
border-bottom: 0.1px solid $light-grey;
@include table__title-text;
}
.notification__mark-read-btn {
@ -45,7 +40,7 @@
}
.notification-data__container {
font-size: 0.7rem;
font-size: 0.7rem;
}
.notification__mark-read {

View file

@ -56,7 +56,7 @@ const submit = async (e: Event) => {
const res = await fetch(formUrl, genJsonPayload(payload));
if (res.ok) {
const data = await res.json();
window.location.assign(VIEWS.listSitekey(data.key));
window.location.assign(VIEWS.viewSitekey(data.key));
} else {
const err = await res.json();
createError(err.error);

View file

@ -18,38 +18,87 @@
@import '../../../../reset';
@import '../../../../vars';
@import '../../../../components/box';
@import '../../../../components/table/main';
.sitekey-list__box {
@include box;
padding-bottom: 0px;
.sitekey__table {
@include table;
margin: auto;
}
.sitekey-list__title {
@include box-title;
}
.sitekey-list__item-container {
display: block;
width: 100%;
box-sizing: border-box;
border-bottom: 0.1px solid $light-grey;
padding: 20px;
color: $black-text;
}
.sitekey-list__item {
display: flex;
width: 100%;
}
.sitekey-list__item-container:hover {
background-color: $light-grey;
.sitekey__table-title-text {
@include table__title-text;
}
.sitekey-list__name {
flex: 3;
min-width: 450px;
}
.sitekey-list__key {
flex: 1;
width: 10px;
}
@mixin copy-icon-base {
margin: auto;
padding: 5px;
}
.sitekey__copy-icon {
@include copy-icon-base;
}
.sitekey__copy-icon:hover {
cursor: pointer;
filter: invert(17%) sepia(93%) saturate(5039%) hue-rotate(204deg)
brightness(100%) contrast(98%);
}
.sitekey__copy-done-icon {
@include copy-icon-base;
display: none;
filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
brightness(91%) contrast(92%);
}
.sitekey__key-container {
border-radius: 10px;
background: $backdrop;
margin: 2px;
padding: 5px;
display: flex;
max-width: 150px;
border: 0.1px solid rgba(0, 0, 0, 0.125);
}
.sitekey__widget-link {
border-left: 0.1px solid $light-grey;
margin: 5;
margin: auto;
padding-left: 20px;
height: 100%;
padding-right: 15px;
}
.sitekey-list__sitekey-link {
display: inline-block;
width: 100%;
text-decoration: none;
color: $blue-link;
padding: 20px;
}
.sitekey-list__sitekey-link:visited {
color: $blue-link;
}
.sitekey-list__sitekey-link:hover {
background-color: $light-grey;
cursor: pointer;
}
.sitekey__widget-link {
color: $blue-link;
}
.sitekey__widget-link:visited {
color: $blue-link;
}

View file

@ -0,0 +1,20 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
.sitekey-list__name {
min-width: 120px;
}

View file

@ -1,23 +1,53 @@
<. include!("../../../components/headers/index.html"); .>
<. include!("../../navbar/index.html"); .>
<. include!("../../../components/headers/index.html"); .> <.
include!("../../navbar/index.html"); .>
<div class="tmp-layout">
<. include!("../../header/index.html"); .>
<main class="panel-main">
<.include!("../../help-banner/index.html"); .>
<!-- Main content container -->
<div class="inner-container">
<!-- Main menu/ important actions roaster -->
<ul class="sitekey-list__box">
<h1 class="sitekey-list__title">Your Sitekeys</h1>
<. for sitekey in sitekeys.iter() { .>
<a href="/sitekey/<.= sitekey.key .>/" class="sitekey-list__item-container">
<li class="sitekey-list__item">
<span class="sitekey-list__name"><.= sitekey.name .></span>
<span class="sitekey-list__key"><.= sitekey.key .></span>
</li>
</a>
<. } .>
</ul>
</div>
<!-- end of container -->
<. include!("../../../components/footers.html"); .>
<. include!("../../header/index.html"); .>
<main class="panel-main">
<.include!("../../help-banner/index.html"); .>
<!-- Main content container -->
<div class="inner-container">
<!-- Main menu/ important actions roaster -->
<table class="sitekey__table">
<thead class="sitekey__table-heading">
<tr>
<th colspan="4" class="sitekey__table-title-text">
Your Sitekeys
</th>
</tr>
</thead>
<tbody class="sitekey__body">
<. for sitekey in sitekeys.iter() { .>
<tr class="sitekey__item">
<td class="sitekey-list__name">
<a
href="/sitekey/<.= sitekey.key .>/"
class="sitekey-list__sitekey-link"
>
<.= sitekey.name .>
</a>
</td>
<td class="sitekey-list__key">
<div class="sitekey__key-container">
<img class="sitekey__copy-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/clipboard.svg") .unwrap() .>"
alt="copy sitekey" data-sitekey="<.= sitekey.key .>" /> <img
class="sitekey__copy-done-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/check.svg") .unwrap() .>"
alt="sitekey copied" data-sitekey="<.= sitekey.key .>" />
<a
class="sitekey__widget-link"
href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"
>
<.= &sitekey.key[0..5] .>
</a>
</div>
</td>
</tr>
<. } .>
</tbody>
</table>
</div>
<!-- end of container -->
<. include!("../../../components/footers.html"); .>
</main>
</div>

View file

@ -15,4 +15,39 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const index = () => {};
export const index = () => {
registerCopySitekey();
};
const SITEKEY_COPY_ICON = `sitekey__copy-icon`;
const SITEKEY_COPY_DONE_ICON = `sitekey__copy-done-icon`;
const registerCopySitekey = () => {
const icons = document.querySelectorAll(`.${SITEKEY_COPY_ICON}`);
icons.forEach(icon => {
icon.addEventListener('click', e => copySitekey(e));
});
};
/*
* Copy sitekey to clipboard
*/
const copySitekey = async (e: Event) => {
const image = <HTMLElement>e.target;
if (!image.classList.contains(SITEKEY_COPY_ICON)) {
throw new Error(
'This method should only be called when sitekey copy button/icon is clicked',
);
}
const copyDoneIcon = <HTMLElement>(
image.parentElement.querySelector(`.${SITEKEY_COPY_DONE_ICON}`)
);
const sitekey = image.dataset.sitekey;
await navigator.clipboard.writeText(sitekey);
image.style.display = 'none';
copyDoneIcon.style.display = 'block';
setTimeout(() => {
copyDoneIcon.style.display = 'none';
image.style.display = 'block';
}, 1200);
};

View file

@ -8,47 +8,51 @@
<!-- Main content container -->
<div class="inner-container">
<!-- Main menu/ important actions roaster -->
<form class="sitekey-form" action="<.= crate::V1_API_ROUTES.levels.add .>" method="post">
<h1 class="form__title">Sitekey: <.= name .>
<a href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= key.>"
>Click here to see CAPTCHA widget in action</a>
</h1>
<label class="sitekey-form__label" for="description">
Description
<input
readonly="readonly"
class="sitekey-form__input"
type="text"
name="description"
id="description"
required
<. if !name.trim().is_empty() { .>
value="<.= name .>"
<. } .>
/>
</label>
<label class="sitekey-form__label" for="duration">
Cooldown Duratoin(in seconds)
<input
readonly="readonly"
class="sitekey-form__input"
type="number"
name="duration"
id="duration"
min=0
required
value="<.= duration .>"
/>
</label>
<. for (count, level) in levels.iter().enumerate() { .>
<. include!("./existing-level.html"); .>
<. } .>
</form>
<form class="sitekey-form" action="<.= crate::V1_API_ROUTES.levels.add .>" method="post">
<h1 class="form__title">Sitekey: <.= name .>
<a
target="_blank"
href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= key.>"
>View widget
<img class="sitekey-form__widget-link"
src="<.= crate::FILES.get("./static/cache/img/svg/external-link.svg").unwrap() .>"
alt="View widget deployment"
/>
</a>
</h1>
<label class="sitekey-form__label" for="description">
Description
<input
readonly="readonly"
class="sitekey-form__input"
type="text"
name="description"
id="description"
required
<. if !name.trim().is_empty() { .>
value="<.= name .>"
<. } .>
/>
</label>
<label class="sitekey-form__label" for="duration">
Cooldown Duratoin(in seconds)
<input
readonly="readonly"
class="sitekey-form__input"
type="number"
name="duration"
id="duration"
min=0
required
value="<.= duration .>"
/>
</label>
<. for (count, level) in levels.iter().enumerate() { .>
<. include!("./existing-level.html"); .>
<. } .>
</form>
</div>
<!-- end of container -->
<. include!("../../../components/footers.html"); .>

View file

@ -15,72 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as listSitekeys from '../sitekey/list/ts/';
export const index = () => {
// const html = document.documentElement;
// const body = document.body;
// const menuLinks = document.querySelectorAll('.admin-menu a');
// const collapseBtn = document.querySelector('.admin-menu .collapse-btn');
// const toggleMobileMenu = document.querySelector('.toggle-mob-menu');
// const switchInput = document.querySelector('.switch input');
// const switchLabel = document.querySelector('.switch label');
// const switchLabelText = switchLabel.querySelector('span:last-child');
// const collapsedClass = 'collapsed';
// const lightModeClass = 'light-mode';
//
// /*TOGGLE HEADER STATE*/
// collapseBtn.addEventListener('click', function() {
// body.classList.toggle(collapsedClass);
// this.getAttribute('aria-expanded') == 'true'
// ? this.setAttribute('aria-expanded', 'false')
// : this.setAttribute('aria-expanded', 'true');
// this.getAttribute('aria-label') == 'collapse menu'
// ? this.setAttribute('aria-label', 'expand menu')
// : this.setAttribute('aria-label', 'collapse menu');
// });
//
// /*TOGGLE MOBILE MENU*/
// toggleMobileMenu.addEventListener('click', function() {
// body.classList.toggle('mob-menu-opened');
// this.getAttribute('aria-expanded') == 'true'
// ? this.setAttribute('aria-expanded', 'false')
// : this.setAttribute('aria-expanded', 'true');
// this.getAttribute('aria-label') == 'open menu'
// ? this.setAttribute('aria-label', 'close menu')
// : this.setAttribute('aria-label', 'open menu');
// });
//
// /*SHOW TOOLTIP ON MENU LINK HOVER*/
// for (const link of menuLinks) {
// link.addEventListener('mouseenter', function() {
// if (
// body.classList.contains(collapsedClass) &&
// window.matchMedia('(min-width: 768px)').matches
// ) {
// const tooltip = this.querySelector('span').textContent;
// this.setAttribute('title', tooltip);
// } else {
// this.removeAttribute('title');
// }
// });
// }
//
// /*TOGGLE LIGHT/DARK MODE*/
// if (localStorage.getItem('dark-mode') === 'false') {
// html.classList.add(lightModeClass);
// switchInput.checked = false;
// switchLabelText.textContent = 'Light';
// }
//
// switchInput.addEventListener('input', function() {
// html.classList.toggle(lightModeClass);
// if (html.classList.contains(lightModeClass)) {
// switchLabelText.textContent = 'Light';
// localStorage.setItem('dark-mode', 'false');
// } else {
// switchLabelText.textContent = 'Dark';
// localStorage.setItem('dark-mode', 'true');
// }
// });
//
// let a;
listSitekeys.index();
};

View file

@ -21,8 +21,9 @@ const ROUTES = {
signoutUser: '/api/v1/signout',
panelHome: '/',
docsHome: '/docs/',
listSitekey: (key: string) => `/sitekey/${key}/`,
addSiteKey: '/sitekey/add',
listSitekey: '/sitekeys/',
viewSitekey: (key: string) => `/sitekey/${key}/`,
addSiteKey: '/sitekeys/add',
};
export default ROUTES;