Hardcode agent stale timeout
This commit is contained in:
@@ -27,14 +27,12 @@ def update_config(body: UpdateConfigRequest):
|
|||||||
cfg = config_service.update_config(
|
cfg = config_service.update_config(
|
||||||
default_wait_seconds=body.default_wait_seconds,
|
default_wait_seconds=body.default_wait_seconds,
|
||||||
default_empty_response=body.default_empty_response,
|
default_empty_response=body.default_empty_response,
|
||||||
agent_stale_after_seconds=body.agent_stale_after_seconds,
|
|
||||||
)
|
)
|
||||||
event_service.broadcast(
|
event_service.broadcast(
|
||||||
"config.updated",
|
"config.updated",
|
||||||
{
|
{
|
||||||
"default_wait_seconds": cfg.default_wait_seconds,
|
"default_wait_seconds": cfg.default_wait_seconds,
|
||||||
"default_empty_response": cfg.default_empty_response,
|
"default_empty_response": cfg.default_empty_response,
|
||||||
"agent_stale_after_seconds": cfg.agent_stale_after_seconds,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return cfg
|
return cfg
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from dataclasses import dataclass, field
|
|||||||
|
|
||||||
APP_VERSION = "1.0.1"
|
APP_VERSION = "1.0.1"
|
||||||
DEFAULT_EMPTY_RESPONSE = "call this tool `get_user_request` again to fetch latest user input..."
|
DEFAULT_EMPTY_RESPONSE = "call this tool `get_user_request` again to fetch latest user input..."
|
||||||
|
AGENT_STALE_AFTER_SECONDS = 30
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -28,7 +29,6 @@ class Settings:
|
|||||||
# MCP / queue behaviour (runtime-editable values are stored in DB; these are defaults for first run)
|
# MCP / queue behaviour (runtime-editable values are stored in DB; these are defaults for first run)
|
||||||
default_wait_seconds: int = 10
|
default_wait_seconds: int = 10
|
||||||
default_empty_response: str = DEFAULT_EMPTY_RESPONSE
|
default_empty_response: str = DEFAULT_EMPTY_RESPONSE
|
||||||
agent_stale_after_seconds: int = 30
|
|
||||||
|
|
||||||
# MCP server name
|
# MCP server name
|
||||||
mcp_server_name: str = "local-mcp"
|
mcp_server_name: str = "local-mcp"
|
||||||
@@ -58,7 +58,6 @@ def load_settings() -> Settings:
|
|||||||
log_level=os.getenv("LOG_LEVEL", "INFO"),
|
log_level=os.getenv("LOG_LEVEL", "INFO"),
|
||||||
default_wait_seconds=int(os.getenv("DEFAULT_WAIT_SECONDS", "10")),
|
default_wait_seconds=int(os.getenv("DEFAULT_WAIT_SECONDS", "10")),
|
||||||
default_empty_response=os.getenv("DEFAULT_EMPTY_RESPONSE", DEFAULT_EMPTY_RESPONSE),
|
default_empty_response=os.getenv("DEFAULT_EMPTY_RESPONSE", DEFAULT_EMPTY_RESPONSE),
|
||||||
agent_stale_after_seconds=int(os.getenv("AGENT_STALE_AFTER_SECONDS", "30")),
|
|
||||||
mcp_server_name=os.getenv("MCP_SERVER_NAME", "local-mcp"),
|
mcp_server_name=os.getenv("MCP_SERVER_NAME", "local-mcp"),
|
||||||
mcp_stateless=_parse_bool(os.getenv("MCP_STATELESS", "true"), default=True),
|
mcp_stateless=_parse_bool(os.getenv("MCP_STATELESS", "true"), default=True),
|
||||||
api_token=os.getenv("API_TOKEN", ""),
|
api_token=os.getenv("API_TOKEN", ""),
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ CREATE TABLE IF NOT EXISTS agent_activity (
|
|||||||
_DEFAULT_SETTINGS = {
|
_DEFAULT_SETTINGS = {
|
||||||
"default_wait_seconds": "10",
|
"default_wait_seconds": "10",
|
||||||
"default_empty_response": DEFAULT_EMPTY_RESPONSE,
|
"default_empty_response": DEFAULT_EMPTY_RESPONSE,
|
||||||
"agent_stale_after_seconds": "30",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -125,6 +124,7 @@ def _seed_defaults(conn: sqlite3.Connection) -> None:
|
|||||||
"UPDATE settings SET value = ? WHERE key = 'default_empty_response' AND value = ''",
|
"UPDATE settings SET value = ? WHERE key = 'default_empty_response' AND value = ''",
|
||||||
(_DEFAULT_SETTINGS["default_empty_response"],),
|
(_DEFAULT_SETTINGS["default_empty_response"],),
|
||||||
)
|
)
|
||||||
|
conn.execute("DELETE FROM settings WHERE key = 'agent_stale_after_seconds'")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
logger.debug("Default settings seeded")
|
logger.debug("Default settings seeded")
|
||||||
|
|
||||||
|
|||||||
@@ -109,13 +109,11 @@ class StatusResponse(BaseModel):
|
|||||||
class ConfigResponse(BaseModel):
|
class ConfigResponse(BaseModel):
|
||||||
default_wait_seconds: int
|
default_wait_seconds: int
|
||||||
default_empty_response: str
|
default_empty_response: str
|
||||||
agent_stale_after_seconds: int
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateConfigRequest(BaseModel):
|
class UpdateConfigRequest(BaseModel):
|
||||||
default_wait_seconds: Optional[int] = None
|
default_wait_seconds: Optional[int] = None
|
||||||
default_empty_response: Optional[str] = None
|
default_empty_response: Optional[str] = None
|
||||||
agent_stale_after_seconds: Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from app.models import ConfigResponse
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_SETTING_KEYS = {"default_wait_seconds", "default_empty_response", "agent_stale_after_seconds"}
|
|
||||||
def get_config() -> ConfigResponse:
|
def get_config() -> ConfigResponse:
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
rows = conn.execute("SELECT key, value FROM settings").fetchall()
|
rows = conn.execute("SELECT key, value FROM settings").fetchall()
|
||||||
@@ -21,22 +20,18 @@ def get_config() -> ConfigResponse:
|
|||||||
return ConfigResponse(
|
return ConfigResponse(
|
||||||
default_wait_seconds=int(data.get("default_wait_seconds", 10)),
|
default_wait_seconds=int(data.get("default_wait_seconds", 10)),
|
||||||
default_empty_response=data.get("default_empty_response") or DEFAULT_EMPTY_RESPONSE,
|
default_empty_response=data.get("default_empty_response") or DEFAULT_EMPTY_RESPONSE,
|
||||||
agent_stale_after_seconds=int(data.get("agent_stale_after_seconds", 30)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_config(
|
def update_config(
|
||||||
default_wait_seconds: int | None = None,
|
default_wait_seconds: int | None = None,
|
||||||
default_empty_response: str | None = None,
|
default_empty_response: str | None = None,
|
||||||
agent_stale_after_seconds: int | None = None,
|
|
||||||
) -> ConfigResponse:
|
) -> ConfigResponse:
|
||||||
updates: dict[str, str] = {}
|
updates: dict[str, str] = {}
|
||||||
if default_wait_seconds is not None:
|
if default_wait_seconds is not None:
|
||||||
updates["default_wait_seconds"] = str(default_wait_seconds)
|
updates["default_wait_seconds"] = str(default_wait_seconds)
|
||||||
if default_empty_response is not None:
|
if default_empty_response is not None:
|
||||||
updates["default_empty_response"] = default_empty_response
|
updates["default_empty_response"] = default_empty_response
|
||||||
if agent_stale_after_seconds is not None:
|
|
||||||
updates["agent_stale_after_seconds"] = str(agent_stale_after_seconds)
|
|
||||||
|
|
||||||
if updates:
|
if updates:
|
||||||
with get_write_conn() as conn:
|
with get_write_conn() as conn:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import sqlite3
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from app.config import AGENT_STALE_AFTER_SECONDS
|
||||||
from app.database import get_conn, get_write_conn
|
from app.database import get_conn, get_write_conn
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -55,25 +56,15 @@ def get_latest_agent_activity() -> Optional[sqlite3.Row]:
|
|||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
|
|
||||||
def get_agent_stale_seconds() -> int:
|
|
||||||
"""Read agent_stale_after_seconds from settings table."""
|
|
||||||
with get_conn() as conn:
|
|
||||||
row = conn.execute(
|
|
||||||
"SELECT value FROM settings WHERE key = 'agent_stale_after_seconds'"
|
|
||||||
).fetchone()
|
|
||||||
return int(row["value"]) if row else 30
|
|
||||||
|
|
||||||
|
|
||||||
def is_agent_connected() -> bool:
|
def is_agent_connected() -> bool:
|
||||||
"""True if the most recent agent activity is within the stale threshold."""
|
"""True if the most recent agent activity is within the stale threshold."""
|
||||||
row = get_latest_agent_activity()
|
row = get_latest_agent_activity()
|
||||||
if row is None:
|
if row is None:
|
||||||
return False
|
return False
|
||||||
stale_seconds = get_agent_stale_seconds()
|
|
||||||
last_seen = datetime.fromisoformat(row["last_seen_at"])
|
last_seen = datetime.fromisoformat(row["last_seen_at"])
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
if last_seen.tzinfo is None:
|
if last_seen.tzinfo is None:
|
||||||
last_seen = last_seen.replace(tzinfo=timezone.utc)
|
last_seen = last_seen.replace(tzinfo=timezone.utc)
|
||||||
delta = (now - last_seen).total_seconds()
|
delta = (now - last_seen).total_seconds()
|
||||||
return delta <= stale_seconds
|
return delta <= AGENT_STALE_AFTER_SECONDS
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ func handleUpdateConfig(stores Stores, broker *events.Broker) http.HandlerFunc {
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Decode partial patch
|
// Decode partial patch
|
||||||
var patch struct {
|
var patch struct {
|
||||||
DefaultWaitSeconds *int `json:"default_wait_seconds"`
|
DefaultWaitSeconds *int `json:"default_wait_seconds"`
|
||||||
AgentStaleAfterSeconds *int `json:"agent_stale_after_seconds"`
|
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
|
||||||
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
||||||
@@ -41,9 +40,6 @@ func handleUpdateConfig(stores Stores, broker *events.Broker) http.HandlerFunc {
|
|||||||
if patch.DefaultWaitSeconds != nil {
|
if patch.DefaultWaitSeconds != nil {
|
||||||
current.DefaultWaitSeconds = *patch.DefaultWaitSeconds
|
current.DefaultWaitSeconds = *patch.DefaultWaitSeconds
|
||||||
}
|
}
|
||||||
if patch.AgentStaleAfterSeconds != nil {
|
|
||||||
current.AgentStaleAfterSeconds = *patch.AgentStaleAfterSeconds
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stores.Settings.Update(current); err != nil {
|
if err := stores.Settings.Update(current); err != nil {
|
||||||
writeError(w, http.StatusInternalServerError, err.Error())
|
writeError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func handleStatus(stores Stores) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if latest != nil {
|
if latest != nil {
|
||||||
connected := time.Since(latest.LastSeenAt).Seconds() <= float64(cfg.AgentStaleAfterSeconds)
|
connected := time.Since(latest.LastSeenAt).Seconds() <= float64(config.AgentStaleAfterSeconds)
|
||||||
agent = map[string]any{
|
agent = map[string]any{
|
||||||
"connected": connected,
|
"connected": connected,
|
||||||
"last_seen_at": latest.LastSeenAt.Format(time.RFC3339Nano),
|
"last_seen_at": latest.LastSeenAt.Format(time.RFC3339Nano),
|
||||||
@@ -69,7 +69,6 @@ func handleStatus(stores Stores) http.HandlerFunc {
|
|||||||
"settings": models.Settings{
|
"settings": models.Settings{
|
||||||
DefaultWaitSeconds: cfg.DefaultWaitSeconds,
|
DefaultWaitSeconds: cfg.DefaultWaitSeconds,
|
||||||
DefaultEmptyResponse: cfg.DefaultEmptyResponse,
|
DefaultEmptyResponse: cfg.DefaultEmptyResponse,
|
||||||
AgentStaleAfterSeconds: cfg.AgentStaleAfterSeconds,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
AppVersion = "1.0.1"
|
AppVersion = "1.0.1"
|
||||||
|
AgentStaleAfterSeconds = 30
|
||||||
DefaultEmptyResponse = "call this tool `get_user_request` again to fetch latest user input..."
|
DefaultEmptyResponse = "call this tool `get_user_request` again to fetch latest user input..."
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,7 +19,6 @@ type Config struct {
|
|||||||
DBPath string
|
DBPath string
|
||||||
LogLevel string
|
LogLevel string
|
||||||
DefaultWaitSeconds int
|
DefaultWaitSeconds int
|
||||||
AgentStaleAfterSeconds int
|
|
||||||
MCPStateless bool
|
MCPStateless bool
|
||||||
APIToken string
|
APIToken string
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,6 @@ func Load() Config {
|
|||||||
DBPath: getEnv("DB_PATH", "data/local_mcp.sqlite3"),
|
DBPath: getEnv("DB_PATH", "data/local_mcp.sqlite3"),
|
||||||
LogLevel: getEnv("LOG_LEVEL", "INFO"),
|
LogLevel: getEnv("LOG_LEVEL", "INFO"),
|
||||||
DefaultWaitSeconds: getEnvInt("DEFAULT_WAIT_SECONDS", 10),
|
DefaultWaitSeconds: getEnvInt("DEFAULT_WAIT_SECONDS", 10),
|
||||||
AgentStaleAfterSeconds: getEnvInt("AGENT_STALE_AFTER_SECONDS", 30),
|
|
||||||
MCPStateless: getEnvBool("MCP_STATELESS", true),
|
MCPStateless: getEnvBool("MCP_STATELESS", true),
|
||||||
APIToken: getEnv("API_TOKEN", ""),
|
APIToken: getEnv("API_TOKEN", ""),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ CREATE TABLE IF NOT EXISTS agent_activity (
|
|||||||
// defaultSettings seeds initial values; OR IGNORE means existing rows are unchanged.
|
// defaultSettings seeds initial values; OR IGNORE means existing rows are unchanged.
|
||||||
const defaultSettings = "" +
|
const defaultSettings = "" +
|
||||||
"INSERT OR IGNORE INTO settings (key, value) VALUES ('default_wait_seconds', '10');\n" +
|
"INSERT OR IGNORE INTO settings (key, value) VALUES ('default_wait_seconds', '10');\n" +
|
||||||
"INSERT OR IGNORE INTO settings (key, value) VALUES ('agent_stale_after_seconds', '30');\n" +
|
"DELETE FROM settings WHERE key = 'default_empty_response';\n" +
|
||||||
"DELETE FROM settings WHERE key = 'default_empty_response';\n"
|
"DELETE FROM settings WHERE key = 'agent_stale_after_seconds';\n"
|
||||||
|
|
||||||
// Open opens (creating if necessary) a SQLite database at dbPath, applies the
|
// Open opens (creating if necessary) a SQLite database at dbPath, applies the
|
||||||
// schema, and seeds default settings.
|
// schema, and seeds default settings.
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ type Instruction struct {
|
|||||||
type Settings struct {
|
type Settings struct {
|
||||||
DefaultWaitSeconds int `json:"default_wait_seconds"`
|
DefaultWaitSeconds int `json:"default_wait_seconds"`
|
||||||
DefaultEmptyResponse string `json:"default_empty_response"`
|
DefaultEmptyResponse string `json:"default_empty_response"`
|
||||||
AgentStaleAfterSeconds int `json:"agent_stale_after_seconds"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AgentActivity tracks the last time an agent called get_user_request.
|
// AgentActivity tracks the last time an agent called get_user_request.
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ func (s *SettingsStore) Get() (models.Settings, error) {
|
|||||||
cfg := models.Settings{
|
cfg := models.Settings{
|
||||||
DefaultWaitSeconds: 10,
|
DefaultWaitSeconds: 10,
|
||||||
DefaultEmptyResponse: config.DefaultEmptyResponse,
|
DefaultEmptyResponse: config.DefaultEmptyResponse,
|
||||||
AgentStaleAfterSeconds: 30,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
@@ -43,10 +42,6 @@ func (s *SettingsStore) Get() (models.Settings, error) {
|
|||||||
if n, err := strconv.Atoi(value); err == nil {
|
if n, err := strconv.Atoi(value); err == nil {
|
||||||
cfg.DefaultWaitSeconds = n
|
cfg.DefaultWaitSeconds = n
|
||||||
}
|
}
|
||||||
case "agent_stale_after_seconds":
|
|
||||||
if n, err := strconv.Atoi(value); err == nil {
|
|
||||||
cfg.AgentStaleAfterSeconds = n
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cfg, rows.Err()
|
return cfg, rows.Err()
|
||||||
@@ -57,10 +52,8 @@ func (s *SettingsStore) Get() (models.Settings, error) {
|
|||||||
func (s *SettingsStore) Update(patch models.Settings) error {
|
func (s *SettingsStore) Update(patch models.Settings) error {
|
||||||
_, err := s.db.Exec(`
|
_, err := s.db.Exec(`
|
||||||
INSERT OR REPLACE INTO settings (key, value) VALUES
|
INSERT OR REPLACE INTO settings (key, value) VALUES
|
||||||
('default_wait_seconds', ?),
|
('default_wait_seconds', ?)` ,
|
||||||
('agent_stale_after_seconds', ?)`,
|
|
||||||
strconv.Itoa(patch.DefaultWaitSeconds),
|
strconv.Itoa(patch.DefaultWaitSeconds),
|
||||||
strconv.Itoa(patch.AgentStaleAfterSeconds),
|
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,6 @@
|
|||||||
color: var(--cyan);
|
color: var(--cyan);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-version-badge {
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-indicators {
|
.header-indicators {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -30,8 +30,6 @@
|
|||||||
<line x1="12" y1="17" x2="12" y2="21"/>
|
<line x1="12" y1="17" x2="12" y2="21"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="header-brand-name">local<span>-mcp</span></span>
|
<span class="header-brand-name">local<span>-mcp</span></span>
|
||||||
<span id="server-type-badge" class="badge badge--muted header-version-badge">server</span>
|
|
||||||
<span id="app-version-badge" class="badge badge--muted header-version-badge">v–</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-indicators">
|
<div class="header-indicators">
|
||||||
@@ -159,17 +157,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form id="config-form">
|
<p class="subtle" style="margin:0; line-height:1.6">
|
||||||
<div class="config-field">
|
Agent connectivity is now inferred with a fixed 30-second timeout managed by the server.
|
||||||
<label class="config-label" for="cfg-stale">Agent Stale After (sec)</label>
|
</p>
|
||||||
<input id="cfg-stale" class="input input--sm" type="number" min="5" max="600" value="30" />
|
|
||||||
<span class="config-hint">Inactivity before agent shown as idle</span>
|
|
||||||
</div>
|
|
||||||
<button id="cfg-save" type="submit" class="btn btn--ghost btn--sm" style="width:100%">
|
|
||||||
<svg class="icon icon--sm" viewBox="0 0 24 24"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
|
||||||
Save Settings
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { state } from './state.js';
|
import { state } from './state.js';
|
||||||
import { api } from './api.js';
|
|
||||||
import { toast } from './app.js';
|
|
||||||
|
|
||||||
// ── Time helpers ──────────────────────────────────────────────────────────
|
// ── Time helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -126,35 +124,7 @@ export function initStatus() {
|
|||||||
// ── Config panel ──────────────────────────────────────────────────────────
|
// ── Config panel ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function initConfig() {
|
export function initConfig() {
|
||||||
const form = document.getElementById('config-form');
|
// Intentionally empty: the web UI no longer exposes editable settings.
|
||||||
const staleInput = document.getElementById('cfg-stale');
|
|
||||||
const saveBtn = document.getElementById('cfg-save');
|
|
||||||
|
|
||||||
// Populate from state
|
|
||||||
state.subscribe('config', (cfg) => {
|
|
||||||
if (!cfg) return;
|
|
||||||
staleInput.value = cfg.agent_stale_after_seconds;
|
|
||||||
});
|
|
||||||
|
|
||||||
form.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
saveBtn.disabled = true;
|
|
||||||
const original = saveBtn.innerHTML;
|
|
||||||
saveBtn.innerHTML = '<span class="spinner"></span>';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cfg = await api.updateConfig({
|
|
||||||
agent_stale_after_seconds: parseInt(staleInput.value, 10) || 30,
|
|
||||||
});
|
|
||||||
state.set('config', cfg);
|
|
||||||
toast('Settings saved', 'success');
|
|
||||||
} catch (e) {
|
|
||||||
toast(e.message, 'error');
|
|
||||||
} finally {
|
|
||||||
saveBtn.disabled = false;
|
|
||||||
saveBtn.innerHTML = original;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
|
|||||||
Reference in New Issue
Block a user