hetki ennen webgpu inferenssiä
This commit is contained in:
@@ -157,13 +157,30 @@ async function load() {
|
||||
{v: stats.avg_overhead_pct + '%', l: 'FI ylikust. (ka.)'},
|
||||
].map(s => `<div class="stat-card"><div class="val">${s.v}</div><div class="label">${s.l}</div></div>`).join('');
|
||||
|
||||
// Sessions
|
||||
// Sessions — lajittelu: 1) aktiiviset nodet (online + ei viewer), 2) katsojat (online + viewer), 3) offline
|
||||
const taskNames = {'tokenize':'Tokenisaatio','smollm-135m':'SmolLM 135M','qwen-05b':'Qwen2.5 0.5B','phi3-mini':'Phi-3 Mini','qwen-coder-05b':'Coder 0.5B','qwen-coder-3b':'Coder 3B','viewer':'Katsoja'};
|
||||
sessions.sort((a, b) => {
|
||||
const aOnline = !a.disconnected_at;
|
||||
const bOnline = !b.disconnected_at;
|
||||
const aViewer = a.selected_task === 'viewer';
|
||||
const bViewer = b.selected_task === 'viewer';
|
||||
// Online ennen offlinea
|
||||
if (aOnline !== bOnline) return aOnline ? -1 : 1;
|
||||
// Online: aktiiviset nodet ennen katsojia
|
||||
if (aOnline && bOnline && aViewer !== bViewer) return aViewer ? 1 : -1;
|
||||
// Saman ryhmän sisällä: uusin ensin
|
||||
return new Date(b.connected_at) - new Date(a.connected_at);
|
||||
});
|
||||
|
||||
document.getElementById('sessions-body').innerHTML = sessions.map(s => {
|
||||
const online = !s.disconnected_at;
|
||||
const status = online ? '<span class="online">ONLINE</span>' : '<span class="offline">offline</span>';
|
||||
const isViewer = s.selected_task === 'viewer';
|
||||
const status = online
|
||||
? (isViewer ? '<span style="color:#d29922">CONNECTED</span>' : '<span class="online">ACTIVE</span>')
|
||||
: '<span class="offline">offline</span>';
|
||||
const typeBadge = s.node_type === 'native' ? badge('native','blue') : badge('browser','yellow');
|
||||
const taskNames = {'tokenize':'Tokenisaatio','smollm-135m':'SmolLM 135M','qwen-05b':'Qwen2.5 0.5B','phi3-mini':'Phi-3 Mini'};
|
||||
const taskBadge = badge(taskNames[s.selected_task] || s.selected_task || 'tokenize', s.selected_task === 'tokenize' ? 'green' : 'blue');
|
||||
const taskColor = isViewer ? 'yellow' : s.selected_task === 'tokenize' ? 'green' : 'blue';
|
||||
const taskBadge = badge(taskNames[s.selected_task] || s.selected_task || '?', taskColor);
|
||||
const gpuBadge = s.has_webgpu ? badge('WebGPU','green') : badge('CPU','red');
|
||||
const gpu = s.gpu_name ? `${s.gpu_name}` : '-';
|
||||
const vram = s.vram_total_mb ? `${s.vram_total_mb} MB` : '-';
|
||||
@@ -346,27 +363,73 @@ async fn main() {
|
||||
}
|
||||
|
||||
async fn api_sessions(
|
||||
headers: axum::http::HeaderMap,
|
||||
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
axum::Json(state.db.get_sessions(200))
|
||||
) -> axum::response::Response {
|
||||
if !check_admin_auth(&headers) { return admin_unauthorized(); }
|
||||
axum::Json(state.db.get_sessions(200)).into_response()
|
||||
}
|
||||
|
||||
async fn api_pairs(
|
||||
headers: axum::http::HeaderMap,
|
||||
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
axum::Json(state.db.get_pair_results(500))
|
||||
) -> axum::response::Response {
|
||||
if !check_admin_auth(&headers) { return admin_unauthorized(); }
|
||||
axum::Json(state.db.get_pair_results(500)).into_response()
|
||||
}
|
||||
|
||||
async fn api_stats(
|
||||
headers: axum::http::HeaderMap,
|
||||
axum::extract::State(state): axum::extract::State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
) -> axum::response::Response {
|
||||
if !check_admin_auth(&headers) { return admin_unauthorized(); }
|
||||
let mut stats = state.db.get_stats();
|
||||
stats.as_object_mut().unwrap().insert("version".to_string(), serde_json::json!(env!("CARGO_PKG_VERSION")));
|
||||
axum::Json(stats)
|
||||
axum::Json(stats).into_response()
|
||||
}
|
||||
|
||||
async fn admin_page() -> impl IntoResponse {
|
||||
axum::response::Html(ADMIN_HTML)
|
||||
fn check_admin_auth(headers: &axum::http::HeaderMap) -> bool {
|
||||
let password = std::env::var("ADMIN_PASSWORD").unwrap_or_else(|_| "kipina".to_string());
|
||||
if let Some(auth) = headers.get("authorization").and_then(|v| v.to_str().ok()) {
|
||||
if auth.starts_with("Basic ") {
|
||||
if let Ok(decoded) = String::from_utf8(
|
||||
base64_decode(auth.trim_start_matches("Basic ").trim())
|
||||
) {
|
||||
// Tarkistetaan "user:password" — käyttäjänimi ei väliä
|
||||
if let Some(pass) = decoded.split(':').nth(1) {
|
||||
return pass == password;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn base64_decode(input: &str) -> Vec<u8> {
|
||||
// Yksinkertainen base64-dekooderi
|
||||
const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
let mut out = Vec::new();
|
||||
let bytes: Vec<u8> = input.bytes().filter(|&b| b != b'=').collect();
|
||||
for chunk in bytes.chunks(4) {
|
||||
let vals: Vec<u8> = chunk.iter().filter_map(|&b| TABLE.iter().position(|&t| t == b).map(|p| p as u8)).collect();
|
||||
if vals.len() >= 2 { out.push((vals[0] << 2) | (vals[1] >> 4)); }
|
||||
if vals.len() >= 3 { out.push((vals[1] << 4) | (vals[2] >> 2)); }
|
||||
if vals.len() >= 4 { out.push((vals[2] << 6) | vals[3]); }
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn admin_unauthorized() -> axum::response::Response {
|
||||
axum::response::Response::builder()
|
||||
.status(401)
|
||||
.header("WWW-Authenticate", "Basic realm=\"Kipinä Admin\"")
|
||||
.body(axum::body::Body::from("Unauthorized"))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn admin_page(headers: axum::http::HeaderMap) -> axum::response::Response {
|
||||
if !check_admin_auth(&headers) { return admin_unauthorized(); }
|
||||
axum::response::Html(ADMIN_HTML).into_response()
|
||||
}
|
||||
|
||||
async fn ws_handler(
|
||||
|
||||
Reference in New Issue
Block a user