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') const Authentication = require('./authentication')
exports.sendRequest = function(options, isPagination) { const api = {
let token = Authentication.getToken() loading: false,
let pagination = isPagination sendRequest: function(options, isPagination) {
api.loading = true
if (token) { let token = Authentication.getToken()
options.headers = options.headers || {} let pagination = isPagination
options.headers['Authorization'] = 'Bearer ' + token
} if (token) {
options.headers = options.headers || {}
options.extract = function(xhr) { options.headers['Authorization'] = 'Bearer ' + token
if (xhr.responseText && xhr.responseText.slice(0, 9) === '<!doctype') {
throw new Error('Expected JSON but got HTML (' + xhr.status + ': ' + this.url.split('?')[0] + ')')
} }
let out = null
if (pagination && xhr.status < 300) { options.extract = function(xhr) {
let headers = {} if (xhr.responseText && xhr.responseText.slice(0, 9) === '<!doctype') {
if (xhr.status === 500) {
xhr.getAllResponseHeaders().split('\r\n').forEach(function(item) { throw new Error('Server is temporarily down, try again later.')
var splitted = item.split(': ') }
headers[splitted[0]] = splitted[1] throw new Error('Expected JSON but got HTML (' + xhr.status + ': ' + this.url.split('?')[0] + ')')
})
out = {
headers: headers || {},
data: JSON.parse(xhr.responseText),
} }
} else { let out = null
if (xhr.responseText) { if (pagination && xhr.status < 300) {
out = JSON.parse(xhr.responseText) 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 { } 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 m.request(options)
} .then(function(res) {
return out 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 storageName = 'nfp_sites_filadelfia_web_logintoken'
const Authentication = { const Authentication = {
@ -33,6 +34,16 @@ const Authentication = {
getToken: function() { getToken: function() {
return localStorage.getItem(storageName) 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)) Authentication.updateToken(localStorage.getItem(storageName))

View File

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

View File

@ -3,6 +3,7 @@ const Authentication = require('./authentication')
const Header = require('./header') const Header = require('./header')
const Login = require('./page_login') const Login = require('./page_login')
const Browse = require('./page_browse') const Browse = require('./page_browse')
const Upload = require('./page_upload')
window.m = m window.m = m
var fileref = document.createElement("link"); var fileref = document.createElement("link");
@ -28,6 +29,7 @@ m.route.prefix = ''
const allRoutes = { const allRoutes = {
'/': Login, '/': Login,
'/browse': Browse, '/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. // 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 = { const Browse = {
oninit: function(vnode) { oninit: function(vnode) {
Authentication.requiresLogin()
if (!videos.Tree.length) { if (!videos.Tree.length) {
this.refreshTree() this.refreshTree()
} }
}, },
oncreate: function() {
if (Authentication.currentUser) return
},
refreshTree: function(vnode) { refreshTree: function(vnode) {
videos.refreshTree() videos.refreshTree()
}, },

View File

@ -1,55 +1,42 @@
const m = require('mithril') const m = require('mithril')
const Authentication = require('./authentication') const Authentication = require('./authentication')
const api = require('./api') const api = require('./api')
const Input = require('./input')
const Login = { const Login = {
oninit: function(vnode) { oninit: function(vnode) {
this.redirect = vnode.attrs.redirect || '' this.redirect = vnode.attrs.redirect || ''
if (Authentication.currentUser) return m.route.set('/browse') Authentication.requiresNotLogin()
this.error = '' this.error = ''
this.loading = false this.form = {
this.username = '' email: '',
this.password = '' password: '',
}, }
oncreate: function() {
if (Authentication.currentUser) return
}, },
loginuser: function(vnode, e) { loginuser: function(vnode, e) {
e.preventDefault() e.preventDefault()
this.error = '' this.error = ''
if (!this.password) this.error = 'Password is missing' if (!this.form.password) this.error = 'Password is missing'
if (!this.username) this.error = 'Email is missing' if (!this.form.email) this.error = 'Email is missing'
if (this.error) return false if (this.error) return false
this.loading = true
api.sendRequest({ api.sendRequest({
method: 'POST', method: 'POST',
url: '/api/authentication/login', url: '/api/authentication/login',
body: { body: this.form,
email: this.username,
password: this.password,
},
}) })
.then((result) => { .then((result) => {
if (!result.token) { if (!result.token) return Promise.reject(new Error('Server authentication down.'))
return Promise.reject(new Error('Server authentication down.'))
}
Authentication.updateToken(result.token) Authentication.updateToken(result.token)
m.route.set(this.redirect || '/browse') m.route.set(this.redirect || '/browse')
}) })
.catch((error) => { .catch((error) => {
this.error = 'Error while logging in! ' + error.message this.error = 'Error while logging in: ' + error.message
vnode.state.password = '' this.form.password = ''
})
.then(() => {
this.loading = false
m.redraw()
}) })
return false return false
@ -57,32 +44,39 @@ const Login = {
view: function(vnode) { view: function(vnode) {
return [ return [
m('div.modal', [ m('div.page.page-login', [
m('form', { m('div.modal', [
onsubmit: this.loginuser.bind(this, vnode), m('form', {
}, [ onsubmit: this.loginuser.bind(this, vnode),
m('h3', 'Filadelfia archival center'), }, [
this.error ? m('p.error', this.error) : null, m('h3', 'Filadelfia archival center'),
m('label', 'Email or name'), this.error ? m('p.error', this.error) : null,
m('input', { m(Input, {
type: 'text', label: 'Email or name',
value: this.username, form: this.form,
oninput: (e) => { this.username = e.currentTarget.value }, formKey: 'email',
}), }),
m('label', 'Password'), m(Input, {
m('input', { label: 'Password',
type: 'password', type: 'password',
value: this.password, form: this.form,
oninput: (e) => { this.password = e.currentTarget.value }, formKey: 'password',
}), }),
m('input.spinner', { m('input.spinner', {
hidden: this.loading, hidden: api.loading,
type: 'submit', type: 'submit',
value: 'Log in', value: 'Log in',
}), }),
this.loading ? m('div.loading-spinner') : null, 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 m = require('mithril')
const Authentication = require('./authentication') const Authentication = require('./authentication')
const api = require('./api') const api = require('./api')
const Input = require('./input')
const Login = { const Upload = {
oninit: function(vnode) { oninit: function(vnode) {
this.redirect = vnode.attrs.redirect || '' Authentication.requiresLogin()
if (!Authentication.currentUser) return m.route.set('/')
this.error = '' this.error = ''
this.loading = false this.form = {
this.body = {
year: new Date().getFullYear(),
month: new Date().getMonth() + 1,
title: '', title: '',
date: new Date(),
file: null, file: null,
} }
}, },
uploadfile: function(vnode, e) { uploadvideo: 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
}, },
view: function(vnode) { view: function(vnode) {
return [ return [
m('div.modal', [ m('div.page.page-upload', [
m('form', { m('div.modal', [
onsubmit: this.uploadfile.bind(this, vnode), m('form', {
}, [ onsubmit: this.uploadvideo.bind(this, vnode),
m('h3', 'Filadelfia archival center'), }, [
this.error ? m('p.error', this.error) : null, m('h3', 'Upload new video'),
m('label', 'Email or name'), this.error ? m('p.error', this.error) : null,
m('input', { m(Input, {
type: 'text', label: 'Title',
value: this.username, form: this.form,
oninput: (e) => { this.username = e.currentTarget.value }, formKey: 'title',
}), }),
m('label', 'Password'), m(Input, {
m('input', { label: 'Date',
type: 'password', type: 'datetime',
value: this.password, form: this.form,
oninput: (e) => { this.password = e.currentTarget.value }, formKey: 'date',
}), }),
m('input.spinner', { m('input.spinner', {
hidden: this.loading, hidden: api.loading,
type: 'submit', type: 'submit',
value: 'Log in', value: 'Log in',
}), }),
this.loading ? m('div.loading-spinner') : null, 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", "build": "asbundle app/index.js public/assets/app.js",
"dev:build": "eltro --watch build --npm build", "dev:build": "eltro --watch build --npm build",
"dev:server": "eltro --watch server --npm server", "dev:server": "eltro --watch server --npm server",
"dev:build:old": "npm-watch build",
"dev:server:old": "npm-watch server",
"server": "node dev.mjs | bunyan" "server": "node dev.mjs | bunyan"
}, },
"watch": { "watch": {
@ -52,6 +54,8 @@
"nconf-lite": "^2.0.0" "nconf-lite": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@eonasdan/tempus-dominus": "^6.7.19",
"@popperjs/core": "^2.11.8",
"asbundle": "^2.6.1", "asbundle": "^2.6.1",
"eltro": "^1.4.4", "eltro": "^1.4.4",
"flaska": "^1.3.2", "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 { :root {
--bg: #fff; --bg: #fff;
--bg-component: #f3f7ff; --bg-component: #f3f7ff;
--bg-component-half: #f3f7ff77;
--bg-component-alt: #ffd99c; --bg-component-alt: #ffd99c;
--color: #031131; --color: #031131;
--main: #1066ff; --main: #1066ff;
@ -121,6 +122,12 @@ h1 {
flex-direction: column; flex-direction: column;
} }
.page {
flex: 2 1 auto;
display: flex;
flex-direction: column;
}
.modal { .modal {
flex: 2 1 auto; flex: 2 1 auto;
display: flex; display: flex;
@ -236,6 +243,37 @@ form p, label {
align-items: center; 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> </style>
</head> </head>
<body> <body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB