294 lines
11 KiB
Plaintext
294 lines
11 KiB
Plaintext
<%- include('top') %>
|
|
<script type="text/javascript">
|
|
app.auth.forceLogin(['admin', 'app_sso_admin']);
|
|
|
|
$(document).ready(function() {
|
|
loadConf();
|
|
loadTos();
|
|
});
|
|
|
|
async function loadConf() {
|
|
try {
|
|
const data = await app.api.get('conf');
|
|
// Populate SMTP
|
|
if (data.smtp) {
|
|
$('#smtp-host').val(data.smtp.host || '');
|
|
$('#smtp-port').val(data.smtp.port || 587);
|
|
$('#smtp-user').val(data.smtp.user || '');
|
|
$('#smtp-pass').val(data.smtp.pass || '');
|
|
$('#smtp-from').val(data.smtp.from || '');
|
|
$('#smtp-secure').prop('checked', !!data.smtp.secure);
|
|
}
|
|
|
|
// Populate OAuth
|
|
if (data.oauth) {
|
|
$('#oauth-issuer').val(data.oauth.issuer || '');
|
|
$('#oauth-jwtsecret').val(data.oauth.jwtSecret || '');
|
|
if (data.oauth.token_lifetime) {
|
|
$('#oauth-token-access').val(data.oauth.token_lifetime.access_token || 3600);
|
|
$('#oauth-token-refresh').val(data.oauth.token_lifetime.refresh_token || 2592000);
|
|
}
|
|
}
|
|
|
|
// Populate SMS (VoIP.ms)
|
|
if (data.voipms) {
|
|
$('#voipms-username').val(data.voipms.username || '');
|
|
$('#voipms-did').val(data.voipms.did || '');
|
|
$('#voipms-password').val(data.voipms.password || '');
|
|
}
|
|
} catch (error) {
|
|
app.messages.toast('Failed to load configuration: ' + (error.message || 'Unknown error'), 'danger');
|
|
}
|
|
}
|
|
|
|
async function saveConf() {
|
|
const btn = $('#btn-save');
|
|
btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Saving...');
|
|
|
|
const payload = {
|
|
smtp: {
|
|
host: $('#smtp-host').val(),
|
|
port: parseInt($('#smtp-port').val(), 10) || 587,
|
|
user: $('#smtp-user').val(),
|
|
pass: $('#smtp-pass').val(),
|
|
from: $('#smtp-from').val(),
|
|
secure: $('#smtp-secure').is(':checked')
|
|
},
|
|
oauth: {
|
|
issuer: $('#oauth-issuer').val(),
|
|
jwtSecret: $('#oauth-jwtsecret').val(),
|
|
token_lifetime: {
|
|
access_token: parseInt($('#oauth-token-access').val(), 10) || 3600,
|
|
refresh_token: parseInt($('#oauth-token-refresh').val(), 10) || 2592000
|
|
}
|
|
},
|
|
voipms: {
|
|
username: $('#voipms-username').val(),
|
|
did: $('#voipms-did').val(),
|
|
password: $('#voipms-password').val()
|
|
}
|
|
};
|
|
|
|
try {
|
|
await app.api.post('conf', payload);
|
|
app.messages.toast('Configuration saved successfully! It will take effect immediately.', 'success');
|
|
} catch (error) {
|
|
app.messages.toast('Failed to save configuration: ' + error.message, 'danger');
|
|
} finally {
|
|
btn.prop('disabled', false).html('<i class="fas fa-save"></i> Save Configuration');
|
|
}
|
|
}
|
|
|
|
function togglePassword(id) {
|
|
const el = document.getElementById(id);
|
|
if (el.type === 'password') {
|
|
el.type = 'text';
|
|
} else {
|
|
el.type = 'password';
|
|
}
|
|
}
|
|
|
|
// ── Terms of Service editor ──────────────────────────────────────────
|
|
// Moved here from the admin Overview dashboard — it's a configuration
|
|
// control, so it belongs on the System Configuration page. The API is
|
|
// routes/tos.js (GET to read, PUT to save; PUT is app_sso_admin-gated, which
|
|
// matches this page's gate). app.tos.get/update are the shared frontend
|
|
// helpers (@simpleworkjs/frontend).
|
|
async function loadTos() {
|
|
try {
|
|
const tos = await app.tos.get();
|
|
document.getElementById('tos-content').value = tos.content;
|
|
document.getElementById('tos-meta').textContent =
|
|
'Last updated ' + moment(tos.updated_on, 'x').fromNow() + ' by ' + tos.updated_by;
|
|
} catch(e) {
|
|
console.error('Failed to load ToS:', e);
|
|
}
|
|
}
|
|
|
|
function saveTos() {
|
|
const content = document.getElementById('tos-content').value.trim();
|
|
const resetAcceptance = document.getElementById('tos-reset-acceptance').checked;
|
|
const msgEl = document.getElementById('tos-result');
|
|
|
|
if (!content) {
|
|
msgEl.className = 'alert alert-danger mt-2';
|
|
msgEl.textContent = 'Terms of Service text cannot be empty.';
|
|
msgEl.style.display = '';
|
|
return;
|
|
}
|
|
|
|
app.tos.update({content, resetAcceptance}, function(error, data) {
|
|
if (error) {
|
|
msgEl.className = 'alert alert-danger mt-2';
|
|
msgEl.textContent = 'Failed: ' + ((data && data.message) || error);
|
|
msgEl.style.display = '';
|
|
return;
|
|
}
|
|
msgEl.className = 'alert alert-success mt-2';
|
|
msgEl.textContent = 'Saved.' + (data.resetCount ? ' ' + data.resetCount + ' user(s) will be asked to re-accept.' : '');
|
|
msgEl.style.display = '';
|
|
document.getElementById('tos-reset-acceptance').checked = false;
|
|
loadTos();
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<div class="container py-4">
|
|
<div class="row mb-4">
|
|
<div class="col d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h2><i class="fas fa-cogs"></i> System Configuration</h2>
|
|
<p class="text-muted mb-0">
|
|
Manage runtime configuration such as SMTP, SMS, OAuth, and Terms of Service
|
|
settings. These are stored securely in OpenBao and take effect immediately.
|
|
Secret fields (the SMTP password, OAuth JWT secret, and VoIP.ms API password)
|
|
are masked — leave them unchanged to keep the stored value.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-secondary me-2" onclick="loadConf()"><i class="fas fa-undo"></i> Reset</button>
|
|
<button id="btn-save" class="btn btn-primary" onclick="saveConf()"><i class="fas fa-save"></i> Save Configuration</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<ul class="nav nav-tabs mb-4" id="confTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="smtp-tab" data-bs-toggle="tab" data-bs-target="#smtp" type="button" role="tab">SMTP Settings</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="oauth-tab" data-bs-toggle="tab" data-bs-target="#oauth" type="button" role="tab">OAuth & JWT</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="sms-tab" data-bs-toggle="tab" data-bs-target="#sms" type="button" role="tab">SMS (VoIP.ms)</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="tos-tab" data-bs-toggle="tab" data-bs-target="#tos" type="button" role="tab">Terms of Service</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content" id="confTabsContent">
|
|
<!-- SMTP Tab -->
|
|
<div class="tab-pane fade show active" id="smtp" role="tabpanel">
|
|
<div class="card shadow-sm border-0 mb-4">
|
|
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
|
|
<h5 class="mb-0"><i class="fas fa-envelope text-primary me-2"></i> SMTP Settings</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Host</label>
|
|
<input type="text" class="form-control" id="smtp-host">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Port</label>
|
|
<input type="number" class="form-control" id="smtp-port">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">User</label>
|
|
<input type="text" class="form-control" id="smtp-user">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Password</label>
|
|
<div class="input-group">
|
|
<input type="password" class="form-control" id="smtp-pass" placeholder="********">
|
|
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword('smtp-pass')"><i class="fas fa-eye"></i></button>
|
|
</div>
|
|
<div class="form-text">Leave unchanged to keep the current password stored in OpenBao. Clear and type a new value to replace it.</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">From Address</label>
|
|
<input type="text" class="form-control" id="smtp-from">
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="smtp-secure">
|
|
<label class="form-check-label">Use Secure (TLS)</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OAuth Tab -->
|
|
<div class="tab-pane fade" id="oauth" role="tabpanel">
|
|
<div class="card shadow-sm border-0 mb-4">
|
|
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
|
|
<h5 class="mb-0"><i class="fas fa-key text-success me-2"></i> OAuth & JWT Settings</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Issuer URL</label>
|
|
<input type="text" class="form-control" id="oauth-issuer">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">JWT Secret</label>
|
|
<div class="input-group">
|
|
<input type="password" class="form-control" id="oauth-jwtsecret" placeholder="********">
|
|
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword('oauth-jwtsecret')"><i class="fas fa-eye"></i></button>
|
|
</div>
|
|
<div class="form-text">Leave unchanged to keep the current secret stored in OpenBao. Clear and type a new value to replace it.</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Access Token Lifetime (seconds)</label>
|
|
<input type="number" class="form-control" id="oauth-token-access">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Refresh Token Lifetime (seconds)</label>
|
|
<input type="number" class="form-control" id="oauth-token-refresh">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SMS Tab -->
|
|
<div class="tab-pane fade" id="sms" role="tabpanel">
|
|
<div class="card shadow-sm border-0 mb-4">
|
|
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
|
|
<h5 class="mb-0"><i class="fas fa-comment text-info me-2"></i> SMS (VoIP.ms)</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="form-text">Used to deliver SMS 2FA login codes. The API password is stored in OpenBao and masked below.</p>
|
|
<div class="mb-3">
|
|
<label class="form-label">API Username</label>
|
|
<input type="text" class="form-control" id="voipms-username">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">DID (sender number)</label>
|
|
<input type="text" class="form-control" id="voipms-did" placeholder="15551234567">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">API Password</label>
|
|
<div class="input-group">
|
|
<input type="password" class="form-control" id="voipms-password" placeholder="********">
|
|
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword('voipms-password')"><i class="fas fa-eye"></i></button>
|
|
</div>
|
|
<div class="form-text">Leave unchanged to keep the current password stored in OpenBao. Clear and type a new value to replace it.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ToS Tab -->
|
|
<div class="tab-pane fade" id="tos" role="tabpanel">
|
|
<div class="card shadow-sm border-0 mb-4">
|
|
<div class="card-header bg-white border-bottom-0 pt-4 pb-0 d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="fas fa-file-contract me-2"></i> Terms of Service</h5>
|
|
<small class="text-muted" id="tos-meta"></small>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Content <small class="text-muted">(Markdown)</small></label>
|
|
<textarea class="form-control" id="tos-content" rows="8"></textarea>
|
|
</div>
|
|
<div class="form-check mb-3">
|
|
<input class="form-check-input" type="checkbox" id="tos-reset-acceptance">
|
|
<label class="form-check-label" for="tos-reset-acceptance">Require all users to re-accept these terms</label>
|
|
</div>
|
|
<button class="btn btn-primary" onclick="saveTos()"><i class="fas fa-floppy-disk"></i> Save Terms</button>
|
|
<div id="tos-result" style="display:none" class="mt-2"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<%- include('bottom') %>
|