!!! RUN "yarn migrate" !!!

Added "yarn migrate" as alias for "node ./database/migration.js".
Updated README.md about it.

Added a new column to users database: registration.
It will be used to store user's registration timestamp.
Registration date will be displayed in Dashboard's Manage Users.
Since this is a new column,
existing users will not have registration dates.

Last token change date will now be displayed in Dashboard as well.

<code> elements will now properly have relative font size.

User ID will now be displayed in Edit user dialog for reference purpose.

Bumped v1 version string and rebuilt client assets.
This commit is contained in:
Bobby Wibowo 2020-05-16 22:32:32 +07:00
parent 93c6031bb1
commit 51c5a81b18
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
12 changed files with 35 additions and 14 deletions

View File

@ -10,7 +10,7 @@
This fork is the one being used at [https://safe.fiery.me](https://safe.fiery.me). If you are looking for the original, head to [WeebDev/lolisafe](https://github.com/WeebDev/lolisafe).
If you want to use an existing lolisafe database with this fork, run `node ./database/migration.js` at least once to create the new columns introduced in this branch (don't forget to make a backup).
If you want to use an existing lolisafe database with this fork, run `node ./database/migration.js` (or `yarn migrate`) at least once to create the new columns introduced in this branch (don't forget to make a backup).
Configuration file of lolisafe, `config.js`, is also NOT fully compatible with this fork. There are some options that had been renamed and/or restructured. Please make sure your config matches the sample in `config.sample.js` before starting.

View File

@ -101,7 +101,8 @@ self.register = async (req, res, next) => {
password: hash,
token,
enabled: 1,
permission: perms.permissions.user
permission: perms.permissions.user,
registration: Math.floor(Date.now() / 1000)
})
utils.invalidateStatsCache('users')
tokens.onHold.delete(token)
@ -200,7 +201,8 @@ self.createUser = async (req, res, next) => {
password: hash,
token,
enabled: 1,
permission
permission,
registration: Math.floor(Date.now() / 1000)
})
utils.invalidateStatsCache('users')
tokens.onHold.delete(token)
@ -377,7 +379,7 @@ self.listUsers = async (req, res, next) => {
const users = await db.table('users')
.limit(25)
.offset(25 * offset)
.select('id', 'username', 'enabled', 'permission')
.select('id', 'username', 'enabled', 'timestamp', 'permission', 'registration')
const pointers = {}
for (const user of users) {

View File

@ -44,6 +44,7 @@ const init = async db => {
table.integer('enabled')
table.integer('timestamp')
table.integer('permission')
table.integer('registration')
})
})
@ -53,12 +54,14 @@ const init = async db => {
if (!root) {
const hash = await require('bcrypt').hash('changeme', 10)
const timestamp = Math.floor(Date.now() / 1000)
await db.table('users').insert({
username: 'root',
password: hash,
token: require('randomstring').generate(64),
timestamp: Math.floor(Date.now() / 1000),
permission: require('./../controllers/permissionController').permissions.superadmin
timestamp,
permission: require('./../controllers/permissionController').permissions.superadmin,
registration: timestamp
})
}
}

View File

@ -15,7 +15,8 @@ const map = {
},
users: {
enabled: 'integer',
permission: 'integer'
permission: 'integer',
registration: 'integer'
}
}

2
dist/css/style.css vendored
View File

@ -1,2 +1,2 @@
html{background-color:#000;overflow-y:auto}body{color:#eff0f1;-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}@-webkit-keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}@keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}a{color:#209cee}a:hover{color:#67c3ff}hr{background-color:#585858}.message-body code,code{background-color:#000;border-radius:5px;font-size:1em}.subtitle,.subtitle strong{color:#bdc3c7}.subtitle.is-brighter,.subtitle.is-brighter strong,.title{color:#eff0f1}.input,.select select,.textarea{color:#eff0f1;border-color:#585858;background-color:#000}.input::-moz-placeholder,.textarea::-moz-placeholder{color:#bdc3c7}.input::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:#bdc3c7}.input:-moz-placeholder,.textarea:-moz-placeholder{color:#bdc3c7}.input:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:#bdc3c7}.input.is-active,.input.is-focused,.input:active,.input:focus,.input:not([disabled]):hover,.select fieldset:not([disabled]) select:hover,.select select:not([disabled]):hover,.textarea.is-active,.textarea.is-focused,.textarea:active,.textarea:focus,.textarea:not([disabled]):hover,fieldset:not([disabled]) .input:hover,fieldset:not([disabled]) .select select:hover,fieldset:not([disabled]) .textarea:hover{border-color:#209cee}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{border-color:#585858;background-color:#2f2f2f}.label{color:#eff0f1;font-weight:400}.help{color:#bdc3c7}.progress{background-color:#585858}.button.is-info.is-hovered [class*=" icon-"]:before,.button.is-info.is-hovered [class^=icon-]:before,.button.is-info:hover [class*=" icon-"]:before,.button.is-info:hover [class^=icon-]:before{fill:#fff}.checkbox:hover,.radio:hover{color:#7f8c8d}.select:not(.is-multiple):not(.is-loading):after,.select:not(.is-multiple):not(.is-loading):hover:after{border-color:#eff0f1}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#585858}.message{background-color:#2f2f2f}.message-body{color:#eff0f1;border:0}.section.has-extra-bottom-padding{padding-bottom:6.5rem}a.floating-home-button{display:flex;position:fixed;right:1.5rem;bottom:1.5rem;border-radius:100%;background-color:#209cee;color:#fff;width:3.5rem;height:3.5rem;justify-content:center;align-items:center;transition:background-color .25s}a.floating-home-button:hover{background-color:#67c3ff;color:#fff}a.floating-home-button>.icon{margin-top:-2px}.hero.is-fullheight>.hero-body{min-height:100vh;height:100%}.hero.is-fullheight>.hero-body>.container{width:100%}
html{background-color:#000;overflow-y:auto}body{color:#eff0f1;-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}@-webkit-keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}@keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}a{color:#209cee}a:hover{color:#67c3ff}hr{background-color:#585858}.message-body code,code{background-color:#000;border-radius:5px;font-size:1rem}.subtitle,.subtitle strong{color:#bdc3c7}.subtitle.is-brighter,.subtitle.is-brighter strong,.title{color:#eff0f1}.input,.select select,.textarea{color:#eff0f1;border-color:#585858;background-color:#000}.input::-moz-placeholder,.textarea::-moz-placeholder{color:#bdc3c7}.input::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:#bdc3c7}.input:-moz-placeholder,.textarea:-moz-placeholder{color:#bdc3c7}.input:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:#bdc3c7}.input.is-active,.input.is-focused,.input:active,.input:focus,.input:not([disabled]):hover,.select fieldset:not([disabled]) select:hover,.select select:not([disabled]):hover,.textarea.is-active,.textarea.is-focused,.textarea:active,.textarea:focus,.textarea:not([disabled]):hover,fieldset:not([disabled]) .input:hover,fieldset:not([disabled]) .select select:hover,fieldset:not([disabled]) .textarea:hover{border-color:#209cee}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{border-color:#585858;background-color:#2f2f2f}.label{color:#eff0f1;font-weight:400}.help{color:#bdc3c7}.progress{background-color:#585858}.button.is-info.is-hovered [class*=" icon-"]:before,.button.is-info.is-hovered [class^=icon-]:before,.button.is-info:hover [class*=" icon-"]:before,.button.is-info:hover [class^=icon-]:before{fill:#fff}.checkbox:hover,.radio:hover{color:#7f8c8d}.select:not(.is-multiple):not(.is-loading):after,.select:not(.is-multiple):not(.is-loading):hover:after{border-color:#eff0f1}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#585858}.message{background-color:#2f2f2f}.message-body{color:#eff0f1;border:0}.section.has-extra-bottom-padding{padding-bottom:6.5rem}a.floating-home-button{display:flex;position:fixed;right:1.5rem;bottom:1.5rem;border-radius:100%;background-color:#209cee;color:#fff;width:3.5rem;height:3.5rem;justify-content:center;align-items:center;transition:background-color .25s}a.floating-home-button:hover{background-color:#67c3ff;color:#fff}a.floating-home-button>.icon{margin-top:-2px}.hero.is-fullheight>.hero-body{min-height:100vh;height:100%}.hero.is-fullheight>.hero-body>.container{width:100%}
/*# sourceMappingURL=style.css.map */

View File

@ -1 +1 @@
{"version":3,"sources":["css/style.css"],"names":[],"mappings":"AAAA,KACE,qBAAsB,CACtB,eACF,CAEA,KACE,aAAc,CACd,mCAA4B,CAA5B,2BACF,CAEA,iCACE,GACE,SACF,CAEA,GACE,SACF,CACF,CAEA,yBACE,GACE,SACF,CAEA,GACE,SACF,CACF,CAEA,EACE,aACF,CAEA,QACE,aACF,CAEA,GACE,wBACF,CAEA,wBAEE,qBAAsB,CACtB,iBAAkB,CAClB,aACF,CAMA,2BACE,aACF,CAEA,0DAGE,aACF,CAEA,gCAGE,aAAc,CACd,oBAAqB,CACrB,qBACF,CAEA,qDAEE,aACF,CAEA,uEAEE,aACF,CAEA,mDAEE,aACF,CAEA,6DAEE,aACF,CAYA,qZAQE,oBACF,CAEA,yLAOE,oBAAqB,CACrB,wBACF,CAEA,OACE,aAAc,CACd,eACF,CAEA,MACE,aACF,CAEA,UACE,wBACF,CAEA,gMAIE,SACF,CAEA,6BAEE,aACF,CAMA,wGACE,oBACF,CAEA,uEAEE,oBACF,CAEA,SACE,wBACF,CAEA,cACE,aAAc,CACd,QACF,CAGA,kCACE,qBACF,CAEA,uBACE,YAAa,CACb,cAAe,CACf,YAAa,CACb,aAAc,CACd,kBAAmB,CACnB,wBAAyB,CACzB,UAAY,CACZ,YAAa,CACb,aAAc,CACd,sBAAuB,CACvB,kBAAmB,CACnB,gCACF,CAEA,6BACE,wBAAyB,CACzB,UACF,CAEA,6BACE,eACF,CAGA,+BACE,gBAAiB,CACjB,WACF,CAGA,0CACE,UACF","file":"style.css","sourcesContent":["html {\n background-color: #000;\n overflow-y: auto\n}\n\nbody {\n color: #eff0f1;\n animation: fadeInOpacity 0.5s\n}\n\n@-webkit-keyframes fadeInOpacity {\n 0% {\n opacity: 0\n }\n\n 100% {\n opacity: 1\n }\n}\n\n@keyframes fadeInOpacity {\n 0% {\n opacity: 0\n }\n\n 100% {\n opacity: 1\n }\n}\n\na {\n color: #209cee\n}\n\na:hover {\n color: #67c3ff\n}\n\nhr {\n background-color: #585858\n}\n\ncode,\n.message-body code {\n background-color: #000;\n border-radius: 5px;\n font-size: 1em\n}\n\n.subtitle {\n color: #bdc3c7\n}\n\n.subtitle strong {\n color: #bdc3c7\n}\n\n.title,\n.subtitle.is-brighter,\n.subtitle.is-brighter strong {\n color: #eff0f1\n}\n\n.input,\n.select select,\n.textarea {\n color: #eff0f1;\n border-color: #585858;\n background-color: #000\n}\n\n.input::-moz-placeholder,\n.textarea::-moz-placeholder {\n color: #bdc3c7\n}\n\n.input::-webkit-input-placeholder,\n.textarea::-webkit-input-placeholder {\n color: #bdc3c7\n}\n\n.input:-moz-placeholder,\n.textarea:-moz-placeholder {\n color: #bdc3c7\n}\n\n.input:-ms-input-placeholder,\n.textarea:-ms-input-placeholder {\n color: #bdc3c7\n}\n\n.input:not([disabled]):hover,\n.select fieldset:not([disabled]) select:hover,\n.select select:not([disabled]):hover,\n.textarea:not([disabled]):hover,\nfieldset:not([disabled]) .input:hover,\nfieldset:not([disabled]) .select select:hover,\nfieldset:not([disabled]) .textarea:hover {\n border-color: #209cee\n}\n\n.input.is-active,\n.input.is-focused,\n.input:active,\n.input:focus,\n.textarea.is-active,\n.textarea.is-focused,\n.textarea:active,\n.textarea:focus {\n border-color: #209cee\n}\n\n.input[disabled],\n.select fieldset[disabled] select,\n.select select[disabled],\n.textarea[disabled],\nfieldset[disabled] .input,\nfieldset[disabled] .select select,\nfieldset[disabled] .textarea {\n border-color: #585858;\n background-color: #2f2f2f\n}\n\n.label {\n color: #eff0f1;\n font-weight: normal\n}\n\n.help {\n color: #bdc3c7\n}\n\n.progress {\n background-color: #585858\n}\n\n.button.is-info.is-hovered [class^=\"icon-\"]::before,\n.button.is-info.is-hovered [class*=\" icon-\"]::before,\n.button.is-info:hover [class^=\"icon-\"]::before,\n.button.is-info:hover [class*=\" icon-\"]::before {\n fill: #fff\n}\n\n.checkbox:hover,\n.radio:hover {\n color: #7f8c8d\n}\n\n.select:not(.is-multiple):not(.is-loading)::after {\n border-color: #eff0f1\n}\n\n.select:not(.is-multiple):not(.is-loading):hover::after {\n border-color: #eff0f1\n}\n\n.select select[disabled]:hover,\nfieldset[disabled] .select select:hover {\n border-color: #585858\n}\n\n.message {\n background-color: #2f2f2f\n}\n\n.message-body {\n color: #eff0f1;\n border: 0\n}\n\n/* floating button's bottom offset + height + bottom offset */\n.section.has-extra-bottom-padding {\n padding-bottom: 6.5rem\n}\n\na.floating-home-button {\n display: flex;\n position: fixed;\n right: 1.5rem;\n bottom: 1.5rem;\n border-radius: 100%;\n background-color: #209cee;\n color: white;\n width: 3.5rem;\n height: 3.5rem;\n justify-content: center;\n align-items: center;\n transition: background-color 0.25s\n}\n\na.floating-home-button:hover {\n background-color: #67c3ff;\n color: white\n}\n\na.floating-home-button > .icon {\n margin-top: -2px\n}\n\n/* https://github.com/philipwalton/flexbugs#flexbug-3 */\n.hero.is-fullheight > .hero-body {\n min-height: 100vh;\n height: 100%\n}\n\n/* https://github.com/philipwalton/flexbugs#flexbug-2 */\n.hero.is-fullheight > .hero-body > .container {\n width: 100%\n}\n"]}
{"version":3,"sources":["css/style.css"],"names":[],"mappings":"AAAA,KACE,qBAAsB,CACtB,eACF,CAEA,KACE,aAAc,CACd,mCAA4B,CAA5B,2BACF,CAEA,iCACE,GACE,SACF,CAEA,GACE,SACF,CACF,CAEA,yBACE,GACE,SACF,CAEA,GACE,SACF,CACF,CAEA,EACE,aACF,CAEA,QACE,aACF,CAEA,GACE,wBACF,CAEA,wBAEE,qBAAsB,CACtB,iBAAkB,CAClB,cACF,CAMA,2BACE,aACF,CAEA,0DAGE,aACF,CAEA,gCAGE,aAAc,CACd,oBAAqB,CACrB,qBACF,CAEA,qDAEE,aACF,CAEA,uEAEE,aACF,CAEA,mDAEE,aACF,CAEA,6DAEE,aACF,CAYA,qZAQE,oBACF,CAEA,yLAOE,oBAAqB,CACrB,wBACF,CAEA,OACE,aAAc,CACd,eACF,CAEA,MACE,aACF,CAEA,UACE,wBACF,CAEA,gMAIE,SACF,CAEA,6BAEE,aACF,CAMA,wGACE,oBACF,CAEA,uEAEE,oBACF,CAEA,SACE,wBACF,CAEA,cACE,aAAc,CACd,QACF,CAGA,kCACE,qBACF,CAEA,uBACE,YAAa,CACb,cAAe,CACf,YAAa,CACb,aAAc,CACd,kBAAmB,CACnB,wBAAyB,CACzB,UAAY,CACZ,YAAa,CACb,aAAc,CACd,sBAAuB,CACvB,kBAAmB,CACnB,gCACF,CAEA,6BACE,wBAAyB,CACzB,UACF,CAEA,6BACE,eACF,CAGA,+BACE,gBAAiB,CACjB,WACF,CAGA,0CACE,UACF","file":"style.css","sourcesContent":["html {\n background-color: #000;\n overflow-y: auto\n}\n\nbody {\n color: #eff0f1;\n animation: fadeInOpacity 0.5s\n}\n\n@-webkit-keyframes fadeInOpacity {\n 0% {\n opacity: 0\n }\n\n 100% {\n opacity: 1\n }\n}\n\n@keyframes fadeInOpacity {\n 0% {\n opacity: 0\n }\n\n 100% {\n opacity: 1\n }\n}\n\na {\n color: #209cee\n}\n\na:hover {\n color: #67c3ff\n}\n\nhr {\n background-color: #585858\n}\n\ncode,\n.message-body code {\n background-color: #000;\n border-radius: 5px;\n font-size: 1rem\n}\n\n.subtitle {\n color: #bdc3c7\n}\n\n.subtitle strong {\n color: #bdc3c7\n}\n\n.title,\n.subtitle.is-brighter,\n.subtitle.is-brighter strong {\n color: #eff0f1\n}\n\n.input,\n.select select,\n.textarea {\n color: #eff0f1;\n border-color: #585858;\n background-color: #000\n}\n\n.input::-moz-placeholder,\n.textarea::-moz-placeholder {\n color: #bdc3c7\n}\n\n.input::-webkit-input-placeholder,\n.textarea::-webkit-input-placeholder {\n color: #bdc3c7\n}\n\n.input:-moz-placeholder,\n.textarea:-moz-placeholder {\n color: #bdc3c7\n}\n\n.input:-ms-input-placeholder,\n.textarea:-ms-input-placeholder {\n color: #bdc3c7\n}\n\n.input:not([disabled]):hover,\n.select fieldset:not([disabled]) select:hover,\n.select select:not([disabled]):hover,\n.textarea:not([disabled]):hover,\nfieldset:not([disabled]) .input:hover,\nfieldset:not([disabled]) .select select:hover,\nfieldset:not([disabled]) .textarea:hover {\n border-color: #209cee\n}\n\n.input.is-active,\n.input.is-focused,\n.input:active,\n.input:focus,\n.textarea.is-active,\n.textarea.is-focused,\n.textarea:active,\n.textarea:focus {\n border-color: #209cee\n}\n\n.input[disabled],\n.select fieldset[disabled] select,\n.select select[disabled],\n.textarea[disabled],\nfieldset[disabled] .input,\nfieldset[disabled] .select select,\nfieldset[disabled] .textarea {\n border-color: #585858;\n background-color: #2f2f2f\n}\n\n.label {\n color: #eff0f1;\n font-weight: normal\n}\n\n.help {\n color: #bdc3c7\n}\n\n.progress {\n background-color: #585858\n}\n\n.button.is-info.is-hovered [class^=\"icon-\"]::before,\n.button.is-info.is-hovered [class*=\" icon-\"]::before,\n.button.is-info:hover [class^=\"icon-\"]::before,\n.button.is-info:hover [class*=\" icon-\"]::before {\n fill: #fff\n}\n\n.checkbox:hover,\n.radio:hover {\n color: #7f8c8d\n}\n\n.select:not(.is-multiple):not(.is-loading)::after {\n border-color: #eff0f1\n}\n\n.select:not(.is-multiple):not(.is-loading):hover::after {\n border-color: #eff0f1\n}\n\n.select select[disabled]:hover,\nfieldset[disabled] .select select:hover {\n border-color: #585858\n}\n\n.message {\n background-color: #2f2f2f\n}\n\n.message-body {\n color: #eff0f1;\n border: 0\n}\n\n/* floating button's bottom offset + height + bottom offset */\n.section.has-extra-bottom-padding {\n padding-bottom: 6.5rem\n}\n\na.floating-home-button {\n display: flex;\n position: fixed;\n right: 1.5rem;\n bottom: 1.5rem;\n border-radius: 100%;\n background-color: #209cee;\n color: white;\n width: 3.5rem;\n height: 3.5rem;\n justify-content: center;\n align-items: center;\n transition: background-color 0.25s\n}\n\na.floating-home-button:hover {\n background-color: #67c3ff;\n color: white\n}\n\na.floating-home-button > .icon {\n margin-top: -2px\n}\n\n/* https://github.com/philipwalton/flexbugs#flexbug-3 */\n.hero.is-fullheight > .hero-body {\n min-height: 100vh;\n height: 100%\n}\n\n/* https://github.com/philipwalton/flexbugs#flexbug-2 */\n.hero.is-fullheight > .hero-body > .container {\n width: 100%\n}\n"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,7 @@
"build": "gulp default",
"watch": "gulp watch",
"develop": "env NODE_ENV=development yarn watch",
"migrate": "node ./database/migration.js",
"bump-versions": "node ./scripts/bump-versions.js",
"cf-purge": "node ./scripts/cf-purge.js",
"clean-up": "node ./scripts/clean-up.js",

View File

@ -44,7 +44,7 @@ code,
.message-body code {
background-color: #000;
border-radius: 5px;
font-size: 1em
font-size: 1rem
}
.subtitle {

View File

@ -733,7 +733,7 @@ page.getUploads = (params = {}) => {
${allAlbums ? '<th>Album</th>' : ''}
<th>Size</th>
${params.all ? '<th>IP</th>' : ''}
<th>Date</th>
<th>Upload date</th>
${hasExpiryDateColumn ? '<th>Expiry date</th>' : ''}
<th></th>
</tr>
@ -2044,6 +2044,8 @@ page.getUsers = (params = {}) => {
<th>Uploads</th>
<th>Usage</th>
<th>Group</th>
<th>Registration date</th>
<th>Last token update</th>
<th></th>
</tr>
</thead>
@ -2079,6 +2081,13 @@ page.getUsers = (params = {}) => {
displayGroup
}
const prettyDate = user.registration
? page.getPrettyDate(new Date(user.registration * 1000))
: '-'
const prettyTokenUpdate = user.timestamp
? page.getPrettyDate(new Date(user.timestamp * 1000))
: '-'
const tr = document.createElement('tr')
tr.dataset.id = user.id
tr.innerHTML = `
@ -2087,6 +2096,8 @@ page.getUsers = (params = {}) => {
<th>${user.uploads}</th>
<td>${page.getPrettyBytes(user.usage)}</td>
<td>${displayGroup}</td>
<td>${prettyDate}</td>
<td>${prettyTokenUpdate}</td>
<td class="controls has-text-right">
<a class="button is-small is-primary is-outlined" title="Edit user" data-action="edit-user">
<span class="icon">
@ -2223,6 +2234,9 @@ page.editUser = id => {
const div = document.createElement('div')
div.innerHTML = `
<div class="field">
<p>User ID: ${id}</p>
</div>
<div class="field">
<label class="label">Username</label>
<div class="controls">

View File

@ -1,5 +1,5 @@
{
"1": "1589010026",
"1": "1589642953",
"2": "1589010026",
"3": "1581416390",
"4": "1581416390",