Compare commits
9 Commits
release-pki
...
v1.19.3
| Author | SHA1 | Date | |
|---|---|---|---|
| f0ceb750a8 | |||
| 6e95defcf5 | |||
| 92c2e8a03b | |||
| 230e5be2fd | |||
| 0331cb976a | |||
| 90cf65e920 | |||
| 2d202b4979 | |||
| 8f04c20cd7 | |||
| df330c6c0f |
Binary file not shown.
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.19.0",
|
"version": "1.19.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.19.0",
|
"version": "1.19.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^7.3.0",
|
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.19.0",
|
"version": "1.19.3",
|
||||||
"description": "A very simple LDAP management and SSO system",
|
"description": "A very simple LDAP management and SSO system",
|
||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,17 +31,19 @@ module.exports = {
|
|||||||
if (!targetRange) throw new Error("Missing targetRange for Nmap");
|
if (!targetRange) throw new Error("Missing targetRange for Nmap");
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const scan = new nmap.OsAndPortScan(targetRange);
|
// OsAndPortScan requires root (for -O). NmapScan does a basic port scan (TCP connect if non-root).
|
||||||
|
const scan = new nmap.NmapScan(targetRange);
|
||||||
scan.command.push('-Pn');
|
scan.command.push('-Pn');
|
||||||
scan.on('complete', function(data) {
|
scan.on('complete', function(data) {
|
||||||
const resources = [];
|
const resources = [];
|
||||||
const edges = [];
|
const edges = [];
|
||||||
|
|
||||||
for (const host of data) {
|
for (const host of data) {
|
||||||
if (!host.mac || !host.ip) continue;
|
if (!host.ip) continue;
|
||||||
const hostSlug = `nmap-host-${host.mac.replace(/:/g, '')}`;
|
const hostId = host.mac ? host.mac.replace(/:/g, '') : host.ip.replace(/\\./g, '_');
|
||||||
|
const hostSlug = `nmap-host-${hostId}`;
|
||||||
|
|
||||||
const interfaces = [{ mac: host.mac, ip: host.ip }];
|
const interfaces = [{ mac: host.mac || null, ip: host.ip }];
|
||||||
|
|
||||||
resources.push({
|
resources.push({
|
||||||
kind: 'host',
|
kind: 'host',
|
||||||
@@ -52,7 +54,7 @@ module.exports = {
|
|||||||
|
|
||||||
if (host.openPorts && host.openPorts.length > 0) {
|
if (host.openPorts && host.openPorts.length > 0) {
|
||||||
for (const port of host.openPorts) {
|
for (const port of host.openPorts) {
|
||||||
const svcSlug = `nmap-svc-${host.mac.replace(/:/g, '')}-${port.port}`;
|
const svcSlug = `nmap-svc-${hostId}-${port.port}`;
|
||||||
resources.push({
|
resources.push({
|
||||||
kind: 'service',
|
kind: 'service',
|
||||||
name: `${port.service} on ${port.port}`,
|
name: `${port.service} on ${port.port}`,
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ module.exports = {
|
|||||||
const headers = {
|
const headers = {
|
||||||
'Authorization': `PVEAPIToken=${tokenId}=${tokenSecret}`
|
'Authorization': `PVEAPIToken=${tokenId}=${tokenSecret}`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure URL has no trailing slash
|
||||||
|
url = url.endsWith('/') ? url.slice(0, -1) : url;
|
||||||
|
|
||||||
const resources = [];
|
const resources = [];
|
||||||
const edges = [];
|
const edges = [];
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class DiscoveryReconciler {
|
|||||||
|
|
||||||
for (const res of resources) {
|
for (const res of resources) {
|
||||||
if (!res.metadata) res.metadata = {};
|
if (!res.metadata) res.metadata = {};
|
||||||
|
res._originalSlug = res.slug; // Keep track for edge mapping
|
||||||
|
|
||||||
let existing = null;
|
let existing = null;
|
||||||
|
|
||||||
@@ -94,10 +95,11 @@ class DiscoveryReconciler {
|
|||||||
metadata: mergedMeta,
|
metadata: mergedMeta,
|
||||||
updated_on: Math.floor(Date.now() / 1000)
|
updated_on: Math.floor(Date.now() / 1000)
|
||||||
});
|
});
|
||||||
|
res._actualId = existing.id;
|
||||||
} else {
|
} else {
|
||||||
// Create new
|
// Create new
|
||||||
const sources = [sourceName];
|
const sources = new Set([sourceName]);
|
||||||
res.metadata.discovery_sources = sources;
|
res.metadata.discovery_sources = [...sources];
|
||||||
res.metadata.last_seen = Date.now();
|
res.metadata.last_seen = Date.now();
|
||||||
|
|
||||||
const slug = res.slug || `${res.kind}-${crypto.randomBytes(4).toString('hex')}`;
|
const slug = res.slug || `${res.kind}-${crypto.randomBytes(4).toString('hex')}`;
|
||||||
@@ -112,12 +114,48 @@ class DiscoveryReconciler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
newDevices++;
|
newDevices++;
|
||||||
|
res._actualId = created.id; // Map original slug to actual ID
|
||||||
WebhookEmitter.emit('discovery.new_device', created.toJSON());
|
WebhookEmitter.emit('discovery.new_device', created.toJSON());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can handle edges similarly if needed, but for simplicity we assume edges are managed elsewhere
|
// Now process edges
|
||||||
// or we just trust the plugins to give us explicit parent-child mappings by slug.
|
const allRes = await Resource.list();
|
||||||
|
const existingEdges = await ResourceEdge.list();
|
||||||
|
|
||||||
|
for (const edge of edges) {
|
||||||
|
// Find parent ID. It might be in the current payload (mapped to _actualId) or in DB by slug
|
||||||
|
let parentId = null;
|
||||||
|
const parentResInPayload = resources.find(r => r._originalSlug === edge.parentSlug);
|
||||||
|
if (parentResInPayload && parentResInPayload._actualId) {
|
||||||
|
parentId = parentResInPayload._actualId;
|
||||||
|
} else {
|
||||||
|
const parentResInDb = allRes.find(r => r.slug === edge.parentSlug);
|
||||||
|
if (parentResInDb) parentId = parentResInDb.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find child ID
|
||||||
|
let childId = null;
|
||||||
|
const childResInPayload = resources.find(r => r._originalSlug === edge.childSlug);
|
||||||
|
if (childResInPayload && childResInPayload._actualId) {
|
||||||
|
childId = childResInPayload._actualId;
|
||||||
|
} else {
|
||||||
|
const childResInDb = allRes.find(r => r.slug === edge.childSlug);
|
||||||
|
if (childResInDb) childId = childResInDb.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentId && childId) {
|
||||||
|
const edgeExists = existingEdges.find(e => e.parentId === parentId && e.childId === childId && e.relation === edge.relation);
|
||||||
|
if (!edgeExists) {
|
||||||
|
await ResourceEdge.create({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
parentId,
|
||||||
|
childId,
|
||||||
|
relation: edge.relation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (newDevices > 0) {
|
if (newDevices > 0) {
|
||||||
console.log(`[DiscoveryReconciler] Source ${sourceName} discovered ${newDevices} new devices.`);
|
console.log(`[DiscoveryReconciler] Source ${sourceName} discovered ${newDevices} new devices.`);
|
||||||
|
|||||||
@@ -164,7 +164,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$('.actionMessage').html('<div class="alert alert-success alert-dismissible"><button type="button" class="btn-close" data-bs-dismiss="alert"></button>Successfully promoted! Created groups: ' + res.groups.join(', ') + '</div>').show();
|
$('.actionMessage').html('<div class="alert alert-success alert-dismissible"><button type="button" class="btn-close" data-bs-dismiss="alert"></button>Successfully promoted! Created groups: ' + res.groups.join(', ') + '</div>').show();
|
||||||
renderTable();
|
loadResources();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
<button class="btn btn-sm btn-warning" title="Edit Secrets" onclick="openSecretsModal('{{id}}')"><i class="fa-solid fa-key"></i></button>
|
<button class="btn btn-sm btn-warning" title="Edit Secrets" onclick="openSecretsModal('{{id}}')"><i class="fa-solid fa-key"></i></button>
|
||||||
<button class="btn btn-sm btn-info" title="Test" onclick="testPlugin('{{id}}')"><i class="fa-solid fa-vial"></i></button>
|
<button class="btn btn-sm btn-info" title="Test" onclick="testPlugin('{{id}}')"><i class="fa-solid fa-vial"></i></button>
|
||||||
<button class="btn btn-sm btn-success" title="Run now" onclick="runNow('{{id}}')"><i class="fa-solid fa-play"></i></button>
|
<button class="btn btn-sm btn-success" title="Run now" onclick="runNow('{{id}}')"><i class="fa-solid fa-play"></i></button>
|
||||||
|
{{#lastError}}<button class="btn btn-sm btn-secondary" title="View Logs" onclick="showLogs('{{id}}')"><i class="fa-solid fa-file-lines"></i></button>{{/lastError}}
|
||||||
{{#enabled}}<button class="btn btn-sm btn-outline-danger" title="Unload" onclick="togglePlugin('{{id}}', false)">Unload</button>{{/enabled}}
|
{{#enabled}}<button class="btn btn-sm btn-outline-danger" title="Unload" onclick="togglePlugin('{{id}}', false)">Unload</button>{{/enabled}}
|
||||||
{{^enabled}}<button class="btn btn-sm btn-outline-success" title="Load" onclick="togglePlugin('{{id}}', true)">Load</button>{{/enabled}}
|
{{^enabled}}<button class="btn btn-sm btn-outline-success" title="Load" onclick="togglePlugin('{{id}}', true)">Load</button>{{/enabled}}
|
||||||
<button class="btn btn-sm btn-outline-danger" title="Delete" onclick="deletePlugin('{{id}}')"><i class="fa-solid fa-trash"></i></button>
|
<button class="btn btn-sm btn-outline-danger" title="Delete" onclick="deletePlugin('{{id}}')"><i class="fa-solid fa-trash"></i></button>
|
||||||
@@ -280,7 +281,7 @@
|
|||||||
'<div class="form-text">Secret fields are edited separately with the <i class="fa-solid fa-key"></i> button.</div>',
|
'<div class="form-text">Secret fields are edited separately with the <i class="fa-solid fa-key"></i> button.</div>',
|
||||||
footer: {
|
footer: {
|
||||||
metaHtml: app.modal.formatAudit ? app.modal.formatAudit(p, { formatDate: function(ms){ return moment(ms).format('YYYY-MM-DD HH:mm'); } }) : '',
|
metaHtml: app.modal.formatAudit ? app.modal.formatAudit(p, { formatDate: function(ms){ return moment(ms).format('YYYY-MM-DD HH:mm'); } }) : '',
|
||||||
buttonsHtml: app.modal.footerButtons({ onSave: 'saveEdit("' + id + '")', saveLabel: 'Save' })
|
buttonsHtml: app.modal.footerButtons({ onSave: 'saveEdit(\'' + id + '\')', saveLabel: 'Save' })
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -324,7 +325,7 @@
|
|||||||
app.modal.open({
|
app.modal.open({
|
||||||
title: 'Edit Secrets — ' + p.name,
|
title: 'Edit Secrets — ' + p.name,
|
||||||
bodyHtml: html,
|
bodyHtml: html,
|
||||||
footer: { buttonsHtml: app.modal.footerButtons({ onSave: 'saveSecrets("' + id + '")', saveLabel: 'Save Secrets' }) }
|
footer: { buttonsHtml: app.modal.footerButtons({ onSave: 'saveSecrets(\'' + id + '\')', saveLabel: 'Save Secrets' }) }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,6 +370,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showLogs(id) {
|
||||||
|
var p = pluginsById[id];
|
||||||
|
if (!p || !p.lastError) return;
|
||||||
|
app.modal.open({
|
||||||
|
title: 'Logs — ' + p.name,
|
||||||
|
bodyHtml: '<pre class="bg-dark text-white p-3 rounded" style="white-space: pre-wrap; font-size: 0.85em;">' + String(p.lastError).replace(/</g, '<').replace(/>/g, '>') + '</pre>',
|
||||||
|
footer: { buttonsHtml: '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function togglePlugin(id, enable) {
|
async function togglePlugin(id, enable) {
|
||||||
try {
|
try {
|
||||||
await app.api.post('plugins/' + id + (enable ? '/load' : '/unload'), {});
|
await app.api.post('plugins/' + id + (enable ? '/load' : '/unload'), {});
|
||||||
|
|||||||
Reference in New Issue
Block a user