filadelfia_web: More development
/ deploy (push) Failing after -65h42m51s Details

master
Jonatan Nilsson 2023-11-14 07:27:04 +00:00
parent 531c7acefe
commit 638e6cc435
14 changed files with 218 additions and 170 deletions

1
filadelfia_web/.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

View File

@ -1,53 +1,73 @@
const Authentication = require('./authentication')
exports.sendRequest = function(options, isPagination) {
let token = Authentication.getToken()
let pagination = isPagination
if (token) {
options.headers = options.headers || {}
options.headers['Authorization'] = 'Bearer ' + token
}
options.extract = function(xhr) {
if (xhr.responseText && xhr.responseText.slice(0, 9) === '<!doctype') {
throw new Error('Expected JSON but got HTML (' + xhr.status + ': ' + this.url.split('?')[0] + ')')
const api = {
loading: false,
sendRequest: function(options, isPagination) {
api.loading = true
let token = Authentication.getToken()
let pagination = isPagination
if (token) {
options.headers = options.headers || {}
options.headers['Authorization'] = 'Bearer ' + token
}
let out = null
if (pagination && xhr.status < 300) {
let headers = {}
xhr.getAllResponseHeaders().split('\r\n').forEach(function(item) {
var splitted = item.split(': ')
headers[splitted[0]] = splitted[1]
})
out = {
headers: headers || {},
data: JSON.parse(xhr.responseText),
options.extract = function(xhr) {
if (xhr.responseText && xhr.responseText.slice(0, 9) === '<!doctype') {
if (xhr.status === 500) {
throw new Error('Server is temporarily down, try again later.')
}
throw new Error('Expected JSON but got HTML (' + xhr.status + ': ' + this.url.split('?')[0] + ')')
}
} else {
if (xhr.responseText) {
out = JSON.parse(xhr.responseText)
let out = null
if (pagination && xhr.status < 300) {
let headers = {}
xhr.getAllResponseHeaders().split('\r\n').forEach(function(item) {
var splitted = item.split(': ')
headers[splitted[0]] = splitted[1]
})
out = {
headers: headers || {},
data: JSON.parse(xhr.responseText),
}
} else {
out = {}
if (xhr.responseText) {
out = JSON.parse(xhr.responseText)
} else {
out = {}
}
}
if (xhr.status >= 300) {
if (out.message) {
throw out
}
console.error('Got error ' + xhr.status + ' but no error message:', out)
throw new Error('Unknown or empty response from server.')
}
return out
}
if (xhr.status >= 300) {
throw out
}
return out
return m.request(options)
.then(function(res) {
api.loading = false
window.requestAnimationFrame(m.redraw.bind(m))
return res
})
.catch(function (error) {
api.loading = false
window.requestAnimationFrame(m.redraw.bind(m))
if (error.status === 403) {
Authentication.clearToken()
m.route.set('/', { redirect: m.route.get() })
}
if (error.response && error.response.status) {
return Promise.reject(error.response)
}
return Promise.reject(error)
})
}
return m.request(options)
.catch(function (error) {
if (error.status === 403) {
Authentication.clearToken()
m.route.set('/', { redirect: m.route.get() })
}
if (error.response && error.response.status) {
return Promise.reject(error.response)
}
return Promise.reject(error)
})
}
module.exports = api

View File

@ -1,3 +1,4 @@
const m = require('mithril')
const storageName = 'nfp_sites_filadelfia_web_logintoken'
const Authentication = {
@ -33,6 +34,16 @@ const Authentication = {
getToken: function() {
return localStorage.getItem(storageName)
},
requiresLogin: function() {
if (Authentication.currentUser) return
m.route.set('/')
},
requiresNotLogin: function() {
if (!Authentication.currentUser) return
m.route.set('/browse')
},
}
Authentication.updateToken(localStorage.getItem(storageName))

View File

@ -23,7 +23,6 @@ const Menu = {
},
view: function() {
console.log(Authentication.currentUser)
return Authentication.currentUser
? [
m('nav', [

View File

@ -3,6 +3,7 @@ const Authentication = require('./authentication')
const Header = require('./header')
const Login = require('./page_login')
const Browse = require('./page_browse')
const Upload = require('./page_upload')
window.m = m
var fileref = document.createElement("link");
@ -28,6 +29,7 @@ m.route.prefix = ''
const allRoutes = {
'/': Login,
'/browse': Browse,
'/upload': Upload,
}
// Wait until we finish checking avif support, some views render immediately and will ask for this immediately before the callback gets called.

View File

@ -0,0 +1,19 @@
const m = require('mithril')
const Input = {
oninit: function(vnode) {
},
view: function(vnode) {
return [
m('label', vnode.attrs.label),
m('input', {
type: vnode.attrs.type || 'text',
value: vnode.attrs.form[vnode.attrs.formKey],
oninput: (e) => { vnode.attrs.form[vnode.attrs.formKey] = e.currentTarget.value },
}),
]
},
}
module.exports = Input

View File

@ -4,15 +4,12 @@ const videos = require('./videos')
const Browse = {
oninit: function(vnode) {
Authentication.requiresLogin()
if (!videos.Tree.length) {
this.refreshTree()
}
},
oncreate: function() {
if (Authentication.currentUser) return
},
refreshTree: function(vnode) {
videos.refreshTree()
},

View File

@ -1,55 +1,42 @@
const m = require('mithril')
const Authentication = require('./authentication')
const api = require('./api')
const Input = require('./input')
const Login = {
oninit: function(vnode) {
this.redirect = vnode.attrs.redirect || ''
if (Authentication.currentUser) return m.route.set('/browse')
Authentication.requiresNotLogin()
this.error = ''
this.loading = false
this.username = ''
this.password = ''
},
oncreate: function() {
if (Authentication.currentUser) return
this.form = {
email: '',
password: '',
}
},
loginuser: function(vnode, e) {
e.preventDefault()
this.error = ''
if (!this.password) this.error = 'Password is missing'
if (!this.username) this.error = 'Email is missing'
if (!this.form.password) this.error = 'Password is missing'
if (!this.form.email) this.error = 'Email is missing'
if (this.error) return false
this.loading = true
api.sendRequest({
method: 'POST',
url: '/api/authentication/login',
body: {
email: this.username,
password: this.password,
},
body: this.form,
})
.then((result) => {
if (!result.token) {
return Promise.reject(new Error('Server authentication down.'))
}
if (!result.token) return Promise.reject(new Error('Server authentication down.'))
Authentication.updateToken(result.token)
m.route.set(this.redirect || '/browse')
})
.catch((error) => {
this.error = 'Error while logging in! ' + error.message
vnode.state.password = ''
})
.then(() => {
this.loading = false
m.redraw()
this.error = 'Error while logging in: ' + error.message
this.form.password = ''
})
return false
@ -57,32 +44,39 @@ const Login = {
view: function(vnode) {
return [
m('div.modal', [
m('form', {
onsubmit: this.loginuser.bind(this, vnode),
}, [
m('h3', 'Filadelfia archival center'),
this.error ? m('p.error', this.error) : null,
m('label', 'Email or name'),
m('input', {
type: 'text',
value: this.username,
oninput: (e) => { this.username = e.currentTarget.value },
}),
m('label', 'Password'),
m('input', {
type: 'password',
value: this.password,
oninput: (e) => { this.password = e.currentTarget.value },
}),
m('input.spinner', {
hidden: this.loading,
type: 'submit',
value: 'Log in',
}),
this.loading ? m('div.loading-spinner') : null,
m('div.page.page-login', [
m('div.modal', [
m('form', {
onsubmit: this.loginuser.bind(this, vnode),
}, [
m('h3', 'Filadelfia archival center'),
this.error ? m('p.error', this.error) : null,
m(Input, {
label: 'Email or name',
form: this.form,
formKey: 'email',
}),
m(Input, {
label: 'Password',
type: 'password',
form: this.form,
formKey: 'password',
}),
m('input.spinner', {
hidden: api.loading,
type: 'submit',
value: 'Log in',
}),
api.loading ? m('div.loading-spinner') : null,
]),
m('div.login--asuna.spritesheet'),
]),
m('footer', [
'Photo by ',
m('a', { href: 'https://unsplash.com/@franhotchin?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Francesca Hotchin'),
' on ',
m('a', { href: 'https://unsplash.com/photos/landscape-photo-of-mountain-covered-with-snow-FN-cedy6NHA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Unsplash'),
]),
m('div.login--asuna.spritesheet'),
]),
]
},

View File

@ -1,91 +1,54 @@
const m = require('mithril')
const Authentication = require('./authentication')
const api = require('./api')
const Input = require('./input')
const Login = {
const Upload = {
oninit: function(vnode) {
this.redirect = vnode.attrs.redirect || ''
if (!Authentication.currentUser) return m.route.set('/')
Authentication.requiresLogin()
this.error = ''
this.loading = false
this.body = {
year: new Date().getFullYear(),
month: new Date().getMonth() + 1,
this.form = {
title: '',
date: new Date(),
file: null,
}
},
uploadfile: function(vnode, e) {
e.preventDefault()
this.error = ''
if (!this.password) this.error = 'Password is missing'
if (!this.username) this.error = 'Email is missing'
if (this.error) return false
this.loading = true
api.sendRequest({
method: 'POST',
url: '/api/authentication/login',
body: {
email: this.username,
password: this.password,
},
})
.then((result) => {
if (!result.token) {
return Promise.reject(new Error('Server authentication down.'))
}
Authentication.updateToken(result.token)
m.route.set(this.redirect || '/browse')
})
.catch((error) => {
this.error = 'Error while logging in! ' + error.message
vnode.state.password = ''
})
.then(() => {
this.loading = false
m.redraw()
})
return false
uploadvideo: function(vnode, e) {
},
view: function(vnode) {
return [
m('div.modal', [
m('form', {
onsubmit: this.uploadfile.bind(this, vnode),
}, [
m('h3', 'Filadelfia archival center'),
this.error ? m('p.error', this.error) : null,
m('label', 'Email or name'),
m('input', {
type: 'text',
value: this.username,
oninput: (e) => { this.username = e.currentTarget.value },
}),
m('label', 'Password'),
m('input', {
type: 'password',
value: this.password,
oninput: (e) => { this.password = e.currentTarget.value },
}),
m('input.spinner', {
hidden: this.loading,
type: 'submit',
value: 'Log in',
}),
this.loading ? m('div.loading-spinner') : null,
m('div.page.page-upload', [
m('div.modal', [
m('form', {
onsubmit: this.uploadvideo.bind(this, vnode),
}, [
m('h3', 'Upload new video'),
this.error ? m('p.error', this.error) : null,
m(Input, {
label: 'Title',
form: this.form,
formKey: 'title',
}),
m(Input, {
label: 'Date',
type: 'datetime',
form: this.form,
formKey: 'date',
}),
m('input.spinner', {
hidden: api.loading,
type: 'submit',
value: 'Log in',
}),
api.loading ? m('div.loading-spinner') : null,
]),
m('div.login--asuna.spritesheet'),
]),
m('div.login--asuna.spritesheet'),
]),
]
},
}
module.exports = Login
module.exports = Upload

View File

@ -14,6 +14,8 @@
"build": "asbundle app/index.js public/assets/app.js",
"dev:build": "eltro --watch build --npm build",
"dev:server": "eltro --watch server --npm server",
"dev:build:old": "npm-watch build",
"dev:server:old": "npm-watch server",
"server": "node dev.mjs | bunyan"
},
"watch": {
@ -52,6 +54,8 @@
"nconf-lite": "^2.0.0"
},
"devDependencies": {
"@eonasdan/tempus-dominus": "^6.7.19",
"@popperjs/core": "^2.11.8",
"asbundle": "^2.6.1",
"eltro": "^1.4.4",
"flaska": "^1.3.2",

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -13,6 +13,7 @@
:root {
--bg: #fff;
--bg-component: #f3f7ff;
--bg-component-half: #f3f7ff77;
--bg-component-alt: #ffd99c;
--color: #031131;
--main: #1066ff;
@ -121,6 +122,12 @@ h1 {
flex-direction: column;
}
.page {
flex: 2 1 auto;
display: flex;
flex-direction: column;
}
.modal {
flex: 2 1 auto;
display: flex;
@ -236,6 +243,37 @@ form p, label {
align-items: center;
}
footer {
text-align: center;
padding: 1rem;
}
footer a {
font-size: 0.8rem;
}
/* login */
.page-login {
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
.page-login .modal form {
backdrop-filter: blur(10px);
background: var(--bg-component-half);
}
.avifsupport .page-login {
background-image: url('./assets/bg.avif');
}
.jpegonly .page-login {
background-image: url('./assets/bg.jpg');
}
</style>
</head>
<body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB