mirror of
https://github.com/sol1/rustguac.git
synced 2026-09-10 09:35:45 +00:00
v0.7.0: Login script dropdown, batch address book, Docker non-root
New features: - Login script selector: dropdown populated from server scripts dir (#52) - Batch address book endpoint eliminates N+1 API calls (#56) - Clone button for address book entries (#56) - Increased API rate limits (#56) Fixes: - Docker container runs as non-root user (#50) - Conditional --no-sandbox when running as root (#50) - Post-spawn Chromium liveness check with stderr capture (#50) Docs: - Theme/branding configuration guide (#55) - Vault metadata policy for deletes (#54) - TLS config clarification (no boolean toggle)
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustguac"
|
||||
version = "0.6.2"
|
||||
version = "0.7.0"
|
||||
edition = "2021"
|
||||
description = "Lightweight Rust replacement for Apache Guacamole client"
|
||||
|
||||
|
||||
+21
@@ -195,6 +195,27 @@ pub async fn delete_session(
|
||||
}
|
||||
}
|
||||
|
||||
/// GET /api/login-scripts — List available login scripts. Requires operator+.
|
||||
pub async fn list_login_scripts(State(manager): State<AppState>) -> impl IntoResponse {
|
||||
let scripts_dir = std::path::Path::new(&manager.config().login_scripts_dir);
|
||||
let mut scripts: Vec<String> = Vec::new();
|
||||
if let Ok(entries) = std::fs::read_dir(scripts_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
||||
// Only list executable-looking scripts, skip hidden files
|
||||
if !name.starts_with('.') {
|
||||
scripts.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scripts.sort();
|
||||
Json(json!({ "scripts": scripts }))
|
||||
}
|
||||
|
||||
/// GET /api/health — Health check.
|
||||
pub async fn health() -> impl IntoResponse {
|
||||
Json(json!({ "status": "ok" }))
|
||||
|
||||
@@ -733,6 +733,8 @@ async fn run_server(config: Config, database: Db) {
|
||||
delete(api::admin_revoke_user_token),
|
||||
)
|
||||
.route("/api/admin/token-audit", get(api::admin_token_audit))
|
||||
// Login scripts listing
|
||||
.route("/api/login-scripts", get(api::list_login_scripts))
|
||||
// Address book routes
|
||||
.route("/api/addressbook", get(api::ab_list_all))
|
||||
.route("/api/addressbook/folders", get(api::ab_list_folders))
|
||||
|
||||
@@ -284,6 +284,11 @@ impl SessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only access to the config.
|
||||
pub fn config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Create a new session: connect to guacd, perform handshake, return session info.
|
||||
pub async fn create_session(
|
||||
&self,
|
||||
|
||||
+38
-4
@@ -453,9 +453,11 @@
|
||||
<input type="password" id="em-web-password">
|
||||
</label>
|
||||
<label>Login Script <span style="color:var(--text-muted);font-size:0.85em">(optional)</span>
|
||||
<input type="text" id="em-login-script" placeholder="e.g. portal-login.sh">
|
||||
<select id="em-login-script">
|
||||
<option value="">None</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="field-hint">Server-side script in the scripts directory. Receives CDP port and credentials as env vars. Runs after Chromium spawns.</div>
|
||||
<div class="field-hint">Server-side script from the scripts directory. Receives CDP port and credentials as env vars. Runs after Chromium spawns.</div>
|
||||
<div style="margin-top:1em;padding-top:0.8em;border-top:1px solid var(--border)">
|
||||
<label style="cursor:pointer;color:var(--accent);font-size:0.95em" id="em-autofill-toggle">
|
||||
<span id="em-autofill-arrow">▶</span> Autofill
|
||||
@@ -623,6 +625,35 @@
|
||||
var folderEntryCounts = {}; // "scope/name" -> count
|
||||
var folderEntries = {}; // "scope/name" -> entries array (from batch load)
|
||||
var currentEntries = []; // entries for the currently selected folder
|
||||
var loginScriptsCache = null; // cached list from /api/login-scripts
|
||||
|
||||
function populateLoginScriptDropdown(selectedValue) {
|
||||
var sel = document.getElementById('em-login-script');
|
||||
sel.innerHTML = '<option value="">None</option>';
|
||||
if (loginScriptsCache) {
|
||||
loginScriptsCache.forEach(function(s) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = s;
|
||||
opt.textContent = s;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
sel.value = selectedValue || '';
|
||||
}
|
||||
|
||||
function loadLoginScripts(cb) {
|
||||
if (loginScriptsCache !== null) { if (cb) cb(); return; }
|
||||
fetch('/api/login-scripts', { headers: authHeaders() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
loginScriptsCache = data.scripts || [];
|
||||
if (cb) cb();
|
||||
})
|
||||
.catch(function() {
|
||||
loginScriptsCache = [];
|
||||
if (cb) cb();
|
||||
});
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
document.getElementById('global-error').textContent = msg;
|
||||
@@ -1190,7 +1221,10 @@
|
||||
if (val === 'ssh') document.getElementById('em-ssh-fields').style.display = '';
|
||||
else if (val === 'rdp') document.getElementById('em-rdp-fields').style.display = '';
|
||||
else if (val === 'vnc') document.getElementById('em-vnc-fields').style.display = '';
|
||||
else if (val === 'web') document.getElementById('em-web-fields').style.display = '';
|
||||
else if (val === 'web') {
|
||||
document.getElementById('em-web-fields').style.display = '';
|
||||
loadLoginScripts(function() { populateLoginScriptDropdown(''); });
|
||||
}
|
||||
// Show drive option for SSH and RDP only
|
||||
document.getElementById('em-drive-section').style.display = (val === 'ssh' || val === 'rdp') ? '' : 'none';
|
||||
// Show RemoteApp for RDP only
|
||||
@@ -1627,7 +1661,7 @@
|
||||
} else if (type === 'web') {
|
||||
document.getElementById('em-url').value = entryData.url || '';
|
||||
document.getElementById('em-web-username').value = entryData.username || '';
|
||||
document.getElementById('em-login-script').value = entryData.login_script || '';
|
||||
loadLoginScripts(function() { populateLoginScriptDropdown(entryData.login_script || ''); });
|
||||
// Populate autofill rows
|
||||
if (entryData.autofill) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user