diff --git a/services/arbiter-3.0/src/routes/admin/about.js b/services/arbiter-3.0/src/routes/admin/about.js index 73b2e26..f32f0a7 100644 --- a/services/arbiter-3.0/src/routes/admin/about.js +++ b/services/arbiter-3.0/src/routes/admin/about.js @@ -17,6 +17,7 @@ const fs = require('fs'); const MODULES = [ { name: 'Dashboard', version: '1.0.0', path: '/admin/dashboard', icon: '๐Ÿ“Š', status: 'stable' }, + { name: 'Tasks', version: '1.0.0', path: '/admin/tasks', icon: '๐Ÿ“‹', status: 'new' }, { name: 'Servers', version: '1.0.0', path: '/admin/servers', icon: '๐Ÿ–ฅ๏ธ', status: 'stable' }, { name: 'Players', version: '1.0.0', path: '/admin/players', icon: '๐Ÿ‘ฅ', status: 'stable' }, { name: 'Financials', version: '1.0.0', path: '/admin/financials', icon: '๐Ÿ’ฐ', status: 'stable' }, diff --git a/services/arbiter-3.0/src/routes/admin/index.js b/services/arbiter-3.0/src/routes/admin/index.js index 9b86102..72662de 100644 --- a/services/arbiter-3.0/src/routes/admin/index.js +++ b/services/arbiter-3.0/src/routes/admin/index.js @@ -18,6 +18,7 @@ const socialRouter = require('./social'); const infrastructureRouter = require('./infrastructure'); const aboutRouter = require('./about'); const mcpLogsRouter = require('./mcp-logs'); +const tasksRouter = require('./tasks'); router.use(requireTrinityAccess); @@ -123,5 +124,6 @@ router.use('/social', socialRouter); router.use('/infrastructure', infrastructureRouter); router.use('/about', aboutRouter); router.use('/mcp-logs', mcpLogsRouter); +router.use('/tasks', tasksRouter); module.exports = router; diff --git a/services/arbiter-3.0/src/routes/admin/tasks.js b/services/arbiter-3.0/src/routes/admin/tasks.js new file mode 100644 index 0000000..8c2e8cc --- /dev/null +++ b/services/arbiter-3.0/src/routes/admin/tasks.js @@ -0,0 +1,129 @@ +const express = require('express'); +const router = express.Router(); +const db = require('../../database'); + +/** + * Tasks Module โ€” Trinity Console + * + * Web interface for task management. Source of truth: PostgreSQL tasks table. + * Complements Discord /tasks command for ChatOps workflow. + * + * GET /admin/tasks โ€” Task board view + * POST /admin/tasks/update/:id โ€” Update task (HTMX) + * POST /admin/tasks/create โ€” Create task (HTMX) + * + * Chronicler #78 | April 11, 2026 + */ + +const PRIORITIES = ['critical', 'high', 'medium', 'low', 'wish']; +const STATUSES = ['open', 'in_progress', 'blocked', 'done', 'obsolete']; +const OWNERS = ['Michael', 'Meg', 'Holly', 'Trinity', 'unassigned']; + +router.get('/', async (req, res) => { + try { + const { status, priority, owner } = req.query; + let where = 'WHERE 1=1'; + const params = []; + let p = 0; + + // Default: hide done and obsolete + if (status) { + p++; where += ` AND status = $${p}`; params.push(status); + } else if (!req.query.all) { + where += ` AND status NOT IN ('done', 'obsolete')`; + } + if (priority) { p++; where += ` AND priority = $${p}`; params.push(priority); } + if (owner) { p++; where += ` AND owner = $${p}`; params.push(owner); } + + const result = await db.query( + `SELECT * FROM tasks ${where} ORDER BY + CASE status WHEN 'in_progress' THEN 1 WHEN 'blocked' THEN 2 WHEN 'open' THEN 3 WHEN 'done' THEN 4 WHEN 'obsolete' THEN 5 END, + CASE priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 WHEN 'low' THEN 4 WHEN 'wish' THEN 5 END, + task_number`, + params + ); + + // Stats + const statsResult = await db.query(` + SELECT + COUNT(*) FILTER (WHERE status NOT IN ('done','obsolete')) as active, + COUNT(*) FILTER (WHERE status = 'done') as done, + COUNT(*) FILTER (WHERE status = 'in_progress') as in_progress, + COUNT(*) FILTER (WHERE status = 'blocked') as blocked, + COUNT(*) FILTER (WHERE priority IN ('critical','high') AND status NOT IN ('done','obsolete')) as high_priority + FROM tasks + `); + + res.render('admin/tasks/index', { + title: 'Tasks', + currentPath: '/tasks', + tasks: result.rows, + stats: statsResult.rows[0], + filters: { status, priority, owner, all: req.query.all }, + priorities: PRIORITIES, + statuses: STATUSES, + owners: OWNERS, + adminUser: req.user, + layout: 'layout' + }); + } catch (err) { + console.error('[Tasks] Route error:', err); + res.status(500).send('Error loading tasks'); + } +}); + +// POST /admin/tasks/update/:id โ€” Quick status update (HTMX) +router.post('/update/:id', async (req, res) => { + try { + const { id } = req.params; + const { status, priority, owner } = req.body; + const updates = []; + const params = []; + let p = 0; + + if (status) { p++; updates.push(`status = $${p}`); params.push(status); } + if (priority) { p++; updates.push(`priority = $${p}`); params.push(priority); } + if (owner) { p++; updates.push(`owner = $${p}`); params.push(owner); } + + if (status === 'done') { + updates.push(`completed_at = NOW()`); + const completedBy = req.user?.username || 'Trinity Console'; + p++; updates.push(`completed_by = $${p}`); params.push(completedBy); + } + + updates.push('updated_at = NOW()'); + p++; params.push(id); + + await db.query(`UPDATE tasks SET ${updates.join(', ')} WHERE id = $${p}`, params); + + res.redirect('/admin/tasks'); + } catch (err) { + console.error('[Tasks] Update error:', err); + res.status(500).send('Error updating task'); + } +}); + +// POST /admin/tasks/create โ€” Create new task +router.post('/create', async (req, res) => { + try { + const { title, description, priority, owner } = req.body; + if (!title) return res.redirect('/admin/tasks'); + + const maxResult = await db.query('SELECT COALESCE(MAX(task_number), 0) + 1 as next FROM tasks'); + const taskNumber = maxResult.rows[0].next; + + await db.query( + `INSERT INTO tasks (task_number, title, description, priority, owner) + VALUES ($1, $2, $3, $4, $5)`, + [taskNumber, title, description || null, priority || 'medium', owner || 'unassigned'] + ); + + console.log(`๐Ÿ“‹ [Tasks] Created #${taskNumber}: ${title} via Trinity Console`); + res.redirect('/admin/tasks'); + } catch (err) { + console.error('[Tasks] Create error:', err); + res.status(500).send('Error creating task'); + } +}); + +module.exports = router; diff --git a/services/arbiter-3.0/src/views/admin/tasks/index.ejs b/services/arbiter-3.0/src/views/admin/tasks/index.ejs new file mode 100644 index 0000000..120c69e --- /dev/null +++ b/services/arbiter-3.0/src/views/admin/tasks/index.ejs @@ -0,0 +1,199 @@ + + + + +
+
+
Active
+
<%= stats.active %>
+
+
+
In Progress
+
<%= stats.in_progress %>
+
+
+
Blocked
+
<%= stats.blocked %>
+
+
+
High Priority
+
<%= stats.high_priority %>
+
+
+
Completed
+
<%= stats.done %>
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + Reset + Show All +
+
+ +
+ + +
+ + + + + + + + + + + + + <% if (tasks.length === 0) { %> + + <% } %> + <% tasks.forEach(task => { + const priColors = { critical:'bg-red-500/20 text-red-500', high:'bg-fire/20 text-fire', medium:'bg-yellow-500/20 text-yellow-500', low:'bg-blue-500/20 text-blue-400', wish:'bg-purple-500/20 text-purple-400' }; + const staColors = { open:'bg-gray-500/20 text-gray-400', in_progress:'bg-frost/20 text-frost', blocked:'bg-red-500/20 text-red-500', done:'bg-green-500/20 text-green-500', obsolete:'bg-gray-700/20 text-gray-600' }; + const priClass = priColors[task.priority] || 'bg-gray-500/20 text-gray-400'; + const staClass = staColors[task.status] || 'bg-gray-500/20 text-gray-400'; + %> + + + + + + + + + <% }); %> + +
#TaskPriorityStatusOwnerActions
No tasks match your filters.
<%= task.task_number %> +
<%= task.title %>
+ <% if (task.description) { %> +
<%= task.description.substring(0, 80) %><%= task.description.length > 80 ? '...' : '' %>
+ <% } %> + <% if (task.tags && task.tags.length > 0) { %> +
+ <% task.tags.forEach(tag => { %> + <%= tag %> + <% }); %> +
+ <% } %> +
+ <%= task.priority %> + +
+ +
+
+
+ +
+
+ <% if (task.status !== 'done' && task.status !== 'obsolete') { %> +
+ + +
+ <% } else if (task.completed_by) { %> + <%= task.completed_by %> + <% } %> +
+
+ +
+ <%= tasks.length %> task(s) shown ยท Source: PostgreSQL ยท Also available via /tasks in Discord +
+ + + diff --git a/services/arbiter-3.0/src/views/layout.ejs b/services/arbiter-3.0/src/views/layout.ejs index 6f6c88e..69c81c6 100644 --- a/services/arbiter-3.0/src/views/layout.ejs +++ b/services/arbiter-3.0/src/views/layout.ejs @@ -76,6 +76,9 @@ ๐Ÿ“Š Dashboard + + ๐Ÿ“‹ Tasks + ๐Ÿ–ฅ๏ธ Servers