Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69883836e1 | |||
| 17df21041a | |||
| c19fffe3c9 | |||
| b6abfe8f03 | |||
| 420ccfab3b | |||
| 8ed4505dc0 | |||
| 451054f0c2 | |||
| 3a46680c8b |
@@ -4,6 +4,19 @@ All notable changes to this project are documented here. Format loosely
|
|||||||
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
|
||||||
correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
|
correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
|
||||||
|
|
||||||
|
## [1.7.0] - 2026-07-28
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **`formAJAX`'s loading indicator showed literal HTML** ("<div class=..."), not a spinner — it passed raw markup to `app.messages.action`, which HTML-escapes its message by design. Replaced with plain text.
|
||||||
|
- **`POST /api/user/` (create) and `PUT /api/user/password` had no `message` field** in their response, so the success notification rendered empty. Added messages matching every other route's convention.
|
||||||
|
- **The user landing on `/login` with a `?redirect=` had no explanation why** — happens whenever another app's "Log in with SSO" bounces an unauthenticated user through `/oauth/authorize`. Now shows a contextual banner explaining what's happening.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Directory: tree view is now the only view** (the list/tree toggle is gone) — simpler, one code path.
|
||||||
|
- **Directory: clicking a resource's name opens its detail modal**, not just the pencil/edit icon.
|
||||||
|
|
||||||
|
Found via a fresh production install's feedback — see the [theta-env v1.13.0 release](https://github.com/theta42/theta-env/releases) for the full cross-repo summary.
|
||||||
|
|
||||||
## [1.6.3] - 2026-07-28
|
## [1.6.3] - 2026-07-28
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.6.3",
|
"version": "1.7.0",
|
||||||
"description": "A very simple LDAP management and SSO system",
|
"description": "A very simple LDAP management and SSO system",
|
||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -679,13 +679,10 @@ function formAJAX(btn){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.messages.action(
|
// Plain text: app.messages.action HTML-escapes its message (by design,
|
||||||
`<div class="spinner-border" role="status">
|
// see @simpleworkjs/frontend), so raw markup like a spinner <div> would
|
||||||
<span class="visually-hidden">Loading...</span>
|
// render literally instead of as an element.
|
||||||
</div>`,
|
app.messages.action('Saving…', $form, 'info');
|
||||||
$form,
|
|
||||||
'info'
|
|
||||||
);
|
|
||||||
|
|
||||||
app.api[method]($form.attr('action'), formData, function(error, data){
|
app.api[method]($form.attr('action'), formData, function(error, data){
|
||||||
app.messages.action(data.message, $form, error ? 'danger' : 'success'); //re-populate table
|
app.messages.action(data.message, $form, error ? 'danger' : 'success'); //re-populate table
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ router.post('/', async function(req, res, next){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({results: user});
|
return res.json({results: user, message: `User ${user.uid} created.`});
|
||||||
}catch(error){
|
}catch(error){
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ router.put('/password', async function(req, res, next){
|
|||||||
const verif = await UserVerification.getOrCreate(req.user.uid);
|
const verif = await UserVerification.getOrCreate(req.user.uid);
|
||||||
await verif.update({ password_must_change: false });
|
await verif.update({ password_must_change: false });
|
||||||
User.clearCache();
|
User.clearCache();
|
||||||
return res.json({results: result});
|
return res.json({results: result, message: 'Password changed.'});
|
||||||
}catch(error){
|
}catch(error){
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-45
@@ -15,12 +15,6 @@
|
|||||||
<option value="kind">Kind</option>
|
<option value="kind">Kind</option>
|
||||||
<option value="env">Environment</option>
|
<option value="env">Environment</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="btn-group btn-group-sm shadow-sm" role="group">
|
|
||||||
<input type="radio" class="btn-check" name="viewMode" id="view-list" value="list" autocomplete="off" checked onchange="renderTable()">
|
|
||||||
<label class="btn btn-outline-secondary" for="view-list"><i class="fa-solid fa-list"></i></label>
|
|
||||||
<input type="radio" class="btn-check" name="viewMode" id="view-tree" value="tree" autocomplete="off" onchange="renderTable()">
|
|
||||||
<label class="btn btn-outline-secondary" for="view-tree"><i class="fa-solid fa-folder-tree"></i></label>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-sm btn-primary ms-1 shadow-sm" onclick="openAddModal()">
|
<button class="btn btn-sm btn-primary ms-1 shadow-sm" onclick="openAddModal()">
|
||||||
<i class="fas fa-plus"></i> Add Resource
|
<i class="fas fa-plus"></i> Add Resource
|
||||||
</button>
|
</button>
|
||||||
@@ -49,7 +43,12 @@
|
|||||||
{{{indentHtml}}}
|
{{{indentHtml}}}
|
||||||
<span class="badge bg-secondary">{{kind}}{{#metadata.subType}} ({{metadata.subType}}){{/metadata.subType}}</span>
|
<span class="badge bg-secondary">{{kind}}{{#metadata.subType}} ({{metadata.subType}}){{/metadata.subType}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td><strong>{{name}}</strong><br><small class="text-muted">{{slug}}</small></td>
|
<td>
|
||||||
|
<a href="#" class="text-reset text-decoration-none" onclick="openEditModal('{{id}}'); return false;" title="View details">
|
||||||
|
<strong>{{name}}</strong>
|
||||||
|
</a>
|
||||||
|
<br><small class="text-muted">{{slug}}</small>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{#metadata.isProduction}}<span class="badge bg-danger">Prod</span>{{/metadata.isProduction}}
|
{{#metadata.isProduction}}<span class="badge bg-danger">Prod</span>{{/metadata.isProduction}}
|
||||||
{{^metadata.isProduction}}<span class="badge bg-info">Dev</span>{{/metadata.isProduction}}
|
{{^metadata.isProduction}}<span class="badge bg-info">Dev</span>{{/metadata.isProduction}}
|
||||||
@@ -376,8 +375,7 @@
|
|||||||
function renderTable() {
|
function renderTable() {
|
||||||
const filter = $('#search-filter').val().toLowerCase();
|
const filter = $('#search-filter').val().toLowerCase();
|
||||||
const sort = $('#sort-by').val();
|
const sort = $('#sort-by').val();
|
||||||
const viewMode = $('input[name="viewMode"]:checked').val();
|
|
||||||
|
|
||||||
let filtered = rawResources.filter(r => {
|
let filtered = rawResources.filter(r => {
|
||||||
if (!filter) return true;
|
if (!filter) return true;
|
||||||
return (r.name || '').toLowerCase().includes(filter) ||
|
return (r.name || '').toLowerCase().includes(filter) ||
|
||||||
@@ -402,42 +400,38 @@
|
|||||||
|
|
||||||
let finalRenderList = [];
|
let finalRenderList = [];
|
||||||
|
|
||||||
if (viewMode === 'tree') {
|
const map = {};
|
||||||
const map = {};
|
const roots = [];
|
||||||
const roots = [];
|
filtered.forEach(r => { map[r.id] = { ...r, children: [] }; });
|
||||||
filtered.forEach(r => { map[r.id] = { ...r, children: [] }; });
|
|
||||||
|
filtered.forEach(r => {
|
||||||
filtered.forEach(r => {
|
const node = map[r.id];
|
||||||
const node = map[r.id];
|
if (node.parentId && map[node.parentId]) {
|
||||||
if (node.parentId && map[node.parentId]) {
|
map[node.parentId].children.push(node);
|
||||||
map[node.parentId].children.push(node);
|
} else {
|
||||||
} else {
|
roots.push(node);
|
||||||
roots.push(node);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
const flatten = (nodes, depth) => {
|
||||||
const flatten = (nodes, depth) => {
|
nodes.forEach(n => {
|
||||||
nodes.forEach(n => {
|
let indentHtml = '';
|
||||||
let indentHtml = '';
|
for(let i = 0; i < depth; i++) {
|
||||||
for(let i = 0; i < depth; i++) {
|
indentHtml += '<span style="display:inline-block; width: 1.5rem;"></span>';
|
||||||
indentHtml += '<span style="display:inline-block; width: 1.5rem;"></span>';
|
}
|
||||||
}
|
if (depth > 0) {
|
||||||
if (depth > 0) {
|
indentHtml += '<i class="fa-solid fa-turn-up fa-rotate-90 text-muted me-2"></i>';
|
||||||
indentHtml += '<i class="fa-solid fa-turn-up fa-rotate-90 text-muted me-2"></i>';
|
}
|
||||||
}
|
n.indentHtml = indentHtml;
|
||||||
n.indentHtml = indentHtml;
|
finalRenderList.push(n);
|
||||||
finalRenderList.push(n);
|
if (n.children.length > 0) {
|
||||||
if (n.children.length > 0) {
|
flatten(n.children, depth + 1);
|
||||||
flatten(n.children, depth + 1);
|
}
|
||||||
}
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
flatten(roots, 0);
|
||||||
flatten(roots, 0);
|
|
||||||
} else {
|
|
||||||
finalRenderList = filtered.map(r => ({ ...r, indentHtml: '' }));
|
|
||||||
}
|
|
||||||
|
|
||||||
$.scope.resources.empty();
|
$.scope.resources.empty();
|
||||||
for (const r of finalRenderList) {
|
for (const r of finalRenderList) {
|
||||||
$.scope.resources.push(r);
|
$.scope.resources.push(r);
|
||||||
|
|||||||
@@ -7,6 +7,22 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Landing here with no explanation ("why am I on the SSO login page?") is
|
||||||
|
// exactly what happens when another app's "Log in with SSO" button sends
|
||||||
|
// an unauthenticated user through /oauth/authorize, which bounces them
|
||||||
|
// here with ?redirect=. Tell them what's happening instead of leaving it
|
||||||
|
// a mystery.
|
||||||
|
$(document).ready(function(){
|
||||||
|
var redirect = <%- JSON.stringify(redirect || '') %>;
|
||||||
|
if(redirect){
|
||||||
|
var isOauth = /\/oauth\/authorize/.test(redirect);
|
||||||
|
var message = isOauth
|
||||||
|
? 'Log in to continue — an application is requesting access to your account.'
|
||||||
|
: "Log in to continue to what you were doing — you'll be sent back afterward.";
|
||||||
|
app.messages.action(message, $('.card').first(), 'info');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function setOtpMethod(method) {
|
function setOtpMethod(method) {
|
||||||
$('#otpMethodInput').val(method);
|
$('#otpMethodInput').val(method);
|
||||||
$('#otpMethodEmail').toggleClass('active', method === 'email').toggleClass('btn-secondary', method === 'email').toggleClass('btn-outline-secondary', method !== 'email');
|
$('#otpMethodEmail').toggleClass('active', method === 'email').toggleClass('btn-secondary', method === 'email').toggleClass('btn-outline-secondary', method !== 'email');
|
||||||
|
|||||||
Reference in New Issue
Block a user