v0.6.5 various updates
This commit is contained in:
@@ -7,7 +7,7 @@ TZ=UTC
|
||||
# Copy this file to .env and customize
|
||||
|
||||
# Image version to run (set by build.sh, or use 'latest')
|
||||
JAMA_VERSION=0.6.4
|
||||
JAMA_VERSION=0.6.5
|
||||
|
||||
# Default admin credentials (used on FIRST RUN only)
|
||||
ADMIN_NAME=Admin User
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jama-backend",
|
||||
"version": "0.6.4",
|
||||
"version": "0.6.5",
|
||||
"description": "TeamChat backend server",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -182,6 +182,12 @@ function initDb() {
|
||||
console.log('[DB] Migration: added direct_peer2_id column');
|
||||
} catch (e) { /* column already exists */ }
|
||||
|
||||
// Migration: last_login timestamp per user
|
||||
try {
|
||||
db.exec("ALTER TABLE users ADD COLUMN last_login TEXT");
|
||||
console.log('[DB] Migration: added last_login column');
|
||||
} catch (e) { /* column already exists */ }
|
||||
|
||||
// Migration: help_dismissed preference per user
|
||||
try {
|
||||
db.exec("ALTER TABLE users ADD COLUMN help_dismissed INTEGER NOT NULL DEFAULT 0");
|
||||
|
||||
@@ -24,6 +24,9 @@ router.post('/login', (req, res) => {
|
||||
const valid = bcrypt.compareSync(password, user.password);
|
||||
if (!valid) return res.status(401).json({ error: 'Invalid credentials' });
|
||||
|
||||
// Record last login timestamp
|
||||
db.prepare("UPDATE users SET last_login = datetime('now') WHERE id = ?").run(user.id);
|
||||
|
||||
const token = generateToken(user.id);
|
||||
const ua = req.headers['user-agent'] || '';
|
||||
const device = setActiveSession(user.id, token, ua); // displaces prior session on same device class
|
||||
|
||||
@@ -49,7 +49,7 @@ function getDefaultPassword(db) {
|
||||
router.get('/', authMiddleware, adminMiddleware, (req, res) => {
|
||||
const db = getDb();
|
||||
const users = db.prepare(`
|
||||
SELECT id, name, email, role, status, is_default_admin, must_change_password, avatar, about_me, display_name, created_at
|
||||
SELECT id, name, email, role, status, is_default_admin, must_change_password, avatar, about_me, display_name, created_at, last_login
|
||||
FROM users WHERE status != 'deleted'
|
||||
ORDER BY created_at ASC
|
||||
`).all();
|
||||
|
||||
2
build.sh
2
build.sh
@@ -13,7 +13,7 @@
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${1:-0.6.4}"
|
||||
VERSION="${1:-0.6.5}"
|
||||
ACTION="${2:-}"
|
||||
REGISTRY="${REGISTRY:-}"
|
||||
IMAGE_NAME="jama"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jama-frontend",
|
||||
"version": "0.6.4",
|
||||
"version": "0.6.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -22,7 +22,6 @@ export default function HelpModal({ onClose, dismissed: initialDismissed }) {
|
||||
setDismissed(val);
|
||||
try {
|
||||
await api.dismissHelp(val);
|
||||
if (val) onClose(); // immediately close when "do not show again" checked
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ function useAppSettings() {
|
||||
|
||||
useEffect(() => {
|
||||
const name = settings.app_name || 'jama';
|
||||
document.title = name;
|
||||
// Preserve any unread badge prefix already set by Chat.jsx
|
||||
const prefix = document.title.match(/^(\(\d+\)\s*)/)?.[1] || '';
|
||||
document.title = prefix + name;
|
||||
const logoUrl = settings.logo_url;
|
||||
const faviconUrl = logoUrl || '/icons/jama.png';
|
||||
let link = document.querySelector("link[rel~='icon']");
|
||||
|
||||
@@ -91,7 +91,12 @@ function UserRow({ u, onUpdated }) {
|
||||
{u.status !== 'active' && <span className="role-badge status-suspended">{u.status}</span>}
|
||||
{!!u.is_default_admin && <span className="text-xs" style={{ color: 'var(--text-tertiary)' }}>Default Admin</span>}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{u.email}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 1, display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{u.email}</span>
|
||||
<span style={{ color: 'var(--text-tertiary)', whiteSpace: 'nowrap', flexShrink: 0 }}>
|
||||
Last login: {u.last_login ? new Date(u.last_login + 'Z').toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }) : 'Never'}
|
||||
</span>
|
||||
</div>
|
||||
{!!u.must_change_password && <div className="text-xs" style={{ color: 'var(--warning)' }}>⚠ Must change password</div>}
|
||||
</div>
|
||||
<svg
|
||||
|
||||
@@ -190,11 +190,20 @@ export default function Chat() {
|
||||
setUnreadGroups(prev => { const next = new Map(prev); next.delete(id); return next; });
|
||||
};
|
||||
|
||||
// Update page title with total unread badge count
|
||||
// Update page title AND PWA app badge with total unread count
|
||||
useEffect(() => {
|
||||
const totalUnread = [...unreadGroups.values()].reduce((a, b) => a + b, 0);
|
||||
// Strip any existing badge prefix to get the clean base title
|
||||
const base = document.title.replace(/^\(\d+\)\s*/, '');
|
||||
document.title = totalUnread > 0 ? `(${totalUnread}) ${base}` : base;
|
||||
// PWA app icon badge (Chrome/Edge desktop + Android, Safari 16.4+)
|
||||
if ('setAppBadge' in navigator) {
|
||||
if (totalUnread > 0) {
|
||||
navigator.setAppBadge(totalUnread).catch(() => {});
|
||||
} else {
|
||||
navigator.clearAppBadge().catch(() => {});
|
||||
}
|
||||
}
|
||||
}, [unreadGroups]);
|
||||
|
||||
const activeGroup = [
|
||||
|
||||
Reference in New Issue
Block a user