Files
yacy_search_server/htroot/AIShield_p.html

205 lines
8.4 KiB
HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>YaCy '#[clientname]#': AI Shield</title>
#%env/templates/metas.template%#
<style type="text/css">
.shield-card {
border: 1px solid #e6e9ef;
border-left: 6px solid #5bc0de;
padding: 16px;
margin-bottom: 14px;
background: #fff;
box-shadow: 0 3px 10px rgba(0,0,0,0.06);
}
.shield-card h3 {
margin-top: 0;
margin-bottom: 6px;
color: #0f2b46;
}
.shield-card p {
margin: 0 0 8px 0;
color: #3b4a5e;
}
.shield-inline {
display: flex;
gap: 16px;
flex-wrap: wrap;
align-items: center;
}
.shield-inline label {
margin: 0;
}
.shield-rate {
max-width: 120px;
}
.telemetry-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
margin-top: 10px;
}
.telemetry-item {
border: 1px solid #e6e9ef;
border-radius: 6px;
padding: 10px;
background: #f9fbff;
box-shadow: 0 2px 6px rgba(0,0,0,0.04);
}
.telemetry-label {
font-weight: 700;
color: #0f2b46;
margin-bottom: 4px;
}
.gauge {
position: relative;
height: 12px;
background: #e9edf5;
border-radius: 999px;
overflow: hidden;
}
.gauge-fill {
height: 100%;
width: 0%;
border-radius: 999px;
transition: width 0.3s ease;
}
.gauge-meta {
margin-top: 4px;
font-size: 0.9em;
color: #3b4a5e;
display: flex;
justify-content: space-between;
}
.gauge-green { background: #5cb85c; }
.gauge-amber { background: #f0ad4e; }
.gauge-red { background: #d9534f; }
.gauge-neutral { background: #5bc0de; }
</style>
</head>
<body id="IndexControl">
#%env/templates/header.template%#
#%env/templates/submenuAI.template%#
<h2>Wire RAG Retrieval Shield</h2>
<p>Control who can access the chat interface and rate-limit non-localhost clients to protect your peer and LLM backends from overload.</p>
<form method="post" action="AIShield_p.html" id="shieldForm">
<input type="hidden" name="submit" value="1" />
<div class="shield-card">
<h3>Overall Load Protection</h3>
<p>Recent access volume across all clients (localhost included). You can enforce global limits here to protect the host.</p>
<div class="telemetry-grid">
<div class="telemetry-item">
<div class="telemetry-label">Requests / minute</div>
<div class="gauge"><div class="gauge-fill" id="gaugeMinute"></div></div>
<div class="gauge-meta">
<span id="gaugeMinuteValue">#[telemetry.per-minute]#</span>
<span id="gaugeMinuteLimit">#[ai.shield.all.per-minute]#</span>
</div>
</div>
<div class="telemetry-item">
<div class="telemetry-label">Requests / hour</div>
<div class="gauge"><div class="gauge-fill" id="gaugeHour"></div></div>
<div class="gauge-meta">
<span id="gaugeHourValue">#[telemetry.per-hour]#</span>
<span id="gaugeHourLimit">#[ai.shield.all.per-hour]#</span>
</div>
</div>
<div class="telemetry-item">
<div class="telemetry-label">Requests / day</div>
<div class="gauge"><div class="gauge-fill" id="gaugeDay"></div></div>
<div class="gauge-meta">
<span id="gaugeDayValue">#[telemetry.per-day]#</span>
<span id="gaugeDayLimit">#[ai.shield.all.per-day]#</span>
</div>
</div>
</div>
<p style="margin-top:8px;">
<label>
<input type="checkbox" name="ai.shield.limit-all" id="limitAll" #(ai.shield.limit-all)#::checked="checked"#(/ai.shield.limit-all)# />
Limit for all requests, including localhost
</label>
</p>
<div class="shield-inline">
<label>Per minute: <input type="number" class="form-control shield-rate" min="0" name="ai.shield.all.per-minute" id="allMinute" value="#[ai.shield.all.per-minute]#" /></label>
<label>Per hour: <input type="number" class="form-control shield-rate" min="0" name="ai.shield.all.per-hour" id="allHour" value="#[ai.shield.all.per-hour]#" /></label>
<label>Per day: <input type="number" class="form-control shield-rate" min="0" name="ai.shield.all.per-day" id="allDay" value="#[ai.shield.all.per-day]#" /></label>
</div>
</div>
<div class="shield-card">
<h3>Guest Access Control & Rate Limits</h3>
<p>By default only localhost may reach the chat UI. Enable non-localhost access and throttle requests to reduce abuse.</p>
<label>
<input type="checkbox" name="ai.shield.allow-nonlocalhost" id="allowNonLocal" #(ai.shield.allow-nonlocalhost)#::checked="checked"#(/ai.shield.allow-nonlocalhost)# />
Allow non-localhost clients to access the chat interface
</label>
<p style="margin-top:8px;">Requests from non-localhost will be throttled using these caps:</p>
<div class="shield-inline">
<label>Per minute: <input type="number" class="form-control shield-rate" min="0" name="ai.shield.rate.per-minute" id="rateMinute" value="#[ai.shield.rate.per-minute]#" /></label>
<label>Per hour: <input type="number" class="form-control shield-rate" min="0" name="ai.shield.rate.per-hour" id="rateHour" value="#[ai.shield.rate.per-hour]#" /></label>
<label>Per day: <input type="number" class="form-control shield-rate" min="0" name="ai.shield.rate.per-day" id="rateDay" value="#[ai.shield.rate.per-day]#" /></label>
</div>
</div>
<div class="shield-card">
<h3>Front Page Link</h3>
<p>Expose a shortcut to the chat UI on the search front page if you want users to discover it.</p>
<label>
<input type="checkbox" name="ai.shield.show-chat-link" id="showLink" #(ai.shield.show-chat-link)#::checked="checked"#(/ai.shield.show-chat-link)# />
Show a link to yacychat.html on the search front page
</label>
</div>
<button type="submit" class="btn btn-primary">Save Shield Settings</button>
</form>
<script type="text/javascript">
(function() {
const allowBox = document.getElementById("allowNonLocal");
const rateInputs = [document.getElementById("rateMinute"), document.getElementById("rateHour"), document.getElementById("rateDay")];
const limitAllBox = document.getElementById("limitAll");
const allRates = [document.getElementById("allMinute"), document.getElementById("allHour"), document.getElementById("allDay")];
function syncRates() {
const enabled = allowBox && allowBox.checked;
rateInputs.forEach(inp => { if (inp) inp.disabled = !enabled; });
const allEnabled = limitAllBox && limitAllBox.checked;
allRates.forEach(inp => { if (inp) inp.disabled = !allEnabled; });
}
if (allowBox) {
allowBox.addEventListener("change", syncRates);
}
if (limitAllBox) {
limitAllBox.addEventListener("change", syncRates);
}
syncRates();
function updateGauge(id, valueId, limitId) {
const fill = document.getElementById(id);
const valEl = document.getElementById(valueId);
const limitEl = document.getElementById(limitId);
if (!fill || !valEl || !limitEl) return;
const current = parseInt(valEl.textContent, 10) || 0;
const limit = parseInt(limitEl.textContent.replace('/', '').trim(), 10) || 0;
let pct = limit > 0 ? Math.min(100, Math.round((current / limit) * 100)) : 0;
fill.style.width = (limit > 0 ? pct : 100) + "%";
fill.className = "gauge-fill " + (limit > 0 ? (pct < 60 ? "gauge-green" : pct < 90 ? "gauge-amber" : "gauge-red") : "gauge-neutral");
}
updateGauge("gaugeMinute", "gaugeMinuteValue", "gaugeMinuteLimit");
updateGauge("gaugeHour", "gaugeHourValue", "gaugeHourLimit");
updateGauge("gaugeDay", "gaugeDayValue", "gaugeDayLimit");
})();
</script>
#%env/templates/footer.template%#
</body>
</html>