3 Commits

Author SHA1 Message Date
7582d1054e 0.0.6
All checks were successful
NPM Audit Check / Check NPM audit (push) Successful in -2m6s
App Image CI / Build app image (push) Successful in 1m4s
2025-11-01 00:37:14 -05:00
77ea3018c1 create admin form
All checks were successful
App Image CI / Build app image (push) Successful in 37s
NPM Audit Check / Check NPM audit (push) Successful in -2m8s
2025-11-01 00:31:52 -05:00
f5c9d8c0c4 Add install count to homepage 2025-10-31 23:18:18 -05:00
10 changed files with 141 additions and 8 deletions

View File

@@ -7,5 +7,10 @@ services:
context: ./
dockerfile: Dockerfile
restart: "no"
environment:
ADMINPARAM: password
ADMINPASS: letmein
volumes:
- ./config/:/usr/src/app/config/
ports:
- 8080:8080

5
package-lock.json generated
View File

@@ -1,14 +1,15 @@
{
"name": "madisonlinux",
"version": "0.0.5",
"version": "0.0.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "madisonlinux",
"version": "0.0.5",
"version": "0.0.6",
"license": "MIT",
"dependencies": {
"body-parser": "^2.2.0",
"ejs": "3.1.10",
"express": "5.1.0",
"ics": "^3.8.1",

View File

@@ -1,6 +1,6 @@
{
"name": "madisonlinux",
"version": "0.0.5",
"version": "0.0.6",
"description": "Website for upcoming Linux install party in Madison WI",
"keywords": [
"web",
@@ -24,6 +24,7 @@
"views"
],
"dependencies": {
"body-parser": "^2.2.0",
"ejs": "3.1.10",
"express": "5.1.0",
"ics": "^3.8.1",

28
scripts/form.js Normal file
View File

@@ -0,0 +1,28 @@
document.addEventListener('DOMContentLoaded', function () {
const submit = document.getElementById('submitbtn');
submit.addEventListener('click', async () => {
submit.disabled = true;
const action = submit.parentElement.action;
try {
const resp = await fetch(action, {
method: 'POST',
body: JSON.stringify({
os: document.getElementById('os-select').value,
form: document.getElementById('form-select').value
}),
headers: {
"Content-Type": "application/json",
}
});
if (!resp.ok) {
throw new Error(`Response status: ${resp.status}`);
}
location.reload();
return false;
}
catch (error) {
console.error(error.message);
alert(error.message);
}
});
});

View File

@@ -4,11 +4,18 @@ import express from 'express';
import bodyParser from 'body-parser';
import type { Express } from 'express';
import { createEvent } from 'ics';
import path from 'path';
import { promises as fsp } from 'fs';
interface WebConfig {
port?: number;
}
interface Install {
os: 'manjaro' | 'mint' | 'chrome' | 'other',
form: 'laptop' | 'desktop' | 'aio'
}
const DATE = process.env['DATE'] || 'November 1st';
const TIME = process.env['TIME'] || '2:30PM-5:30PM';
@@ -23,6 +30,7 @@ class Web {
private _webserver: http.Server | null = null;
private app: Express | null = null;
private port: number;
private installs: Install[] = [];
// private options: WebConfig;
constructor(options: WebConfig = {}) {
@@ -33,13 +41,21 @@ class Web {
initialize = async () => {
// const options = this.options;
const app: Express = this.app = express();
const installPath = process.env['installstat'] || process.env['INSTALLSTAT'] || path.join(process.cwd(), 'config', 'installs.json');
try {
this.installs = JSON.parse(await fsp.readFile(installPath, 'utf-8'));
}
catch (ex) {
console.error(ex);
this.installs = [];
}
app.set('trust proxy', 1);
app.set('view engine', 'ejs');
app.set('view options', { outputFunctionName: 'echo' });
app.use('/assets', express.static('assets', { maxAge: '30 days' }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((_req, res, next) => {
crypto.randomBytes(32, (err, randomBytes) => {
if (err) {
@@ -56,11 +72,15 @@ class Web {
res.send('Healthy');
});
const adminParam = process.env['ADMINPARAM'];
const adminPass = process.env['ADMINPASS'];
app.get('/', (req, res) => {
const adminMode = adminParam && adminPass && adminParam in req.query && req.query[adminParam] === adminPass;
if (req.query?.['utm_medium']) {
console.log(`${req.query['utm_medium']} | ${req.headers?.['user-agent']}`);
}
else {
else if(!adminMode){
console.log(req.headers?.['user-agent']);
}
res.render('index', {
@@ -70,10 +90,29 @@ class Web {
description: `Windows 10 support is ending, but you may not need a brand-new PC! Sector67 in Madison, Wisconsin is hosting a Linux install party to help the community keep their current computers usable and up-to-date. Join us on ${DATE}`
},
date: DATE,
time: TIME
installs: this.installs,
time: TIME,
adminMode
});
});
app.post('/', async (req, res) => {
const adminMode = adminParam && adminPass && adminParam in req.query && req.query[adminParam] === adminPass;
if (!adminMode) {
res.redirect('/');
return;
}
const submit = req.body;
if (!('os' in submit && 'form' in submit)) {
res.status(400).send('bad request');
return;
}
this.installs.push(submit);
console.log(`updating ${installPath}`);
await fsp.writeFile(installPath, JSON.stringify(this.installs, null, 2));
res.send('ok');
});
app.get('/os', (_, res) => {
res.render('os', {
page: {

View File

@@ -73,6 +73,34 @@ img {
max-width: 100%;
}
#installs {
h1 {
animation-iteration-count: infinite;
animation-name: zoomie;
animation-duration: 1.5s;
animation-timing-function: ease-out;
}
span {
padding: .05em .25em;
border-radius: .15em;
}
}
@keyframes zoomie {
0% {
transform: perspective(800px) translate3d(0, 0, 0px);
}
50% {
transform: perspective(800px) translate3d(0, 0, 40px);
}
100% {
transform: perspective(800px) translate3d(0, 0, 0px);
}
}
@media screen and (max-width:540px) {
.content {
width: 95%;

View File

@@ -44,4 +44,8 @@ a.btn {
color: #fff;
}
}
}
}
#installs span {
background: #1A237E;
}

View File

@@ -14,7 +14,7 @@
<link rel="canonical" href="<%= page.canonical%>"/>
<% } %>
<link rel="shortcut icon" href="/assets/svg/favicon.svg">
<link rel="stylesheet" href="/assets/css/styles.css?v5">
<link rel="stylesheet" href="/assets/css/styles.css?v6">
<script nonce="<%= cspNonce %>">
document.addEventListener("DOMContentLoaded", function() {
document.body.classList.remove('preload');

View File

@@ -8,6 +8,14 @@
<body class="preload">
<%- include("navigation", locals) %>
<div class="content">
<% if (adminMode) { %>
<%- include("installform", locals) %>
<% } %>
<% if (installs && installs.length) { %>
<div id="installs">
<h1>We have saved <span><%= installs.length %></span> computer<% if (installs.length > 1) { %>s<% } %>!</h1>
</div>
<% } %>
<p><a href="https://www.sector67.org/">Sector67</a> presents...</p>
<h1>Linux Install Party Madison</h1>
<h2>Are you ready to reduce e-waste and save some money?</h2>

19
views/installform.ejs Normal file
View File

@@ -0,0 +1,19 @@
<div>
<select name="OS" id="os-select">
<option value="mint">Mint</option>
<option value="manjaro">Manjaro</option>
<option value="chrome">ChromeOS</option>
<option value="other">Other</option>
</select>
</div>
<div>
<select name="form" id="form-select">
<option value="laptop">Laptop</option>
<option value="desktop">Desktop</option>
<option value="aio">AIO</option>
</select>
</div>
<form method="post">
<input type="button" value="Submit" id="submitbtn" />
</form>
<script src="/assets/js/form.js?v1" nonce="<%= cspNonce %>"></script>