mirror of
https://github.com/Casvt/MIND.git
synced 2026-02-19 11:54:46 -05:00
Refactored and refined login and admin UI
This commit is contained in:
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -1,7 +1,15 @@
|
||||
{
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 4,
|
||||
|
||||
"editor.linkedEditing": true,
|
||||
|
||||
"[html]": {
|
||||
"editor.rulers": [120],
|
||||
},
|
||||
"html.format.wrapLineLength": 120,
|
||||
"html.format.wrapAttributes": "aligned-multiple",
|
||||
"html.format.templating": true,
|
||||
|
||||
"[python]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "ms-python.autopep8",
|
||||
|
||||
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -2,7 +2,7 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Format All",
|
||||
"label": "Python: Format All",
|
||||
"type": "shell",
|
||||
"command": "python3 -m isort .; python3 -m autopep8 --in-place -r .",
|
||||
"windows": {
|
||||
|
||||
@@ -1,160 +1,108 @@
|
||||
main {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
main a {
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
--spacing: .5rem;
|
||||
|
||||
position: absolute;
|
||||
margin: var(--spacing);
|
||||
inset: 0 0 auto 0;
|
||||
height: var(--nav-width);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing) * 3);
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
|
||||
padding: var(--spacing);
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-gray);
|
||||
padding: 1rem .5rem;
|
||||
|
||||
& > button {
|
||||
width: 10rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
|
||||
padding: .5rem;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-gray);
|
||||
color: var(--color-light);
|
||||
|
||||
transition:
|
||||
background-color 150ms ease-in-out,
|
||||
box-shadow 150ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-light-gray);
|
||||
box-shadow: var(--default-shadow);
|
||||
}
|
||||
|
||||
&.submit-error {
|
||||
background-color: var(--color-error);
|
||||
}
|
||||
|
||||
& > svg {
|
||||
height: 1.8rem;
|
||||
width: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons > button {
|
||||
height: 100%;
|
||||
#grid-container {
|
||||
--grid-spacing: 1.25rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: min(100%, 80rem + 3 * var(--grid-spacing));
|
||||
margin-inline: auto;
|
||||
|
||||
display: grid;
|
||||
grid-template: auto auto auto auto / 1fr 1fr;
|
||||
gap: var(--grid-spacing);
|
||||
|
||||
padding: var(--grid-spacing);
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
#grid-container > section {
|
||||
max-height: 40rem;
|
||||
min-width: 0;
|
||||
|
||||
padding: .5rem;
|
||||
border-radius: 4px;
|
||||
border: 3px solid var(--color-gray);
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
|
||||
transition: background-color .1s ease-in-out;
|
||||
}
|
||||
|
||||
.action-buttons > button:hover {
|
||||
background-color: var(--color-gray);
|
||||
}
|
||||
|
||||
.action-buttons > button > svg {
|
||||
height: 1.8rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
height: calc(100vh - var(--header-height));
|
||||
overflow-y: auto;
|
||||
|
||||
padding: .5rem;
|
||||
padding-top: var(--nav-width);
|
||||
}
|
||||
|
||||
#settings-form,
|
||||
#hosting-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
box-shadow: 0px 0px 8px 3px rgba(0 0 0 / 0.8);
|
||||
}
|
||||
|
||||
h2 {
|
||||
width: 100%;
|
||||
|
||||
padding: .4rem .75rem;
|
||||
border-bottom: 1px solid var(--color-gray);
|
||||
padding: 1rem 1rem .25rem 1rem;
|
||||
|
||||
font-size: clamp(1rem, 10vw, 2rem);
|
||||
}
|
||||
|
||||
.settings-table-container,
|
||||
.user-table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
|
||||
.collaps-table {
|
||||
--min-width: 18rem;
|
||||
--max-width: 40rem;
|
||||
--header-width: 30%;
|
||||
--min-data-width: 15rem;
|
||||
}
|
||||
|
||||
#power-section > .table-container {
|
||||
padding: 1rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.settings-table {
|
||||
--max-width: 55rem;
|
||||
width: 100%;
|
||||
max-width: var(--max-width);
|
||||
min-width: 20rem;
|
||||
|
||||
border-spacing: 0px;
|
||||
border: none;
|
||||
tr:has(:where(input, select).changed) label {
|
||||
color: var(--color-changed);
|
||||
}
|
||||
|
||||
.settings-table td {
|
||||
--middle-spacing: .75rem;
|
||||
padding-bottom: 1rem;
|
||||
vertical-align: top;
|
||||
:where(input, select).changed,
|
||||
div.input-style:has(input.changed) {
|
||||
border-color: var(--color-changed);
|
||||
}
|
||||
|
||||
.settings-table td:first-child {
|
||||
width: 50%;
|
||||
padding-right: var(--middle-spacing);
|
||||
padding-top: .55rem;
|
||||
text-align: right;
|
||||
#backup-settings-section td:has(div.checked-input-container) {
|
||||
height: 6.6rem;
|
||||
}
|
||||
|
||||
.settings-table td:nth-child(2) {
|
||||
min-width: calc(var(--max-width) * 0.5);
|
||||
padding-left: var(--middle-spacing);
|
||||
}
|
||||
.add-item-container {
|
||||
margin: 1rem;
|
||||
|
||||
.settings-table td p {
|
||||
color: var(--color-light-gray);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.settings-table td > p {
|
||||
margin-top: .25rem;
|
||||
}
|
||||
|
||||
.number-input {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
border: 2px solid var(--color-gray);
|
||||
border-radius: 4px;
|
||||
|
||||
box-shadow: var(--default-shadow);
|
||||
}
|
||||
|
||||
.number-input > input {
|
||||
width: auto;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.number-input > * {
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
.number-input > p {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.settings-table select {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.add-user-container,
|
||||
.database-container {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -162,140 +110,103 @@ h2 {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#download-logs-button,
|
||||
#save-hosting-button,
|
||||
#add-user-button,
|
||||
#upload-db-button,
|
||||
#restart-button,
|
||||
#shutdown-button {
|
||||
width: min(15rem, 100%);
|
||||
.entries-table {
|
||||
margin-inline: auto;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
padding: .5rem 1rem;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-gray);
|
||||
|
||||
box-shadow: var(--default-shadow);
|
||||
width: clamp(22rem, 100%, 40rem);
|
||||
}
|
||||
|
||||
.database-container:has(#download-logs-button) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#save-hosting-button {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#add-user-button {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
#add-user-button > svg {
|
||||
aspect-ratio: 1/1;
|
||||
height: 1rem;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
#user-table,
|
||||
#backup-table {
|
||||
min-width: 25rem;
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
:where(#user-table, #backup-table) :where(th, td) {
|
||||
height: 2.65rem;
|
||||
padding: .25rem .5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:where(#user-table, #backup-table) tr td {
|
||||
border-top: 1px solid var(--color-gray);
|
||||
}
|
||||
|
||||
:where(#user-table, #backup-table) :where(th, td):first-child {
|
||||
width: 10rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
:where(#user-table, #backup-table) :where(th, td):nth-child(2) {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
:where(#user-table, #backup-table) :where(th, td):last-child {
|
||||
width: 5.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
:where(#user-table, #backup-table) button {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
:where(#user-table, #backup-table) svg {
|
||||
aspect-ratio: 1/1;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#user-table input {
|
||||
#user-table td:first-child {
|
||||
width: 100%;
|
||||
padding: .25rem;
|
||||
}
|
||||
|
||||
#upload-database-form {
|
||||
#user-table td:last-child {
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
#backup-table {
|
||||
width: clamp(30rem, 100%, 40rem);
|
||||
}
|
||||
|
||||
#backup-table td:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#backup-table td:nth-child(2) {
|
||||
text-wrap-mode: nowrap;
|
||||
}
|
||||
|
||||
#add-user-form,
|
||||
#edit-user-form {
|
||||
width: 16rem;
|
||||
height: 8rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
margin-block: 2rem 1rem;
|
||||
}
|
||||
|
||||
#hosting-form > p,
|
||||
#upload-database-form > p {
|
||||
max-width: 50rem;
|
||||
margin-inline: auto;
|
||||
#edit-user-form:has(div.hidden) {
|
||||
height: unset;
|
||||
}
|
||||
|
||||
dialog span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dialog:where(#upload-db-dialog, #import-db-dialog) {
|
||||
--max-height: 25rem;
|
||||
}
|
||||
|
||||
:where(#upload-db-dialog, #import-db-dialog) .dialog-content > p {
|
||||
padding-inline: 1rem;
|
||||
text-align: center;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#backup-table :where(th, td):first-child {
|
||||
width: 20rem;
|
||||
#upload-db-form tr:first-child td:last-child {
|
||||
height: 6.1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
#reset-settings-dialog .dialog-content {
|
||||
height: calc(100% - 2 * 3.2rem);
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#reset-settings-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#reset-table {
|
||||
max-width: 30rem;
|
||||
}
|
||||
|
||||
@media (max-width: 24rem) {
|
||||
.action-buttons button {
|
||||
width: 100%;
|
||||
|
||||
&:last-of-type {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 29rem) {
|
||||
.entries-table {
|
||||
--inline-padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 52.25rem) {
|
||||
#grid-container {
|
||||
grid-template: repeat(8, auto) / 1fr;
|
||||
padding-bottom: var(--grid-spacing);
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.settings-table-container,
|
||||
.user-table-container {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.settings-table tbody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings-table tr {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.settings-table td {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-table td:first-child {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings-table td:nth-child(2) {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,28 +2,41 @@
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
|
||||
'Helvetica Neue', sans-serif;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-light: #ffffff;
|
||||
--color-light-gray: #6b6b6b;
|
||||
--color-gray: #3c3c3c;
|
||||
--color-dark: #1b1b1b;
|
||||
--color-mid-gray: #5c5c5c;
|
||||
--color-gray: #232323;
|
||||
--color-dark: #111111;
|
||||
|
||||
--color-changed: #b87000;
|
||||
--color-error: #db5461;
|
||||
--color-dim-error: #bc4551;
|
||||
--color-success: #54db68;
|
||||
|
||||
|
||||
--header-height: 4.5rem;
|
||||
--nav-width: 4rem;
|
||||
--scrollbar-width: 12px;
|
||||
|
||||
--rem-clamp: clamp(.5rem, 2vw, 1rem);
|
||||
--default-shadow: 0 1px 2px 0 rgb(0 0 0 / 60%), 0 2px 6px 2px rgb(0 0 0 / 30%);
|
||||
--default-shadow:
|
||||
0 1px 2px 0 rgb(0 0 0 / 60%),
|
||||
0 2px 6px 2px rgb(0 0 0 / 30%);
|
||||
}
|
||||
|
||||
/* */
|
||||
/* Default properties */
|
||||
/* */
|
||||
dialog {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -31,9 +44,6 @@ img {
|
||||
button,
|
||||
label {
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
button:hover,
|
||||
@@ -41,22 +51,6 @@ label:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]),
|
||||
select,
|
||||
textarea,
|
||||
.as-button {
|
||||
border: 2px solid var(--color-gray);
|
||||
border-radius: 4px;
|
||||
padding: .6rem;
|
||||
outline: 0;
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
|
||||
box-shadow: var(--default-shadow);
|
||||
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: var(--color-gray);
|
||||
@@ -66,14 +60,21 @@ input[type="datetime-local"] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
svg path,
|
||||
svg rect {
|
||||
svg :where(path, rect) {
|
||||
fill: var(--color-light);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: var(--scrollbar-width);
|
||||
height: var(--scrollbar-width);
|
||||
background-color: var(--color-dark);
|
||||
}
|
||||
|
||||
@@ -120,11 +121,255 @@ svg rect {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
button.spinning svg,
|
||||
svg.spinning {
|
||||
animation: spin-element 2.5s linear infinite forwards;
|
||||
}
|
||||
|
||||
@keyframes spin-element {
|
||||
from { transform: rotate(0deg) }
|
||||
to { transform: rotate(360deg) }
|
||||
}
|
||||
|
||||
:where(input, textarea, select, button, div).input-style {
|
||||
height: 2.7rem;
|
||||
width: 100%;
|
||||
|
||||
border: 2px solid var(--color-gray);
|
||||
border-radius: 4px;
|
||||
outline: 0;
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
|
||||
box-shadow: var(--default-shadow);
|
||||
transition: background-color 150ms ease-in-out;
|
||||
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:where(input, textarea, select, button, div).input-style:focus-within {
|
||||
border-color: var(--color-light-gray);
|
||||
}
|
||||
|
||||
:where(input, textarea, select, button).input-style,
|
||||
div.input-style > :where(input, p) {
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
button:has(> svg).input-style {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button.input-style > svg {
|
||||
height: 1.3rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div.input-style {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
}
|
||||
}
|
||||
|
||||
button.input-style:hover {
|
||||
background-color: var(--color-gray);
|
||||
}
|
||||
|
||||
div.input-style > input[type="number"],
|
||||
input[type="number"].input-style {
|
||||
max-width: 6rem;
|
||||
}
|
||||
|
||||
div.input-style > input[type="file"],
|
||||
input[type="file"].input-style {
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
div.input-style > :where(input[type="text"], select),
|
||||
:where(input[type="text"], select).input-style {
|
||||
max-width: 16rem;
|
||||
}
|
||||
|
||||
button.input-style {
|
||||
max-width: 16rem;
|
||||
}
|
||||
|
||||
:where(input, textarea, select, button).long-input-style,
|
||||
div.long-input-style > input {
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
.checked-input-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
max-height: 2.7rem;
|
||||
width: 100%;
|
||||
max-width: 16rem;
|
||||
|
||||
transition: max-height 50ms ease-in-out,
|
||||
border-color 50ms ease-in-out;
|
||||
}
|
||||
|
||||
.checked-input-container.error-input-container {
|
||||
max-height: 4.1rem;
|
||||
}
|
||||
|
||||
.checked-input-container.error-input-container input {
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.checked-input-container :where(input, textarea) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.checked-input-container p {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 1.9rem;
|
||||
width: 100%;
|
||||
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border: 2px solid var(--color-error);
|
||||
padding: .6rem .65rem .3rem .65rem;
|
||||
background-color: var(--color-dim-error);
|
||||
color: var(--color-light);
|
||||
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.entries-table {
|
||||
--inline-padding: 1.5rem;
|
||||
|
||||
position: relative;
|
||||
|
||||
padding-inline: var(--inline-padding);
|
||||
}
|
||||
|
||||
.entries-table::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 auto 0 var(--inline-padding);
|
||||
|
||||
width: 1.4rem;
|
||||
background: linear-gradient(90deg, var(--color-dark), transparent);
|
||||
}
|
||||
|
||||
.entries-table::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 var(--inline-padding) 0 auto;
|
||||
|
||||
width: 1.4rem;
|
||||
background: linear-gradient(270deg, var(--color-dark), transparent);
|
||||
}
|
||||
|
||||
.entries-table > tbody > tr {
|
||||
transition: background-color 200ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-gray);
|
||||
}
|
||||
}
|
||||
|
||||
.entries-table :where(th, td) {
|
||||
height: 2.65rem;
|
||||
padding: .25rem .5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.entries-table td {
|
||||
border-top: 1px solid var(--color-gray);
|
||||
}
|
||||
|
||||
.entries-table :where(th, td):first-child {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.entries-table :where(th, td):last-child {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.entries-table td:last-child:has(button) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
}
|
||||
|
||||
.entries-table td:last-child button {
|
||||
height: 1.25rem;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.entries-table svg {
|
||||
aspect-ratio: 1/1;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.collaps-table {
|
||||
--min-width: 20rem;
|
||||
--max-width: 55rem;
|
||||
--header-width: 50%;
|
||||
--min-data-width: 25rem;
|
||||
|
||||
width: clamp(var(--min-width), 100%, var(--max-width));
|
||||
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
.collaps-table tr:first-of-type :where(th, td) {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.collaps-table :where(th, td) {
|
||||
--middle-spacing: .75rem;
|
||||
padding-bottom: 1rem;
|
||||
vertical-align: top;
|
||||
|
||||
&:first-child {
|
||||
width: var(--header-width);
|
||||
padding-right: var(--middle-spacing);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
min-width: var(--min-data-width);
|
||||
padding-left: var(--middle-spacing);
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin-top: .25rem;
|
||||
|
||||
color: var(--color-light-gray);
|
||||
|
||||
font-size: .9rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* */
|
||||
/* General styling */
|
||||
/* */
|
||||
body {
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
|
||||
background-color: var(--color-dark);
|
||||
@@ -141,44 +386,23 @@ noscript {
|
||||
padding: 1rem;
|
||||
background-color: var(--color-error);
|
||||
color: var(--color-light);
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* */
|
||||
/* Header */
|
||||
/* */
|
||||
header {
|
||||
width: 100%;
|
||||
height: var(--header-height);
|
||||
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
padding: 1rem;
|
||||
box-shadow: var(--default-shadow);
|
||||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
background-color: var(--color-gray);
|
||||
}
|
||||
|
||||
header > div {
|
||||
height: 100%;
|
||||
transform: translateX(-2.6rem);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
transition: transform .3s ease-in-out;
|
||||
}
|
||||
|
||||
#toggle-nav {
|
||||
--height: 1.5rem;
|
||||
height: var(--height);
|
||||
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#toggle-nav svg {
|
||||
height: var(--height);
|
||||
width: var(--height);
|
||||
box-shadow: var(--default-shadow);
|
||||
}
|
||||
|
||||
header img {
|
||||
@@ -186,152 +410,207 @@ header img {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* */
|
||||
/* Nav */
|
||||
/* */
|
||||
.nav-divider {
|
||||
position: relative;
|
||||
height: calc(100% - var(--header-height));
|
||||
dialog {
|
||||
--ani-duration: 100ms;
|
||||
--max-height: 23rem;
|
||||
|
||||
width: min(100%, 40rem);
|
||||
height: min(100%, var(--max-height));
|
||||
|
||||
border-radius: 6px;
|
||||
border: 4px solid var(--color-gray);
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
|
||||
box-shadow: 0px 0px 20px 5px rgba(0 0 0 / 0.8);
|
||||
|
||||
display: flex;
|
||||
|
||||
padding-block: var(--rem-clamp);
|
||||
animation: vanish-dialog var(--ani-duration);
|
||||
}
|
||||
|
||||
body:has(#nav-switch:checked) .nav-divider > nav {
|
||||
left: var(--rem-clamp);
|
||||
dialog[open] {
|
||||
animation: appear-dialog var(--ani-duration);
|
||||
}
|
||||
|
||||
body:has(#nav-switch:checked) .nav-divider > .window-container {
|
||||
margin-left: calc(var(--nav-width) + var(--rem-clamp));
|
||||
dialog::backdrop {
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
nav {
|
||||
--padding: .5rem;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
left: var(--rem-clamp);
|
||||
height: calc(100% - (2 * var(--rem-clamp)));
|
||||
width: var(--nav-width);
|
||||
|
||||
dialog[open]::backdrop {
|
||||
animation: appear-dialog-bg var(--ani-duration);
|
||||
}
|
||||
|
||||
@keyframes appear-dialog {
|
||||
from {
|
||||
opacity: 0;
|
||||
translate: 0 -5%;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
translate: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vanish-dialog {
|
||||
from {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
translate: 0 0;
|
||||
}
|
||||
to {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
translate: 0 -5%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes appear-dialog-bg {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--padding);
|
||||
overflow-y: auto;
|
||||
|
||||
padding: var(--padding);
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-gray);
|
||||
|
||||
transition: left .3s ease-in-out;
|
||||
}
|
||||
|
||||
nav > div {
|
||||
width: 100%;
|
||||
.dialog-header, .dialog-footer {
|
||||
height: 3.2rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
padding: .4rem .75rem;
|
||||
|
||||
&.dialog-header {
|
||||
justify-content: flex-start;
|
||||
border-bottom: 2px solid var(--color-gray);
|
||||
}
|
||||
|
||||
&.dialog-footer {
|
||||
justify-content: flex-end;
|
||||
border-top: 2px solid var(--color-gray);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-header h2 {
|
||||
padding: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.dialog-footer > button,
|
||||
.confirm-container > button {
|
||||
width: 5rem;
|
||||
height: 2.3rem;
|
||||
padding: 0;
|
||||
|
||||
&:first-of-type:where(:hover, :focus-within) {
|
||||
background-color: var(--color-dim-error);
|
||||
}
|
||||
|
||||
&:last-of-type:where(:hover, :focus-within) {
|
||||
background-color: var(--color-success);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
nav > div > button {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
padding: .5rem;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
|
||||
transition: background-color .1s ease-in-out;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
nav > div > button:hover {
|
||||
background-color: var(--color-gray);
|
||||
}
|
||||
|
||||
nav > div > button svg {
|
||||
height: 1.8rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
/* */
|
||||
/* Window management */
|
||||
/* */
|
||||
.window-container {
|
||||
margin-left: calc(4rem + var(--rem-clamp));
|
||||
width: 100%;
|
||||
|
||||
.confirm-container {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
transition: margin-left .3s ease-in-out;
|
||||
flex-direction: row-reverse;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.window-container > :where(#home, .extra-window-container) {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
|
||||
translate: 0 0;
|
||||
transition: translate .5s ease-in-out;
|
||||
}
|
||||
|
||||
.window-container.inter-window-ani > :where(#home, .extra-window-container) {
|
||||
transition: translate .5s ease-in-out,
|
||||
transform .5s ease-in-out;
|
||||
}
|
||||
|
||||
.extra-window-container {
|
||||
--y-offset: 0%;
|
||||
transform: translateY(var(--y-offset));
|
||||
}
|
||||
|
||||
.extra-window-container > div {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.window-container.show-window > :where(#home, .extra-window-container) {
|
||||
translate: -100% 0;
|
||||
}
|
||||
|
||||
.window-container.show-window > .extra-window-container {
|
||||
transform: translateY(var(--y-offset));
|
||||
}
|
||||
|
||||
/* */
|
||||
/* Styling extra window */
|
||||
/* */
|
||||
.extra-window-container > div {
|
||||
padding: var(--rem-clamp);
|
||||
}
|
||||
|
||||
.extra-window-container > div > h2 {
|
||||
text-align: center;
|
||||
font-size: clamp(1.3rem, 5vw, 2rem);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.extra-window-container > div > h2:not(:first-of-type) {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.extra-window-container > div > p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 543px) {
|
||||
.window-container {
|
||||
margin-left: 0;
|
||||
.confirm-container > button {
|
||||
width: 6rem;
|
||||
|
||||
&:first-of-type:where(:hover, :focus-within) {
|
||||
background-color: var(--color-success);
|
||||
}
|
||||
|
||||
nav {
|
||||
left: -100%;
|
||||
&:last-of-type:where(:hover, :focus-within) {
|
||||
background-color: var(--color-dim-error);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
* {
|
||||
animation-duration: 0ms !important;
|
||||
transition-duration: 0ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 29rem) {
|
||||
.collaps-table tbody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.collaps-table tr {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
padding-left: .5rem;
|
||||
}
|
||||
|
||||
.collaps-table tr:first-of-type > td {
|
||||
padding-top: 0rem;
|
||||
}
|
||||
|
||||
.collaps-table :where(th, td) {
|
||||
width: 100%;
|
||||
|
||||
&:first-child {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
dialog {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@keyframes appear-dialog {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vanish-dialog {
|
||||
from {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,7 @@ body:has(#wide-toggle:checked) .tab-container > div {
|
||||
border-radius: 4px;
|
||||
padding: .75rem;
|
||||
background-color: var(--color);
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
button.entry.fit {
|
||||
|
||||
@@ -1,65 +1,46 @@
|
||||
main {
|
||||
height: calc(100vh - var(--header-height));
|
||||
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
main:has(#form-switch:checked) .form-container {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.form-container {
|
||||
height: inherit;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
transition: transform .25s ease-in-out;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 30rem;
|
||||
margin-inline: auto;
|
||||
|
||||
height: calc(100% - var(--header-height));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
position: relative;
|
||||
width: min(40rem, 90%);
|
||||
height: 22rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
flex-wrap: wrap;
|
||||
overflow: hidden;
|
||||
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
background-color: var(--color-gray);
|
||||
color: var(--color-light);
|
||||
|
||||
box-shadow: 0px 0px 20px 3px rgba(0 0 0 / 0.25);
|
||||
}
|
||||
|
||||
form {
|
||||
height: inherit;
|
||||
flex: 1 0 auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
padding: 2rem;
|
||||
|
||||
transition: translate 100ms ease-in-out;
|
||||
}
|
||||
|
||||
form h2 {
|
||||
font-size: clamp(1.2rem, 7vw, 2rem);
|
||||
}
|
||||
|
||||
#username-error-container,
|
||||
#password-error-container {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
form input {
|
||||
width: min(100%, 20rem);
|
||||
}
|
||||
|
||||
#username-error-container p,
|
||||
#password-error-container p {
|
||||
width: min(100%, 19rem);
|
||||
}
|
||||
|
||||
.switch-button {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
@@ -69,12 +50,53 @@ form input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.switch-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
width: 7rem;
|
||||
|
||||
padding: .5rem 1rem;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-mid-gray);
|
||||
color: var(--color-light);
|
||||
|
||||
font-size: 1.1rem;
|
||||
|
||||
transition:
|
||||
background-color 150ms ease-in-out,
|
||||
box-shadow 150ms ease-in-out;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: var(--color-light-gray);
|
||||
box-shadow: var(--default-shadow);
|
||||
}
|
||||
|
||||
#form-cover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
z-index: 2;
|
||||
|
||||
display: flex;
|
||||
|
||||
padding: 3rem;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-gray);
|
||||
|
||||
box-shadow: 0px 0px 20px 3px rgba(0 0 0 / 0.25);
|
||||
transition: left 100ms ease-in-out;
|
||||
}
|
||||
|
||||
main:has(#form-switch:checked) #form-cover {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
@media (max-width: 594px) {
|
||||
#form-cover {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main:has(#form-switch:checked) form {
|
||||
translate: 0 -100%;
|
||||
}
|
||||
}
|
||||
|
||||
175
frontend/static/css/reminders.css
Normal file
175
frontend/static/css/reminders.css
Normal file
@@ -0,0 +1,175 @@
|
||||
/* */
|
||||
/* Nav collapse button */
|
||||
/* */
|
||||
header > div {
|
||||
height: 100%;
|
||||
transform: translateX(-2.6rem);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
transition: transform .3s ease-in-out;
|
||||
}
|
||||
|
||||
#toggle-nav {
|
||||
--height: 1.5rem;
|
||||
height: var(--height);
|
||||
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#toggle-nav svg {
|
||||
height: var(--height);
|
||||
width: var(--height);
|
||||
}
|
||||
|
||||
/* */
|
||||
/* Nav */
|
||||
/* */
|
||||
.nav-divider {
|
||||
position: relative;
|
||||
height: calc(100% - var(--header-height));
|
||||
|
||||
display: flex;
|
||||
|
||||
padding-block: var(--rem-clamp);
|
||||
}
|
||||
|
||||
body:has(#nav-switch:checked) .nav-divider > nav {
|
||||
left: var(--rem-clamp);
|
||||
}
|
||||
|
||||
body:has(#nav-switch:checked) .nav-divider > .window-container {
|
||||
margin-left: calc(var(--nav-width) + var(--rem-clamp));
|
||||
}
|
||||
|
||||
nav {
|
||||
--padding: .5rem;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
left: var(--rem-clamp);
|
||||
height: calc(100% - (2 * var(--rem-clamp)));
|
||||
width: var(--nav-width);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--padding);
|
||||
overflow-y: auto;
|
||||
|
||||
padding: var(--padding);
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-gray);
|
||||
|
||||
transition: left .3s ease-in-out;
|
||||
}
|
||||
|
||||
nav > div {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
nav > div > button {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
padding: .5rem;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
|
||||
transition: background-color .1s ease-in-out;
|
||||
}
|
||||
|
||||
nav > div > button:hover {
|
||||
background-color: var(--color-gray);
|
||||
}
|
||||
|
||||
nav > div > button svg {
|
||||
height: 1.8rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
/* */
|
||||
/* Window management */
|
||||
/* */
|
||||
.window-container {
|
||||
margin-left: calc(4rem + var(--rem-clamp));
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
transition: margin-left .3s ease-in-out;
|
||||
}
|
||||
|
||||
.window-container > :where(#home, .extra-window-container) {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
|
||||
translate: 0 0;
|
||||
transition: translate .5s ease-in-out;
|
||||
}
|
||||
|
||||
.window-container.inter-window-ani > :where(#home, .extra-window-container) {
|
||||
transition: translate .5s ease-in-out,
|
||||
transform .5s ease-in-out;
|
||||
}
|
||||
|
||||
.extra-window-container {
|
||||
--y-offset: 0%;
|
||||
transform: translateY(var(--y-offset));
|
||||
}
|
||||
|
||||
.extra-window-container > div {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.window-container.show-window > :where(#home, .extra-window-container) {
|
||||
translate: -100% 0;
|
||||
}
|
||||
|
||||
.window-container.show-window > .extra-window-container {
|
||||
transform: translateY(var(--y-offset));
|
||||
}
|
||||
|
||||
/* */
|
||||
/* Styling extra window */
|
||||
/* */
|
||||
.extra-window-container > div {
|
||||
padding: var(--rem-clamp);
|
||||
}
|
||||
|
||||
.extra-window-container > div > h2 {
|
||||
text-align: center;
|
||||
font-size: clamp(1.3rem, 5vw, 2rem);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.extra-window-container > div > h2:not(:first-of-type) {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.extra-window-container > div > p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 543px) {
|
||||
.window-container {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
left: -100%;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,126 +1,270 @@
|
||||
// The duration of the animation set for the window translation
|
||||
const window_ani_ms = 500;
|
||||
//
|
||||
// region Definitions
|
||||
//
|
||||
const constants = {
|
||||
/**
|
||||
* The duration of the animation set for the window translation
|
||||
*/
|
||||
windowAnimationDuration: 500
|
||||
}
|
||||
|
||||
const Types = {
|
||||
'reminder': document.getElementById('reminder-tab'),
|
||||
'static_reminder': document.getElementById('static-reminder-tab'),
|
||||
'template': document.getElementById('template-tab')
|
||||
};
|
||||
const icons = {
|
||||
save: '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 24 24" style="enable-background:new 0 0 512 512" xml:space="preserve"><g><path d="M12,10a4,4,0,1,0,4,4A4,4,0,0,0,12,10Zm0,6a2,2,0,1,1,2-2A2,2,0,0,1,12,16Z"></path><path d="M22.536,4.122,19.878,1.464A4.966,4.966,0,0,0,16.343,0H5A5.006,5.006,0,0,0,0,5V19a5.006,5.006,0,0,0,5,5H19a5.006,5.006,0,0,0,5-5V7.657A4.966,4.966,0,0,0,22.536,4.122ZM17,2.08V3a3,3,0,0,1-3,3H10A3,3,0,0,1,7,3V2h9.343A2.953,2.953,0,0,1,17,2.08ZM22,19a3,3,0,0,1-3,3H5a3,3,0,0,1-3-3V5A3,3,0,0,1,5,2V3a5.006,5.006,0,0,0,5,5h4a4.991,4.991,0,0,0,4.962-4.624l2.16,2.16A3.02,3.02,0,0,1,22,7.657Z"></path></g></svg>',
|
||||
edit: '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 24 24" style="enable-background:new 0 0 512 512" xml:space="preserve"><g><g id="_01_align_center" data-name="01 align center"><path d="M22.94,1.06a3.626,3.626,0,0,0-5.124,0L0,18.876V24H5.124L22.94,6.184A3.627,3.627,0,0,0,22.94,1.06ZM4.3,22H2V19.7L15.31,6.4l2.3,2.3ZM21.526,4.77,19.019,7.277l-2.295-2.3L19.23,2.474a1.624,1.624,0,0,1,2.3,2.3Z"></path></g></g></svg>',
|
||||
delete: '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 24 24" style="enable-background:new 0 0 512 512" xml:space="preserve"><g><g id="_01_align_center" data-name="01 align center"><path d="M22,4H17V2a2,2,0,0,0-2-2H9A2,2,0,0,0,7,2V4H2V6H4V21a3,3,0,0,0,3,3H17a3,3,0,0,0,3-3V6h2ZM9,2h6V4H9Zm9,19a1,1,0,0,1-1,1H7a1,1,0,0,1-1-1V6H18Z"></path><rect x="9" y="10" width="2" height="8"></rect><rect x="13" y="10" width="2" height="8"></rect></g></g></svg>',
|
||||
add: '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512" xml:space="preserve"><g><g><path d="M480,224H288V32c0-17.673-14.327-32-32-32s-32,14.327-32,32v192H32c-17.673,0-32,14.327-32,32s14.327,32,32,32h192v192 c0,17.673,14.327,32,32,32s32-14.327,32-32V288h192c17.673,0,32-14.327,32-32S497.673,224,480,224z"></path></g></g></svg>',
|
||||
download: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="512" height="512"><g><path d="M210.731,386.603c24.986,25.002,65.508,25.015,90.51,0.029c0.01-0.01,0.019-0.019,0.029-0.029l68.501-68.501 c7.902-8.739,7.223-22.23-1.516-30.132c-8.137-7.357-20.527-7.344-28.649,0.03l-62.421,62.443l0.149-329.109 C277.333,9.551,267.782,0,256,0l0,0c-11.782,0-21.333,9.551-21.333,21.333l-0.192,328.704L172.395,288 c-8.336-8.33-21.846-8.325-30.176,0.011c-8.33,8.336-8.325,21.846,0.011,30.176L210.731,386.603z"/><path d="M490.667,341.333L490.667,341.333c-11.782,0-21.333,9.551-21.333,21.333V448c0,11.782-9.551,21.333-21.333,21.333H64 c-11.782,0-21.333-9.551-21.333-21.333v-85.333c0-11.782-9.551-21.333-21.333-21.333l0,0C9.551,341.333,0,350.885,0,362.667V448 c0,35.346,28.654,64,64,64h384c35.346,0,64-28.654,64-64v-85.333C512,350.885,502.449,341.333,490.667,341.333z"/></g></svg>',
|
||||
upload: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512.008 512.008" style="enable-background:new 0 0 512.008 512.008;" xml:space="preserve" width="512" height="512"><g><path d="M172.399,117.448l62.421-62.443l-0.149,329.344c0,11.782,9.551,21.333,21.333,21.333l0,0 c11.782,0,21.333-9.551,21.333-21.333l0.149-328.981l62.123,62.144c8.475,8.185,21.98,7.951,30.165-0.524 c7.985-8.267,7.985-21.374,0-29.641L301.273,18.76c-24.986-25.002-65.508-25.015-90.51-0.029c-0.01,0.01-0.019,0.019-0.029,0.029 l-68.501,68.523c-8.185,8.475-7.951,21.98,0.524,30.165C151.024,125.433,164.131,125.433,172.399,117.448z"/><path d="M490.671,341.341L490.671,341.341c-11.782,0-21.333,9.551-21.333,21.333v85.333c0,11.782-9.551,21.333-21.333,21.333h-384 c-11.782,0-21.333-9.551-21.333-21.333v-85.333c0-11.782-9.551-21.333-21.333-21.333l0,0c-11.782,0-21.333,9.551-21.333,21.333 v85.333c0,35.346,28.654,64,64,64h384c35.346,0,64-28.654,64-64v-85.333C512.004,350.892,502.453,341.341,490.671,341.341z"/></g></svg>',
|
||||
loading: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m10.5 1.5c0-.828.672-1.5 1.5-1.5s1.5.672 1.5 1.5-.672 1.5-1.5 1.5-1.5-.672-1.5-1.5zm1.5 22.5c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5zm9-12c0 .828.672 1.5 1.5 1.5s1.5-.672 1.5-1.5-.672-1.5-1.5-1.5-1.5.672-1.5 1.5zm-21 0c0 .828.672 1.5 1.5 1.5s1.5-.672 1.5-1.5-.672-1.5-1.5-1.5-1.5.672-1.5 1.5zm17.293-7.577c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5zm3.779 3.798c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5zm-.01 10.567c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5zm-3.788 3.788c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5zm-10.577-.01c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5zm-3.75-3.779c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5zm-.01-10.596c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5zm3.779-3.769c.828 0 1.5-.672 1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5 1.5.672 1.5 1.5 1.5z"/></svg>'
|
||||
}
|
||||
|
||||
const Icons = {
|
||||
'save': '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 24 24" style="enable-background:new 0 0 512 512" xml:space="preserve"><g><path d="M12,10a4,4,0,1,0,4,4A4,4,0,0,0,12,10Zm0,6a2,2,0,1,1,2-2A2,2,0,0,1,12,16Z"></path><path d="M22.536,4.122,19.878,1.464A4.966,4.966,0,0,0,16.343,0H5A5.006,5.006,0,0,0,0,5V19a5.006,5.006,0,0,0,5,5H19a5.006,5.006,0,0,0,5-5V7.657A4.966,4.966,0,0,0,22.536,4.122ZM17,2.08V3a3,3,0,0,1-3,3H10A3,3,0,0,1,7,3V2h9.343A2.953,2.953,0,0,1,17,2.08ZM22,19a3,3,0,0,1-3,3H5a3,3,0,0,1-3-3V5A3,3,0,0,1,5,2V3a5.006,5.006,0,0,0,5,5h4a4.991,4.991,0,0,0,4.962-4.624l2.16,2.16A3.02,3.02,0,0,1,22,7.657Z"></path></g></svg>',
|
||||
'edit': '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 24 24" style="enable-background:new 0 0 512 512" xml:space="preserve"><g><g id="_01_align_center" data-name="01 align center"><path d="M22.94,1.06a3.626,3.626,0,0,0-5.124,0L0,18.876V24H5.124L22.94,6.184A3.627,3.627,0,0,0,22.94,1.06ZM4.3,22H2V19.7L15.31,6.4l2.3,2.3ZM21.526,4.77,19.019,7.277l-2.295-2.3L19.23,2.474a1.624,1.624,0,0,1,2.3,2.3Z"></path></g></g></svg>',
|
||||
'delete': '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 24 24" style="enable-background:new 0 0 512 512" xml:space="preserve"><g><g id="_01_align_center" data-name="01 align center"><path d="M22,4H17V2a2,2,0,0,0-2-2H9A2,2,0,0,0,7,2V4H2V6H4V21a3,3,0,0,0,3,3H17a3,3,0,0,0,3-3V6h2ZM9,2h6V4H9Zm9,19a1,1,0,0,1-1,1H7a1,1,0,0,1-1-1V6H18Z"></path><rect x="9" y="10" width="2" height="8"></rect><rect x="13" y="10" width="2" height="8"></rect></g></g></svg>',
|
||||
'add': '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512" xml:space="preserve"><g><g><path d="M480,224H288V32c0-17.673-14.327-32-32-32s-32,14.327-32,32v192H32c-17.673,0-32,14.327-32,32s14.327,32,32,32h192v192 c0,17.673,14.327,32,32,32s32-14.327,32-32V288h192c17.673,0,32-14.327,32-32S497.673,224,480,224z"></path></g></g></svg>',
|
||||
'download': '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="512" height="512"><g><path d="M210.731,386.603c24.986,25.002,65.508,25.015,90.51,0.029c0.01-0.01,0.019-0.019,0.029-0.029l68.501-68.501 c7.902-8.739,7.223-22.23-1.516-30.132c-8.137-7.357-20.527-7.344-28.649,0.03l-62.421,62.443l0.149-329.109 C277.333,9.551,267.782,0,256,0l0,0c-11.782,0-21.333,9.551-21.333,21.333l-0.192,328.704L172.395,288 c-8.336-8.33-21.846-8.325-30.176,0.011c-8.33,8.336-8.325,21.846,0.011,30.176L210.731,386.603z"/><path d="M490.667,341.333L490.667,341.333c-11.782,0-21.333,9.551-21.333,21.333V448c0,11.782-9.551,21.333-21.333,21.333H64 c-11.782,0-21.333-9.551-21.333-21.333v-85.333c0-11.782-9.551-21.333-21.333-21.333l0,0C9.551,341.333,0,350.885,0,362.667V448 c0,35.346,28.654,64,64,64h384c35.346,0,64-28.654,64-64v-85.333C512,350.885,502.449,341.333,490.667,341.333z"/></g></svg>',
|
||||
'upload': '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512.008 512.008" style="enable-background:new 0 0 512.008 512.008;" xml:space="preserve" width="512" height="512"><g><path d="M172.399,117.448l62.421-62.443l-0.149,329.344c0,11.782,9.551,21.333,21.333,21.333l0,0 c11.782,0,21.333-9.551,21.333-21.333l0.149-328.981l62.123,62.144c8.475,8.185,21.98,7.951,30.165-0.524 c7.985-8.267,7.985-21.374,0-29.641L301.273,18.76c-24.986-25.002-65.508-25.015-90.51-0.029c-0.01,0.01-0.019,0.019-0.029,0.029 l-68.501,68.523c-8.185,8.475-7.951,21.98,0.524,30.165C151.024,125.433,164.131,125.433,172.399,117.448z"/><path d="M490.671,341.341L490.671,341.341c-11.782,0-21.333,9.551-21.333,21.333v85.333c0,11.782-9.551,21.333-21.333,21.333h-384 c-11.782,0-21.333-9.551-21.333-21.333v-85.333c0-11.782-9.551-21.333-21.333-21.333l0,0c-11.782,0-21.333,9.551-21.333,21.333 v85.333c0,35.346,28.654,64,64,64h384c35.346,0,64-28.654,64-64v-85.333C512.004,350.892,502.453,341.341,490.671,341.341z"/></g></svg>'
|
||||
};
|
||||
|
||||
const InfoClasses = [
|
||||
'show-add-reminder', 'show-add-template', 'show-add-static-reminder',
|
||||
'show-edit-reminder', 'show-edit-template', 'show-edit-static-reminder'
|
||||
];
|
||||
//
|
||||
// region Helpers
|
||||
//
|
||||
|
||||
function showWindow(id) {
|
||||
const window_container = document.querySelector('.window-container');
|
||||
if (id === "home") {
|
||||
window_container.classList.remove(
|
||||
'show-window',
|
||||
'inter-window-ani'
|
||||
);
|
||||
} else {
|
||||
const extra_window_container =
|
||||
document.querySelector('.extra-window-container');
|
||||
/**
|
||||
* Hide and show elements.
|
||||
*
|
||||
* @param {Array<HTMLElement>} to_hide The elements to hide,
|
||||
* by adding the `hidden` class.
|
||||
*
|
||||
* @param {Array<HTMLElement>?} to_show The elements to show,
|
||||
* by removing the `hidden` class.
|
||||
*/
|
||||
function hide(to_hide, to_show) {
|
||||
to_hide.forEach(el => el.classList.add('hidden'))
|
||||
if (to_show !== null && to_show !== undefined)
|
||||
to_show.forEach(el => el.classList.remove('hidden'))
|
||||
}
|
||||
|
||||
const offset = [
|
||||
...extra_window_container.children
|
||||
].indexOf(document.getElementById(id)) * -100;
|
||||
|
||||
extra_window_container.style.setProperty(
|
||||
'--y-offset',
|
||||
`${offset}%`
|
||||
)
|
||||
window_container.classList.add('show-window');
|
||||
setTimeout(
|
||||
() => window_container.classList.add('inter-window-ani'),
|
||||
window_ani_ms
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
function logout() {
|
||||
fetch(`${url_prefix}/api/auth/logout?api_key=${api_key}`, {
|
||||
'method': 'POST'
|
||||
})
|
||||
.then(response => {
|
||||
setLocalStorage({'api_key': null});
|
||||
window.location.href = `${url_prefix}/`;
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// LocalStorage
|
||||
//
|
||||
const default_values = {
|
||||
'api_key': null,
|
||||
'locale': 'en-GB',
|
||||
'default_service': null,
|
||||
'sorting_reminders': 'time',
|
||||
'sorting_static': 'title',
|
||||
'sorting_templates': 'title'
|
||||
};
|
||||
|
||||
function setupLocalStorage() {
|
||||
if (!localStorage.getItem('MIND'))
|
||||
localStorage.setItem('MIND', JSON.stringify(default_values));
|
||||
|
||||
const missing_keys = [
|
||||
...Object.keys(default_values)
|
||||
].filter(e =>
|
||||
![...Object.keys(JSON.parse(localStorage.getItem('MIND')))].includes(e)
|
||||
)
|
||||
|
||||
if (missing_keys.length) {
|
||||
const storage = JSON.parse(localStorage.getItem('MIND'));
|
||||
|
||||
missing_keys.forEach(missing_key => {
|
||||
storage[missing_key] = default_values[missing_key]
|
||||
})
|
||||
|
||||
localStorage.setItem('MIND', JSON.stringify(storage));
|
||||
};
|
||||
return;
|
||||
};
|
||||
//
|
||||
// region LocalStorage
|
||||
//
|
||||
const defaultValues = {
|
||||
api_key: null,
|
||||
locale: 'en-GB',
|
||||
default_service: null,
|
||||
sorting_reminders: 'time',
|
||||
sorting_static: 'title',
|
||||
sorting_templates: 'title',
|
||||
allow_new_accounts_cache: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration stored in the local storage of the client (browser).
|
||||
*
|
||||
* @param {string | string[] | null | undefined} keys The keys to fetch, or all
|
||||
* if null or undefined.
|
||||
*
|
||||
* @returns {Map<string, any>} The keys and their values.
|
||||
*/
|
||||
function getLocalStorage(keys) {
|
||||
const storage = JSON.parse(localStorage.getItem('MIND'));
|
||||
const result = {};
|
||||
const storage = JSON.parse(localStorage.getItem('MIND'))
|
||||
|
||||
if (keys === undefined || keys === null)
|
||||
return storage
|
||||
|
||||
const result = {}
|
||||
if (typeof keys === 'string')
|
||||
result[keys] = storage[keys];
|
||||
|
||||
result[keys] = storage[keys]
|
||||
|
||||
else if (typeof keys === 'object')
|
||||
for (const key in keys)
|
||||
result[key] = storage[key];
|
||||
result[key] = storage[key]
|
||||
|
||||
return result;
|
||||
};
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the configuration stored in the local storage of the client (browser).
|
||||
* @param {Map<string, any>} keys_values The new values for the given keys.
|
||||
*/
|
||||
function setLocalStorage(keys_values) {
|
||||
const storage = JSON.parse(localStorage.getItem('MIND'));
|
||||
const storage = JSON.parse(localStorage.getItem('MIND'))
|
||||
|
||||
for (const [key, value] of Object.entries(keys_values))
|
||||
storage[key] = value;
|
||||
|
||||
localStorage.setItem('MIND', JSON.stringify(storage));
|
||||
return;
|
||||
};
|
||||
storage[key] = value
|
||||
|
||||
// code run on load
|
||||
localStorage.setItem('MIND', JSON.stringify(storage))
|
||||
}
|
||||
|
||||
setupLocalStorage();
|
||||
/**
|
||||
* Setup the configuration stored in the local storage of the client (browser)
|
||||
* with default values.
|
||||
*/
|
||||
function setupLocalStorage() {
|
||||
if (!localStorage.getItem('MIND'))
|
||||
localStorage.setItem('MIND', JSON.stringify(defaultValues))
|
||||
|
||||
const url_prefix = document.getElementById('url_prefix').dataset.value;
|
||||
const api_key = getLocalStorage('api_key')['api_key'];
|
||||
if (api_key === null) {
|
||||
window.location.href = `${url_prefix}/`;
|
||||
};
|
||||
const currentValues = getLocalStorage()
|
||||
const cleanedVersion = {}
|
||||
Object.keys(defaultValues).forEach(k => {
|
||||
if (currentValues[k] === undefined)
|
||||
cleanedVersion[k] = defaultValues[k]
|
||||
else
|
||||
cleanedVersion[k] = currentValues[k]
|
||||
})
|
||||
|
||||
localStorage.setItem('MIND', JSON.stringify(cleanedVersion))
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// region Fetching
|
||||
//
|
||||
|
||||
/**
|
||||
* Make a GET request to the API.
|
||||
*
|
||||
* @param {string} endpoint The endpoint to make the request to, without API URL
|
||||
* prefix.
|
||||
*
|
||||
* @param {Map<string, any>} params The URL parameters to use in the request.
|
||||
* API key doesn't need to be supplied.
|
||||
*
|
||||
* @param {bool} jsonReturn Whether to return the json response, or the raw
|
||||
* response.
|
||||
*
|
||||
* @param {bool} checkAuth Check whether authentication failed and if so
|
||||
* automatically redirect to login page.
|
||||
*
|
||||
* @returns The response.
|
||||
*/
|
||||
async function fetchAPI(endpoint, params={}, jsonReturn=true, checkAuth=true) {
|
||||
let formattedParams = ''
|
||||
if (apiKey !== null)
|
||||
params.api_key = apiKey
|
||||
if (Object.keys(params).length) {
|
||||
formattedParams = '?' + Object.entries(params).map(p => p.join('=')).join('&')
|
||||
}
|
||||
|
||||
return fetch(`${urlPrefix}/api${endpoint}${formattedParams}`)
|
||||
.then(response => {
|
||||
if (!response.ok)
|
||||
return Promise.reject(response)
|
||||
if (jsonReturn)
|
||||
return response.json()
|
||||
else
|
||||
return response
|
||||
})
|
||||
.catch(response => {
|
||||
if (checkAuth && response.status === 401) {
|
||||
setLocalStorage({api_key: null})
|
||||
window.location.href = `${urlPrefix}/`
|
||||
} else {
|
||||
return Promise.reject(response)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a POST, PUT or DELETE request to the API.
|
||||
*
|
||||
* @param {string} method The HTTP method to use.
|
||||
*
|
||||
* @param {string} endpoint The endpoint to make the request to, without API URL
|
||||
* prefix.
|
||||
*
|
||||
* @param {Map<string, any>} params The URL parameters to use in the request.
|
||||
* API key doesn't need to be supplied.
|
||||
*
|
||||
* @param {any} body The JSON to supply in the body of the request.
|
||||
*
|
||||
* @param {bool} jsonReturn Whether to return the json response, or the raw
|
||||
* response.
|
||||
*
|
||||
* @param {bool} checkAuth Check whether authentication failed and if so
|
||||
* automatically redirect to login page.
|
||||
*
|
||||
* @returns The response.
|
||||
*/
|
||||
async function sendAPI(
|
||||
method,
|
||||
endpoint,
|
||||
params={},
|
||||
body={},
|
||||
jsonReturn=true,
|
||||
checkAuth=true
|
||||
) {
|
||||
let formattedParams = ''
|
||||
if (apiKey !== null)
|
||||
params.api_key = apiKey
|
||||
if (Object.keys(params).length) {
|
||||
formattedParams = '?' + Object.entries(params).map(p => p.join('=')).join('&')
|
||||
}
|
||||
|
||||
return fetch(`${urlPrefix}/api${endpoint}${formattedParams}`, {
|
||||
'method': method,
|
||||
'headers': {'Content-Type': 'application/json'},
|
||||
'body': Object.entries(body).length !== 0 ? JSON.stringify(body) : ''
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok)
|
||||
return Promise.reject(response)
|
||||
if (jsonReturn)
|
||||
return response.json()
|
||||
else
|
||||
return response
|
||||
})
|
||||
.catch(response => {
|
||||
if (checkAuth && response.status === 401) {
|
||||
setLocalStorage({api_key: null})
|
||||
window.location.href = `${urlPrefix}/`
|
||||
} else {
|
||||
return Promise.reject(response)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the login status, and redirect to proper place if not already there.
|
||||
*/
|
||||
function checkLogin() {
|
||||
if (apiKey === null)
|
||||
window.location.href = `${urlPrefix}/`
|
||||
|
||||
fetchAPI("/auth/status")
|
||||
.then(json => {
|
||||
if (
|
||||
json.result.admin
|
||||
&& window.location.pathname !== `${urlPrefix}/admin`
|
||||
)
|
||||
window.location.href = `${urlPrefix}/admin`
|
||||
|
||||
else if (
|
||||
!json.result.admin
|
||||
&& window.location.pathname !== `${urlPrefix}/reminders`
|
||||
)
|
||||
window.location.href = `${urlPrefix}/reminders`
|
||||
})
|
||||
.catch(e => console.log(e))
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out and redirect to login page.
|
||||
*/
|
||||
function logout() {
|
||||
sendAPI("POST", "/auth/logout")
|
||||
.then(response => {
|
||||
setLocalStorage({'api_key': null})
|
||||
window.location.href = `${urlPrefix}/`
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// region On load
|
||||
//
|
||||
setupLocalStorage()
|
||||
|
||||
/**
|
||||
* The URL prefix that the application is running on.
|
||||
*/
|
||||
const urlPrefix = document.getElementById('url_prefix').dataset.value
|
||||
|
||||
/**
|
||||
* The API key that can be used to authenticate API requests. Not guaranteed to
|
||||
* be valid.
|
||||
*/
|
||||
const apiKey = getLocalStorage('api_key')['api_key']
|
||||
|
||||
@@ -22,11 +22,11 @@ const LibEls = {
|
||||
//
|
||||
function getSorting(type, key=false) {
|
||||
let sorting_key;
|
||||
if (type === Types.reminder)
|
||||
if (type === reminderTypes.reminder)
|
||||
sorting_key = 'sorting_reminders';
|
||||
else if (type === Types.static_reminder)
|
||||
else if (type === reminderTypes.static_reminder)
|
||||
sorting_key = 'sorting_static';
|
||||
else if (type === Types.template)
|
||||
else if (type === reminderTypes.template)
|
||||
sorting_key = 'sorting_templates';
|
||||
|
||||
if (key)
|
||||
@@ -36,7 +36,7 @@ function getSorting(type, key=false) {
|
||||
};
|
||||
|
||||
function getActiveTab() {
|
||||
for (let t of Object.values(Types)) {
|
||||
for (let t of Object.values(reminderTypes)) {
|
||||
if (getComputedStyle(t).display === 'flex')
|
||||
return t
|
||||
};
|
||||
@@ -63,7 +63,7 @@ function fillTable(table, results) {
|
||||
title.innerText = r.title;
|
||||
entry.appendChild(title);
|
||||
|
||||
if (table === Types.reminder) {
|
||||
if (table === reminderTypes.reminder) {
|
||||
const time = document.createElement('p');
|
||||
let offset = new Date(r.time * 1000).getTimezoneOffset() * -60;
|
||||
let d = new Date((r.time + offset) * 1000);
|
||||
@@ -97,21 +97,21 @@ function fillLibrary(type=null) {
|
||||
let tab_type = type || getActiveTab();
|
||||
|
||||
let url;
|
||||
if (tab_type === Types.reminder)
|
||||
url = `${url_prefix}/api/reminders`;
|
||||
else if (tab_type === Types.static_reminder)
|
||||
url = `${url_prefix}/api/staticreminders`;
|
||||
else if (tab_type === Types.template)
|
||||
url = `${url_prefix}/api/templates`;
|
||||
if (tab_type === reminderTypes.reminder)
|
||||
url = `${urlPrefix}/api/reminders`;
|
||||
else if (tab_type === reminderTypes.static_reminder)
|
||||
url = `${urlPrefix}/api/staticreminders`;
|
||||
else if (tab_type === reminderTypes.template)
|
||||
url = `${urlPrefix}/api/templates`;
|
||||
else
|
||||
return;
|
||||
|
||||
const sorting = getSorting(tab_type);
|
||||
const query = LibEls.search_bar.input.value;
|
||||
if (query)
|
||||
url = `${url}/search?api_key=${api_key}&sort_by=${sorting}&query=${query}`;
|
||||
url = `${url}/search?api_key=${apiKey}&sort_by=${sorting}&query=${query}`;
|
||||
else
|
||||
url = `${url}?api_key=${api_key}&sort_by=${sorting}`;
|
||||
url = `${url}?api_key=${apiKey}&sort_by=${sorting}`;
|
||||
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
@@ -121,7 +121,7 @@ function fillLibrary(type=null) {
|
||||
.then(json => fillTable(tab_type, json.result))
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
@@ -148,8 +148,8 @@ function evaluateSizing() {
|
||||
|
||||
// code run on load
|
||||
|
||||
Object.values(Types).forEach(t => fillLibrary(t));
|
||||
setInterval(() => fillLibrary(Types.reminder), 60000);
|
||||
Object.values(reminderTypes).forEach(t => fillLibrary(t));
|
||||
setInterval(() => fillLibrary(reminderTypes.reminder), 60000);
|
||||
|
||||
const week_days = ["Mo", "Tu", "We", "Thu", "Fr", "Sa", "Su"];
|
||||
|
||||
|
||||
@@ -1,139 +1,112 @@
|
||||
const forms = {
|
||||
'login': {
|
||||
'form': document.querySelector('#login-form'),
|
||||
'inputs': {
|
||||
'username': document.querySelector('#login-form input[type="text"]'),
|
||||
'password': document.querySelector('#login-form input[type="password"]')
|
||||
},
|
||||
'errors': {
|
||||
'username': document.querySelector("#username-error-container"),
|
||||
'password': document.querySelector("#password-error-container")
|
||||
const els = {
|
||||
switchButton: document.querySelector(".switch-button"),
|
||||
login: {
|
||||
form: document.querySelector('#login-form'),
|
||||
inputContainers: {
|
||||
username: document.querySelector('#login-form .checked-input-container:has(input[type="text"])'),
|
||||
password: document.querySelector('#login-form .checked-input-container:has(input[type="password"])')
|
||||
},
|
||||
inputs: {
|
||||
username: document.querySelector('#login-username-input'),
|
||||
password: document.querySelector('#login-password-input')
|
||||
}
|
||||
},
|
||||
'create': {
|
||||
'form': document.querySelector('#create-form'),
|
||||
'inputs': {
|
||||
'username': document.querySelector('#create-form input[type="text"]'),
|
||||
'password': document.querySelector('#create-form input[type="password"]')
|
||||
create: {
|
||||
form: document.querySelector('#register-form'),
|
||||
inputContainers: {
|
||||
username: document.querySelector('#register-form .checked-input-container:has(input[type="text"])'),
|
||||
},
|
||||
inputs: {
|
||||
username: document.querySelector('#register-username-input'),
|
||||
password: document.querySelector('#register-password-input')
|
||||
},
|
||||
'errors': {
|
||||
'username_invalid': document.querySelector('#new-username-error'),
|
||||
'username_taken': document.querySelector('#taken-username-error')
|
||||
errors: {
|
||||
usernameInvalid: document.querySelector('#invalid-username-error'),
|
||||
usernameTaken: document.querySelector('#taken-username-error')
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function login(data=null) {
|
||||
forms.login.errors.username.classList.remove('error-container');
|
||||
forms.login.errors.password.classList.remove('error-container');
|
||||
function login(data = null) {
|
||||
els.login.inputContainers.username.classList.remove('error-input-container')
|
||||
els.login.inputContainers.password.classList.remove('error-input-container')
|
||||
|
||||
if (data === null)
|
||||
data = {
|
||||
'username': forms.login.inputs.username.value,
|
||||
'password': forms.login.inputs.password.value
|
||||
};
|
||||
username: els.login.inputs.username.value,
|
||||
password: els.login.inputs.password.value
|
||||
}
|
||||
|
||||
fetch(`${url_prefix}/api/auth/login`, {
|
||||
'method': 'POST',
|
||||
'headers': {'Content-Type': 'application/json'},
|
||||
'body': JSON.stringify(data)
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) return Promise.reject(response.status);
|
||||
return response.json();
|
||||
})
|
||||
sendAPI("POST", "/auth/login", {}, data, true, false)
|
||||
.then(json => {
|
||||
const new_stor = JSON.parse(localStorage.getItem('MIND'));
|
||||
new_stor.api_key = json.result.api_key;
|
||||
localStorage.setItem('MIND', JSON.stringify(new_stor));
|
||||
setLocalStorage({api_key: json.result.api_key})
|
||||
if (json.result.admin)
|
||||
window.location.href = `${url_prefix}/admin`;
|
||||
window.location.href = `${urlPrefix}/admin`
|
||||
else
|
||||
window.location.href = `${url_prefix}/reminders`;
|
||||
window.location.href = `${urlPrefix}/reminders`
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 404)
|
||||
forms.login.errors.username.classList.add('error-container');
|
||||
else if (e === 401)
|
||||
forms.login.errors.password.classList.add('error-container');
|
||||
if (e.status === 404)
|
||||
els.login.inputContainers.username.classList.add('error-input-container')
|
||||
else if (e.status === 401)
|
||||
els.login.inputContainers.password.classList.add('error-input-container')
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
console.log(e)
|
||||
})
|
||||
}
|
||||
|
||||
function create() {
|
||||
forms.create.inputs.username.classList.remove('error-input');
|
||||
forms.create.errors.username_invalid.classList.add('hidden');
|
||||
forms.create.errors.username_taken.classList.add('hidden');
|
||||
els.create.inputContainers.username.classList.remove('error-input-container')
|
||||
hide([els.create.errors.usernameInvalid, els.create.errors.usernameTaken])
|
||||
|
||||
const data = {
|
||||
'username': forms.create.inputs.username.value,
|
||||
'password': forms.create.inputs.password.value
|
||||
};
|
||||
fetch(`${url_prefix}/api/user/add`, {
|
||||
'method': 'POST',
|
||||
'headers': {'Content-Type': 'application/json'},
|
||||
'body': JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
if (json.error !== null) return Promise.reject(json.error);
|
||||
login(data);
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 'UsernameInvalid') {
|
||||
forms.create.inputs.username.classList.add('error-input');
|
||||
forms.create.errors.username_invalid.classList.remove('hidden');
|
||||
} else if (e === 'UsernameTaken') {
|
||||
forms.create.inputs.username.classList.add('error-input');
|
||||
forms.create.errors.username_taken.classList.remove('hidden');
|
||||
} else {
|
||||
console.log(e);
|
||||
};
|
||||
});
|
||||
};
|
||||
username: els.create.inputs.username.value,
|
||||
password: els.create.inputs.password.value
|
||||
}
|
||||
|
||||
function checkLogin() {
|
||||
fetch(`${url_prefix}/api/auth/status?api_key=${api_key}`)
|
||||
.then(response => {
|
||||
if (!response.ok) return Promise.reject(response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(json => {
|
||||
if (json.result.admin)
|
||||
window.location.href = `${url_prefix}/admin`;
|
||||
else
|
||||
window.location.href = `${url_prefix}/reminders`;
|
||||
})
|
||||
sendAPI("POST", "/user/add", {}, data)
|
||||
.then(json => login(data))
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
console.log('API key expired')
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
e.json().then(e => {
|
||||
if (e.error === 'UsernameInvalid') {
|
||||
els.create.errors.usernameInvalid.innerText = e.result.reason
|
||||
hide([], [els.create.errors.usernameInvalid])
|
||||
els.create.inputContainers.username.classList.add('error-input-container')
|
||||
|
||||
} else if (e.error === 'UsernameTaken') {
|
||||
hide([], [els.create.errors.usernameTaken])
|
||||
els.create.inputContainers.username.classList.add('error-input-container')
|
||||
|
||||
} else {
|
||||
console.log(e)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function checkAllowNewAccounts() {
|
||||
fetch(`${url_prefix}/api/settings`)
|
||||
.then(response => response.json())
|
||||
const cachedValue = getLocalStorage("allow_new_accounts_cache").allow_new_accounts_cache
|
||||
if (!cachedValue)
|
||||
hide([els.switchButton])
|
||||
|
||||
fetchAPI("/settings")
|
||||
.then(json => {
|
||||
if (!json.result.allow_new_accounts)
|
||||
document.querySelector('.switch-button').classList.add('hidden');
|
||||
});
|
||||
};
|
||||
hide([els.switchButton])
|
||||
else
|
||||
hide([], [els.switchButton])
|
||||
|
||||
// code run on load
|
||||
if (cachedValue !== json.result.allow_new_accounts)
|
||||
setLocalStorage({
|
||||
allow_new_accounts_cache: json.result.allow_new_accounts
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (localStorage.getItem('MIND') === null)
|
||||
localStorage.setItem('MIND', JSON.stringify(
|
||||
{'api_key': null, 'locale': 'en-GB', 'default_service': null}
|
||||
))
|
||||
if (apiKey !== null)
|
||||
checkLogin()
|
||||
|
||||
const url_prefix = document.getElementById('url_prefix').dataset.value;
|
||||
const api_key = JSON.parse(localStorage.getItem('MIND')).api_key;
|
||||
checkAllowNewAccounts()
|
||||
|
||||
checkLogin();
|
||||
checkAllowNewAccounts();
|
||||
|
||||
forms.login.form.action = 'javascript:login();';
|
||||
forms.create.form.action = 'javascript:create();';
|
||||
els.login.form.action = 'javascript:login();'
|
||||
els.create.form.action = 'javascript:create();'
|
||||
|
||||
@@ -88,11 +88,11 @@ function setNoNotificationServiceMsg(json) {
|
||||
LibEls.tab_container.querySelectorAll('.add-entry').forEach(ae => {
|
||||
ae.classList.remove('error', 'error-icon');
|
||||
if (ae.id === 'add-reminder')
|
||||
ae.onclick = e => showAdd(Types.reminder);
|
||||
ae.onclick = e => showAdd(reminderTypes.reminder);
|
||||
else if (ae.id === 'add-static-reminder')
|
||||
ae.onclick = e => showAdd(Types.static_reminder);
|
||||
ae.onclick = e => showAdd(reminderTypes.static_reminder);
|
||||
else if (ae.id === 'add-template')
|
||||
ae.onclick = e => showAdd(Types.template);
|
||||
ae.onclick = e => showAdd(reminderTypes.template);
|
||||
});
|
||||
|
||||
} else {
|
||||
@@ -104,7 +104,7 @@ function setNoNotificationServiceMsg(json) {
|
||||
};
|
||||
|
||||
function fillNotificationServices() {
|
||||
fetch(`${url_prefix}/api/notificationservices?api_key=${api_key}`)
|
||||
fetch(`${urlPrefix}/api/notificationservices?api_key=${apiKey}`)
|
||||
.then(response => {
|
||||
if (!response.ok) return Promise.reject(response.status);
|
||||
return response.json();
|
||||
@@ -116,7 +116,7 @@ function fillNotificationServices() {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
@@ -132,7 +132,7 @@ function saveService(id) {
|
||||
'title': row.querySelector(`td.title-column > input`).value,
|
||||
'url': row.querySelector(`td.url-column > input`).value
|
||||
};
|
||||
fetch(`${url_prefix}/api/notificationservices/${id}?api_key=${api_key}`, {
|
||||
fetch(`${urlPrefix}/api/notificationservices/${id}?api_key=${apiKey}`, {
|
||||
'method': 'PUT',
|
||||
'headers': {'Content-Type': 'application/json'},
|
||||
'body': JSON.stringify(data)
|
||||
@@ -144,7 +144,7 @@ function saveService(id) {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else if (e === 400) {
|
||||
save_button.classList.add('error-icon');
|
||||
save_button.title = 'Invalid Apprise URL';
|
||||
@@ -155,7 +155,7 @@ function saveService(id) {
|
||||
|
||||
function deleteService(id, delete_reminders_using=false) {
|
||||
const row = document.querySelector(`tr[data-id="${id}"]`);
|
||||
fetch(`${url_prefix}/api/notificationservices/${id}?api_key=${api_key}&delete_reminders_using=${delete_reminders_using}`, {
|
||||
fetch(`${urlPrefix}/api/notificationservices/${id}?api_key=${apiKey}&delete_reminders_using=${delete_reminders_using}`, {
|
||||
'method': 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
@@ -165,14 +165,14 @@ function deleteService(id, delete_reminders_using=false) {
|
||||
row.remove();
|
||||
fillNotificationServices();
|
||||
if (delete_reminders_using) {
|
||||
fillLibrary(Types.reminder);
|
||||
fillLibrary(Types.static_reminder);
|
||||
fillLibrary(Types.template);
|
||||
fillLibrary(reminderTypes.reminder);
|
||||
fillLibrary(reminderTypes.static_reminder);
|
||||
fillLibrary(reminderTypes.template);
|
||||
};
|
||||
})
|
||||
.catch(e => {
|
||||
if (e.error === 'ApiKeyExpired' || e.error === 'ApiKeyInvalid')
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
|
||||
else if (e.error === 'NotificationServiceInUse') {
|
||||
const delete_reminders = confirm(
|
||||
@@ -200,7 +200,7 @@ function showServiceList(e) {
|
||||
if (notification_services !== null)
|
||||
return;
|
||||
|
||||
fetch(`${url_prefix}/api/notificationservices/available?api_key=${api_key}`)
|
||||
fetch(`${urlPrefix}/api/notificationservices/available?api_key=${apiKey}`)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
notification_services = json.result;
|
||||
@@ -322,7 +322,7 @@ function createEntriesList(token) {
|
||||
|
||||
const add_button = document.createElement('button');
|
||||
add_button.type = 'button';
|
||||
add_button.innerHTML = Icons.add;
|
||||
add_button.innerHTML = icons.add;
|
||||
add_button.onclick = e => toggleAddRow(add_row);
|
||||
entries_list.appendChild(add_button);
|
||||
|
||||
@@ -568,7 +568,7 @@ function testService() {
|
||||
test_button.title = 'Required field missing';
|
||||
return;
|
||||
};
|
||||
fetch(`${url_prefix}/api/notificationservices/test?api_key=${api_key}`, {
|
||||
fetch(`${urlPrefix}/api/notificationservices/test?api_key=${apiKey}`, {
|
||||
'method': 'POST',
|
||||
'headers': {'Content-Type': 'application/json'},
|
||||
'body': JSON.stringify(data)
|
||||
@@ -582,7 +582,7 @@ function testService() {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else if (e === 400) {
|
||||
test_button.classList.add('error-input');
|
||||
test_button.title = 'Invalid Apprise URL';
|
||||
@@ -624,7 +624,7 @@ function addService() {
|
||||
add_button.title = 'Required field missing';
|
||||
return;
|
||||
};
|
||||
fetch(`${url_prefix}/api/notificationservices?api_key=${api_key}`, {
|
||||
fetch(`${urlPrefix}/api/notificationservices?api_key=${apiKey}`, {
|
||||
'method': 'POST',
|
||||
'headers': {'Content-Type': 'application/json'},
|
||||
'body': JSON.stringify(data)
|
||||
@@ -641,7 +641,7 @@ function addService() {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else if (e === 400) {
|
||||
add_button.classList.add('error-input');
|
||||
add_button.title = 'Invalid Apprise URL';
|
||||
|
||||
38
frontend/static/js/reminders.js
Normal file
38
frontend/static/js/reminders.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const reminderTypes = {
|
||||
reminder: document.getElementById('reminder-tab'),
|
||||
static_reminder: document.getElementById('static-reminder-tab'),
|
||||
template: document.getElementById('template-tab')
|
||||
}
|
||||
|
||||
const infoClasses = [
|
||||
'show-add-reminder', 'show-add-template', 'show-add-static-reminder',
|
||||
'show-edit-reminder', 'show-edit-template', 'show-edit-static-reminder'
|
||||
]
|
||||
|
||||
function showWindow(id) {
|
||||
const window_container = document.querySelector('.window-container')
|
||||
if (id === "home") {
|
||||
window_container.classList.remove(
|
||||
'show-window',
|
||||
'inter-window-ani'
|
||||
)
|
||||
} else {
|
||||
const extra_window_container = document.querySelector('.extra-window-container')
|
||||
|
||||
const offset = [
|
||||
...extra_window_container.children
|
||||
].indexOf(document.getElementById(id)) * -100
|
||||
|
||||
extra_window_container.style.setProperty(
|
||||
'--y-offset',
|
||||
`${offset}%`
|
||||
)
|
||||
window_container.classList.add('show-window')
|
||||
setTimeout(
|
||||
() => window_container.classList.add('inter-window-ani'),
|
||||
constants.windowAnimationDuration
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
checkLogin()
|
||||
@@ -13,7 +13,7 @@ function loadSettings() {
|
||||
|
||||
function updateLocale(e) {
|
||||
setLocalStorage({'locale': e.target.value});
|
||||
fillLibrary(Types.reminder);
|
||||
fillLibrary(reminderTypes.reminder);
|
||||
};
|
||||
|
||||
function updateDefaultService(e) {
|
||||
@@ -25,7 +25,7 @@ function changePassword() {
|
||||
const data = {
|
||||
'new_password': document.getElementById('password-input').value
|
||||
};
|
||||
fetch(`${url_prefix}/api/user?api_key=${api_key}`, {
|
||||
fetch(`${urlPrefix}/api/user?api_key=${apiKey}`, {
|
||||
'method': 'PUT',
|
||||
'headers': {'Content-Type': 'application/json'},
|
||||
'body': JSON.stringify(data)
|
||||
@@ -36,18 +36,18 @@ function changePassword() {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
|
||||
function deleteAccount() {
|
||||
fetch(`${url_prefix}/api/user?api_key=${api_key}`, {
|
||||
fetch(`${urlPrefix}/api/user?api_key=${apiKey}`, {
|
||||
'method': 'DELETE'
|
||||
})
|
||||
.then(response => {
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -19,21 +19,21 @@ function showAdd(type) {
|
||||
|
||||
const cl = document.getElementById('info').classList;
|
||||
cl.forEach(c => {
|
||||
if (InfoClasses.includes(c)) cl.remove(c)
|
||||
if (infoClasses.includes(c)) cl.remove(c)
|
||||
});
|
||||
document.querySelector('.options > button[type="submit"]').innerText = 'Add';
|
||||
|
||||
document.querySelector('#test-reminder > div:first-child').innerText = 'Test';
|
||||
const title = document.querySelector('#info h2');
|
||||
if (type === Types.reminder) {
|
||||
if (type === reminderTypes.reminder) {
|
||||
cl.add('show-add-reminder');
|
||||
title.innerText = 'Add a reminder';
|
||||
inputs.time.setAttribute('required', '');
|
||||
} else if (type === Types.template) {
|
||||
} else if (type === reminderTypes.template) {
|
||||
cl.add('show-add-template');
|
||||
title.innerText = 'Add a template';
|
||||
inputs.time.removeAttribute('required');
|
||||
} else if (type === Types.static_reminder) {
|
||||
} else if (type === reminderTypes.static_reminder) {
|
||||
cl.add('show-add-static-reminder');
|
||||
title.innerText = 'Add a static reminder';
|
||||
inputs.time.removeAttribute('required');
|
||||
@@ -44,15 +44,15 @@ function showAdd(type) {
|
||||
|
||||
function showEdit(id, type) {
|
||||
let url;
|
||||
if (type === Types.reminder) {
|
||||
url = `${url_prefix}/api/reminders/${id}?api_key=${api_key}`;
|
||||
if (type === reminderTypes.reminder) {
|
||||
url = `${urlPrefix}/api/reminders/${id}?api_key=${apiKey}`;
|
||||
inputs.time.setAttribute('required', '');
|
||||
} else if (type === Types.template) {
|
||||
url = `${url_prefix}/api/templates/${id}?api_key=${api_key}`;
|
||||
} else if (type === reminderTypes.template) {
|
||||
url = `${urlPrefix}/api/templates/${id}?api_key=${apiKey}`;
|
||||
inputs.time.removeAttribute('required');
|
||||
type_buttons.repeat_interval.removeAttribute('required');
|
||||
} else if (type === Types.static_reminder) {
|
||||
url = `${url_prefix}/api/staticreminders/${id}?api_key=${api_key}`;
|
||||
} else if (type === reminderTypes.static_reminder) {
|
||||
url = `${urlPrefix}/api/staticreminders/${id}?api_key=${apiKey}`;
|
||||
document.getElementById('test-reminder').classList.remove('show-sent');
|
||||
inputs.time.removeAttribute('required');
|
||||
type_buttons.repeat_interval.removeAttribute('required');
|
||||
@@ -69,7 +69,7 @@ function showEdit(id, type) {
|
||||
selectColor(json.result.color || colors[0]);
|
||||
inputs.title.value = json.result.title;
|
||||
|
||||
if (type === Types.reminder) {
|
||||
if (type === reminderTypes.reminder) {
|
||||
inputs.enabled.checked = json.result.enabled;
|
||||
var trigger_date = new Date(
|
||||
(json.result.time
|
||||
@@ -86,7 +86,7 @@ function showEdit(id, type) {
|
||||
c => c.checked = json.result.notification_services.includes(parseInt(c.dataset.id))
|
||||
);
|
||||
|
||||
if (type == Types.reminder) {
|
||||
if (type == reminderTypes.reminder) {
|
||||
if (json.result.repeat_interval !== null) {
|
||||
toggleRepeated();
|
||||
type_buttons.repeat_interval.value = json.result.repeat_interval;
|
||||
@@ -107,27 +107,27 @@ function showEdit(id, type) {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
const cl = document.getElementById('info').classList;
|
||||
cl.forEach(c => {
|
||||
if (InfoClasses.includes(c)) cl.remove(c)
|
||||
if (infoClasses.includes(c)) cl.remove(c)
|
||||
});
|
||||
document.querySelector('.options > button[type="submit"]').innerText = 'Save';
|
||||
const title = document.querySelector('#info h2');
|
||||
const test_text = document.querySelector('#test-reminder > div:first-child');
|
||||
if (type === Types.reminder) {
|
||||
if (type === reminderTypes.reminder) {
|
||||
cl.add('show-edit-reminder');
|
||||
title.innerText = 'Edit a reminder';
|
||||
test_text.innerText = 'Test';
|
||||
} else if (type === Types.template) {
|
||||
} else if (type === reminderTypes.template) {
|
||||
cl.add('show-edit-template');
|
||||
title.innerText = 'Edit a template';
|
||||
test_text.innerText = 'Test';
|
||||
} else if (type === Types.static_reminder) {
|
||||
} else if (type === reminderTypes.static_reminder) {
|
||||
cl.add('show-edit-static-reminder');
|
||||
title.innerText = 'Edit a static reminder';
|
||||
test_text.innerText = 'Trigger';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
function loadTemplateSelection() {
|
||||
fetch(`${url_prefix}/api/templates?api_key=${api_key}`)
|
||||
fetch(`${urlPrefix}/api/templates?api_key=${apiKey}`)
|
||||
.then(response => {
|
||||
if (!response.ok) return Promise.reject(response.status);
|
||||
return response.json();
|
||||
@@ -17,7 +17,7 @@ function loadTemplateSelection() {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
@@ -33,7 +33,7 @@ function applyTemplate() {
|
||||
selectColor(colors[0]);
|
||||
|
||||
} else {
|
||||
fetch(`${url_prefix}/api/templates/${inputs.template.value}?api_key=${api_key}`)
|
||||
fetch(`${urlPrefix}/api/templates/${inputs.template.value}?api_key=${apiKey}`)
|
||||
.then(response => {
|
||||
if (!response.ok) return Promise.reject(response.status);
|
||||
else return response.json();
|
||||
@@ -48,7 +48,7 @@ function applyTemplate() {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
@@ -102,7 +102,7 @@ function testReminder() {
|
||||
let url;
|
||||
if (cl.contains('show-edit-static-reminder')) {
|
||||
// Trigger static reminder
|
||||
url = `${url_prefix}/api/staticreminders/${r_id}?api_key=${api_key}`;
|
||||
url = `${urlPrefix}/api/staticreminders/${r_id}?api_key=${apiKey}`;
|
||||
} else {
|
||||
// Test reminder draft
|
||||
if (inputs.title.value === '') {
|
||||
@@ -132,7 +132,7 @@ function testReminder() {
|
||||
'text': inputs.text.value
|
||||
};
|
||||
headers.body = JSON.stringify(data);
|
||||
url = `${url_prefix}/api/reminders/test?api_key=${api_key}`;
|
||||
url = `${urlPrefix}/api/reminders/test?api_key=${apiKey}`;
|
||||
};
|
||||
fetch(url, headers)
|
||||
.then(response => {
|
||||
@@ -141,7 +141,7 @@ function testReminder() {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
@@ -153,13 +153,13 @@ function deleteInfo() {
|
||||
const cl = document.getElementById('info').classList;
|
||||
if (cl.contains('show-edit-reminder')) {
|
||||
// Delete reminder
|
||||
url = `${url_prefix}/api/reminders/${e_id}?api_key=${api_key}`;
|
||||
url = `${urlPrefix}/api/reminders/${e_id}?api_key=${apiKey}`;
|
||||
} else if (cl.contains('show-edit-template')) {
|
||||
// Delete template
|
||||
url = `${url_prefix}/api/templates/${e_id}?api_key=${api_key}`;
|
||||
url = `${urlPrefix}/api/templates/${e_id}?api_key=${apiKey}`;
|
||||
} else if (cl.contains('show-edit-static-reminder')) {
|
||||
// Delete static reminder
|
||||
url = `${url_prefix}/api/staticreminders/${e_id}?api_key=${api_key}`;
|
||||
url = `${urlPrefix}/api/staticreminders/${e_id}?api_key=${apiKey}`;
|
||||
} else return;
|
||||
|
||||
fetch(url, {'method': 'DELETE'})
|
||||
@@ -168,20 +168,20 @@ function deleteInfo() {
|
||||
|
||||
if (cl.contains('show-edit-reminder')) {
|
||||
// Delete reminder
|
||||
fillLibrary(Types.reminder);
|
||||
fillLibrary(reminderTypes.reminder);
|
||||
} else if (cl.contains('show-edit-template')) {
|
||||
// Delete template
|
||||
fillLibrary(Types.template);
|
||||
fillLibrary(reminderTypes.template);
|
||||
loadTemplateSelection();
|
||||
} else if (cl.contains('show-edit-static-reminder')) {
|
||||
// Delete static reminder
|
||||
fillLibrary(Types.static_reminder);
|
||||
fillLibrary(reminderTypes.static_reminder);
|
||||
};
|
||||
showWindow("home");
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401)
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
else
|
||||
console.log(e);
|
||||
});
|
||||
@@ -244,24 +244,24 @@ function submitInfo() {
|
||||
};
|
||||
};
|
||||
|
||||
fetch_data.url = `${url_prefix}/api/reminders?api_key=${api_key}`;
|
||||
fetch_data.url = `${urlPrefix}/api/reminders?api_key=${apiKey}`;
|
||||
fetch_data.method = 'POST';
|
||||
fetch_data.call_back = () => fillLibrary(Types.reminder);
|
||||
fetch_data.call_back = () => fillLibrary(reminderTypes.reminder);
|
||||
|
||||
} else if (cl.contains('show-add-template')) {
|
||||
// Add template
|
||||
fetch_data.url = `${url_prefix}/api/templates?api_key=${api_key}`;
|
||||
fetch_data.url = `${urlPrefix}/api/templates?api_key=${apiKey}`;
|
||||
fetch_data.method = 'POST';
|
||||
fetch_data.call_back = () => {
|
||||
loadTemplateSelection();
|
||||
fillLibrary(Types.template);
|
||||
fillLibrary(reminderTypes.template);
|
||||
};
|
||||
|
||||
} else if (cl.contains('show-add-static-reminder')) {
|
||||
// Add static reminder
|
||||
fetch_data.url = `${url_prefix}/api/staticreminders?api_key=${api_key}`;
|
||||
fetch_data.url = `${urlPrefix}/api/staticreminders?api_key=${apiKey}`;
|
||||
fetch_data.method = 'POST';
|
||||
fetch_data.call_back = () => fillLibrary(Types.static_reminder);
|
||||
fetch_data.call_back = () => fillLibrary(reminderTypes.static_reminder);
|
||||
|
||||
} else if (cl.contains('show-edit-reminder')) {
|
||||
// Edit reminder
|
||||
@@ -289,24 +289,24 @@ function submitInfo() {
|
||||
};
|
||||
};
|
||||
|
||||
fetch_data.url = `${url_prefix}/api/reminders/${e_id}?api_key=${api_key}`;
|
||||
fetch_data.url = `${urlPrefix}/api/reminders/${e_id}?api_key=${apiKey}`;
|
||||
fetch_data.method = 'PUT';
|
||||
fetch_data.call_back = () => fillLibrary(Types.reminder);
|
||||
fetch_data.call_back = () => fillLibrary(reminderTypes.reminder);
|
||||
|
||||
} else if (cl.contains('show-edit-template')) {
|
||||
// Edit template
|
||||
fetch_data.url = `${url_prefix}/api/templates/${e_id}?api_key=${api_key}`;
|
||||
fetch_data.url = `${urlPrefix}/api/templates/${e_id}?api_key=${apiKey}`;
|
||||
fetch_data.method = 'PUT';
|
||||
fetch_data.call_back = () => {
|
||||
loadTemplateSelection();
|
||||
fillLibrary(Types.template);
|
||||
fillLibrary(reminderTypes.template);
|
||||
};
|
||||
|
||||
} else if (cl.contains('show-edit-static-reminder')) {
|
||||
// Edit a static reminder
|
||||
fetch_data.url = `${url_prefix}/api/staticreminders/${e_id}?api_key=${api_key}`;
|
||||
fetch_data.url = `${urlPrefix}/api/staticreminders/${e_id}?api_key=${apiKey}`;
|
||||
fetch_data.method = 'PUT';
|
||||
fetch_data.call_back = () => fillLibrary(Types.static_reminder);
|
||||
fetch_data.call_back = () => fillLibrary(reminderTypes.static_reminder);
|
||||
|
||||
} else return;
|
||||
|
||||
@@ -323,7 +323,7 @@ function submitInfo() {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 401) {
|
||||
window.location.href = `${url_prefix}/`;
|
||||
window.location.href = `${urlPrefix}/`;
|
||||
} else if (e === 400) {
|
||||
inputs.time.classList.add('error-input');
|
||||
inputs.time.title = 'Time is in the past';
|
||||
|
||||
@@ -25,40 +25,265 @@
|
||||
<img src="{{ url_for('static', filename='img/favicon.svg') }}" alt="">
|
||||
</header>
|
||||
<main>
|
||||
<section class="action-buttons">
|
||||
<button id="save-button" title="Save Settings" type="submit" form="settings-form">
|
||||
<dialog id="reset-settings-dialog">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog-header">
|
||||
<h2>Reset Settings</h2>
|
||||
</div>
|
||||
<div class="dialog-content">
|
||||
<form id="reset-settings-form">
|
||||
<div class="table-container">
|
||||
<table id="reset-table" class="entries-table">
|
||||
<tbody id="reset-list">
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="allow_new_accounts"></td>
|
||||
<td>Allow New Accounts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="login_time"></td>
|
||||
<td>Login Time</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="login_time_reset"></td>
|
||||
<td>Login Time Trigger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="host"></td>
|
||||
<td>Host</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="port"></td>
|
||||
<td>Port</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="url_prefix"></td>
|
||||
<td>URL Prefix</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="log_level"></td>
|
||||
<td>Logging Level</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="db_backup_interval"></td>
|
||||
<td>Database Backup Interval</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="db_backup_amount"></td>
|
||||
<td>Database Backup Retention</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" data-setting="db_backup_folder"></td>
|
||||
<td>Database Backup Folder</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button id="close-reset-settings" class="input-style">Cancel</button>
|
||||
<button type="submit" id="submit-reset-button" form="reset-settings-form" class="input-style">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog id="add-user-dialog">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog-header">
|
||||
<h2>Add User</h2>
|
||||
</div>
|
||||
<div class="dialog-content">
|
||||
<form id="add-user-form">
|
||||
<div class="checked-input-container">
|
||||
<input type="text" id="add-user-username-input" autocomplete="username" placeholder="Username" class="input-style" required>
|
||||
<p id="add-invalid-username-error">Username invalid</p>
|
||||
<p id="add-taken-username-error">Username already taken</p>
|
||||
</div>
|
||||
|
||||
<input type="password" id="add-user-password-input" autocomplete="new-password" placeholder="Password" class="input-style" required>
|
||||
</form>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button id="close-add-user" class="input-style">Cancel</button>
|
||||
<button type="submit" form="add-user-form" class="input-style">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog id="edit-user-dialog">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog-header">
|
||||
<h2>Edit User</h2>
|
||||
</div>
|
||||
<div class="dialog-content">
|
||||
<p>Leave fields empty to not change for <span id="username-edit-user"></span></p>
|
||||
<form id="edit-user-form">
|
||||
<div class="checked-input-container">
|
||||
<input type="text" id="edit-user-username-input" autocomplete="username" placeholder="New username" class="input-style">
|
||||
<p id="edit-invalid-username-error">Username invalid</p>
|
||||
<p id="edit-taken-username-error">Username already taken</p>
|
||||
</div>
|
||||
|
||||
<input type="password" id="edit-user-password-input" autocomplete="new-password" placeholder="New password" class="input-style">
|
||||
</form>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button id="close-edit-user" class="input-style">Cancel</button>
|
||||
<button type="submit" form="edit-user-form" class="input-style">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog id="delete-user-dialog">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog-content">
|
||||
<p>Are you sure you want to delete the user <span id="username-delete-user"></span>?</p>
|
||||
<div class="confirm-container">
|
||||
<button id="confirm-delete-user" class="input-style">Confirm</button>
|
||||
<button id="close-delete-user" class="input-style">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog id="upload-db-dialog">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog-header">
|
||||
<h2>Upload Database</h2>
|
||||
</div>
|
||||
<div class="dialog-content">
|
||||
<p>You will be uploading a database file. Login into MIND within one minute to keep the new database, or the upload will automatically be reverted.</p>
|
||||
<form id="upload-db-form">
|
||||
<div class="table-container">
|
||||
<table class="collaps-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="database-file-input">Database File</label></td>
|
||||
<td>
|
||||
<div class="checked-input-container">
|
||||
<input type="file" id="database-file-input" class="input-style" required>
|
||||
<p>Invalid database file</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="copy-hosting-input-upload">Keep Hosting Settings</label></td>
|
||||
<td>
|
||||
<input type="checkbox" id="copy-hosting-input-upload">
|
||||
<p>Keep the current hosting settings instead of using the settings in the uploaded database when importing.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button id="close-upload-db" class="input-style">Cancel</button>
|
||||
<button type="submit" form="upload-db-form" class="input-style">Import</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog id="import-db-dialog">
|
||||
<div class="dialog-container">
|
||||
<div class="dialog-header">
|
||||
<h2>Import Database</h2>
|
||||
</div>
|
||||
<div class="dialog-content">
|
||||
<p>You will be importing database backup <span id="db-backup-name"></span>, created on <span id="db-creation-date"></span>.
|
||||
Login into MIND within one minute to keep the new database, or the import will automatically be reverted.</p>
|
||||
<form id="import-db-form">
|
||||
<div class="table-container">
|
||||
<table class="collaps-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="copy-hosting-input-import">Keep Hosting Settings</label></td>
|
||||
<td>
|
||||
<input type="checkbox" id="copy-hosting-input-import">
|
||||
<p>Keep the current hosting settings instead of using the settings in the selected database backup when importing.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button id="close-import-db" class="input-style">Cancel</button>
|
||||
<button type="submit" form="import-db-form" class="input-style">Import</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<form id="settings-form"></form>
|
||||
<section class="action-buttons" aria-label="Actions">
|
||||
<button id="save-button" aria-label="Save settings" title="Save Settings" type="submit" form="settings-form">
|
||||
<span id="changes-count" data-count="0">0 changes</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="512" height="512">
|
||||
<path d="M12,10a4,4,0,1,0,4,4A4,4,0,0,0,12,10Zm0,6a2,2,0,1,1,2-2A2,2,0,0,1,12,16Z"/>
|
||||
<path d="M22.536,4.122,19.878,1.464A4.966,4.966,0,0,0,16.343,0H5A5.006,5.006,0,0,0,0,5V19a5.006,5.006,0,0,0,5,5H19a5.006,5.006,0,0,0,5-5V7.657A4.966,4.966,0,0,0,22.536,4.122ZM17,2.08V3a3,3,0,0,1-3,3H10A3,3,0,0,1,7,3V2h9.343A2.953,2.953,0,0,1,17,2.08ZM22,19a3,3,0,0,1-3,3H5a3,3,0,0,1-3-3V5A3,3,0,0,1,5,2V3a5.006,5.006,0,0,0,5,5h4a4.991,4.991,0,0,0,4.962-4.624l2.16,2.16A3.02,3.02,0,0,1,22,7.657Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="logout-button" aria-label="Log out of MIND" title="Logout">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 24 24" style="enable-background:new 0 0 512 512" xml:space="preserve">
|
||||
<button id="logout-button" aria-label="Log out of MIND" title="Log out of MIND">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M11.476,15a1,1,0,0,0-1,1v3a3,3,0,0,1-3,3H5a3,3,0,0,1-3-3V5A3,3,0,0,1,5,2H7.476a3,3,0,0,1,3,3V8a1,1,0,0,0,2,0V5a5.006,5.006,0,0,0-5-5H5A5.006,5.006,0,0,0,0,5V19a5.006,5.006,0,0,0,5,5H7.476a5.006,5.006,0,0,0,5-5V16A1,1,0,0,0,11.476,15Z"></path>
|
||||
<path d="M22.867,9.879,18.281,5.293a1,1,0,1,0-1.414,1.414l4.262,4.263L6,11a1,1,0,0,0,0,2H6l15.188-.031-4.323,4.324a1,1,0,1,0,1.414,1.414l4.586-4.586A3,3,0,0,0,22.867,9.879Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<span>Log out</span>
|
||||
</button>
|
||||
</section>
|
||||
<div class="form-container">
|
||||
<form id="settings-form">
|
||||
<h2>Authentication</h2>
|
||||
<div class="settings-table-container">
|
||||
<table class="settings-table">
|
||||
<div id="grid-container">
|
||||
<section id="about-section">
|
||||
<h2>About</h2>
|
||||
<div class="table-container">
|
||||
<table class="collaps-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="allow-new-accounts-input">Allow New Accounts</label></td>
|
||||
<th>MIND Version</th>
|
||||
<td id="mind-version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Python Version</th>
|
||||
<td id="python-version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Database Version</th>
|
||||
<td id="db-version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Database Location</th>
|
||||
<td id="db-location"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Data Folder</th>
|
||||
<td id="data-folder"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section id="power-section">
|
||||
<h2>Power</h2>
|
||||
<div class="table-container">
|
||||
<button id="restart-button" class="input-style">Restart</button>
|
||||
<button id="shutdown-button" class="input-style">Shutdown</button>
|
||||
</div>
|
||||
</section>
|
||||
<section id="auth-section">
|
||||
<h2>Authentication</h2>
|
||||
<div class="table-container">
|
||||
<table class="collaps-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><label for="allow-new-accounts-input">Allow New Accounts</label></th>
|
||||
<td>
|
||||
<input type="checkbox" id="allow-new-accounts-input">
|
||||
<input type="checkbox" id="allow-new-accounts-input" form="settings-form">
|
||||
<p>Allow users to register a new account. The admin can always add a new account from this panel.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="login-time-input">Login Time</label></td>
|
||||
<th><label for="login-time-input">Login Time</label></th>
|
||||
<td>
|
||||
<div class="number-input">
|
||||
<input type="number" id="login-time-input" min="1" max="43200" required>
|
||||
<div class="input-style">
|
||||
<input type="number" id="login-time-input" form="settings-form" min="1" max="43200" required>
|
||||
<p>Min</p>
|
||||
</div>
|
||||
<p>For how long users stay logged in before having to authenticate again. Between 1 minute and 1 month.</p>
|
||||
@@ -66,9 +291,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="login-time-reset-input">Login Time Trigger</label></td>
|
||||
<th><label for="login-time-reset-input">Login Time Trigger</label></th>
|
||||
<td>
|
||||
<select id="login-time-reset-input">
|
||||
<select id="login-time-reset-input" class="input-style" form="settings-form" required>
|
||||
<option value="true">After Last Use</option>
|
||||
<option value="false">After Login</option>
|
||||
</select>
|
||||
@@ -78,195 +303,163 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h2>Logging</h2>
|
||||
<div class="settings-table-container">
|
||||
<table class="settings-table">
|
||||
</section>
|
||||
<section id="hosting-section">
|
||||
<h2>Hosting</h2>
|
||||
<div class="table-container">
|
||||
<table class="collaps-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="log-level-input">Logging Level</label></td>
|
||||
<th><label for="host-input">Host</label></th>
|
||||
<td>
|
||||
<select id="log-level-input">
|
||||
<option value="20">Info</option>
|
||||
<option value="10">Debug</option>
|
||||
</select>
|
||||
<input type="text" id="host-input" class="input-style" form="settings-form" spellcheck="false" required>
|
||||
<p>Valid IPv4 address (default is '0.0.0.0' for all available interfaces).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="port-input">Port</label></th>
|
||||
<td>
|
||||
<input type="number" id="port-input" class="input-style" form="settings-form" min="1" max="65535" required>
|
||||
<p>The port used to access the web UI (default is '8080').</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="url-prefix-input">URL Prefix</label></th>
|
||||
<td>
|
||||
<input type="text" id="url-prefix-input" class="input-style" form="settings-form" spellcheck="false">
|
||||
<p>For reverse proxy support (default is empty).</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="database-container">
|
||||
<button id="download-logs-button" type="button">Download Debug Logs</button>
|
||||
</div>
|
||||
<h2>Database Backups</h2>
|
||||
<div class="settings-table-container">
|
||||
<table class="settings-table">
|
||||
</section>
|
||||
<section id="logging-section">
|
||||
<h2>Logging and Resetting</h2>
|
||||
<div class="table-container">
|
||||
<table class="collaps-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="db-backup-interval-input">Database Backup Interval</label></td>
|
||||
<th><label for="log-level-input">Logging Level</label></th>
|
||||
<td>
|
||||
<div class="number-input">
|
||||
<input type="number" id="db-backup-interval-input" min="1" required>
|
||||
<select id="log-level-input" class="input-style" form="settings-form" required>
|
||||
<option value="20">Info</option>
|
||||
<option value="10">Debug</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="download-logs-button">Download Logs</label></th>
|
||||
<td>
|
||||
<button id="download-logs-button" class="input-style">Download Debug Logs</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="open-reset-button">Reset Setting</label></th>
|
||||
<td>
|
||||
<button id="open-reset-button" class="input-style">Reset Setting</button>
|
||||
<p>Opens a window to select settings for which to reset their value.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section id="backup-settings-section">
|
||||
<h2>Database Backup Settings</h2>
|
||||
<div class="table-container">
|
||||
<table class="collaps-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><label for="db-backup-interval-input">Database Backup Interval</label></th>
|
||||
<td>
|
||||
<div class="input-style">
|
||||
<input type="number" id="db-backup-interval-input" form="settings-form" min="1" required>
|
||||
<p>Hours</p>
|
||||
</div>
|
||||
<p>How often to make a backup of the database.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="db-backup-amount-input">Database Backup Retention</label></td>
|
||||
<th><label for="db-backup-amount-input">Database Backup Retention</label></th>
|
||||
<td>
|
||||
<div class="number-input">
|
||||
<input type="number" id="db-backup-amount-input" min="1" required>
|
||||
<div class="input-style">
|
||||
<input type="number" id="db-backup-amount-input" form="settings-form" min="1" required>
|
||||
<p>Backups</p>
|
||||
</div>
|
||||
<p>How many backups to keep. The oldest one will be removed if needed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="db-backup-folder-input">Database Backup Folder</label></td>
|
||||
<th><label for="db-backup-folder-input">Database Backup Folder</label></th>
|
||||
<td>
|
||||
<input type="text" id="db-backup-folder-input" required>
|
||||
<div class="checked-input-container">
|
||||
<input type="text" id="db-backup-folder-input" class="input-style long-input-style" form="settings-form" spellcheck="false" required>
|
||||
<p>Path doesn't exist or isn't a folder</p>
|
||||
</div>
|
||||
<p>The folder to store the backups in.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
<form id="hosting-form">
|
||||
<h2>Hosting</h2>
|
||||
<div class="settings-table-container">
|
||||
<table class="settings-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="host-input">Host</label></td>
|
||||
<td>
|
||||
<input type="text" id="host-input" required>
|
||||
<p>Valid IPv4 address (default is '0.0.0.0' for all available interfaces).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="port-input">Port</label></td>
|
||||
<td>
|
||||
<input type="number" id="port-input" min="1" max="65535" required>
|
||||
<p>The port used to access the web UI (default is '8080').</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="url-prefix-input">URL Prefix</label></td>
|
||||
<td>
|
||||
<input type="text" id="url-prefix-input">
|
||||
<p>For reverse proxy support (default is empty).</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit" id="save-hosting-button">Save and Restart</button>
|
||||
</div>
|
||||
<p>IMPORTANT: Login into MIND within one minute to keep the new database, or the import will automatically be reverted.
|
||||
See <a href="https://casvt.github.io/MIND/general_info/admin_panel#database">the documentation</a> for more information.</p>
|
||||
</form>
|
||||
<h2>User Management</h2>
|
||||
<div class="add-user-container">
|
||||
<button id="add-user-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
</section>
|
||||
<section id="user-section">
|
||||
<h2>User Management</h2>
|
||||
<div class="add-item-container">
|
||||
<button id="add-user-button" class="input-style">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M480,224H288V32c0-17.673-14.327-32-32-32s-32,14.327-32,32v192H32c-17.673,0-32,14.327-32,32s14.327,32,32,32h192v192 c0,17.673,14.327,32,32,32s32-14.327,32-32V288h192c17.673,0,32-14.327,32-32S497.673,224,480,224z"></path>
|
||||
<g>
|
||||
<path d="M480,224H288V32c0-17.673-14.327-32-32-32s-32,14.327-32,32v192H32c-17.673,0-32,14.327-32,32s14.327,32,32,32h192v192 c0,17.673,14.327,32,32,32s32-14.327,32-32V288h192c17.673,0,32-14.327,32-32S497.673,224,480,224z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="user-table-container">
|
||||
<form id="add-user-form"></form>
|
||||
<table id="user-table">
|
||||
<thead>
|
||||
<th>User</th>
|
||||
<th></th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody id="add-user-row" class="hidden">
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="new-username-input" form="add-user-form" placeholder="Username" required>
|
||||
</td>
|
||||
<td>
|
||||
<input type="password" id="new-password-input" form="add-user-form" placeholder="Password" required>
|
||||
</td>
|
||||
<td>
|
||||
<button type="submit" form="add-user-form">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M480,224H288V32c0-17.673-14.327-32-32-32s-32,14.327-32,32v192H32c-17.673,0-32,14.327-32,32s14.327,32,32,32h192v192 c0,17.673,14.327,32,32,32s32-14.327,32-32V288h192c17.673,0,32-14.327,32-32S497.673,224,480,224z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody id="user-list">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h2>Database</h2>
|
||||
<form id="upload-database-form">
|
||||
<div class="settings-table-container">
|
||||
<table class="settings-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="copy-hosting-input">Keep Hosting Settings</label></td>
|
||||
<td>
|
||||
<input type="checkbox" id="copy-hosting-input">
|
||||
<p>Keep the current hosting settings instead of using the settings in the uploaded database when importing.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="database-file-input">Database File</label></td>
|
||||
<td>
|
||||
<input type="file" id="database-file-input" required>
|
||||
<p>Instead of importing a backup, import a database by uploading the file.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table id="user-table" class="entries-table">
|
||||
<thead>
|
||||
<th>User</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody id="user-list">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button type="submit" id="upload-db-button">Import Database</button>
|
||||
<p>IMPORTANT: Login into MIND within one minute to keep the new database, or the import will automatically be reverted.
|
||||
See <a href="https://casvt.github.io/MIND/general_info/admin_panel#database">the documentation</a> for more information.</p>
|
||||
</form>
|
||||
<div class="settings-table-container">
|
||||
<table id="backup-table">
|
||||
<thead>
|
||||
<th>File</th>
|
||||
<th>Creation</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Current Database</td>
|
||||
<td></td>
|
||||
<td>
|
||||
<button id="download-db-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="512" height="512">
|
||||
<g>
|
||||
<path d="M210.731,386.603c24.986,25.002,65.508,25.015,90.51,0.029c0.01-0.01,0.019-0.019,0.029-0.029l68.501-68.501 c7.902-8.739,7.223-22.23-1.516-30.132c-8.137-7.357-20.527-7.344-28.649,0.03l-62.421,62.443l0.149-329.109 C277.333,9.551,267.782,0,256,0l0,0c-11.782,0-21.333,9.551-21.333,21.333l-0.192,328.704L172.395,288 c-8.336-8.33-21.846-8.325-30.176,0.011c-8.33,8.336-8.325,21.846,0.011,30.176L210.731,386.603z" />
|
||||
<path d="M490.667,341.333L490.667,341.333c-11.782,0-21.333,9.551-21.333,21.333V448c0,11.782-9.551,21.333-21.333,21.333H64 c-11.782,0-21.333-9.551-21.333-21.333v-85.333c0-11.782-9.551-21.333-21.333-21.333l0,0C9.551,341.333,0,350.885,0,362.667V448 c0,35.346,28.654,64,64,64h384c35.346,0,64-28.654,64-64v-85.333C512,350.885,502.449,341.333,490.667,341.333z" />
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody id="backup-list"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h2>Power</h2>
|
||||
<div class="database-container">
|
||||
<button id="restart-button">Restart</button>
|
||||
<button id="shutdown-button">Shutdown</button>
|
||||
</div>
|
||||
</section>
|
||||
<section id="backup-management-section">
|
||||
<h2>Database Backup Management</h2>
|
||||
<div class="add-item-container">
|
||||
<button id="upload-db-button" class="input-style">Upload Database</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table id="backup-table" class="entries-table">
|
||||
<thead>
|
||||
<th>File</th>
|
||||
<th>Creation</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Current Database</td>
|
||||
<td></td>
|
||||
<td>
|
||||
<button id="download-db-button" title="Download current database">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="512" height="512">
|
||||
<g>
|
||||
<path d="M210.731,386.603c24.986,25.002,65.508,25.015,90.51,0.029c0.01-0.01,0.019-0.019,0.029-0.029l68.501-68.501 c7.902-8.739,7.223-22.23-1.516-30.132c-8.137-7.357-20.527-7.344-28.649,0.03l-62.421,62.443l0.149-329.109 C277.333,9.551,267.782,0,256,0l0,0c-11.782,0-21.333,9.551-21.333,21.333l-0.192,328.704L172.395,288 c-8.336-8.33-21.846-8.325-30.176,0.011c-8.33,8.336-8.325,21.846,0.011,30.176L210.731,386.603z" />
|
||||
<path d="M490.667,341.333L490.667,341.333c-11.782,0-21.333,9.551-21.333,21.333V448c0,11.782-9.551,21.333-21.333,21.333H64 c-11.782,0-21.333-9.551-21.333-21.333v-85.333c0-11.782-9.551-21.333-21.333-21.333l0,0C9.551,341.333,0,350.885,0,362.667V448 c0,35.346,28.654,64,64,64h384c35.346,0,64-28.654,64-64v-85.333C512,350.885,502.449,341.333,490.667,341.333z" />
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody id="backup-list"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/general.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/general.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/login.js') }}" defer></script>
|
||||
|
||||
<title>Login - MIND</title>
|
||||
@@ -26,40 +27,42 @@
|
||||
<main>
|
||||
<input type="checkbox" id="form-switch" class="hidden">
|
||||
|
||||
<div class="form-container">
|
||||
<noscript>Javascript is disabled. The web-ui of MIND does not work with JavaScript disabled.</noscript>
|
||||
<div class="login-container">
|
||||
<form id="login-form">
|
||||
<h2>Login</h2>
|
||||
|
||||
<noscript>Javascript is disabled. The web-ui of MIND does not work with JavaScript disabled.</noscript>
|
||||
|
||||
<div id="username-error-container">
|
||||
<input type="text" autocomplete="username" placeholder="Username" required autofocus>
|
||||
<p class="hidden">*Username not found</p>
|
||||
<div class="checked-input-container">
|
||||
<input type="text" id="login-username-input" autocomplete="username" placeholder="Username" class="input-style" required autofocus>
|
||||
<p>Username not found</p>
|
||||
</div>
|
||||
|
||||
<div id="password-error-container">
|
||||
<input type="password" autocomplete="current-password" placeholder="Password" required>
|
||||
<p class="hidden">*Password incorrect</p>
|
||||
<div class="checked-input-container">
|
||||
<input type="password" id="login-password-input" autocomplete="current-password" placeholder="Password" class="input-style" required>
|
||||
<p>Password incorrect</p>
|
||||
</div>
|
||||
|
||||
<label for="form-switch" class="switch-button">Or create an account</label>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-container">
|
||||
<form id="create-form">
|
||||
<h2>Create</h2>
|
||||
<form id="register-form">
|
||||
<h2>Register</h2>
|
||||
|
||||
<input type="text" autocomplete="username" placeholder="Username" required>
|
||||
<p class="error hidden" id="new-username-error">*Username invalid</p>
|
||||
<p class="error hidden" id="taken-username-error">*Username already taken</p>
|
||||
<div class="checked-input-container">
|
||||
<input type="text" id="register-username-input" autocomplete="username" placeholder="Username" class="input-style" required>
|
||||
<p id="invalid-username-error">Username invalid</p>
|
||||
<p id="taken-username-error">Username already taken</p>
|
||||
</div>
|
||||
|
||||
<input type="password" autocomplete="new-password" placeholder="Password" required>
|
||||
<input type="password" id="register-password-input" autocomplete="new-password" placeholder="Password" class="input-style" required>
|
||||
|
||||
<label for="form-switch" class="switch-button">Or log into an account</label>
|
||||
<button type="submit">Create</button>
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
<div id="form-cover">
|
||||
<img src="{{ url_for('static', filename='img/favicon.svg') }}" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.svg') }}" type="image/x-icon">
|
||||
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/general.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/reminders.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/info.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/library.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/notification.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/general.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/reminders.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/library.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/window.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/templates.js') }}" defer></script>
|
||||
@@ -142,7 +144,7 @@
|
||||
<noscript>Javascript is disabled. The web-ui of MIND does not work with JavaScript disabled.</noscript>
|
||||
<form id="search-form">
|
||||
<div class="search-bar">
|
||||
<button type="submit">
|
||||
<button type="submit" class="input-style">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 513.749 513.749" style="enable-background:new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
@@ -151,8 +153,8 @@
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
<input type="text" id="search-input" required placeholder="Search..." aria-placeholder="Search for reminders">
|
||||
<button type="button" id="clear-button">
|
||||
<input type="text" id="search-input" class="input-style" required placeholder="Search..." aria-placeholder="Search for reminders">
|
||||
<button type="button" id="clear-button" class="input-style">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="256" height="256" x="0" y="0" viewBox="0 0 512.021 512.021" style="enable-background:new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
@@ -161,7 +163,7 @@
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
<select id="sort-input">
|
||||
<select id="sort-input" class="input-style">
|
||||
<option value="time">Time</option>
|
||||
<option value="time_reversed">Time Reversed</option>
|
||||
<option value="title">Title</option>
|
||||
|
||||
Reference in New Issue
Block a user