V1.1.0 (#24)
## Update Info 🍆 **DICK** v1.1.0 🍆 Our number is getting bigger! Though, I hear size is not what matters but how you use it. So, I am happy to introduce you to a bunch of new stuff within the front end! 🔀 **UPDATING** Updating your instance should be easy, unless you already edited the code base, at that point your on your own. If you have a direct clone of the master of the current 1.0.2(old master) branch, then all you need to do is 1. Browse to your DICK folder 2. Run `git pull` to pull new changes 3. Run `npm i` to install new dependancies 4. Delete the `dist` folder 5. Start DICK using `npm start` 6. Enjoy > **Note** > If you load your instance and styling seems wrong, please clear your browsers cache, and reload the page. > **Note** > The first user to log into your DICK WebUI will be marked as the instance admin. You can change which users are admin by editing the user database file located at `/src/database/users.json`. This file will only appear once you've started your instance for the first time. ✏️ **CHANGELOG** ```diff ADDITIONS + Admin Dashboard > This page will be where system administrators can view their syatem settings and stats! + Database > Added DICK database, inside JSON files with management utils. + Added new app settings page to Admin Dashboard > This will allow administrators to customize their instance on the fly without having to edit the codebase. White labeling! + Added user list to Admin Dashboard > This allows administrators to view which users are registered in their ASS currently, and their roles set. You can also create new users from this page. (There are a lot of hidden divs in this page so imstance admins can add extra code to dick to enable stuff like deletion of users) + Registrations > Administrators can toggle registrations into their ASS from their DICK UI via the /register page! + Captcha > By default, when a user gets login information wrong they will be forwarded to a Rick Roll. Now you can add a hCaptcha site key to DICK to enable hCaptcha for your login and register public pages! + Added a "default profile picture" > Every users default profile picture. This is planned to be able to be set per user in the future, so users can pick their own seperate from the default. REMOVALS - Removeed STAFF_IDs from codebase. > This means you can remove this CONSTANT from your instance CONSTANTS file, please see the repo's constants example file to see if yours matches it. ``` ```fix CHANGES = Large codebase cleanup = Seperated js for components into their own files based on job = Redid some naming for tailwind colour theme classes to provide proper theming from the tailwind config file = Cleaned up a lot of the utils = Added embed gen page as a hidden extra for devs to add themselves in their own time if they wish (please PR if you achieve this 🤘) = Fixed the flash message warning colours to actually be red or green depending on error/success ``` ## Issues Resolved / Fixes In This Release Resolves #17 , Resolves #14 , Resolves #10 , Resolves #7pull/28/head v1.1
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,2 @@
|
||||
users.json
|
||||
settings.json
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 70 KiB |
@ -0,0 +1,18 @@
|
||||
// DOM Ready Function
|
||||
function ready(fn) {
|
||||
if (document.readyState != 'loading') {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
}
|
||||
|
||||
// DOM Ready Called
|
||||
ready(function () {
|
||||
const colorPicker = document.querySelector('.custom-color-picker-border');
|
||||
|
||||
colorPicker.addEventListener('input',()=>{
|
||||
colorPicker.style.setProperty('--color', colorPicker.value)
|
||||
})
|
||||
|
||||
});
|
@ -1,196 +0,0 @@
|
||||
// DOM Ready Function
|
||||
function ready(fn) {
|
||||
if (document.readyState != 'loading') {
|
||||
fn()
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn)
|
||||
}
|
||||
}
|
||||
|
||||
// DOM Ready Called
|
||||
ready(function () {
|
||||
|
||||
const openFile = document.getElementById('openFile')
|
||||
const showFile = document.getElementById('showFile')
|
||||
|
||||
const traMove = document.getElementsByClassName('traMove')
|
||||
const tabsProfile = document.getElementsByClassName('tabsProfile')
|
||||
const tabsProfileContent = document.getElementsByClassName('tabsProfileContent')
|
||||
const dropdownNavBtn = document.getElementById('dropdownNavBtn')
|
||||
const dropdownNav = document.getElementById('dropdownNav')
|
||||
|
||||
const dropdownProfileBtn = document.getElementsByClassName('dropdownProfileBtn')
|
||||
const dropdownProfile = document.getElementsByClassName('dropdownProfile')
|
||||
|
||||
const dropdownSearchBtn = document.getElementsByClassName('dropdownSearchBtn')
|
||||
const dropdownSearch = document.getElementsByClassName('dropdownSearch')
|
||||
const dropdownFileBtn = document.getElementsByClassName('dropdownFileBtn')
|
||||
|
||||
// Open File Manager Event
|
||||
/*
|
||||
function fireFileManager(){
|
||||
openFile.addEventListener('click', () => {
|
||||
showFile.click();
|
||||
});
|
||||
}
|
||||
fireFileManager();
|
||||
*/
|
||||
|
||||
// Dropdown Navbar Event
|
||||
function fireDropdownNav(){
|
||||
dropdownNavBtn.addEventListener('click', () => {
|
||||
if (dropdownNav.classList.contains('-translate-y-full')) {
|
||||
dropdownNav.classList.remove('-translate-y-full')
|
||||
dropdownNav.classList.add('translate-y-0', 'ease-linear')
|
||||
// When the event get fired, every 'traMove' id attibute will move down
|
||||
Array.prototype.forEach.call(traMove, (e) => {
|
||||
e.classList.remove('-translate-y-32')
|
||||
e.classList.add('translate-y-0', 'ease-linear')
|
||||
})
|
||||
} else {
|
||||
dropdownNav.classList.add('-translate-y-full',)
|
||||
dropdownNav.classList.remove('translate-y-0', 'ease-linear')
|
||||
// Then if you hide the event, every 'traMove' id attribute will back to normal
|
||||
Array.prototype.forEach.call(traMove, (e) => {
|
||||
e.classList.add('-translate-y-32')
|
||||
e.classList.remove('translate-y-0', 'ease-linear')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
fireDropdownNav()
|
||||
|
||||
// Dropdown Profile Event
|
||||
function fireDropdownProfile() {
|
||||
Array.prototype.forEach.call(dropdownProfileBtn, function (e, index) {
|
||||
e.addEventListener('click', () => {
|
||||
var timer
|
||||
if (dropdownProfile[index].classList.contains('opacity-0')) {
|
||||
window.clearTimeout(timer)
|
||||
dropdownProfile[index].classList.remove('opacity-0', 'translate-y-6', 'invisible')
|
||||
dropdownProfile[index].classList.add('translate-y-0', 'opacity-100')
|
||||
} else {
|
||||
dropdownProfile[index].classList.add('opacity-0', 'translate-y-6')
|
||||
dropdownProfile[index].classList.remove('translate-y-0', 'opacity-100')
|
||||
//Set timer to hide the dropdown
|
||||
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownProfile attribute
|
||||
timer = window.setTimeout( () => {
|
||||
dropdownProfile[index].classList.add('invisible')
|
||||
}, 250)
|
||||
}
|
||||
})
|
||||
// Click outside event
|
||||
window.addEventListener('click', (eve) => {
|
||||
if (!dropdownProfileBtn[index].contains(eve.target) && !dropdownProfile[index].contains(eve.target)) {
|
||||
dropdownProfile[index].classList.add('opacity-0', 'translate-y-6')
|
||||
dropdownProfile[index].classList.remove('translate-y-0', 'opacity-100')
|
||||
// Same as above
|
||||
timer = window.setTimeout( () => {
|
||||
dropdownProfile[index].classList.add('invisible')
|
||||
}, 250)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
fireDropdownProfile()
|
||||
|
||||
|
||||
// Dropdown Search Event
|
||||
function fireDropdownSearch() {
|
||||
Array.prototype.forEach.call(dropdownSearchBtn, (e, index) => {
|
||||
e.addEventListener('click', () => {
|
||||
var timer
|
||||
if (dropdownSearch[index].classList.contains('opacity-0')) {
|
||||
window.clearTimeout(timer)
|
||||
dropdownSearch[index].classList.remove('opacity-0', 'translate-y-6', 'invisible')
|
||||
dropdownSearch[index].classList.add('translate-y-0', 'opacity-100')
|
||||
} else {
|
||||
dropdownSearch[index].classList.add('opacity-0', 'translate-y-6')
|
||||
dropdownSearch[index].classList.remove('translate-y-0', 'opacity-100')
|
||||
|
||||
//Set timer to hide the dropdown
|
||||
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownSearch attribute
|
||||
timer = window.setTimeout( () => {
|
||||
dropdownSearch[index].classList.add('invisible')
|
||||
}, 250)
|
||||
}
|
||||
})
|
||||
|
||||
// Click outside event
|
||||
window.addEventListener('click', (eve) => {
|
||||
if (!dropdownSearchBtn[index].contains(eve.target) && !dropdownSearch[index].contains(eve.target)) {
|
||||
dropdownSearch[index].classList.add('opacity-0', 'translate-y-6')
|
||||
dropdownSearch[index].classList.remove('translate-y-0', 'opacity-100')
|
||||
|
||||
// Same as above
|
||||
timer = window.setTimeout( () => {
|
||||
dropdownSearch[index].classList.add('invisible')
|
||||
}, 250)
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
fireDropdownSearch()
|
||||
|
||||
// Dropdown File Event
|
||||
function fireDropdownFile() {
|
||||
Array.prototype.forEach.call(dropdownFileBtn, (e, index) => {
|
||||
const findSibling = e.parentElement.children[1]
|
||||
e.addEventListener('click', () => {
|
||||
var timer
|
||||
if (findSibling.classList.contains('opacity-0')) {
|
||||
window.clearTimeout(timer)
|
||||
findSibling.classList.remove('opacity-0', 'translate-y-6', 'invisible')
|
||||
findSibling.classList.add('translate-y-0', 'opacity-100')
|
||||
} else {
|
||||
findSibling.classList.add('opacity-0', 'translate-y-6')
|
||||
findSibling.classList.remove('translate-y-0', 'opacity-100')
|
||||
|
||||
//Set timer to hide the dropdown
|
||||
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownFile attribute
|
||||
timer = window.setTimeout( () => {
|
||||
findSibling.classList.add('invisible')
|
||||
}, 250)
|
||||
}
|
||||
})
|
||||
|
||||
// Click outside event
|
||||
window.addEventListener('click', (eve) => {
|
||||
if (!dropdownFileBtn[index].contains(eve.target) && !findSibling.contains(eve.target)) {
|
||||
findSibling.classList.add('opacity-0', 'translate-y-6')
|
||||
findSibling.classList.remove('translate-y-0', 'opacity-100')
|
||||
|
||||
// Same as above
|
||||
timer = window.setTimeout( () => {
|
||||
findSibling.classList.add('invisible')
|
||||
}, 250)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
fireDropdownFile()
|
||||
|
||||
// Tabs Profile Event
|
||||
function fireTabsProfile(){
|
||||
//File Manager Tab = 0
|
||||
tabsProfile[0].addEventListener('click', () => {
|
||||
if (tabsProfile[1].classList.contains('border-b-2', 'border-purple-400', 'font-semibold')) {
|
||||
tabsProfile[1].classList.remove('border-b-2', 'border-purple-400', 'font-semibold')
|
||||
tabsProfile[0].classList.add('border-b-2', 'border-purple-400', 'font-semibold')
|
||||
tabsProfileContent[1].classList.add('hidden')
|
||||
tabsProfileContent[0].classList.remove('hidden')
|
||||
}
|
||||
});
|
||||
//Config Gen Tab = 1
|
||||
tabsProfile[1].addEventListener('click', () => {
|
||||
if (tabsProfile[0].classList.contains('border-b-2', 'border-purple-400', 'font-semibold')) {
|
||||
tabsProfile[0].classList.remove('border-b-2', 'border-purple-400', 'font-semibold')
|
||||
tabsProfile[1].classList.add('border-b-2', 'border-purple-400', 'font-semibold')
|
||||
tabsProfileContent[0].classList.add('hidden')
|
||||
tabsProfileContent[1].classList.remove('hidden')
|
||||
}
|
||||
})
|
||||
}
|
||||
fireTabsProfile()
|
||||
})
|
@ -0,0 +1,162 @@
|
||||
// DOM Ready Function
|
||||
function ready(fn) {
|
||||
if (document.readyState != 'loading') {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
}
|
||||
|
||||
// DOM Ready Called
|
||||
ready(function () {
|
||||
|
||||
const traMove = document.getElementsByClassName('traMove');
|
||||
|
||||
const dropdownNavBtn = document.getElementById('dropdownNavBtn');
|
||||
const dropdownNav = document.getElementById('dropdownNav');
|
||||
|
||||
const dropdownProfileBtn = document.getElementsByClassName('dropdownProfileBtn');
|
||||
const dropdownProfile = document.getElementsByClassName('dropdownProfile');
|
||||
|
||||
const dropdownSearchBtn = document.getElementsByClassName('dropdownSearchBtn');
|
||||
const dropdownSearch = document.getElementsByClassName('dropdownSearch');
|
||||
|
||||
const dropdownFileBtn = document.getElementsByClassName('dropdownFileBtn');
|
||||
|
||||
// Dropdown Navbar Event
|
||||
function fireDropdownNav() {
|
||||
dropdownNavBtn.addEventListener('click', () => {
|
||||
if (dropdownNav.classList.contains('-translate-y-full')) {
|
||||
dropdownNav.classList.remove('-translate-y-full');
|
||||
dropdownNav.classList.add('translate-y-0', 'ease-linear');
|
||||
// When the event get fired, every 'traMove' id attibute will move down
|
||||
for (let index = 0; index < traMove.length; index++) {
|
||||
traMove[index].classList.remove('-translate-y-32');
|
||||
traMove[index].classList.add('translate-y-0', 'ease-linear');
|
||||
}
|
||||
} else {
|
||||
dropdownNav.classList.add('-translate-y-full',);
|
||||
dropdownNav.classList.remove('translate-y-0', 'ease-linear');
|
||||
// Then if you hide the event, every 'traMove' id attribute will back to normal
|
||||
for (let index = 0; index < traMove.length; index++) {
|
||||
traMove[index].classList.add('-translate-y-32');
|
||||
traMove[index].classList.remove('translate-y-0', 'ease-linear');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
fireDropdownNav();
|
||||
|
||||
// Dropdown Profile Event
|
||||
function fireDropdownProfile() {
|
||||
for (let index = 0; index < dropdownProfileBtn.length; index++) {
|
||||
dropdownProfileBtn[index].addEventListener('click', () => {
|
||||
var timer;
|
||||
if (dropdownProfile[index].classList.contains('opacity-0')) {
|
||||
window.clearTimeout(timer);
|
||||
dropdownProfile[index].classList.remove('opacity-0', 'translate-y-6', 'invisible');
|
||||
dropdownProfile[index].classList.add('translate-y-0', 'opacity-100');
|
||||
} else {
|
||||
dropdownProfile[index].classList.add('opacity-0', 'translate-y-6');
|
||||
dropdownProfile[index].classList.remove('translate-y-0', 'opacity-100');
|
||||
|
||||
//Set timer to hide the dropdown
|
||||
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownProfile attribute
|
||||
timer = window.setTimeout(() => {
|
||||
dropdownProfile[index].classList.add('invisible');
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
// Click outside event
|
||||
window.addEventListener('click', (eve) => {
|
||||
if (!dropdownProfileBtn[index].contains(eve.target) && !dropdownProfile[index].contains(eve.target)) {
|
||||
dropdownProfile[index].classList.add('opacity-0', 'translate-y-6');
|
||||
dropdownProfile[index].classList.remove('translate-y-0', 'opacity-100');
|
||||
|
||||
// Same as above
|
||||
timer = window.setTimeout(() => {
|
||||
dropdownProfile[index].classList.add('invisible');
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
fireDropdownProfile();
|
||||
|
||||
// Dropdown Search Event
|
||||
function fireDropdownSearch() {
|
||||
for (let index = 0; index < dropdownSearchBtn.length; index++) {
|
||||
dropdownSearchBtn[index].addEventListener('click', () => {
|
||||
var timer;
|
||||
if (dropdownSearch[index].classList.contains('opacity-0')) {
|
||||
window.clearTimeout(timer);
|
||||
dropdownSearch[index].classList.remove('opacity-0', 'translate-y-6', 'invisible');
|
||||
dropdownSearch[index].classList.add('translate-y-0', 'opacity-100');
|
||||
} else {
|
||||
dropdownSearch[index].classList.add('opacity-0', 'translate-y-6');
|
||||
dropdownSearch[index].classList.remove('translate-y-0', 'opacity-100');
|
||||
|
||||
//Set timer to hide the dropdown
|
||||
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownSearch attribute
|
||||
timer = window.setTimeout(() => {
|
||||
dropdownSearch[index].classList.add('invisible');
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
// Click outside event
|
||||
window.addEventListener('click', (eve) => {
|
||||
if (!dropdownSearchBtn[index].contains(eve.target) && !dropdownSearch[index].contains(eve.target)) {
|
||||
dropdownSearch[index].classList.add('opacity-0', 'translate-y-6');
|
||||
dropdownSearch[index].classList.remove('translate-y-0', 'opacity-100');
|
||||
|
||||
// Same as above
|
||||
timer = window.setTimeout(() => {
|
||||
dropdownSearch[index].classList.add('invisible');
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
fireDropdownSearch();
|
||||
|
||||
// Dropdown File Event
|
||||
function fireDropdownFile() {
|
||||
for (let index = 0; index < dropdownFileBtn.length; index++) {
|
||||
const findSibling = dropdownFileBtn[index].parentElement.children[1];
|
||||
|
||||
dropdownFileBtn[index].addEventListener('click', () => {
|
||||
var timer;
|
||||
if (findSibling.classList.contains('opacity-0')) {
|
||||
window.clearTimeout(timer);
|
||||
findSibling.classList.remove('opacity-0', 'translate-y-6', 'invisible');
|
||||
findSibling.classList.add('translate-y-0', 'opacity-100');
|
||||
} else {
|
||||
findSibling.classList.add('opacity-0', 'translate-y-6');
|
||||
findSibling.classList.remove('translate-y-0', 'opacity-100');
|
||||
|
||||
//Set timer to hide the dropdown
|
||||
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownFile attribute
|
||||
timer = window.setTimeout(() => {
|
||||
findSibling.classList.add('invisible');
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
// Click outside event
|
||||
window.addEventListener('click', (eve) => {
|
||||
if (!dropdownFileBtn[index].contains(eve.target) && !findSibling.contains(eve.target)) {
|
||||
findSibling.classList.add('opacity-0', 'translate-y-6');
|
||||
findSibling.classList.remove('translate-y-0', 'opacity-100');
|
||||
|
||||
// Same as above
|
||||
timer = window.setTimeout(() => {
|
||||
findSibling.classList.add('invisible');
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
fireDropdownFile();
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
// DOM Ready Function
|
||||
function ready(fn) {
|
||||
if (document.readyState != 'loading') {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
}
|
||||
|
||||
// DOM Ready Called
|
||||
ready(function () {
|
||||
|
||||
const buttonModal = document.getElementsByClassName('buttonModal');
|
||||
const showModal = document.getElementById('showModal');
|
||||
|
||||
function fireModal() {
|
||||
for (let index = 0; index < buttonModal.length; index++) {
|
||||
buttonModal[index].addEventListener('click', () => {
|
||||
if(showModal.classList.contains('flex')){
|
||||
showModal.classList.remove('flex')
|
||||
showModal.classList.add('hidden')
|
||||
}else{
|
||||
showModal.classList.remove('hidden')
|
||||
showModal.classList.add('flex')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
fireModal();
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
// DOM Ready Function
|
||||
function ready(fn) {
|
||||
if (document.readyState != 'loading') {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
}
|
||||
|
||||
// DOM Ready Called
|
||||
ready(function () {
|
||||
const inputPasswordType = document.getElementById('inputPasswordType')
|
||||
const checkboxPassword = document.getElementById('checkboxPassword')
|
||||
|
||||
function changeInputPasswordType() {
|
||||
checkboxPassword.addEventListener('change', (eve) => {
|
||||
|
||||
const checked = eve.target.checked
|
||||
|
||||
if (checked) {
|
||||
inputPasswordType.type = 'text'
|
||||
} else {
|
||||
inputPasswordType.type = 'password'
|
||||
}
|
||||
});
|
||||
}
|
||||
changeInputPasswordType();
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
// DOM Ready Function
|
||||
function ready(fn) {
|
||||
if (document.readyState != 'loading') {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
}
|
||||
|
||||
// DOM Ready Called
|
||||
ready(function () {
|
||||
const tabAdmin = document.getElementsByClassName('tabAdmin');
|
||||
const changeTabAdmin = document.getElementsByClassName('changeTabAdmin');
|
||||
|
||||
// Tabs Admin Event
|
||||
function fireTabsAdmin() {
|
||||
//Dynamic tabs with loops
|
||||
const loop = [
|
||||
{ id: 1, contains: 1, remove: 1, add: 0 },
|
||||
{ id: 2, contains: 0, remove: 0, add: 1 }
|
||||
]
|
||||
|
||||
var currentTab = []
|
||||
//index 0 is for tab app-setting and 1 is users
|
||||
loop.forEach((e, index) => {
|
||||
tabAdmin[index].addEventListener('click', () => {
|
||||
if (tabAdmin[e.contains].classList.contains('border-b-2', 'border-accent', 'font-semibold')) {
|
||||
tabAdmin[e.remove].classList.remove('border-b-2', 'border-accent', 'font-semibold');
|
||||
tabAdmin[e.add].classList.add('border-b-2', 'border-accent', 'font-semibold');
|
||||
}
|
||||
|
||||
currentTab.push(tabAdmin[index].dataset.id)
|
||||
|
||||
if(currentTab.slice(-2)[0] != tabAdmin[index].dataset.id || (currentTab.length <= 1 && tabAdmin[index].dataset.id == 2)){
|
||||
for (let idx = 0; idx < changeTabAdmin.length; idx++) {
|
||||
if (changeTabAdmin[idx].classList.contains('flex-1')) {
|
||||
changeTabAdmin[idx].classList.remove('flex-1');
|
||||
changeTabAdmin[idx].classList.add('hidden');
|
||||
} else {
|
||||
changeTabAdmin[idx].classList.remove('hidden');
|
||||
changeTabAdmin[idx].classList.add('flex-1');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
fireTabsAdmin();
|
||||
});
|
||||
|
@ -0,0 +1,50 @@
|
||||
// DOM Ready Function
|
||||
function ready(fn) {
|
||||
if (document.readyState != 'loading') {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
}
|
||||
|
||||
// DOM Ready Called
|
||||
ready(function () {
|
||||
const tabUser = document.getElementsByClassName('tabUser');
|
||||
const showConfigGen = document.getElementsByClassName('changeTabUser');
|
||||
|
||||
|
||||
function fireTabsProfile() {
|
||||
const loop = [
|
||||
{ id: 1, contains: 1, remove: 1, add: 0 },
|
||||
{ id: 2, contains: 0, remove: 0, add: 1 }
|
||||
]
|
||||
|
||||
var currentTab = []
|
||||
//index 0 is for tab file manager and 1 is config-gen
|
||||
loop.forEach((e, index) => {
|
||||
|
||||
tabUser[index].addEventListener('click', () => {
|
||||
if (tabUser[e.contains].classList.contains('border-b-2', 'border-accent', 'font-semibold')) {
|
||||
tabUser[e.remove].classList.remove('border-b-2', 'border-accent', 'font-semibold');
|
||||
tabUser[e.add].classList.add('border-b-2', 'border-accent', 'font-semibold');
|
||||
}
|
||||
|
||||
currentTab.push(tabUser[index].dataset.id)
|
||||
|
||||
if(currentTab.slice(-2)[0] != tabUser[index].dataset.id || (currentTab.length <= 1 && tabUser[index].dataset.id == 2)){
|
||||
for (let idx = 0; idx < showConfigGen.length; idx++) {
|
||||
|
||||
if (showConfigGen[idx].classList.contains('flex-1')) {
|
||||
showConfigGen[idx].classList.remove('flex-1');
|
||||
showConfigGen[idx].classList.add('hidden');
|
||||
} else {
|
||||
showConfigGen[idx].classList.remove('hidden');
|
||||
showConfigGen[idx].classList.add('flex-1');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
fireTabsProfile();
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
export interface ISettingsDatabase {
|
||||
name: string
|
||||
logo: string
|
||||
siteTitle: string
|
||||
siteDescription: string
|
||||
loginText: string
|
||||
appEmoji: string
|
||||
captchaEnabled: boolean
|
||||
captchaSiteID: string | null
|
||||
captchaSecretKey: string | null
|
||||
defaultProfilePicture: string
|
||||
registrationEnabled: boolean
|
||||
privateModeEnabled: boolean
|
||||
}
|
||||
|
||||
export interface IUsersDatabase extends Array<IUserSettings>{}
|
||||
|
||||
export interface IUserSettings {
|
||||
username: string
|
||||
role: "admin" | "user"
|
||||
profilePicture: string | null
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
import path from "path"
|
||||
import fs from "fs-extra"
|
||||
|
||||
import { ISettingsDatabase, IUsersDatabase, IUserSettings } from "typings/database"
|
||||
import { ASS_LOCATION } from "../constants"
|
||||
import { parseAuthFile } from "./assJSONStructure"
|
||||
import { Log } from "@callmekory/logger/lib"
|
||||
|
||||
const settingsDatabaseLocation = path.resolve(`./src/database/settings.json`)
|
||||
const userDatabaseLocation = path.resolve(`./src/database/users.json`)
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
// Get Database Files
|
||||
//
|
||||
/////////////////////////////
|
||||
|
||||
// Get settings database, creating a default one if it doesnt exist
|
||||
export const getSettingsDatabase = (): ISettingsDatabase => {
|
||||
try {
|
||||
const databaseFile = fs.readFileSync(settingsDatabaseLocation).toString()
|
||||
const database: ISettingsDatabase = JSON.parse(databaseFile)
|
||||
return database
|
||||
} catch (error) {
|
||||
const defaultDatabase: ISettingsDatabase = {
|
||||
name: "dick",
|
||||
appEmoji: "🍆",
|
||||
siteTitle: "DICK (Directly Integrated Client for Keisters)",
|
||||
siteDescription: "The frontend for your backend",
|
||||
loginText: "Sign in to easily manage your nudes.",
|
||||
captchaEnabled: false,
|
||||
captchaSiteID: null,
|
||||
captchaSecretKey: null,
|
||||
registrationEnabled: false,
|
||||
privateModeEnabled: false,
|
||||
logo: "./images/logo.png",
|
||||
defaultProfilePicture: "./images/profile.png"
|
||||
}
|
||||
fs.writeJsonSync(settingsDatabaseLocation, defaultDatabase, { spaces: 4 })
|
||||
return defaultDatabase
|
||||
}
|
||||
}
|
||||
|
||||
// Get users database, creating a default one if it doesnt exist
|
||||
export const getUserDatabase = (): IUsersDatabase => {
|
||||
try {
|
||||
const databaseFile = fs.readFileSync(userDatabaseLocation).toString()
|
||||
const database: IUsersDatabase = JSON.parse(databaseFile)
|
||||
return database
|
||||
} catch (error) {
|
||||
const defaultDatabase: IUsersDatabase = []
|
||||
fs.writeJsonSync(userDatabaseLocation, defaultDatabase, { spaces: 4 })
|
||||
return defaultDatabase
|
||||
}
|
||||
}
|
||||
|
||||
// Get users database object, creating a default one if it doesnt exist
|
||||
export const getUserDatabaseObj = (username: string): IUserSettings | null => {
|
||||
try {
|
||||
const database = getUserDatabase()
|
||||
const user = database.find((e: IUserSettings) => e.username === username)
|
||||
if (!user) {
|
||||
let newUser: IUserSettings
|
||||
|
||||
// If there are no users in the database yet, we will make this user the admin (first user to login will always be admin)
|
||||
if (database.length == 0) {
|
||||
newUser = {
|
||||
username: username,
|
||||
role: "admin",
|
||||
profilePicture: null
|
||||
}
|
||||
} else {
|
||||
// Else we add the user to the database as a regular user
|
||||
newUser = {
|
||||
username: username,
|
||||
role: "user",
|
||||
profilePicture: null
|
||||
}
|
||||
}
|
||||
database.push(newUser)
|
||||
fs.writeJsonSync(userDatabaseLocation, database, { spaces: 4 })
|
||||
return user
|
||||
}
|
||||
return user
|
||||
} catch (error) {
|
||||
Log.error(error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
// MISC
|
||||
//
|
||||
/////////////////////////////
|
||||
|
||||
/**
|
||||
* Checks if the user is in ASS database, returns true if the user exists, or false if it does not
|
||||
* @param username Username we are checking exists
|
||||
* @param token? If passed, will also check ASS token
|
||||
*/
|
||||
export const checkIfUserExistInASS = async (username: string, token?: string): Promise<boolean> => {
|
||||
const assUserDatabase = parseAuthFile()
|
||||
let result = false
|
||||
|
||||
// Check if the user exists in the ASS Database
|
||||
const assUsernameResult = assUserDatabase.find((user) => user.username === username) ? true : false
|
||||
if (token) {
|
||||
const assTokenResult = assUserDatabase.find((user) => user.password === token) ? true : false
|
||||
if (assUsernameResult || assTokenResult) {
|
||||
result = true
|
||||
}
|
||||
}
|
||||
if (assUsernameResult) {
|
||||
result = true
|
||||
}
|
||||
|
||||
// Return true if any of the checks above found the user, else return false
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is in our local JSON database, returns true if the user exists, or false if it does not
|
||||
* @param username Username we are checking exists
|
||||
*/
|
||||
export const checkIfUserExistInDICK = async (username: string): Promise<boolean> => {
|
||||
const dickUserDatabase = getUserDatabase()
|
||||
let result = false
|
||||
|
||||
// Check if the user exists in the DICK Database
|
||||
if (dickUserDatabase.find((e: IUserSettings) => e.username === username)) {
|
||||
result = true
|
||||
}
|
||||
|
||||
// Return true if any of the checks above found the user, else return false
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new user to ASS
|
||||
* @param username Username we will create
|
||||
* @param password Password we will use
|
||||
*/
|
||||
export const createUserInASS = async (username: string, password: string) => {
|
||||
const AUTH_FILE_PATH = path.resolve(`${ASS_LOCATION}/auth.json`)
|
||||
|
||||
fs.readJson(AUTH_FILE_PATH)
|
||||
.then((auth) => {
|
||||
auth.users[password] = { username, count: 0 }
|
||||
fs.writeJsonSync(AUTH_FILE_PATH, auth, { spaces: 4 })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new user to DICK
|
||||
* @param username Username we will create
|
||||
*/
|
||||
export const createUserInDICK = async (username: string) => {
|
||||
const userDatabase = getUserDatabase()
|
||||
|
||||
// If user does not exist in our database, we create it
|
||||
if (!userDatabase.find((e: IUserSettings) => e.username === username)) {
|
||||
let newUser: IUserSettings
|
||||
|
||||
// If there are no users in the database yet, we will make this user the admin (first user to login will always be admin)
|
||||
if (userDatabase.length == 0) {
|
||||
newUser = {
|
||||
username: username,
|
||||
role: "admin",
|
||||
profilePicture: null
|
||||
}
|
||||
} else {
|
||||
// Else we add the user to the database as a regular user
|
||||
newUser = {
|
||||
username: username,
|
||||
role: "user",
|
||||
profilePicture: null
|
||||
}
|
||||
}
|
||||
|
||||
userDatabase.push(newUser)
|
||||
fs.writeJsonSync(userDatabaseLocation, userDatabase, { spaces: 4 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycles through all users in the ASS auth database and adds them to the DICk user database
|
||||
* if they are not already in it.
|
||||
*/
|
||||
export const syncAssUsersToDick = () => {
|
||||
const assUserDatabase = parseAuthFile()
|
||||
const dickUserDatabase = getUserDatabase()
|
||||
|
||||
if(dickUserDatabase.length !== 0){
|
||||
for (const user of assUserDatabase) {
|
||||
if (!dickUserDatabase.find((e: IUserSettings) => e.username === user.username)) {
|
||||
createUserInDICK(user.username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
import { Response, NextFunction } from "express"
|
||||
import { Log } from "@callmekory/logger"
|
||||
import fetch from 'node-fetch'
|
||||
|
||||
import { IExtendedRequest } from "../typings/express-ext"
|
||||
import { checkIfUserExistInDICK, createUserInDICK, getSettingsDatabase, getUserDatabase, getUserDatabaseObj } from "./database"
|
||||
import { parseAuthFile } from "./assJSONStructure"
|
||||
import { IUserSettings } from "typings/database"
|
||||
/**
|
||||
* Wraps the express route in a function that passes the
|
||||
* `next` method from the route to the promise's catch
|
||||
* statement which allows the middleware to catch the
|
||||
* exception.
|
||||
*/
|
||||
export const wrap = async (req: IExtendedRequest, res: Response, next: NextFunction) => {
|
||||
if (req.user) {
|
||||
// If the user does not exist in DICKs database yet, we add them
|
||||
if (await checkIfUserExistInDICK(req.user.username) == false) {
|
||||
createUserInDICK(req.user.username)
|
||||
}
|
||||
// Make sure ASS users stay synced with DICK database
|
||||
const assUserDatabase = parseAuthFile()
|
||||
const dickUserDatabase = getUserDatabase()
|
||||
if(dickUserDatabase.length !== 0){
|
||||
for (const user of assUserDatabase) {
|
||||
if (!dickUserDatabase.find((e: IUserSettings) => e.username === user.username)) {
|
||||
createUserInDICK(user.username)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Log the page the user navigated to
|
||||
Log.info(`${req.user.username} navigated to page ${req.path}`)
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
//Express middleware to check captcha token
|
||||
export const checkCaptcha = async (req: IExtendedRequest, res: Response, next: NextFunction) => {
|
||||
const database = getSettingsDatabase()
|
||||
|
||||
// If captcha is enabled, we verify the captcha
|
||||
if (database.captchaEnabled) {
|
||||
|
||||
// If there is no response for some reason
|
||||
if(!req.body['h-captcha-response']){
|
||||
Log.info(`A user submitted a form on the endpoint ${req.path} and failed captcha due to not being able to reach hCaptcha, redirecting back to login page`)
|
||||
req.flash('error_message', `You failed the captcha due to not being able to reach hCaptcha. Please reach an admin.`)
|
||||
return res.redirect("/login")
|
||||
}
|
||||
|
||||
// Build payload with secret key and captcha response token from form data with key 'h-captcha-response'
|
||||
const params = new URLSearchParams()
|
||||
params.append('secret', database.captchaSecretKey)
|
||||
params.append('response', req.body['h-captcha-response'])
|
||||
|
||||
// Make POST request with data payload to hCaptcha API endpoint
|
||||
const response = await fetch("https://hcaptcha.com/siteverify", { method: 'POST', body: params })
|
||||
const data = await response.json()
|
||||
|
||||
// Parse JSON from response. Check for success or error codes.
|
||||
// If not correct, send back to login screen with error
|
||||
// A missing-input-response means its not getting info from hcaptcha
|
||||
if (data.success == false){
|
||||
Log.info(`A user submitted a form on the endpoint ${req.path} and failed captcha due to: ${data['error-codes']}, redirecting back to login page`)
|
||||
req.flash('error_message', `You failed the captcha due to ${data['error-codes']}. Please try again.`)
|
||||
return res.redirect("/login")
|
||||
}
|
||||
|
||||
// Else continue as they are verified as human
|
||||
next()
|
||||
} else next()
|
||||
}
|
||||
|
||||
|
||||
// Express middleware to check if username/password match one of the users
|
||||
// in auth.json
|
||||
export const authCheck = (req: IExtendedRequest, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
Log.info(`A user navigated to page ${req.path} and is not logged in, redirecting to login page`)
|
||||
req.flash('error_message', 'Please log in to access the requested page')
|
||||
res.redirect('/login')
|
||||
} else next()
|
||||
}
|
||||
|
||||
// Express middleware to check if username trying to access the page matches the users
|
||||
// in CONSTANTS
|
||||
export const adminCheck = (req: IExtendedRequest, res: Response, next: NextFunction) => {
|
||||
const user = getUserDatabaseObj(req.user.username)
|
||||
if (!user) {
|
||||
Log.info(`A user navigated to page ${req.path} and is not logged in, redirecting to login page`)
|
||||
req.flash('error_message', 'Please log in to access the requested page')
|
||||
res.redirect('/login')
|
||||
}
|
||||
if (user.role !== 'admin') {
|
||||
Log.info(`${req.user.username} navigated to page ${req.path} and is not an admin, redirecting to users dashboard`)
|
||||
res.redirect('/')
|
||||
} else next()
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
import { Request } from 'express'
|
||||
import multer, { FileFilterCallback } from 'multer'
|
||||
|
||||
type DestinationCallback = (error: Error | null, destination: string) => void
|
||||
type FileNameCallback = (error: Error | null, filename: string) => void
|
||||
|
||||
export const logoStorage = multer.diskStorage({
|
||||
destination: (request: Request, file: Express.Multer.File, callback: DestinationCallback): void => {
|
||||
callback(null, 'src/public/images')
|
||||
},
|
||||
filename: (req: Request, file: Express.Multer.File, callback: FileNameCallback): void => {
|
||||
callback(null,'logo.png')
|
||||
}
|
||||
})
|
||||
export const logoStorageDist = multer.diskStorage({
|
||||
destination: (request: Request, file: Express.Multer.File, callback: DestinationCallback): void => {
|
||||
callback(null, 'dist/public/images')
|
||||
},
|
||||
filename: (req: Request, file: Express.Multer.File, callback: FileNameCallback): void => {
|
||||
callback(null,'logo.png')
|
||||
}
|
||||
})
|
||||
|
||||
export const defaultPPStorage = multer.diskStorage({
|
||||
destination: (request: Request, file: Express.Multer.File, callback: DestinationCallback): void => {
|
||||
callback(null, 'src/public/images')
|
||||
},
|
||||
filename: (req: Request, file: Express.Multer.File, callback: FileNameCallback): void => {
|
||||
callback(null,'profile.png')
|
||||
}
|
||||
})
|
||||
export const defaultPPStorageDist = multer.diskStorage({
|
||||
destination: (request: Request, file: Express.Multer.File, callback: DestinationCallback): void => {
|
||||
callback(null, 'dist/public/images')
|
||||
},
|
||||
filename: (req: Request, file: Express.Multer.File, callback: FileNameCallback): void => {
|
||||
callback(null,'profile.png')
|
||||
}
|
||||
})
|
||||
|
||||
export const imageFileFilter = (request: Request, file: Express.Multer.File, callback: FileFilterCallback): void => {
|
||||
if (
|
||||
file.mimetype === 'image/png' ||
|
||||
file.mimetype === 'image/jpg' ||
|
||||
file.mimetype === 'image/jpeg'
|
||||
) {
|
||||
callback(null, true)
|
||||
} else {
|
||||
callback(null, false)
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<div id="showModal" class="hidden z-50 items-center flex-col justify-center overflow-hidden fixed inset-0">
|
||||
<!-- Modal Outscreen -->
|
||||
<div class="buttonModal absolute inset-0 bg-gradient-to-tr opacity-90 from-gray-700 via-gray-900 to-gray-700">
|
||||
</div>
|
||||
<!-- Modal Outscreen -->
|
||||
|
||||
<form action="/admin/add/user" method="POST"
|
||||
class="rounded-2xl flex-col bg-tertiary flex shadow-lg max-h-modal w-11/12 md:w-3/5 lg:w-2/5 xl:w-4/12 z-50">
|
||||
<div class="flex-1 p-6">
|
||||
<!--Modal Header -->
|
||||
<div class="flex items-center justify-between mb-3 border-b border-slate-700/50">
|
||||
|
||||
<!-- Modal Title -->
|
||||
<h1 class="text-2xl text-color-primary font-semibold">
|
||||
Add New User
|
||||
</h1>
|
||||
<!-- End Modal Title -->
|
||||
|
||||
<!-- Modal Close Button -->
|
||||
<button type="button" class="buttonModal inline-flex justify-center items-center text-color-secondary"
|
||||
type="button">
|
||||
<span class="inline-flex justify-center items-center w-6 h-6">
|
||||
<svg viewBox="0 0 24 24" width="25" height="25" class="inline-block">
|
||||
<path fill="currentColor"
|
||||
d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z">
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<!-- End Modal Close Button -->
|
||||
|
||||
</div>
|
||||
<!-- End Modal Header -->
|
||||
|
||||
<!-- Modal Body -->
|
||||
<div class="mt-6">
|
||||
<div class="grid grid-cols-2 gap-5 text-sm md:text-base">
|
||||
<div class="md:col-span-1 col-span-2">
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Username
|
||||
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="Imposter" name="username" type="text" />
|
||||
</div>
|
||||
<div class="md:col-span-1 col-span-2">
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Secret Key
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="***************" name="password" type="password" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Modal Body -->
|
||||
</div>
|
||||
<!-- Modal Footer -->
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-end flex-wrap -mb-3">
|
||||
<button type="submit"
|
||||
class="bg-accentsecondary w-auto hover:bg-accent rounded-md px-5 py-2 text-color-primary">
|
||||
Add User
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<!-- End Modal Footer -->
|
||||
</form>
|
||||
</div>
|
@ -1,2 +1,146 @@
|
||||
<p class="text-orange-700 dark:text-orange-400">I already said this page was TBA, what more do you want from me? To
|
||||
actually code it? 😜 </p>
|
||||
<!-- Content Title -->
|
||||
<span class="text-lg font-semibold text-color-secondary md:text-xl lg:text-xl">
|
||||
Admin Settings
|
||||
</span>
|
||||
<!-- End Content Title -->
|
||||
|
||||
<div class="bg-tertiary mt-6 h-auto w-full rounded-lg">
|
||||
<div
|
||||
class="flex w-full flex-row flex-wrap justify-center space-x-3 border-b border-purple-400/25 py-3 px-6 md:space-x-6 md:px-5 md:py-5 lg:space-x-6 lg:px-5 lg:py-5">
|
||||
<!-- System Stat Cards -->
|
||||
<div class="flex flex-wrap lg:flex-row gap-6 pb-5">
|
||||
|
||||
<div class="col-span-12 sm:col-span-6 xl:col-span-2 min-w-30">
|
||||
<div class="bg-secondary shadow-xl rounded-lg p-5 hover:bg-secondary-hover">
|
||||
<div class="flex">
|
||||
<i data-lucide="users" class="text-color-accent mr-2"></i>
|
||||
</div>
|
||||
<div class="text-color-primary sm:text-lg md:text-3xl font-medium leading-8 mt-6">
|
||||
<%= totalUsers %>
|
||||
</div>
|
||||
<div class="sm:text-sm md:text-base text-base text-slate-500 mt-1">
|
||||
Total Users
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 sm:col-span-6 xl:col-span-2 min-w-30">
|
||||
<div class="bg-secondary shadow-xl rounded-lg p-5 hover:bg-secondary-hover">
|
||||
<div class="flex">
|
||||
<i data-lucide="files" class="text-color-accent mr-2"></i>
|
||||
<p class="ml-auto text-slate-500 text-sm">
|
||||
<%= totalSize %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-color-primary sm:text-lg md:text-3xl font-medium leading-8 mt-6">
|
||||
<%= usersDataObj.totalFiles %>
|
||||
</div>
|
||||
<div class="sm:text-sm md:text-base text-base text-slate-500 mt-1">
|
||||
Total Files
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="col-span-6 sm:col-span-3 xl:col-span-1 w-px h-40 border border-r border-accent border-dashed mx-4 xl:mx-6 hidden md:table-cell">
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 sm:col-span-6 xl:col-span-2 min-w-30">
|
||||
<div class="bg-secondary shadow-xl rounded-lg p-5 hover:bg-secondary-hover ">
|
||||
<div class="flex">
|
||||
<i data-lucide="image" class="text-blue-400 mr-2"></i>
|
||||
<p class="ml-auto text-slate-500 text-sm">
|
||||
<%= appDataObj.totalImageSize %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-color-primary sm:text-lg md:text-3xl font-medium leading-8 mt-6">
|
||||
<%= appDataObj.allImages.length %>
|
||||
</div>
|
||||
<div class="sm:text-sm md:text-base text-base text-slate-500 mt-1">
|
||||
<% if(appDataObj.allImages.length==1){ %> Image <% } else { %> Images <% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 sm:col-span-6 xl:col-span-2 min-w-30">
|
||||
<div class="bg-secondary shadow-xl rounded-lg p-5 hover:bg-secondary-hover">
|
||||
<div class="flex">
|
||||
<i data-lucide="video" class="text-yellow-400 mr-2"></i>
|
||||
<p class="ml-auto text-slate-500 text-sm">
|
||||
<%= appDataObj.totalVideosSize %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-color-primary sm:text-lg md:text-3xl font-medium leading-8 mt-6">
|
||||
<%= appDataObj.allVideos.length %>
|
||||
</div>
|
||||
<div class="sm:text-sm md:text-base text-base text-slate-500 mt-1">
|
||||
<% if(appDataObj.allVideos.length==1){ %> Video <% } else { %> Videos <% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 sm:col-span-6 xl:col-span-2">
|
||||
<div class="bg-secondary shadow-xl rounded-lg p-5 hover:bg-secondary-hover">
|
||||
<div class="flex">
|
||||
<i data-lucide="headphones" class="text-orange-400 mr-2"></i>
|
||||
<p class="ml-auto text-slate-500 text-sm">
|
||||
<%= appDataObj.totalAudioSize %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-color-primary sm:text-lg md:text-3xl font-medium leading-8 mt-6">
|
||||
<%= appDataObj.allAudio.length %>
|
||||
</div>
|
||||
<div class="sm:text-sm md:text-base text-base text-slate-500 mt-1">
|
||||
<% if(appDataObj.allAudio.length==1){ %> Audio File <% } else { %> Audio Files <% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 sm:col-span-6 xl:col-span-2 min-w-30">
|
||||
<div class="bg-secondary shadow-xl rounded-lg p-5 hover:bg-secondary-hover">
|
||||
<div class="flex">
|
||||
<i data-lucide="ghost" class="text-green-400 mr-2"></i>
|
||||
<p class="ml-auto text-slate-500 text-sm">
|
||||
<%= appDataObj.totalOthersSize %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-color-primary sm:text-lg md:text-3xl font-medium leading-8 mt-6">
|
||||
<%= appDataObj.allOthers.length %>
|
||||
</div>
|
||||
<div class="sm:text-sm md:text-base text-slate-500 mt-1">
|
||||
<% if(appDataObj.allOthers.length==1){ %> Other File <% } else { %> Other Files <% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- End System Stat Cards -->
|
||||
</div>
|
||||
|
||||
<!-- Admin Tabs -->
|
||||
|
||||
<div
|
||||
class="flex flex-col flex-wrap items-center space-y-4 px-5 pt-5 md:flex-row md:space-y-0 lg:flex-row lg:space-y-0">
|
||||
<button data-id="1"
|
||||
class="tabAdmin inline-flex w-full items-center space-x-2 border-b-2 border-accent px-6 pb-5 text-sm font-semibold text-color-primary md:w-auto md:text-base lg:w-auto lg:text-base">
|
||||
<i data-lucide="Settings" class="text-color-secondary"></i>
|
||||
<span class="truncate"> App Settings </span>
|
||||
</button>
|
||||
|
||||
<button data-id="2"
|
||||
class="tabAdmin inline-flex w-full items-center space-x-2 px-6 pb-5 text-sm text-color-primary md:w-auto md:text-base lg:w-auto lg:text-base">
|
||||
<i data-lucide="Users" class="text-color-secondary"></i>
|
||||
<span> Users </span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- End Admin Tabs -->
|
||||
</div>
|
||||
|
||||
<div class="mt-10 flex flex-col flex-wrap">
|
||||
<div class="changeTabAdmin flex-1 basis-full">
|
||||
<%- include("./tabs/appsettings") %>
|
||||
</div>
|
||||
<div class="changeTabAdmin hidden basis-full">
|
||||
<%- include("./tabs/users") %>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,470 @@
|
||||
<div class="flex lg:flex-row lg:justify-center lg:items-center flex-col flex-wrap">
|
||||
<div class="flex-none basis-1/2">
|
||||
<div class="flex flex-col flex-wrap">
|
||||
<div class="flex flex-col lg:gap-8 gap-3 ">
|
||||
|
||||
<% if(success_alert_message !='' ){ %>
|
||||
<div class="flex-1 bg-tertiary p-6 rounded-md">
|
||||
<p class="text-green-600 pb-2 text-center">
|
||||
<%= success_alert_message %>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if(error_message !='' ){ %>
|
||||
<div class="flex-1 bg-tertiary p-6 rounded-md">
|
||||
<p class="text-red-600 pb-2 text-center">
|
||||
<%= error_message %>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<form method="post" action="/admin/save/settings">
|
||||
<!-- Main Setting -->
|
||||
<div class="flex-1 bg-tertiary p-6 rounded-md">
|
||||
<div class="py-9">
|
||||
<div class="border-b w-full border-gray-300/10 pb-3">
|
||||
<span class="text-lg text-color-primary font-semibold">
|
||||
Main Settings
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-5 text-sm md:text-base">
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
App Name
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This is the title shown in the top left of the app, as well as other various places.
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="<%= settingsDatabase.name ? settingsDatabase.name : "DICK" %>" name="name" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
App Emoji
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This is the emoji shown in the breadcrumps at the top of the screen, as well as on each file.
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="<%= settingsDatabase.appEmoji ? settingsDatabase.appEmoji : "🍆" %>" name="appEmoji" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Site Name
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This is the text shown on the browser tab, and title in the embed when directly linking the <%= settingsDatabase.name ? settingsDatabase.name.toUpperCase() : "DICK" %> dashboard.
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="<%= settingsDatabase.siteTitle ? settingsDatabase.siteTitle : "DICK (Directly Integrated Client for Keisters)" %>" name="siteTitle" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Site Description
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This is the description text shown in the embed when directly linking the <%= settingsDatabase.name ? settingsDatabase.name.toUpperCase() : "DICK" %> dashboard.
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="<%= settingsDatabase.siteDescription ? settingsDatabase.siteDescription : "The frontend for your backend" %>" name="siteDescription" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-2">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Login Text
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This is the text shown directly under the app title in the login and register pages!
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="<%= settingsDatabase.loginText ? settingsDatabase.loginText : "Sign in to easily manage your nudes." %>" name="loginText" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Captcha Site ID
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
If set, this will enable hCaptcha. If configured wrong, your users wont be able to log in. Proceed with caution. <a href="https://www.hcaptcha.com/">learn more here.</a>
|
||||
</div>
|
||||
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
|
||||
<!-- Input -->
|
||||
<div class="relative">
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="<%= settingsDatabase.captchaSiteID ? settingsDatabase.captchaSiteID : "No site ID entered. Defaulting to vanilla captcha." %>" name="captchaSiteID" type="text" />
|
||||
|
||||
<div class="absolute top-0 right-3 h-full">
|
||||
<div class="flex items-center justify-center space-x-2 h-full">
|
||||
<!-- Rounded Checkbox -->
|
||||
<div class="relative">
|
||||
<input id="captchaCheckbox" name="captchaCheckbox" class="hidden peer"
|
||||
<%= settingsDatabase.captchaEnabled ? 'checked' : null %> type="checkbox" />
|
||||
<button type="button"
|
||||
class="peer border-2 focus:border-accent text-transparent peer-checked:text-color-primary border-[#354567] flex items-center justify-center peer-checked:bg-accent relative h-5 w-5 md:h-6 md:w-6 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="14" height="14" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<polyline points="20 6 9 17 4 12">
|
||||
</polyline>
|
||||
</svg>
|
||||
<label class="absolute inset-0 cursor-pointer"
|
||||
for="captchaCheckbox">
|
||||
</label>
|
||||
</button>
|
||||
</div>
|
||||
<!-- End Rounded Checkbox -->
|
||||
|
||||
<small class="text-color-tertiary text-xs">
|
||||
Enable
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Input -->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Captcha Secret Key
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This is found in your hCaptcha profile and used to verify captcha checks.
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="<%= settingsDatabase.captchaSecretKey ? settingsDatabase.captchaSecretKey : "No site ID entered. Defaulting to vanilla captcha." %>" name="captchaSecretKey" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-1">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Private Mode
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This will remove the app statisics from the login page.
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
<!-- Switch -->
|
||||
<label class="switch">
|
||||
<!-- <span class="pr-2 text-color-secondary">
|
||||
Off
|
||||
</span> -->
|
||||
<input type="checkbox" name="privateModeEnabled" value="privateModeEnabled" <%= settingsDatabase.privateModeEnabled ? 'checked' : null %>>
|
||||
<span class="check"></span>
|
||||
<!-- <span class="pl-2 text-color-secondary">
|
||||
On
|
||||
</span> -->
|
||||
</label>
|
||||
<!-- End Switch -->
|
||||
</div>
|
||||
|
||||
<div class="col-span-1">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Registration Enabled
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This allows people to create accounts in your ASS instance.
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
<label class="switch">
|
||||
<!-- <span class="pr-2 text-color-primary">
|
||||
Off
|
||||
</span> -->
|
||||
<input type="checkbox" name="registrationEnabled" value="registrationEnabled" <%= settingsDatabase.registrationEnabled ? 'checked' : null %>>
|
||||
<span class="check"></span>
|
||||
<!-- <span class="pl-2 text-color-primary">
|
||||
On
|
||||
</span> -->
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2">
|
||||
<div class="flex w-full justify-center items-center bg-tertiary p-6 rounded-md">
|
||||
<button type="submit" class="bg-accentsecondary w-full hover:bg-accent rounded-md px-8 py-3 text-color-primary">
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Main Setting -->
|
||||
</form>
|
||||
|
||||
<!-- Image Setting -->
|
||||
<div class="flex-1 bg-tertiary p-6 rounded-md">
|
||||
<div class="py-9">
|
||||
<div class="border-b w-full border-gray-300/10 pb-3">
|
||||
<span class="text-lg text-color-primary font-semibold">
|
||||
Image Settings
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-5 text-sm md:text-base">
|
||||
|
||||
<div class="col-span-2">
|
||||
<form method="post" enctype="multipart/form-data" action="/admin/upload/logo">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
App Logo
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This is the logo shown in various areas of the app!
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
|
||||
<!-- Drag Drop File -->
|
||||
<label for="app-logo" class="cursor-pointer flex w-full h-40 border-2 border-form border-dashed rounded-md p-5">
|
||||
<div class="w-full flex flex-col justify-center items-center h-ful">
|
||||
<span class="font-semibold text-color-primary text-lg">
|
||||
Drop files here or click to upload.
|
||||
</span>
|
||||
<p class="text-sm text-color-secondary text-center">
|
||||
All files uploaded are stored directly in the <%= settingsDatabase.name ? settingsDatabase.name.toUpperCase() : "DICK" %> app folder.
|
||||
</p>
|
||||
</div>
|
||||
<input id="app-logo" name="app-logo" class="hidden" type="file" onchange="form.submit()" accept=".jpg, .jpeg, .png">
|
||||
</label>
|
||||
<!-- End Drag Drop File -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2">
|
||||
<form method="post" enctype="multipart/form-data" action="/admin/upload/default-pp">
|
||||
<!-- Label with tooltip -->
|
||||
<label class="mb-2 block text-color-tertiary text-sm">
|
||||
<div class="relative w-max">
|
||||
Default Profile Picture
|
||||
<div class="group z-20 absolute -right-8 top-0 cursor-help">
|
||||
<div>
|
||||
<i data-lucide="help-circle" class="text-color-secondary"></i>
|
||||
</div>
|
||||
<div class="flex justify-center items-center">
|
||||
|
||||
<div class="lg:w-52 w-48 group-hover:block text-color-light text-sm hidden absolute bottom-10 bg-tooltip p-2 rounded-md">
|
||||
<div class="break-words">
|
||||
This is the default profile picture all new users have.
|
||||
</div>
|
||||
<div class="absolute -left-[0.10rem] flex justify-center items-center w-full">
|
||||
<div class="w-0 h-0 border-t-[20px] border-l-[10px] border-r-[10px] border-tooltip border-l-transparent border-r-transparent border-solid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</label>
|
||||
<!-- End Label with tooltip -->
|
||||
|
||||
<!-- Drag Drop File -->
|
||||
<label for="default-pp" class="cursor-pointer flex w-full h-40 border-2 border-form border-dashed rounded-md p-5">
|
||||
<div class="w-full flex flex-col justify-center items-center h-ful">
|
||||
<span class="font-semibold text-color-primary text-lg">
|
||||
Drop files here or click to upload.
|
||||
</span>
|
||||
<p class="text-sm text-color-secondary text-center">
|
||||
All files uploaded are stored directly in the <%= settingsDatabase.name ? settingsDatabase.name.toUpperCase() : "DICK" %> app folder.
|
||||
</p>
|
||||
</div>
|
||||
<input id="default-pp" name="default-pp" class="hidden" type="file" onchange="form.submit()" accept=".jpg, .jpeg, .png">
|
||||
</label>
|
||||
<!-- End Drag Drop File -->
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Image Setting -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EMPTY CARD ON THE RIGHT -->
|
||||
<!-- <div class="flex-none lg:basis-1/2 basis-full self-auto lg:self-start lg:pl-3 pl-0 lg:mt-0 mt-3">
|
||||
<div class="flex flex-col flex-wrap">
|
||||
<div class="flex flex-col lg:gap-8 gap-3 ">
|
||||
|
||||
<div class="flex-1 bg-tertiary p-6 rounded-md">
|
||||
<div class="py-9">
|
||||
<div class="border-b w-full border-gray-300/10 pb-3">
|
||||
<span class="text-lg text-color-primary font-semibold">
|
||||
Title here
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
@ -0,0 +1,121 @@
|
||||
<div class="flex flex-col items-center justify-center w-full">
|
||||
<div class="flex-1 lg:w-4/5 w-full">
|
||||
<div class="flex flex-row flex-wrap w-full items-center mt-4 gap-5">
|
||||
<!-- Add user -->
|
||||
<div class="flex-none order-1">
|
||||
<div class="inline-flex space-x-3">
|
||||
|
||||
<button type="button"
|
||||
class="buttonModal rounded-md bg-accentsecondary py-2 px-2.5 font-medium text-color-primary hover:bg-accent">
|
||||
Add New user
|
||||
</button>
|
||||
<!--
|
||||
<button
|
||||
class="custom-bg-button hover:custom-bg-button-hover rounded-md p-3 text-color-secondary">
|
||||
<i data-lucide="download" class="text-color-primary"></i>
|
||||
</button>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Add User -->
|
||||
|
||||
<!-- Total User -->
|
||||
<div class="flex-1 lg:order-2 order-3 lg:basis-auto basis-full">
|
||||
<div class="text-sm text-color-secondary lg:text-center text-right">
|
||||
Showing <%= totalUsers %> users
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Total User -->
|
||||
|
||||
<!-- Search User -->
|
||||
<!--
|
||||
<div
|
||||
class="lg:flex-none flex-1 lg:basis-80 sm:basis-auto basis-full lg:order-3 order-2">
|
||||
<div class="sm:flex sm:justify-end">
|
||||
<form action="#" class="relative h-auto lg:w-full w-full sm:w-3/5">
|
||||
<input
|
||||
class="bg-tertiary relative z-10 w-full rounded-md pl-4 pr-10 py-3 text-color-primary shadow-lg placeholder:text-color-secondary focus:border focus:border-gray-300/25 focus:outline-none md:w-full lg:w-full"
|
||||
value="" placeholder="Search Users" />
|
||||
<div
|
||||
class="absolute inset-0 flex flex-row flex-nowrap items-center justify-end px-4">
|
||||
<button type="button" class="absolute z-10">
|
||||
<i data-lucide="search" class="text-color-secondary"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<!-- End Search user -->
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<!-- Table User -->
|
||||
<table class="table-fixed w-full md:text-base text-sm text-black">
|
||||
<thead class="text-color-primary font-bold">
|
||||
<tr>
|
||||
<th class="p-3 text-left">
|
||||
PROFILE PICTURE
|
||||
</th>
|
||||
<th class="p-3 text-left">
|
||||
USERNAME
|
||||
</th>
|
||||
<!--
|
||||
<th class="p-3 text-center">
|
||||
UPLOADS
|
||||
</th>
|
||||
<th class="p-3 text-center">
|
||||
DATA USED
|
||||
</th>
|
||||
<th class="p-3 text-center">
|
||||
ACTIONS
|
||||
</th>
|
||||
-->
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% for(const user of dickUsers) {%>
|
||||
<tr class="text-sm border-b-2 border-table">
|
||||
<td class="p-4 bg-tertiary rounded-tl-2xl rounded-bl-2xl text-left ">
|
||||
<img class="relative h-10 w-10 rounded-full object-cover object-center"
|
||||
src=<%=settingsDatabase.defaultProfilePicture ?
|
||||
settingsDatabase.defaultProfilePicture : "./images/profile.png" %> alt="Profile" />
|
||||
</td>
|
||||
<td class="p-4 bg-tertiary">
|
||||
<span class="text-color-secondary font-semibold">
|
||||
<%= user.username %>
|
||||
</span>
|
||||
<div class="text-color-tertiary text-xs">
|
||||
<%= user.role %>
|
||||
</div>
|
||||
</td>
|
||||
<!--
|
||||
<td class="p-4 bg-tertiary text-color-secondary text-center">
|
||||
66
|
||||
</td>
|
||||
<td class="p-4 bg-tertiary text-color-secondary text-center ">
|
||||
<div class="sm:border-r border-slate-700 border-r-0">
|
||||
65GB
|
||||
</div>
|
||||
</td>
|
||||
-->
|
||||
<!--
|
||||
<td class="p-4 bg-tertiary rounded-br-2xl rounded-tr-2xl text-center">
|
||||
<div class="flex justify-center">
|
||||
<button class="text-red-500 flex sm:flex-row flex-col justify-center items-center space-x-1 ">
|
||||
<i data-lucide="trash-2" class="text-color-red"></i>
|
||||
<div>
|
||||
Delete
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
-->
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- End Table User -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,54 @@
|
||||
<div class="bg-secondary max-h-1/2 max-w-md mx-auto overflow-hidden rounded-lg shadow-xl">
|
||||
<div class="flex flex-col items-center justify-center overflow-y-auto md:flex-row">
|
||||
<div class="flex items-center justify-center p-6 sm:p-12">
|
||||
<div class="w-full">
|
||||
<h3 class="text-color-tertiary font-bold text-2xl text-center">
|
||||
<%= settingsDatabase.name ? settingsDatabase.name.toUpperCase() : "DICK" %>
|
||||
</h3>
|
||||
<p class="text-color-accent text-xs pt-2 pb-5 text-center">
|
||||
Please enter a username and desired secret key.
|
||||
</p>
|
||||
|
||||
<% if(success_alert_message !='' ){ %>
|
||||
<p class="text-green-600 pb-2 text-center">
|
||||
<%= success_alert_message %>
|
||||
</p>
|
||||
<% } %>
|
||||
|
||||
<% if(error_message !='' ){ %>
|
||||
<p class="text-red-600 pb-2 text-center">
|
||||
<%= error_message %>
|
||||
</p>
|
||||
<% } %>
|
||||
|
||||
<form class="flex flex-col" method="post" action="/auth/register">
|
||||
<label class="block text-sm">
|
||||
<span class="text-color-tertiary">Username</span>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="Imposter" name="username" type="text" />
|
||||
</label>
|
||||
<label class="block mt-4 text-sm">
|
||||
<span class="text-color-tertiary">Secret Key</span>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="***************" name="password" type="password" />
|
||||
</label>
|
||||
<% if(settingsDatabase.captchaEnabled) { %>
|
||||
<div class="h-captcha mt-4" data-theme="dark" data-sitekey=<%= settingsDatabase.captchaSiteID %>></div>
|
||||
<% } %>
|
||||
<button class="block w-full px-4 py-2 mt-4 text-sm font-medium leading-5 text-center text-color-primary transition-colors duration-150 bg-accentsecondary border border-transparent rounded-lg active:bg-accentsecondary hover:bg-accent focus:outline-none focus:shadow-outline-purple"
|
||||
type="submit">
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="mt-4 text-center">
|
||||
<a class="text-sm font-medium text-color-accent hover:underline" href="/login">
|
||||
Login
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,114 @@
|
||||
<div class=" flex-col flex-wrap mt-6">
|
||||
<div class="flex xl:flex-row lg:flex-row flex-col lg:gap-8 gap-3 ">
|
||||
|
||||
<div class="flex-1 bg-tertiary p-6 rounded-md lg:order-1 md:order-2 order-2">
|
||||
<div class="py-9">
|
||||
<div class="border-b w-full border-gray-300/10 pb-3">
|
||||
<span class="text-lg text-color-primary font-semibold">
|
||||
Config Setting
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<form class="grid grid-cols-2 gap-5 text-sm md:text-base lg:text-base">
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<label class="mb-2 block text-color-tertiary text-sm">Provider</label>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
value="" placeholder="Some text" type="text" />
|
||||
</div>
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<label class="mb-2 block text-color-tertiary text-sm">Provider Url</label>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
value="" placeholder="Some text" type="text" />
|
||||
</div>
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<label class="mb-2 block text-color-tertiary text-sm">Author</label>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
value="" placeholder="Some text" type="text" />
|
||||
</div>
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<label class="mb-2 block text-color-tertiary text-sm">Author Url</label>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
value="" placeholder="Some text" type="text" />
|
||||
</div>
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<label class="mb-2 block text-color-tertiary text-sm">Title</label>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
value="" placeholder="Some text" type="text" />
|
||||
</div>
|
||||
<div class="xl:col-span-1 lg:col-span-2 col-span-1">
|
||||
<label class="mb-2 block text-color-tertiary text-sm">Description</label>
|
||||
<input
|
||||
class="form-input block w-full mt-1 rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
value="" placeholder="Some text" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-3 block text-color-tertiary text-sm">Colour</label>
|
||||
<input type="color" class="mt-1 custom-color-picker-border">
|
||||
</div>
|
||||
<div class="relative">
|
||||
<label class="relative mb-2 block text-color-tertiary text-sm">Url Type</label>
|
||||
<select
|
||||
class="default:bg-gray-30 custom-bg-darker relative block w-full rounded text-sm text-color-tertiary border-form bg-forminput transition duration-500 focus:border-transparent border-transparent focus:ring-0 focus:border-formaccent focus:text-color-accent"
|
||||
placeholder="Zero Width Space">
|
||||
<option value="0">Zero Width Space</option>
|
||||
<option value="1">Mixed-Case Alphas</option>
|
||||
<option value="2">Gfycat</option>
|
||||
<option value="3">Original</option>
|
||||
<option value="3">Timestamp</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-2 pt-5">
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="bg-accentsecondary md:w-auto w-full hover:bg-accent rounded-md px-5 py-2 text-color-primary">
|
||||
Export Config
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
class="place-self-center flex-none h-80 bg-slate-600/25 w-[1px] lg:order-2 md:order-3 lg:block md:hidden hidden">
|
||||
|
||||
</div>
|
||||
<div
|
||||
class="flex-none bg-tertiary xl:basis-[38rem] lg:basis-[28rem] p-6 rounded-md lg:order-3 md:order-1 order-1 lg:-mt-0 md:-mt-0 -mt-7">
|
||||
<div class="py-9">
|
||||
<div class="border-b w-full border-gray-300/10 pb-3">
|
||||
<span class="text-lg text-color-primary font-semibold">
|
||||
Example Embed
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="xl:px-12 lg:px-0 md:px-0 px-0">
|
||||
<div
|
||||
class="p-3 space-y-4 flex flex-col bg-slate-800 rounded-md border-l-8 border-purple-600">
|
||||
<a href ="/" class="text-color-secondary">
|
||||
Provider
|
||||
</a>
|
||||
<span class="font-semibold text-gray-300 text-lg">
|
||||
Author
|
||||
</span>
|
||||
<a href ="/" class="font-semibold text-cyan-500 text-lg">
|
||||
Title
|
||||
</a>
|
||||
<p class="text-gray-300">
|
||||
Description
|
||||
</p>
|
||||
<img class="w-full h-64 object-contain"
|
||||
src="./images/profile.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
@ -1,3 +1,3 @@
|
||||
<div class="flex-1 basis-full space-y-3 md:basis-2/3 lg:basis-3/4">
|
||||
<p class="text-orange-700 dark:text-orange-400">This tab is coming to a DICK near you soon </p>
|
||||
<p class="text-orange-700 dark:text-orange-400">This tab is coming to a <%= settingsDatabase.name ? settingsDatabase.name.toUpperCase() : "DICK" %> near you soon </p>
|
||||
</div>
|
@ -1,12 +1,12 @@
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-center flex-col items-center space-y-2">
|
||||
<svg class="animate-spin text-purple-400 w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||
<svg class="animate-spin text-color-accent w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 2v6h-6"></path>
|
||||
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"></path>
|
||||
<path d="M3 22v-6h6"></path>
|
||||
<path d="M21 12a9 9 0 0 1-15 6.7L3 16"></path>
|
||||
</svg>
|
||||
<span class="font-semibold text-white">Loading more files...</span>
|
||||
<span class="font-semibold text-color-primary">Loading more files...</span>
|
||||
</div>
|
||||
</div>
|
@ -1,11 +1,11 @@
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-center flex-col items-center space-y-2">
|
||||
<svg class="text-purple-400 w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg class="text-color-accent w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M16 16s-1.5-2-4-2-4 2-4 2"></path>
|
||||
<line x1="9" y1="9" x2="9.01" y2="9"></line>
|
||||
<line x1="15" y1="9" x2="15.01" y2="9"></line>
|
||||
</svg>
|
||||
<span class="font-semibold text-white">You have no more files to load!</span>
|
||||
<span class="font-semibold text-color-primary">You have no more files to load! <%= settingsDatabase.appEmoji ? settingsDatabase.appEmoji : '🍆' %></span>
|
||||
</div>
|
||||
</div>
|
@ -1,13 +1,27 @@
|
||||
<% if(path=='/' ){ %>
|
||||
<% if(path=='/register' || path=='/login'){ %>
|
||||
<script src="/js/app.js"></script>
|
||||
<script src="/js/components.js"></script>
|
||||
<script src="/js/theme/theme-switcher.js"></script>
|
||||
<% if( settingsDatabase.captchaEnabled ){ %>
|
||||
<script src="https://js.hcaptcha.com/1/api.js" async defer>
|
||||
console.log('test')
|
||||
</script>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% if(path=='/login' ){ %>
|
||||
|
||||
<% if(path=='/' ){ %>
|
||||
<script src="/js/app.js"></script>
|
||||
<script src="/js/dropdowns.js"></script>
|
||||
<script src="/js/change-color.js"></script>
|
||||
<!-- <script src="/js/show-password.js"></script>-->
|
||||
<script src="/js/tabs-user.js"></script>
|
||||
<script src="/js/theme/theme-switcher.js"></script>
|
||||
<% } %>
|
||||
|
||||
<% if(path=='/admin' ){ %>
|
||||
<script src="/js/app.js"></script>
|
||||
<script src="/js/dropdowns.js"></script>
|
||||
<script src="/js/open-modal.js"></script>
|
||||
<!-- <script src="/js/show-password.js"></script>-->
|
||||
<script src="/js/tabs-admin.js"></script>
|
||||
<script src="/js/theme/theme-switcher.js"></script>
|
||||
<% } %>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="flex h-0 pl-3 md:h-8 md:pl-7 lg:h-8 lg:pl-7">
|
||||
<img alt="DICK" class="w-6" src="/images/dick-logo.png">
|
||||
<span class="text-white ml-3 hidden text-lg font-medium md:block lg:block">DICK</span>
|
||||
<img alt=<%= settingsDatabase.name ? settingsDatabase.name.toUpperCase() : "DICK" %> class="w-6" src=<%= settingsDatabase.logo ? settingsDatabase.logo : "./images/logo.png" %>>
|
||||
<span class="text-color-primary ml-3 hidden text-lg font-medium md:block lg:block"><%= settingsDatabase.name ? settingsDatabase.name.toUpperCase() : "DICK" %></span>
|
||||
</div>
|