Browse Source

update: use lowdb

pull/1228/head
tetuaoro 2 years ago
parent
commit
054ca2e522
  1. 1
      .gitignore
  2. 11
      .vscode/settings.json
  3. 13
      Dockerfile
  4. 4
      How_to_generate_an_bcrypt_hash.md
  5. 3
      README.md
  6. 2
      docker-compose.dev.yml
  7. 1
      docker-compose.yml
  8. 17
      src/lib/Server.js
  9. 0
      src/lib/database/AbstractDatabase.js
  10. 96
      src/lib/database/Lowdb.js
  11. 101
      src/lib/database/SQLite.js
  12. 1391
      src/package-lock.json
  13. 3
      src/package.json
  14. 5
      src/services/Database.js
  15. 14
      src/templates/Settings.vue
  16. 54
      src/wgpw.mjs
  17. 5
      src/wgpw.sh
  18. 10
      src/www/css/app.css
  19. 22
      src/www/index.html
  20. 2
      src/www/js/api.js
  21. 2
      src/www/js/app.js
  22. 11
      src/www/js/i18n.js
  23. 2
      src/www/js/vendor/vue-dashboard.umd.min.js
  24. 1
      src/www/js/vendor/vue-dashboard.umd.min.js.map
  25. 2
      src/www/js/vendor/vue-settings.umd.min.js
  26. 1
      src/www/js/vendor/vue-settings.umd.min.js.map

1
.gitignore

@ -4,4 +4,3 @@
/src/node_modules
.DS_Store
*.swp
/.vscode

11
.vscode/settings.json

@ -0,0 +1,11 @@
{
"[javascript]": {
"editor.formatOnSave": true,
"editor.formatOnType": true
},
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
"javascript.format.semicolons": "insert",
"editor.tabSize": 2,
"editor.indentSize": "tabSize",
"editor.detectIndentation": false
}

13
Dockerfile

@ -5,17 +5,10 @@ FROM docker.io/library/node:18-alpine AS build_node_modules
# Update npm to latest
RUN npm install -g npm@latest
# Sqlite build
RUN apk add --no-cache \
build-base \
sqlite-dev \
python3 \
py3-pip
# Copy Web UI
COPY src /app
WORKDIR /app
RUN npm ci --build-from-source=sqlite3 --omit=dev &&\
RUN npm ci --omit=dev &&\
mv node_modules /node_modules
# Copy build result to a new image.
@ -33,6 +26,10 @@ COPY --from=build_node_modules /app /app
# than what runs inside of docker.
COPY --from=build_node_modules /node_modules /node_modules
# Copy the needed wg-password scripts
COPY --from=build_node_modules /app/wgpw.sh /bin/wgpw
RUN chmod +x /bin/wgpw
# Install Linux packages
RUN apk add --no-cache \
dpkg \

4
How_to_generate_an_bcrypt_hash.md

@ -25,4 +25,8 @@ $ echo "$2b$12$coPqCsPtcF"
b2
$ echo '$2b$12$coPqCsPtcF'
$2b$12$coPqCsPtcF
<<<<<<< HEAD
```
=======
```
>>>>>>> a13c652 (update: use lowdb)

3
README.md

@ -65,6 +65,7 @@ To automatically install & run wg-easy, simply run:
--name=wg-easy \
-e LANG=de \
-e WG_HOST=<🚨YOUR_SERVER_IP> \
-e PASSWORD_HASH=<🚨YOUR_ADMIN_PASSWORD_HASH> \
-e PORT=51821 \
-e WG_PORT=51820 \
-v ~/.wg-easy:/etc/wireguard \
@ -80,6 +81,7 @@ To automatically install & run wg-easy, simply run:
> 💡 Replace `YOUR_SERVER_IP` with your WAN IP, or a Dynamic DNS hostname.
>
> 💡 Replace `YOUR_ADMIN_PASSWORD_HASH` with a bcrypt password hash to log in on the Web UI. See [How_to_generate_an_bcrypt_hash.md](./How_to_generate_an_bcrypt_hash.md) for know how generate the hash.
The Web UI will now be available on `http://0.0.0.0:51821`.
@ -101,6 +103,7 @@ These options can be configured by setting environment variables using `-e KEY="
| - | - | - |------------------------------------------------------------------------------------------------------------------------------------------------------|
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
| `WEBUI_HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
| `PASSWORD_HASH` | - | `$2y$05$Ci...` | When set, requires a password when logging in to the Web UI. See [How to generate an bcrypt hash.md]("https://github.com/wg-easy/wg-easy/blob/master/How_to_generate_an_bcrypt_hash.md") for know how generate the hash. |
| `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. |
| `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. |
| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. |

2
docker-compose.dev.yml

@ -13,5 +13,5 @@ services:
- NET_ADMIN
- SYS_MODULE
environment:
# - PASSWORD=p
# - PASSWORD_HASH=p
- WG_HOST=192.168.1.233

1
docker-compose.yml

@ -12,6 +12,7 @@ services:
- WG_HOST=raspberrypi.local
# Optional:
# - PASSWORD_HASH=$$2y$$10$$hBCoykrB95WSzuV4fafBzOHWKu9sbyVa34GJr8VV5R/pIelfEMYyG (needs double $$, hash of 'foobar123'; see "How_to_generate_an_bcrypt_hash.md" for generate the hash)
# - PORT=51821
# - WG_PORT=51820
# - WG_CONFIG_PORT=92820

17
src/lib/Server.js

@ -138,6 +138,13 @@ module.exports = class Server {
}
if (req.url.startsWith('/api/') && req.headers['authorization']) {
// when I debug req.headers['authorization'], I have nothing
// if (isPasswordValid(req.headers['authorization'])) {
// return next();
// }
// return res.status(401).json({
// error: 'Incorrect Password',
// });
return next();
}
@ -266,12 +273,12 @@ module.exports = class Server {
return { success: true };
}));
// admin dashboard
const routerDashboard = createRouter();
app.use(routerDashboard);
// setting
const routerSetting = createRouter();
app.use(routerSetting);
routerDashboard
.put('/api/dashboard/setting/password', defineEventHandler(async (event) => {
routerSetting
.put('/api/setting/password', defineEventHandler(async (event) => {
const { username, oldPassword, newPassword } = await readBody(event);
await Database.updatePassword(username, oldPassword, newPassword);
return { success: true };

0
src/lib/database/Interface.js → src/lib/database/AbstractDatabase.js

96
src/lib/database/Lowdb.js

@ -0,0 +1,96 @@
'use strict';
const path = require('path');
const { v4: uuidv4 } = require('uuid');
const debug = require('debug')('Database');
const bcrypt = require('bcryptjs');
const ServerError = require('../ServerError');
const DatabaseInterface = require('./AbstractDatabase');
const { WG_PATH } = require('../../config');
/** *
*
* @typedef {Object} Role
* @property {string} ADMIN - Represents an ADMIN user
*
* @typedef {Object} User
* @property {number} id - The unique identifier of user
* @property {string} username - The username of user
* @property {string} password - The password of user
* @property {Array<Role[keyof Role]>} roles - All roles of user
*
* @typedef {Object} Model
* @property {Array<User>} users
*/
/** *
* @type {Role}
*/
const Role = {
ADMIN: 'admin',
};
/** *
* @type {Model}
*/
const Model = {};
module.exports = class Lowdb extends DatabaseInterface {
constructor() {
super();
this.dbPath = path.join(WG_PATH, 'db.json');
this.db = null;
}
async initDb() {
debug('Loading database...');
// lowdb is esm module
// eslint-disable-next-line node/no-unsupported-features/es-syntax, import/no-unresolved, node/no-missing-import
const { JSONFilePreset } = await import('lowdb/node');
this.db = await JSONFilePreset(this.dbPath, Model);
await this.db.read();
if (!this.db.data.users) {
this.db.data = { users: [] };
const defaultPassword = process.env.PASSWORD_HASH || await bcrypt.hash(process.env.PASSWORD || 'admin', 12);
const adminUser = {
id: uuidv4(),
username: 'admin',
password: defaultPassword,
roles: [Role.ADMIN],
};
this.db.data.users.push(adminUser);
await this.db.write();
debug(`Created new database at ${this.dbPath}`);
}
}
async comparePassword(username, password) {
debug('Compare password');
const user = this.db.data.users.find((u) => u.username === username);
if (!user) {
throw new ServerError('User not found');
}
const isMatch = await bcrypt.compare(password, user.password);
return isMatch;
}
async updatePassword(username, oldPassword, newPassword) {
debug('Update password');
const user = this.db.data.users.find((u) => u.username === username);
if (!user) {
throw new ServerError('User not found');
}
const isMatch = await bcrypt.compare(oldPassword, user.password);
if (!isMatch) {
throw new ServerError('Old password is incorrect');
}
// lowdb is atomic
user.password = await bcrypt.hash(newPassword, 12);
await this.db.write();
}
};

101
src/lib/database/SQLite.js

@ -1,101 +0,0 @@
'use strict';
const path = require('path');
const debug = require('debug')('Database');
const bcrypt = require('bcryptjs');
const sqlite = require('sqlite3');
const DatabaseInterface = require('./Interface');
const ServerError = require('../ServerError');
const { WG_PATH } = require('../../config');
module.exports = class SQLiteDatabase extends DatabaseInterface {
constructor() {
super();
this.dbPath = path.join(WG_PATH, 'db.sqlite');
this.provider = 'SQLite';
this.db = null;
}
async initDb() {
return new Promise((resolve, reject) => {
this.db = new sqlite.Database(this.dbPath, async (err) => {
if (err) {
reject(new ServerError('Could not initialize the database'));
}
debug(`Connected to the SQLite database at ${this.dbPath}`);
await this.__setupDatabase();
resolve(true);
});
});
}
async __setupDatabase() {
const createAdminTableQuery = `
CREATE TABLE IF NOT EXISTS admin (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
)
`;
this.db.run(createAdminTableQuery, (err) => {
if (err) {
throw new ServerError('Could not create admin table');
}
});
/* Default username is 'admin' */
this.db.get('SELECT * FROM admin WHERE username = ?', ['admin'], async (err, row) => {
if (err) {
throw new ServerError('Could not query admin table');
}
if (!row) {
const hashedPassword = await bcrypt.hash('admin', 12);
this.db.run('INSERT INTO admin (username, password) VALUES (?, ?)', ['admin', hashedPassword], (err) => {
if (err) {
throw new ServerError('Could not create initial admin user');
}
debug('Init admin username:password "admin:admin"');
});
}
});
}
async comparePassword(username, password) {
return new Promise((resolve, reject) => {
this.db.get('SELECT password FROM admin WHERE username = ?', [username], async (err, row) => {
if (err || !row) {
return reject(new ServerError('Could not query admin table'));
}
const isMatch = await bcrypt.compare(password, row.password);
resolve(isMatch);
});
});
}
async updatePassword(username, oldPassword, newPassword) {
const isMatch = await this.comparePassword(username, oldPassword);
if (!isMatch) {
throw new ServerError('Invalid credentials', 401);
}
const hashedPassword = await bcrypt.hash(newPassword, 10);
return new Promise((resolve, reject) => {
this.db.run('UPDATE admin SET password = ? WHERE username = ?', [hashedPassword, username], (err) => {
if (err) {
return reject(new ServerError('Could not update password'));
}
resolve(true);
});
});
}
};

1391
src/package-lock.json

File diff suppressed because it is too large

3
src/package.json

@ -18,8 +18,9 @@
"debug": "^4.3.6",
"express-session": "^1.18.0",
"h3": "^1.12.0",
"lowdb": "^7.0.1",
"qrcode": "^1.5.3",
"sqlite3": "^5.1.7"
"uuid": "^10.0.0"
},
"devDependencies": {
"eslint-config-athom": "^3.1.3",

5
src/services/Database.js

@ -1,13 +1,14 @@
'use strict';
const SQLiteDatabase = require('../lib/database/SQLite');
const Lowdb = require('../lib/database/Lowdb');
// const MySqlDatabase = require('../lib/database/MySql');
/*
module.exports = {
SQLiteDatabase: new SQLiteDatabase(),
MySqlDatabase: new MySqlDatabase(),
...
};
*/
module.exports = new SQLiteDatabase();
module.exports = new Lowdb();

14
src/templates/Dashboard.vue → src/templates/Settings.vue

@ -1,6 +1,8 @@
<!-- vue-cli-service build --dest src/www/js/vendor --no-module --formats umd-min --target lib --name VueSettings --filename vue-settings --no-clean src/templates/Settings.vue -->
<script>
export default {
name: 'Dashboard',
name: 'Settings',
data() {
return {
currentPassword: '',
@ -21,7 +23,7 @@ export default {
username: 'admin',
oldPassword: this.currentPassword,
newPassword: this.newPassword
}).then((result) => {
}).then((_result) => {
this.currentPassword = '';
this.newPassword = '';
this.confirmNewPassword = '';
@ -41,7 +43,7 @@ export default {
<div class="w-full shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden">
<div class="flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-neutral-500/50 dark:border-neutral-600">
<div class="flex-grow">
<p class="text-2xl font-medium dark:text-neutral-200">{{ $t('dashboard') }}</p>
<p class="text-2xl font-medium dark:text-neutral-200">{{ $t('settings') }}</p>
</div>
</div>
@ -51,7 +53,7 @@ export default {
<label for="currentPassword" class="block text-sm font-medium dark:text-neutral-200 mb-4">
{{ $t('currentPassword') }}
</label>
<input id="currentPassword" v-model="currentPassword" type="password" placeholder="Current Password"
<input id="currentPassword" v-model="currentPassword" type="password" :placeholder="$t('currentPassword')"
class="outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full" required />
</div>
@ -59,7 +61,7 @@ export default {
<label for="newPassword" class="block text-sm font-medium dark:text-neutral-200 mb-4">
{{ $t('newPassword') }}
</label>
<input id="newPassword" v-model="newPassword" type="password" placeholder="New Password"
<input id="newPassword" v-model="newPassword" type="password" :placeholder="$t('newPassword')"
class="outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full" required />
</div>
@ -67,7 +69,7 @@ export default {
<label for="confirmNewPassword" class="block text-sm font-medium dark:text-neutral-200 mb-4">
{{ $t('confirmNewPassword') }}
</label>
<input id="confirmNewPassword" v-model="confirmNewPassword" type="password" placeholder="Confirm New Password"
<input id="confirmNewPassword" v-model="confirmNewPassword" type="password" :placeholder="$t('confirmNewPassword')"
class="outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full" required />
</div>

54
src/wgpw.mjs

@ -0,0 +1,54 @@
'use strict';
// Import needed libraries
import bcrypt from 'bcryptjs';
// Function to generate hash
const generateHash = async (password) => {
try {
const salt = await bcrypt.genSalt(12);
const hash = await bcrypt.hash(password, salt);
// eslint-disable-next-line no-console
console.log(`PASSWORD_HASH='${hash}'`);
} catch (error) {
throw new Error(`Failed to generate hash : ${error}`);
}
};
// Function to compare password with hash
const comparePassword = async (password, hash) => {
try {
const match = await bcrypt.compare(password, hash);
if (match) {
// eslint-disable-next-line no-console
console.log('Password matches the hash !');
} else {
// eslint-disable-next-line no-console
console.log('Password does not match the hash.');
}
} catch (error) {
throw new Error(`Failed to compare password and hash : ${error}`);
}
};
(async () => {
try {
// Retrieve command line arguments
const args = process.argv.slice(2); // Ignore the first two arguments
if (args.length > 2) {
throw new Error('Usage : wgpw YOUR_PASSWORD [HASH]');
}
const [password, hash] = args;
if (password && hash) {
await comparePassword(password, hash);
} else if (password) {
await generateHash(password);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
// eslint-disable-next-line no-process-exit
process.exit(1);
}
})();

5
src/wgpw.sh

@ -0,0 +1,5 @@
#!/bin/sh
# This script is intended to be run only inside a docker container, not on the development host machine
set -e
# proxy command
node /app/wgpw.mjs "$@"

10
src/www/css/app.css

@ -895,8 +895,8 @@ video {
min-width: 5rem;
}
.max-w-5xl {
max-width: 64rem;
.max-w-3xl {
max-width: 48rem;
}
.flex-auto {
@ -1461,12 +1461,6 @@ video {
transition-duration: 150ms;
}
.transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.transition-opacity {
transition-property: opacity;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);

22
src/www/index.html

@ -101,11 +101,21 @@
<div class="flex flex-col">
<button @click="showPage = 'home'"
class="flex items-center rounded hover:bg-red-800 transition-colors px-3 py-2 text-lg">
{{$t('navHome')}}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 mr-2">
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
</svg>
<span>
{{$t('navHome')}}
</span>
</button>
<button @click="showPage = 'dashboard'"
<button @click="showPage = 'settings'"
class="flex items-center rounded hover:bg-red-800 transition-colors px-3 py-2 text-lg">
{{$t('navDashboard')}}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 mr-2">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75" />
</svg>
<span>
{{$t('navSettings')}}
</span>
</button>
</div>
</div>
@ -423,8 +433,8 @@
</div>
</div>
<!-- Dashboard -->
<dashboard v-if="showPage === 'dashboard'"></dashboard>
<!-- Settings -->
<settings v-if="showPage === 'settings'"></settings>
</div>
<!-- QR Code-->
@ -654,7 +664,7 @@
<script src="./js/vendor/vue-apexcharts.min.js"></script>
<script src="./js/vendor/sha256.min.js"></script>
<script src="./js/vendor/timeago.full.min.js"></script>
<script src="./js/vendor/vue-dashboard.umd.min.js"></script>
<script src="./js/vendor/vue-settings.umd.min.js"></script>
<script src="./js/api.js"></script>
<script src="./js/i18n.js"></script>
<script src="./js/app.js"></script>

2
src/www/js/api.js

@ -149,7 +149,7 @@ class API {
async updatePassword({ username, oldPassword, newPassword }) {
return this.call({
method: 'put',
path: '/dashboard/setting/password',
path: '/setting/password',
body: { username, oldPassword, newPassword },
});
}

2
src/www/js/app.js

@ -46,7 +46,7 @@ new Vue({
el: '#app',
components: {
apexchart: VueApexCharts,
dashboard: VueDashboard,
settings: VueSettings,
},
i18n,
data: {

11
src/www/js/i18n.js

@ -35,8 +35,8 @@ const messages = { // eslint-disable-line no-unused-vars
titleRestoreConfig: 'Restore your configuration',
titleBackupConfig: 'Backup your configuration',
navHome: 'Home',
navDashboard: 'Dashboard',
dashboard: 'Dashboard',
navSettings: 'Settings',
settings: 'Settings',
currentPassword: 'Current Password',
newPassword: 'New Password',
confirmNewPassword: 'Confirm New Password',
@ -221,6 +221,13 @@ const messages = { // eslint-disable-line no-unused-vars
backup: 'Sauvegarder',
titleRestoreConfig: 'Restaurer votre configuration',
titleBackupConfig: 'Sauvegarder votre configuration',
navHome: 'Accueil',
navSettings: 'Paramètres',
settings: 'Paramètres',
currentPassword: 'Mot de passe',
newPassword: 'Nouveau mot de passe',
confirmNewPassword: 'Confirmer le mot de passe',
updatePassword: 'Mettre à jour le mot de passe',
},
de: { // github.com/florian-asche
name: 'Name',

2
src/www/js/vendor/vue-dashboard.umd.min.js

@ -1,2 +0,0 @@
(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t():"function"===typeof define&&define.amd?define([],t):"object"===typeof exports?exports["VueDashboard"]=t():e["VueDashboard"]=t()})("undefined"!==typeof self?self:this,(()=>(()=>{"use strict";var e={};(()=>{e.d=(t,r)=>{for(var s in r)e.o(r,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:r[s]})}})(),(()=>{e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})(),(()=>{e.p=""})();var t={};if(e.d(t,{default:()=>c}),"undefined"!==typeof window){var r=window.document.currentScript,s=r&&r.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);s&&(e.p=s[1])}var o=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"w-full shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden"},[r("div",{staticClass:"flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-neutral-500/50 dark:border-neutral-600"},[r("div",{staticClass:"flex-grow"},[r("p",{staticClass:"text-2xl font-medium dark:text-neutral-200"},[e._v(e._s(e.$t("dashboard")))])])]),r("div",{staticClass:"container p-2 flex flex-col md:items-center py-8"},[r("form",{staticClass:"w-full md:w-[75%]",on:{submit:e.updatePassword}},[r("div",{staticClass:"mb-4"},[r("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"currentPassword"}},[e._v(" "+e._s(e.$t("currentPassword"))+" ")]),r("input",{directives:[{name:"model",rawName:"v-model",value:e.currentPassword,expression:"currentPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"currentPassword",type:"password",placeholder:"Current Password",required:""},domProps:{value:e.currentPassword},on:{input:function(t){t.target.composing||(e.currentPassword=t.target.value)}}})]),r("div",{staticClass:"mb-4"},[r("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"newPassword"}},[e._v(" "+e._s(e.$t("newPassword"))+" ")]),r("input",{directives:[{name:"model",rawName:"v-model",value:e.newPassword,expression:"newPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"newPassword",type:"password",placeholder:"New Password",required:""},domProps:{value:e.newPassword},on:{input:function(t){t.target.composing||(e.newPassword=t.target.value)}}})]),r("div",{staticClass:"mb-4"},[r("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"confirmNewPassword"}},[e._v(" "+e._s(e.$t("confirmNewPassword"))+" ")]),r("input",{directives:[{name:"model",rawName:"v-model",value:e.confirmNewPassword,expression:"confirmNewPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"confirmNewPassword",type:"password",placeholder:"Confirm New Password",required:""},domProps:{value:e.confirmNewPassword},on:{input:function(t){t.target.composing||(e.confirmNewPassword=t.target.value)}}})]),r("button",{staticClass:"bg-neutral-400 text-white p-2 rounded-md w-full hover:bg-red-800 transition",attrs:{type:"submit"}},[e._v(" "+e._s(e.$t("updatePassword"))+" ")])])])])},a=[];const n={name:"Dashboard",data(){return{currentPassword:"",newPassword:"",confirmNewPassword:""}},methods:{async updatePassword(e){e.preventDefault(),this.newPassword===this.confirmNewPassword?this.api.updatePassword({username:"admin",oldPassword:this.currentPassword,newPassword:this.newPassword}).then((e=>{this.currentPassword="",this.newPassword="",this.confirmNewPassword="",alert("Password updated")})).catch((e=>{alert(e.message||e.toString())})):alert("Password Mismatch")}},mounted(){this.api=new API}},d=n;function i(e,t,r,s,o,a,n,d){var i,l="function"===typeof e?e.options:e;if(t&&(l.render=t,l.staticRenderFns=r,l._compiled=!0),s&&(l.functional=!0),a&&(l._scopeId="data-v-"+a),n?(i=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(n)},l._ssrRegister=i):o&&(i=d?function(){o.call(this,(l.functional?this.parent:this).$root.$options.shadowRoot)}:o),i)if(l.functional){l._injectStyles=i;var u=l.render;l.render=function(e,t){return i.call(t),u(e,t)}}else{var c=l.beforeCreate;l.beforeCreate=c?[].concat(c,i):[i]}return{exports:e,options:l}}var l=i(d,o,a,!1,null,null,null);const u=l.exports,c=u;return t=t["default"],t})()));
//# sourceMappingURL=vue-dashboard.umd.min.js.map

1
src/www/js/vendor/vue-dashboard.umd.min.js.map

File diff suppressed because one or more lines are too long

2
src/www/js/vendor/vue-settings.umd.min.js

@ -0,0 +1,2 @@
(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t():"function"===typeof define&&define.amd?define([],t):"object"===typeof exports?exports["VueSettings"]=t():e["VueSettings"]=t()})("undefined"!==typeof self?self:this,(()=>(()=>{"use strict";var e={};(()=>{e.d=(t,s)=>{for(var r in s)e.o(s,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:s[r]})}})(),(()=>{e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})(),(()=>{e.p=""})();var t={};if(e.d(t,{default:()=>c}),"undefined"!==typeof window){var s=window.document.currentScript,r=s&&s.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);r&&(e.p=r[1])}var o=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"w-full shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden"},[s("div",{staticClass:"flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-neutral-500/50 dark:border-neutral-600"},[s("div",{staticClass:"flex-grow"},[s("p",{staticClass:"text-2xl font-medium dark:text-neutral-200"},[e._v(e._s(e.$t("settings")))])])]),s("div",{staticClass:"container p-2 flex flex-col md:items-center py-8"},[s("form",{staticClass:"w-full md:w-[75%]",on:{submit:e.updatePassword}},[s("div",{staticClass:"mb-4"},[s("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"currentPassword"}},[e._v(" "+e._s(e.$t("currentPassword"))+" ")]),s("input",{directives:[{name:"model",rawName:"v-model",value:e.currentPassword,expression:"currentPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"currentPassword",type:"password",placeholder:e.$t("currentPassword"),required:""},domProps:{value:e.currentPassword},on:{input:function(t){t.target.composing||(e.currentPassword=t.target.value)}}})]),s("div",{staticClass:"mb-4"},[s("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"newPassword"}},[e._v(" "+e._s(e.$t("newPassword"))+" ")]),s("input",{directives:[{name:"model",rawName:"v-model",value:e.newPassword,expression:"newPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"newPassword",type:"password",placeholder:e.$t("newPassword"),required:""},domProps:{value:e.newPassword},on:{input:function(t){t.target.composing||(e.newPassword=t.target.value)}}})]),s("div",{staticClass:"mb-4"},[s("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"confirmNewPassword"}},[e._v(" "+e._s(e.$t("confirmNewPassword"))+" ")]),s("input",{directives:[{name:"model",rawName:"v-model",value:e.confirmNewPassword,expression:"confirmNewPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"confirmNewPassword",type:"password",placeholder:e.$t("confirmNewPassword"),required:""},domProps:{value:e.confirmNewPassword},on:{input:function(t){t.target.composing||(e.confirmNewPassword=t.target.value)}}})]),s("button",{staticClass:"bg-neutral-400 text-white p-2 rounded-md w-full hover:bg-red-800 transition",attrs:{type:"submit"}},[e._v(" "+e._s(e.$t("updatePassword"))+" ")])])])])},a=[];const n={name:"Settings",data(){return{currentPassword:"",newPassword:"",confirmNewPassword:""}},methods:{async updatePassword(e){e.preventDefault(),this.newPassword===this.confirmNewPassword?this.api.updatePassword({username:"admin",oldPassword:this.currentPassword,newPassword:this.newPassword}).then((e=>{this.currentPassword="",this.newPassword="",this.confirmNewPassword="",alert("Password updated")})).catch((e=>{alert(e.message||e.toString())})):alert("Password Mismatch")}},mounted(){this.api=new API}},d=n;function i(e,t,s,r,o,a,n,d){var i,l="function"===typeof e?e.options:e;if(t&&(l.render=t,l.staticRenderFns=s,l._compiled=!0),r&&(l.functional=!0),a&&(l._scopeId="data-v-"+a),n?(i=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(n)},l._ssrRegister=i):o&&(i=d?function(){o.call(this,(l.functional?this.parent:this).$root.$options.shadowRoot)}:o),i)if(l.functional){l._injectStyles=i;var u=l.render;l.render=function(e,t){return i.call(t),u(e,t)}}else{var c=l.beforeCreate;l.beforeCreate=c?[].concat(c,i):[i]}return{exports:e,options:l}}var l=i(d,o,a,!1,null,null,null);const u=l.exports,c=u;return t=t["default"],t})()));
//# sourceMappingURL=vue-settings.umd.min.js.map

1
src/www/js/vendor/vue-settings.umd.min.js.map

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save