DOING THINGS
This commit is contained in:
parent
c5a5e5893b
commit
952fbf5dbc
45
buttons.json
Normal file
45
buttons.json
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"COLS": [
|
||||||
|
"Normal Announcements",
|
||||||
|
"Emergency Announcements"
|
||||||
|
],
|
||||||
|
"ROWS": [
|
||||||
|
{
|
||||||
|
"text": "Live Page",
|
||||||
|
"btnClass": "btn-primary",
|
||||||
|
"name": "LivePage",
|
||||||
|
"doubleHeight": true,
|
||||||
|
"context": {
|
||||||
|
"context": "custom-emergency.9001.1",
|
||||||
|
"timeout": 30000,
|
||||||
|
"cid": "Live Page"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Emergency Announcement",
|
||||||
|
"btnClass": "btn-danger",
|
||||||
|
"name": "EmergencyAnnouncement",
|
||||||
|
"doubleHeight": true,
|
||||||
|
"faIcon": "fa-solid fa-octagon",
|
||||||
|
"context": {
|
||||||
|
"context": "custom-emergency.9002.1",
|
||||||
|
"timeout": 30000,
|
||||||
|
"cid": "Emergency Announcement"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"text": "Weather Alert",
|
||||||
|
"btnClass": "btn-warning",
|
||||||
|
"name": "WeatherAlert",
|
||||||
|
"faIcon": "fa-regular fa-cloud-bolt",
|
||||||
|
"doubleHeight": false,
|
||||||
|
"context": {
|
||||||
|
"context": "custom-emergency.9003.1",
|
||||||
|
"timeout": 30000,
|
||||||
|
"cid": "\"Weather Alert\" <9000>",
|
||||||
|
"dial": "9000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
64
index.ejs
Normal file
64
index.ejs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<%
|
||||||
|
// buttons is provided to this template
|
||||||
|
const numCols = (buttons && buttons.COLS && buttons.COLS.length) || 1;
|
||||||
|
const colWidth = (12 / numCols) >= 1 ? Math.floor(12 / numCols) : null;
|
||||||
|
|
||||||
|
// chunk the flat ROWS array into logical rows of numCols items
|
||||||
|
const chunks = [];
|
||||||
|
for (let i = 0; i < (buttons.ROWS || []).length; i += numCols) {
|
||||||
|
chunks.push((buttons.ROWS || []).slice(i, i + numCols));
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn-grid { gap: 0.5rem; }
|
||||||
|
.btn-grid .cell { display: flex; align-items: stretch; }
|
||||||
|
.btn-grid .blank-cell { height: 3.75rem; } /* placeholder height */
|
||||||
|
.btn-grid .double-height { height: 9.5rem; } /* adjust to taste */
|
||||||
|
.btn-grid .cell > button { width: 100%; white-space: normal; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- column headers -->
|
||||||
|
<div class="row mb-2">
|
||||||
|
<% for (let c = 0; c < numCols; c++) { %>
|
||||||
|
<div class="<%= colWidth ? 'col-' + colWidth : 'col' %>">
|
||||||
|
<h5><%= buttons.COLS[c] || '' %></h5>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- button grid -->
|
||||||
|
<div class="btn-grid">
|
||||||
|
<% chunks.forEach(function(row) { %>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<% for (let c = 0; c < numCols; c++) {
|
||||||
|
const cell = row[c];
|
||||||
|
const isEmpty = !cell || Object.keys(cell).length === 0;
|
||||||
|
const colClass = '<%= colWidth ? "col-' + colWidth + '" : "col" %>'; // placeholder for layout below
|
||||||
|
%>
|
||||||
|
<div class="<%= colWidth ? 'col-' + colWidth : 'col' %> cell">
|
||||||
|
<% if (isEmpty) { %>
|
||||||
|
<div class="blank-cell"></div>
|
||||||
|
<% } else {
|
||||||
|
// build data attributes from context if present
|
||||||
|
const ctx = cell.context || {};
|
||||||
|
const dataAttrs = [];
|
||||||
|
if (ctx.context) dataAttrs.push('data-context="' + ctx.context + '"');
|
||||||
|
if (ctx.timeout) dataAttrs.push('data-timeout="' + ctx.timeout + '"');
|
||||||
|
if (ctx.cid) dataAttrs.push('data-cid="' + ctx.cid + '"');
|
||||||
|
if (ctx.dial) dataAttrs.push('data-dial="' + ctx.dial + '"');
|
||||||
|
%>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn <%= cell.btnClass || 'btn-secondary' %> <%= cell.doubleHeight ? 'double-height' : '' %>"
|
||||||
|
name="<%= cell.name || '' %>"
|
||||||
|
<%= dataAttrs.join(' ') %>
|
||||||
|
><%= cell.text || '' %></button>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<% }); %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
42
index.js
42
index.js
|
|
@ -3,32 +3,34 @@ const { exec } = require('child_process');
|
||||||
|
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const contexts = {
|
const phonesCfg = require('./phones.json');
|
||||||
"A1": {
|
const buttonsCfg = require('./buttons.json');
|
||||||
context: 'custom-emergency.9001.1',
|
|
||||||
timeout: 30000,
|
const contexts = {};
|
||||||
cid: 'Live Page'
|
|
||||||
},
|
// Generate contexts from buttonsCfg
|
||||||
"E1": {
|
if (buttonsCfg && buttonsCfg.ROWS) {
|
||||||
context: 'custom-emergency.9002.1',
|
buttonsCfg.ROWS.forEach(button => {
|
||||||
timeout: 30000,
|
if (button.name && button.context) {
|
||||||
cid: 'Emergency Live Page'
|
contexts[button.name] = {
|
||||||
},
|
context: button.context.context,
|
||||||
"E2": { // Weather
|
timeout: button.context.timeout,
|
||||||
context: 'custom-emergency.9003.1',
|
cid: button.context.cid,
|
||||||
timeout: 30000,
|
...(button.context.dial && { dial: button.context.dial })
|
||||||
cid: '"Emergency 2" <2100>',
|
};
|
||||||
number: process.env.PAGING_ADAPTER_EXT // Direct to page group, no phone needed
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Generated contexts:', contexts);
|
||||||
|
|
||||||
function trigCall(pageType, phone) {
|
function trigCall(pageType, phone) {
|
||||||
// If contexts[pageType] does not exist, return an error
|
// If contexts[pageType] does not exist, return an error
|
||||||
if (!contexts[pageType]) {
|
if (!contexts[pageType]) {
|
||||||
throw new Error(`Invalid page type: ${pageType}`);
|
throw new Error(`Invalid page type: ${pageType}`);
|
||||||
}
|
}
|
||||||
const { context, timeout, cid, number } = contexts[pageType];
|
const { context, timeout, cid, dial } = contexts[pageType];
|
||||||
const targetNumber = number || phone;
|
const targetNumber = dial || phone;
|
||||||
if (!targetNumber) {
|
if (!targetNumber) {
|
||||||
throw new Error(`Phone number is required for page type: ${pageType}`);
|
throw new Error(`Phone number is required for page type: ${pageType}`);
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +92,7 @@ function auth(req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.render('index', { session: req.session });
|
res.render('index', { session: req.session, phones: phonesCfg, buttons: require("./buttons.json") });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/trig', async (req, res) => {
|
app.post('/trig', async (req, res) => {
|
||||||
|
|
|
||||||
6
phones.json
Normal file
6
phones.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"Front Desk": "2120",
|
||||||
|
"Cam": "2113",
|
||||||
|
"Cam 2": "2112",
|
||||||
|
"Ryan": "2100"
|
||||||
|
}
|
||||||
181
views/index.ejs
181
views/index.ejs
|
|
@ -1,74 +1,121 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||||
<title>Funny goofy test page!!!!!1!</title>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/tuta-amb/fontawesome-pro@latest/web/css/all.min.css">
|
||||||
|
<title>Funny goofy test page!!!!!1!</title>
|
||||||
|
<style>
|
||||||
|
.btn-tall { height: 152px; }
|
||||||
|
.btn-short { height: 76px; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="position-absolute top-0 start-0 m-3">
|
<div class="position-absolute top-0 start-0 m-3">
|
||||||
<select class="form-select" id="phoneSelect">
|
<select class="form-select" id="phoneSelect">
|
||||||
<option value="2120">Front Desk</option>
|
<% Object.entries(phones).forEach(([name, number])=> { %>
|
||||||
<option value="2113">Cam</option>
|
<option value="<%= number %>">
|
||||||
<option value="2112">Cam 2</option>
|
<%= name %>
|
||||||
<option value="2100">Ryan</option>
|
</option>
|
||||||
</select>
|
<% }); %>
|
||||||
</div>
|
</select>
|
||||||
<div class="container-fluid d-flex justify-content-center align-items-center min-vh-100">
|
</div>
|
||||||
<div>
|
|
||||||
<table class="table table-borderless table-sm">
|
<%
|
||||||
<thead>
|
// determine columns and chunk rows
|
||||||
<tr>
|
const cols = (buttons && buttons.COLS) || [''];
|
||||||
<th class="text-left"><h3>Normal Announcements</h3></th>
|
const numCols = Math.max(1, cols.length);
|
||||||
<th class="text-left"><h3>Emergency Announcements</h3></th>
|
const rowsFlat = (buttons && buttons.ROWS) || [];
|
||||||
</tr>
|
const chunks = [];
|
||||||
</thead>
|
for (let i = 0; i < rowsFlat.length; i += numCols) {
|
||||||
<tbody>
|
const slice = rowsFlat.slice(i, i + numCols);
|
||||||
<tr>
|
// pad short slice with empty objects so layout stays consistent
|
||||||
<td class="text-left">
|
while (slice.length < numCols) slice.push({});
|
||||||
<button class="btn btn-primary mb-1 w-100" name="A1" onclick="triggerAnnouncement(this)" style="height: 76px;">Live Page</button>
|
chunks.push(slice);
|
||||||
</td>
|
}
|
||||||
<td class="text-left">
|
%>
|
||||||
<button class="btn btn-danger mb-1 w-100" name="E1" onclick="triggerAnnouncement(this)" style="height: 76px;">Emergency Live Page</button>
|
|
||||||
</td>
|
<div class="container-fluid d-flex justify-content-center align-items-center min-vh-100">
|
||||||
</tr>
|
<div>
|
||||||
<tr>
|
<table class="table table-borderless table-sm">
|
||||||
<td class="text-left">
|
<thead>
|
||||||
<button class="btn btn-success mb-1" name="A2" onclick="triggerAnnouncement(this)">Announcement 2</button>
|
<tr>
|
||||||
</td>
|
<% for (let c = 0; c < numCols; c++) { %>
|
||||||
<td class="text-left">
|
<th class="text-left">
|
||||||
<button class="btn btn-warning mb-1" name="E2" onclick="triggerAnnouncement(this)">⛈️ Weather</button>
|
<h3><%= cols[c] || '' %></h3>
|
||||||
</td>
|
</th>
|
||||||
</tr>
|
<% } %>
|
||||||
<tr>
|
</tr>
|
||||||
<td class="text-left">
|
</thead>
|
||||||
<button class="btn btn-info mb-1" name="A3" onclick="triggerAnnouncement(this)">Announcement 3</button>
|
<tbody>
|
||||||
</td>
|
<% chunks.forEach((row) => { %>
|
||||||
<td class="text-left">
|
<tr>
|
||||||
<button class="btn btn-dark mb-1" name="E3" onclick="triggerAnnouncement(this)">Emergency 3</button>
|
<% for (let c = 0; c < numCols; c++) {
|
||||||
</td>
|
const cell = row[c] || {};
|
||||||
</tr>
|
const isBlank = Object.keys(cell).length === 0;
|
||||||
</tbody>
|
%>
|
||||||
</table>
|
<td class="text-left">
|
||||||
</div>
|
<% if (isBlank) { %>
|
||||||
<script src="/assets/js/bootstrap.min.js"></script>
|
<!-- blank space -->
|
||||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
<% } else {
|
||||||
<script src="/assets/js/jquery.min.js"></script>
|
const ctx = cell.context || {};
|
||||||
<script>
|
%>
|
||||||
function triggerAnnouncement(button) {
|
<button
|
||||||
const ann = button.getAttribute('name');
|
class="btn <%= cell.btnClass || 'btn-secondary' %> mb-1 w-100 <%= cell.doubleHeight ? 'btn-tall' : 'btn-short' %> fw-bold fs-4 <%= cell.faIcon ? 'd-flex justify-content-center align-items-center' : '' %>"
|
||||||
const phoneSelect = document.getElementById('phoneSelect');
|
name="<%= cell.name || '' %>"
|
||||||
const phone = phoneSelect.value;
|
onclick="triggerAnnouncement(this)"
|
||||||
console.log(name)
|
<% if (ctx.context) { %> data-context="<%= ctx.context %>" <% } %>
|
||||||
fetch('/trig', {
|
<% if (ctx.timeout) { %> data-timeout="<%= ctx.timeout %>" <% } %>
|
||||||
method: 'POST',
|
<% if (ctx.cid) { %> data-cid="<%= ctx.cid %>" <% } %>
|
||||||
headers: {
|
<% if (ctx.dial) { %> data-dial="<%= ctx.dial %>" <% } %>
|
||||||
'Content-Type': 'application/json'
|
>
|
||||||
},
|
<% if (cell.faIcon) { %>
|
||||||
body: JSON.stringify({ pageType: ann, phone: phone })
|
<i class="<%= cell.faIcon %> me-2" <% if (cell.faColor) { %> style="color: <%= cell.faColor %>" <% } %>></i>
|
||||||
});
|
<span><%= cell.text || '' %></span>
|
||||||
}
|
<% } else { %>
|
||||||
</script>
|
<%= cell.text || '' %>
|
||||||
|
<% } %>
|
||||||
|
</button>
|
||||||
|
<% } %>
|
||||||
|
</td>
|
||||||
|
<% } %>
|
||||||
|
</tr>
|
||||||
|
<% }); %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/assets/js/bootstrap.min.js"></script>
|
||||||
|
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/assets/js/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function triggerAnnouncement(button) {
|
||||||
|
const ann = button.getAttribute('name');
|
||||||
|
const phoneSelect = document.getElementById('phoneSelect');
|
||||||
|
const phone = phoneSelect.value;
|
||||||
|
console.log(ann);
|
||||||
|
// send the button's context attrs if you want them server-side
|
||||||
|
const payload = {
|
||||||
|
pageType: ann,
|
||||||
|
phone: phone,
|
||||||
|
context: {
|
||||||
|
context: button.dataset.context,
|
||||||
|
timeout: button.dataset.timeout,
|
||||||
|
cid: button.dataset.cid,
|
||||||
|
dial: button.dataset.dial
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetch('/trig', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
Reference in a new issue