working on droplet, adding app components

This commit is contained in:
Sam
2026-03-10 21:15:47 +00:00
parent fc81da9743
commit 4d2c515b7d
10 changed files with 303 additions and 18 deletions

5
.gitignore vendored
View File

@@ -5,3 +5,8 @@ node_modules
db/db.json db/db.json
ui/_/code/env.js ui/_/code/env.js
apps/*
csv.csv
csv.js
csv.json

View File

@@ -5,6 +5,9 @@
"scripts": { "scripts": {
"start": "node server/index.js" "start": "node server/index.js"
}, },
"workspaces": [
"apps/*"
],
"dependencies": { "dependencies": {
"argon2": "^0.44.0", "argon2": "^0.44.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",

View File

@@ -17,12 +17,14 @@ import Database from "./db/db.js"
import AuthHandler from './auth.js'; import AuthHandler from './auth.js';
import PaymentsHandler from "./payments.js" import PaymentsHandler from "./payments.js"
import parchment from '../apps/parchment/index.js';
class Server { class Server {
db; db;
auth; auth;
UIPath = path.join(__dirname, '../ui') UIPath = path.join(__dirname, '../ui')
DBPath = path.join(__dirname, '../db') DBPath = path.join(__dirname, '../db')
ComalPath = path.join(os.homedir(), 'Sites/comalyr.com') ComalPath = path.join(os.homedir(), 'comalyr.com')
registerRoutes(router) { registerRoutes(router) {
/* Stripe */ /* Stripe */
@@ -75,7 +77,7 @@ class Server {
const contact = contactRaw.trim() ? JSON.parse(contactRaw) : []; const contact = contactRaw.trim() ? JSON.parse(contactRaw) : [];
const members = db.members.getByNetwork(networkId) const members = db.members.getByNetwork(networkId)
let stripeMembers = await payments.getCustomers(networkId) let stripeMembers = await payments.getCustomers(networkId)
console.log("stripemenbers: ", stripeMembers) // console.log("stripemenbers: ", stripeMembers)
res.json({ res.json({
join, join,
@@ -263,6 +265,8 @@ class Server {
app.use(this.logRequest); app.use(this.logRequest);
app.use(this.logResponse); app.use(this.logResponse);
app.use('/apps/parchment', parchment.app);
let router = express.Router(); let router = express.Router();
this.registerRoutes(router) this.registerRoutes(router)
app.use('/', router); app.use('/', router);

View File

@@ -56,7 +56,6 @@ export default class PaymentsHandler {
{ limit: 100, expand: ['data.subscriptions'] }, { limit: 100, expand: ['data.subscriptions'] },
{ stripeAccount: network.stripeAccountId } { stripeAccount: network.stripeAccountId }
); );
console.log(customers)
return customers.data.map(customer => ({ return customers.data.map(customer => ({
id: customer.id, id: customer.id,

56
serveradmin.md Normal file
View File

@@ -0,0 +1,56 @@
# ssh
ssh root@167.172.247.123
# certs
certbot --nginx -d comalyr.com -d ...
# Code Server
curl -fsSL https://code-server.dev/install.sh | sh
pm2 start code-server --name code
cat ~/.config/code-server/config.yaml
# pm2
## For Running the Servers
## start your app
pm2 start "npm run start" --name comalyr
## see running processes
pm2 list
## view logs
pm2 logs
pm2 logs comalyr
## restart
pm2 restart comalyr
## stop
pm2 stop comalyr
## save all current processes to be run on startup
pm2 save
# nginx
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
cat /var/log/nginx/access.log
## list config
cat /etc/nginx/sites-available/norn
nano /etc/nginx/sites-available/norn
## restart
systemctl restart nginx
# Postgres
systemctl status postgresql
# debugging
curl http://localhost:8080
curl -v https://comalyr.com 2>&1 | head -30
this will list out the process of connecting

View File

@@ -14,6 +14,7 @@
--quillred: #DE3F3F; --quillred: #DE3F3F;
--brown: #812A18; --brown: #812A18;
--darkbrown: #3f0808; --darkbrown: #3f0808;
--divider: rgb(223 201 169);
--house-src: /_/icons/house.svg; --house-src: /_/icons/house.svg;
--nodes-src: /_/icons/nodes.svg; --nodes-src: /_/icons/nodes.svg;
@@ -39,9 +40,10 @@
:root.dark { :root.dark {
--main: #000000; --main: #000000;
--app: #180404; --app: #180404;
--accent: #8a7454; --accent: #935757;
--accent2: var(--gold); --accent2: var(--gold);
--window: #340f0f; --window: #340f0f;
--divider: rgb(69 34 34);
--house-src: /_/icons/housedark.svg; --house-src: /_/icons/housedark.svg;
--nodes-src: /_/icons/nodesdark.svg; --nodes-src: /_/icons/nodesdark.svg;

View File

@@ -150,6 +150,7 @@ class Dashboard extends Shadow {
.gap(8); .gap(8);
}); });
}) })
.gap(0.5, em)
.paddingTop(4, pct) .paddingTop(4, pct)
.paddingLeft(5, pct) .paddingLeft(5, pct)
.width(100, pct) .width(100, pct)

View File

@@ -0,0 +1,203 @@
class Dashboard extends Shadow {
gridStyle() {
return {
table: {
'background-color': 'var(--window)',
'color': 'var(--accent)',
'box-shadow': 'none',
'font-size': '0.9em',
'max-width': '90%'
},
th: {
'background-color': 'var(--window)',
'color': 'var(--accent)',
'border': 'none',
'border-bottom': '1px solid var(--divider)'
},
td: {
'background-color': 'var(--window)',
'color': 'var(--accent)',
'border': 'none',
'border-bottom': '1px solid var(--divider)'
}
}
}
gridCSS() {
css(`
input.gridjs-input {
background-color: var(--window);
color: var(--accent);
border: none;
border-bottom: 1px solid var(--divider);
border-radius: 0;
margin-left: 5em
}
.gridjs-wrapper {
box-shadow: none;
padding-left: 4em;
max-width: 100%;
overflow-x: auto;
}
`)
}
render() {
VStack(() => {
if(window.location.pathname.startsWith("/my")) {
h1(global.profile.name);
return
}
else if(!window.location.pathname.includes("comalyr")) {
return
}
h1("Website Inquiries")
.paddingLeft(5, pct)
VStack(() => {
VStack(() => {
p("Contact Us")
.marginBottom(2, vh)
.marginLeft(5, em)
.fontStyle("italic")
ZStack(() => {
new gridjs.Grid({
columns: [
{
name: "Time",
sort: {
compare: (a, b) => {
const parse = (str) => {
const [datePart, timePart] = str.trim().split(/\s+/);
const [month, day, year] = datePart.split('.');
const normalized = timePart.replace(/(\d+):(\d+)(am|pm)/i, (_, h, m, meridiem) => {
let hours = parseInt(h);
if (meridiem.toLowerCase() === 'pm' && hours !== 12) hours += 12;
if (meridiem.toLowerCase() === 'am' && hours === 12) hours = 0;
return `${String(hours).padStart(2, '0')}:${m}:00`;
});
return new Date(`${year}-${month}-${day}T${normalized}`);
}
return parse(a) - parse(b);
}
}
},
"First", "Last", "Email", "Phone",
{
name: "Message",
formatter: (cell) => gridjs.html(`
<div class="messageCell" style="
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
cursor: pointer;
max-width: 300px;
" data-message="${cell.replace(/"/g, '&quot;')}">
${cell}
</div>
`)
},
"County"
],
data: global.currentNetwork.data.contact.map(e => [
e.time.replace(/:\d{6}(?=am|pm)/i, '')
.replace('-', ' ')
.replace(/(\d{1,2}:\d{2})(am|pm)/i, (_, time, meridiem) => {
const [h, m] = time.split(':');
return `${h.padStart(2, '0')}:${m}${meridiem.toLowerCase()}`;
}),
e.fname,
e.lname,
e.email,
e.phone,
e.message,
e.county ?? "Not Specified"
]),
sort: {
multiColumn: false,
initialState: {
columnIndex: 0,
direction: "desc"
}
},
search: true,
style: this.gridStyle()
}).render(quill.rendering.last)
this.gridCSS()
})
.overflow("hidden")
})
.gap(0.5, em)
VStack(() => {
p("Join")
.marginBottom(2, vh)
.marginLeft(5, em)
.fontStyle("italic")
ZStack(() => {
new gridjs.Grid({
columns: [
{
name: "Time",
sort: {
compare: (a, b) => {
const parse = (str) => {
const [datePart, timePart] = str.trim().split(/\s+/);
const [month, day, year] = datePart.split('.');
const normalized = timePart.replace(/(\d+):(\d+)(am|pm)/i, (_, h, m, meridiem) => {
let hours = parseInt(h);
if (meridiem.toLowerCase() === 'pm' && hours !== 12) hours += 12;
if (meridiem.toLowerCase() === 'am' && hours === 12) hours = 0;
return `${String(hours).padStart(2, '0')}:${m}:00`;
});
return new Date(`${year}-${month}-${day}T${normalized}`);
}
return parse(a) - parse(b);
}
}
},
"First", "Last", "Email", "Phone", "County"
],
data: global.currentNetwork.data.join.map(e => [
e.time.replace(/:\d{6}(?=am|pm)/i, '')
.replace('-', ' ')
.replace(/(\d{1,2}:\d{2})(am|pm)/i, (_, time, meridiem) => {
const [h, m] = time.split(':');
return `${h.padStart(2, '0')}:${m}${meridiem.toLowerCase()}`;
}),
e.fname,
e.lname,
e.email,
e.phone,
e.county ?? "Not Specified"
]),
sort: true,
search: true,
style: this.gridStyle()
}).render(quill.rendering.last)
this.gridCSS()
})
.marginBottom(3, em)
.overflow("hidden")
})
.gap(0.5, em)
})
.gap(4, em)
})
.gap(0.5, em)
.paddingTop(4, pct)
.width(100, pct)
.height(100, pct);
}
}
register(Dashboard)

View File

@@ -4,13 +4,13 @@ class People extends Shadow {
h1("Members") h1("Members")
.fontWeight("bold") .fontWeight("bold")
.marginBottom(2, em) .marginBottom(2, em)
.marginLeft(4, em) .marginLeft(2, em)
VStack(() => { VStack(() => {
VStack(() => { VStack(() => {
p("Officers") p("Officers")
.marginBottom(2, vh) .marginBottom(2, vh)
.marginLeft(8, em) .marginLeft(4, em)
.fontStyle("italic") .fontStyle("italic")
ZStack(() => { ZStack(() => {
@@ -27,35 +27,36 @@ class People extends Shadow {
'background-color': 'var(--window)', 'background-color': 'var(--window)',
'color': 'var(--accent)', 'color': 'var(--accent)',
'box-shadow': 'none', 'box-shadow': 'none',
'font-size': '0.9em' 'font-size': '0.9em',
'max-width': '90%'
}, },
th: { th: {
'background-color': 'var(--window)', 'background-color': 'var(--window)',
'color': 'var(--accent)', 'color': 'var(--accent)',
'border': 'none', 'border': 'none',
'border-bottom': '1px solid rgb(69 53 53)'
}, },
td: { td: {
'background-color': 'var(--window)', 'background-color': 'var(--window)',
'color': 'var(--accent)', 'color': 'var(--accent)',
'border': 'none', 'border': 'none',
'border-bottom': '1px solid rgb(69 53 53)' 'border-bottom': '1px solid var(--divider)'
} }
} }
}).render(quill.rendering.last) }).render(quill.rendering.last)
}) })
.overflow("hidden")
}) })
.gap(0.5, em) .gap(0.5, em)
VStack(() => { VStack(() => {
p("Stripe Subscribers") p("Stripe Subscribers")
.marginBottom(2, vh) .marginBottom(2, vh)
.marginLeft(8, em) .marginLeft(4, em)
.fontStyle("italic") .fontStyle("italic")
ZStack(() => { ZStack(() => {
new gridjs.Grid({ new gridjs.Grid({
columns: ["Amount", "Name", "Email", "Phone", "Area", "Created"], columns: ["Amount", "Name", "Email", "Phone", "County", "Created"],
data: global.currentNetwork.data.stripeMembers.map(m => [ data: global.currentNetwork.data.stripeMembers.map(m => [
"$" + m.subscriptions[0].amount, "$" + m.subscriptions[0].amount,
m.name, m.name,
@@ -81,19 +82,19 @@ class People extends Shadow {
'background-color': 'var(--window)', 'background-color': 'var(--window)',
'color': 'var(--accent)', 'color': 'var(--accent)',
'box-shadow': 'none', 'box-shadow': 'none',
'font-size': '0.9em' 'font-size': '0.9em',
'max-width': '90%'
}, },
th: { th: {
'background-color': 'var(--window)', 'background-color': 'var(--window)',
'color': 'var(--accent)', 'color': 'var(--accent)',
'border': 'none', 'border': 'none',
'border-bottom': '1px solid rgb(69 53 53)'
}, },
td: { td: {
'background-color': 'var(--window)', 'background-color': 'var(--window)',
'color': 'var(--accent)', 'color': 'var(--accent)',
'border': 'none', 'border': 'none',
'border-bottom': '1px solid rgb(69 53 53)' 'border-bottom': '1px solid var(--divider)'
} }
} }
}).render(quill.rendering.last) }).render(quill.rendering.last)
@@ -103,17 +104,18 @@ class People extends Shadow {
background-color: var(--window); background-color: var(--window);
color: var(--accent); color: var(--accent);
border: none; border: none;
border-bottom: 1px solid rgb(69 53 53); border-bottom: 1px solid var(--divider);
border-radius: 0; border-radius: 0;
margin-left: 8em margin-left: 4em
} }
.gridjs-wrapper { .gridjs-wrapper {
box-shadow: none; box-shadow: none;
padding-left: 8em; padding-left: 3em;
} }
`) `)
}) })
.marginBottom(3, em)
}) })
.gap(0.5, em) .gap(0.5, em)
}) })

View File

@@ -33,6 +33,16 @@ class AppWindow extends Shadow {
case "Settings": case "Settings":
Settings() Settings()
break; break;
default:
this.innerHTML = `
<iframe
src="/apps/parchment"
sandbox="allow-scripts allow-same-origin allow-forms"
width="100%"
height="100%"
>
</iframe>
`
} }
}) })
.overflow("scroll") .overflow("scroll")