Add RLUtils class for managing RL/AI dashboard endpoints
- Implemented methods for fetching AI stats, training history, and recent experiences. - Added functionality to set operation mode (MANUAL, AUTO, AI) with appropriate handling. - Included helper methods for querying the database and sending JSON responses. - Integrated model metadata extraction for visualization purposes.
@@ -1,939 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<title>Bjorn - Actions Launcher</title>
|
||||
<link rel="icon" href="web/images/favicon.ico" type="image/x-icon" />
|
||||
<link rel="stylesheet" href="web/css/global.css" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="web/images/icon-192x192.png" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="theme-color" content="#0b0d10" />
|
||||
<script src="web/js/global.js" defer></script>
|
||||
|
||||
<style>
|
||||
/* =========================================================
|
||||
Actions Launcher — scoped CSS (plays nice with global.css)
|
||||
---------------------------------------------------------
|
||||
• No layout override of .sidebar/.main positions.
|
||||
• Uses global tokens (colors, radii, borders, shadows).
|
||||
• Mobile = single console, toolbar hidden.
|
||||
========================================================= */
|
||||
|
||||
/* Page container (center column only) */
|
||||
#actionsLauncher{
|
||||
min-height:100%;
|
||||
display:grid;
|
||||
grid-template-columns:1fr;
|
||||
gap:var(--gap-3, 10px);
|
||||
}
|
||||
|
||||
/* Panel look consistent with cards/console surfaces */
|
||||
.panel{
|
||||
background: var(--grad-card, var(--c-panel));
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius, 14px);
|
||||
box-shadow: var(--elev, 0 10px 30px var(--acid-1a, #00ff9a1a), inset 0 0 0 1px var(--acid-22, #00ff9a22));
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
/* ---------- Sidebar (only inner content styles) ---------- */
|
||||
.sidebar .sideheader{ padding:10px 10px 6px; border-bottom:1px dashed var(--c-border); }
|
||||
.tabs-container{ display:flex; gap:8px; flex-wrap:wrap; }
|
||||
.tab-btn{
|
||||
all:unset; cursor:pointer; padding:6px 10px; border-radius:10px;
|
||||
background:var(--c-pill-bg); border:1px solid var(--c-border); color:var(--muted);
|
||||
}
|
||||
.tab-btn.active{
|
||||
background:var(--grad-chip-selected);
|
||||
outline:2px solid color-mix(in oklab, var(--acid) 55%, transparent);
|
||||
outline-offset:0;
|
||||
}
|
||||
.sidebar .search{ display:flex; gap:10px; padding:10px; }
|
||||
.sidebar .input{
|
||||
flex:1; background:var(--c-panel); border:1px solid var(--c-border-strong);
|
||||
color:var(--ink); padding:10px 12px; border-radius:var(--control-r,10px); font:inherit;
|
||||
}
|
||||
.sidebar .input:focus{ outline:none; box-shadow:0 0 0 2px color-mix(in oklab, var(--acid) 55%, transparent) inset; }
|
||||
.sidecontent{ padding:8px; overflow:auto; }
|
||||
|
||||
/* Action list */
|
||||
.sidebar .list{ display:flex; flex-direction:column; gap:10px; padding-right:4px; }
|
||||
.sidebar .row{
|
||||
position:relative; display:grid; grid-template-columns:84px 1fr;
|
||||
gap:12px; padding:10px; background:var(--c-panel-2); border-radius:12px; cursor:pointer;
|
||||
transition:transform .15s ease, border-color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.sidebar .row:hover{
|
||||
transform:translateY(-1px);
|
||||
border-color:color-mix(in oklab, var(--accent) 25%, var(--c-border));
|
||||
box-shadow:0 10px 26px var(--glow-weak);
|
||||
}
|
||||
.sidebar .row .ic{
|
||||
width:84px; height:84px; display:grid; place-items:center;
|
||||
border-radius:12px; background:var(--c-panel); overflow:hidden;
|
||||
}
|
||||
.ic-img{ width:70px; height:70px; object-fit:cover; display:block; }
|
||||
.sidebar .row > div:nth-child(2){ min-width:0; display:flex; flex-direction:column; gap:4px; }
|
||||
.name{ font-weight:800; color:var(--acid-2); font-size:14px; line-height:1.2; }
|
||||
.desc{ color:var(--muted); font-size:13px; line-height:1.25; }
|
||||
.sidebar .row .chip{
|
||||
position:absolute; top:6px; left:calc(84px/2 + 10px); transform:translateX(-50%);
|
||||
padding:2px 8px; border-radius:999px; border:1px solid var(--c-border);
|
||||
background:var(--c-chip-bg); color:var(--muted); font-size:11px; line-height:1; pointer-events:none;
|
||||
}
|
||||
.chip.ok{ color:var(--ok); border-color:color-mix(in oklab, var(--ok) 60%, transparent); }
|
||||
.chip.err{ color:var(--danger); border-color:color-mix(in oklab, var(--danger) 60%, transparent); }
|
||||
.chip.run{ color:var(--acid); border-color:color-mix(in oklab, var(--acid) 60%, transparent); }
|
||||
|
||||
/* ---------- Center area ---------- */
|
||||
.center{ display:flex; flex-direction:column; min-height:50vh; }
|
||||
|
||||
/* Secondary toolbar (split controls). Hidden on mobile. */
|
||||
.toolbar2{
|
||||
display:flex; align-items:center; gap:10px; padding:10px;
|
||||
border-bottom:1px solid var(--c-border);
|
||||
background:linear-gradient(180deg, color-mix(in oklab, var(--acid-2) 12%, transparent), transparent);
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
.seg{ display:flex; border-radius:10px; overflow:hidden; border:1px solid var(--c-border); }
|
||||
.seg button{
|
||||
background:var(--c-panel); color:var(--muted);
|
||||
padding:8px 10px; border:none; border-right:1px solid var(--c-border);
|
||||
cursor:pointer; font:inherit;
|
||||
}
|
||||
.seg button:last-child{ border-right:none; }
|
||||
.seg button.active{
|
||||
color:var(--ink-invert);
|
||||
background:linear-gradient(90deg, var(--acid-2), color-mix(in oklab, var(--acid-2) 60%, white));
|
||||
}
|
||||
|
||||
.btn{
|
||||
background:var(--c-btn); color:var(--ink); border:1px solid var(--c-border-strong);
|
||||
border-radius:var(--control-r,10px); padding:8px 12px;
|
||||
display:inline-flex; align-items:center; gap:8px; cursor:pointer; transition:.18s;
|
||||
box-shadow:var(--elev); font:inherit;
|
||||
}
|
||||
.btn:hover{ transform:translateY(-1px); box-shadow:var(--shadow-hover); }
|
||||
.btn.warn{
|
||||
background:linear-gradient(180deg, color-mix(in oklab, var(--warning) 28%, var(--c-btn)), var(--c-btn));
|
||||
color:var(--warning); border-color:color-mix(in oklab, var(--warning) 55%, var(--c-border));
|
||||
}
|
||||
|
||||
/* Multi-console grid */
|
||||
.multiConsole{
|
||||
flex:1; padding:10px; display:grid; gap:10px; height:100%;
|
||||
grid-auto-flow:row; grid-auto-rows:1fr;
|
||||
grid-template-rows:repeat(var(--rows,1), 1fr);
|
||||
}
|
||||
.split-1{ grid-template-columns:1fr; }
|
||||
.split-2{ grid-template-columns:1fr 1fr; }
|
||||
.split-3{ grid-template-columns:1fr 1fr 1fr; }
|
||||
.split-4{ grid-template-columns:1fr 1fr; } /* 2x2 */
|
||||
|
||||
/* Console pane */
|
||||
.pane{
|
||||
position:relative; border:1px solid var(--c-border);
|
||||
border-radius:12px; background:var(--grad-console);
|
||||
display:flex; flex-direction:column;
|
||||
box-shadow:inset 0 0 0 1px var(--c-border-muted);
|
||||
}
|
||||
|
||||
/* Clean two-column header: title/meta | actions */
|
||||
.paneHeader{
|
||||
display:grid; grid-template-columns:1fr auto; align-items:center;
|
||||
gap:10px; padding:8px 10px; border-bottom:1px solid var(--c-border);
|
||||
background:linear-gradient(180deg, color-mix(in oklab, var(--acid-2) 8%, transparent), transparent);
|
||||
}
|
||||
|
||||
/* Left side: dot + icon + stacked title/meta */
|
||||
.paneTitle{
|
||||
display:grid; grid-template-columns:auto auto 1fr; align-items:center; gap:10px; min-width:0;
|
||||
}
|
||||
.paneTitle .dot{ width:8px; height:8px; border-radius:50%; flex:0 0 auto; }
|
||||
.paneIcon{ width:70px; height:70px; border-radius:6px; object-fit:cover; opacity:.95; }
|
||||
.titleBlock{ display:flex; flex-direction:column; gap:4px; min-width:0; }
|
||||
.titleLine strong{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.metaLine{ display:flex; flex-wrap:wrap; gap:6px; }
|
||||
.metaLine .chip{ border:1px solid var(--c-border-strong); background:var(--c-chip-bg); color:var(--muted); padding:3px 8px; border-radius:999px; }
|
||||
|
||||
/* Right side: actions wrap neatly */
|
||||
.paneBtns{ display:flex; flex-wrap:wrap; gap:8px; justify-content:flex-end; }
|
||||
.paneBtns .btn{ padding:6px 8px; font-size:.9rem; }
|
||||
|
||||
.paneLog{
|
||||
flex:1; overflow:auto; padding:6px 8px;
|
||||
font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
||||
font-size:.92rem;
|
||||
}
|
||||
|
||||
/* Log colors */
|
||||
.logline{ white-space:pre-wrap; word-break:break-word; padding:4px 6px; line-height:1.32; color:var(--ink); }
|
||||
.logline.info{ color:#bfefff; }
|
||||
.logline.ok{ color:#9ff7c5; }
|
||||
.logline.warn{ color:#ffd27a; }
|
||||
.logline.err{ color:#ff99b3; }
|
||||
.logline.dim{ color:#6a8596; }
|
||||
|
||||
.paneHighlight{
|
||||
box-shadow:0 0 0 2px var(--acid-2), 0 0 24px color-mix(in oklab, var(--acid-2) 55%, transparent) inset, 0 0 40px color-mix(in oklab, var(--acid-2) 35%, transparent);
|
||||
animation:hi 900ms ease-out 1;
|
||||
}
|
||||
@keyframes hi{ 0%{transform:scale(1)} 50%{transform:scale(1.01)} 100%{transform:scale(1)} }
|
||||
|
||||
/* Arguments section */
|
||||
.section{ padding:12px; border-bottom:1px dashed var(--c-border); }
|
||||
.h{ font-weight:800; letter-spacing:.5px; color:var(--acid-2); }
|
||||
.sub{ color:var(--muted); font-size:.9rem; }
|
||||
.builder{ padding:12px; display:grid; gap:12px; }
|
||||
.field{ display:grid; gap:6px; }
|
||||
.label{ font-size:.85rem; color:var(--muted); }
|
||||
.ctl, .select, .range{
|
||||
background:var(--c-panel); color:var(--ink); border:1px solid var(--c-border-strong);
|
||||
border-radius:var(--control-r,10px); padding:10px 12px; font:inherit;
|
||||
}
|
||||
.ctl:focus, .select:focus{ outline:none; box-shadow:0 0 0 2px color-mix(in oklab, var(--acid) 55%, transparent) inset; }
|
||||
.chips{ display:flex; gap:8px; flex-wrap:wrap; padding:10px; }
|
||||
.chip2{ padding:6px 10px; border-radius:999px; background:var(--c-chip-bg); border:1px solid var(--c-border-hi); cursor:pointer; transition:.18s; }
|
||||
.chip2:hover{ box-shadow:0 0 0 1px var(--c-border-hi) inset, 0 8px 22px var(--glow-weak); }
|
||||
|
||||
/* Mobile tweaks */
|
||||
@media (max-width: 860px){
|
||||
.toolbar2{ display:none !important; } /* hide toolbar in mobile */
|
||||
.paneHeader{ grid-template-columns:1fr; row-gap:8px; }
|
||||
.paneBtns{ justify-content:flex-start; }
|
||||
.paneBtns .btn{ padding:5px 6px; font-size:.85rem; }
|
||||
.main {
|
||||
left: 350px;
|
||||
}
|
||||
.sidebar {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Sidebar content (sidebar frame is from global.css) -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sideheader" id="sideheader">
|
||||
<div class="tabs-container">
|
||||
<button class="tab-btn active" data-page="attacks">Actions</button>
|
||||
<button class="tab-btn" data-page="arguments">Arguments</button>
|
||||
</div>
|
||||
<div class="search">
|
||||
<input class="input" id="searchInput" placeholder="Search actions..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidecontent" id="sidecontent">
|
||||
<!-- Actions tab -->
|
||||
<div id="attacks-sidebar" class="sidebar-page" style="display:block">
|
||||
<div class="list" id="actionsList"></div>
|
||||
</div>
|
||||
|
||||
<!-- Arguments tab -->
|
||||
<div id="arguments-sidebar" class="sidebar-page" style="display:none">
|
||||
<div class="section">
|
||||
<div class="h">Arguments</div>
|
||||
<div class="sub">Auto-generated from action definitions</div>
|
||||
</div>
|
||||
<div class="builder" id="argBuilder"></div>
|
||||
<div class="section">
|
||||
<input id="freeArgs" class="ctl" placeholder="Additional arguments (e.g., --verbose --debug)" />
|
||||
</div>
|
||||
<div class="chips" id="presetChips"></div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main area (positioned by global.css) -->
|
||||
<div class="main">
|
||||
<main id="actionsLauncher">
|
||||
<section class="center panel">
|
||||
<div class="toolbar2">
|
||||
<div class="spacer"></div>
|
||||
<div class="seg" id="splitSeg">
|
||||
<button data-split="1" class="active">1</button>
|
||||
<button data-split="2">2</button>
|
||||
<button data-split="3">3</button>
|
||||
<button data-split="4">4</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="multiConsole split-1" id="multiConsole"></div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* =========================================================
|
||||
Actions Launcher — JS (queue removed)
|
||||
---------------------------------------------------------
|
||||
• Forces single console on mobile and hides toolbar2.
|
||||
• Clean pane header markup + better responsiveness.
|
||||
• API endpoints kept: /list_scripts, /run_script, /stop_script, /get_script_output/:path
|
||||
========================================================= */
|
||||
|
||||
const isMobile = () => window.matchMedia('(max-width: 860px)').matches;
|
||||
|
||||
class ActionsLauncher{
|
||||
constructor(){
|
||||
this.actions = [];
|
||||
this.activeAction = null;
|
||||
this.runningActions = new Map();
|
||||
this.logs = new Map();
|
||||
this.split = 1; // forced to 1 on mobile
|
||||
this.panes = [null, null, null, null];
|
||||
this.assignTargetPaneIndex = null;
|
||||
this.autoClearPane = {0:false,1:false,2:false,3:false};
|
||||
this.filter = 'all';
|
||||
this.pollingIntervals = new Map();
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init(){
|
||||
await this.loadActions();
|
||||
this.setupEventListeners();
|
||||
this.enforceMobileOnePane(); // always one pane on mobile
|
||||
this.renderActions();
|
||||
this.renderConsoles();
|
||||
window.addEventListener('resize', this.onResizeDebounced.bind(this));
|
||||
}
|
||||
|
||||
onResizeDebounced(){
|
||||
clearTimeout(this._rz_t);
|
||||
this._rz_t = setTimeout(()=>{
|
||||
this.enforceMobileOnePane();
|
||||
this.renderConsoles();
|
||||
}, 120);
|
||||
}
|
||||
|
||||
enforceMobileOnePane(){
|
||||
if(isMobile()){
|
||||
this.split = 1;
|
||||
if(!this.panes[0] && this.activeAction){ this.panes[0] = this.activeAction.id; }
|
||||
for(let i=1;i<this.panes.length;i++) this.panes[i] = null;
|
||||
// Disable split buttons visually
|
||||
document.querySelectorAll('#splitSeg button').forEach(b=>{
|
||||
b.classList.toggle('active', b.dataset.split === '1');
|
||||
b.disabled = true; b.style.opacity = .6; b.style.pointerEvents = 'none';
|
||||
});
|
||||
}else{
|
||||
document.querySelectorAll('#splitSeg button').forEach(b=>{
|
||||
b.disabled = false; b.style.opacity = ''; b.style.pointerEvents = '';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async loadActions(){
|
||||
try{
|
||||
const response = await fetch('/list_scripts');
|
||||
const { status, data } = await response.json();
|
||||
|
||||
if(status === 'success' && Array.isArray(data)){
|
||||
this.actions = data.map(action=>{
|
||||
const raw = action.b_args ?? {};
|
||||
let args = raw;
|
||||
if(typeof raw === 'string'){ try{ args = JSON.parse(raw);}catch{ args = {}; } }
|
||||
|
||||
const id = action.b_module || (action.name ? action.name.replace(/\.py$/,'') : 'unknown');
|
||||
const className = action.b_class || id;
|
||||
const icon = action.b_icon || `/actions_icons/${className}.png`;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: action.name || action.b_class || action.b_module || 'Unnamed',
|
||||
module: action.b_module || action.module,
|
||||
category: action.b_action || action.category || 'normal',
|
||||
description: action.description || 'No description',
|
||||
args,
|
||||
status: 'ready',
|
||||
path: action.path || action.module_path,
|
||||
icon,
|
||||
version: action.b_version,
|
||||
author: action.b_author,
|
||||
docsUrl: action.b_docs_url,
|
||||
examples: action.b_examples
|
||||
};
|
||||
});
|
||||
}else{
|
||||
this.actions = [];
|
||||
}
|
||||
}catch(err){
|
||||
console.error('Failed to load actions:', err);
|
||||
this.actions = [];
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners(){
|
||||
// Search
|
||||
const search = document.getElementById('searchInput');
|
||||
if(search) search.addEventListener('input', ()=>this.renderActions());
|
||||
|
||||
// Sidebar tabs
|
||||
document.querySelectorAll('.tab-btn').forEach(btn=>{
|
||||
btn.addEventListener('click', ()=>{
|
||||
const page = btn.dataset.page;
|
||||
document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
document.querySelectorAll('.sidebar-page').forEach(sp=>sp.style.display='none');
|
||||
document.getElementById(page+'-sidebar').style.display='block';
|
||||
});
|
||||
});
|
||||
|
||||
// Split buttons (ignored on mobile)
|
||||
document.querySelectorAll('#splitSeg button').forEach(btn=>{
|
||||
btn.addEventListener('click', ()=>{
|
||||
if(isMobile()){ this.enforceMobileOnePane(); return; }
|
||||
document.querySelectorAll('#splitSeg button').forEach(b=>b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
this.split = parseInt(btn.dataset.split, 10);
|
||||
this.renderConsoles();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- Sidebar: actions ---------- */
|
||||
renderActions(){
|
||||
const q = (document.getElementById('searchInput')?.value || '').trim().toLowerCase();
|
||||
const terms = q ? q.split(/\s+/).filter(Boolean) : [];
|
||||
|
||||
const filtered = this.actions.filter(action=>{
|
||||
const matchesFilter = this.filter==='all' || (action.category||'').toLowerCase()===this.filter;
|
||||
if(!matchesFilter) return false;
|
||||
if(terms.length===0) return true;
|
||||
const hay = [
|
||||
action.name, action.description, action.module, action.id, action.author
|
||||
].concat(Array.isArray(action.tags)?action.tags:[])
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
return terms.every(t=>hay.includes(t));
|
||||
});
|
||||
|
||||
const list = document.getElementById('actionsList');
|
||||
if(!list) return;
|
||||
list.innerHTML = filtered.map(a=>this.createActionRow(a)).join('');
|
||||
this.attachActionHandlers('actionsList');
|
||||
}
|
||||
|
||||
createActionRow(action){
|
||||
const statusChip = this.getStatusChip(action.status);
|
||||
const iconHtml = `
|
||||
<img src="${action.icon}" class="ic-img" alt=""
|
||||
onerror="this.onerror=null; this.src='/actions/actions_icons/default.png';" />
|
||||
`;
|
||||
return `
|
||||
<div class="row" data-action="${action.id}">
|
||||
<div class="ic">${iconHtml}</div>
|
||||
<div>
|
||||
<div class="name">${action.name}</div>
|
||||
<div class="desc">${action.description}</div>
|
||||
</div>
|
||||
<div class="chip ${statusChip.class}">${statusChip.text}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
attachActionHandlers(containerId){
|
||||
const container = document.getElementById(containerId);
|
||||
if(!container) return;
|
||||
container.querySelectorAll('.row').forEach(row=>{
|
||||
const actionId = row.dataset.action;
|
||||
row.draggable = true;
|
||||
|
||||
row.addEventListener('dragstart', e=>{
|
||||
e.dataTransfer.setData('text/plain', actionId);
|
||||
});
|
||||
|
||||
row.addEventListener('click', ()=>{
|
||||
if(this.assignTargetPaneIndex!==null){
|
||||
this.panes[this.assignTargetPaneIndex] = actionId;
|
||||
this.clearAssignTarget();
|
||||
this.renderConsoles();
|
||||
return;
|
||||
}
|
||||
this.selectAction(actionId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getStatusChip(status){
|
||||
switch(status){
|
||||
case 'running': return { class:'run', text:'Running' };
|
||||
case 'success': return { class:'ok', text:'Success' };
|
||||
case 'error': return { class:'err', text:'Error' };
|
||||
default: return { class:'', text:'Ready' };
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Arguments ---------- */
|
||||
selectAction(actionId){
|
||||
const action = this.actions.find(a=>a.id===actionId);
|
||||
if(!action) return;
|
||||
|
||||
this.activeAction = action;
|
||||
this.renderArguments(action);
|
||||
this.renderPresets(action);
|
||||
|
||||
if(this.assignTargetPaneIndex!==null){
|
||||
this.panes[this.assignTargetPaneIndex] = actionId;
|
||||
this.clearAssignTarget();
|
||||
this.renderConsoles();
|
||||
return;
|
||||
}
|
||||
const paneIndex = this.panes.findIndex(p=>p===actionId);
|
||||
if(paneIndex===-1){
|
||||
const emptyIndex = this.panes.slice(0, this.split).findIndex(p=>!p);
|
||||
this.panes[emptyIndex !== -1 ? emptyIndex : 0] = actionId;
|
||||
this.renderConsoles();
|
||||
}else{
|
||||
this.highlightPane(paneIndex);
|
||||
}
|
||||
}
|
||||
|
||||
renderArguments(action){
|
||||
const builder = document.getElementById('argBuilder');
|
||||
if(!builder) return;
|
||||
|
||||
builder.innerHTML = '';
|
||||
|
||||
// Meta + Docs
|
||||
const header = document.createElement('div');
|
||||
header.style.display = 'flex';
|
||||
header.style.alignItems = 'center';
|
||||
header.style.justifyContent = 'space-between';
|
||||
header.style.gap = '10px';
|
||||
header.style.marginBottom = '8px';
|
||||
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'sub';
|
||||
const bits = [];
|
||||
if(action?.version) bits.push(`v${action.version}`);
|
||||
if(action?.author) bits.push(`by ${action.author}`);
|
||||
meta.textContent = bits.join(' · ') || '';
|
||||
header.appendChild(meta);
|
||||
|
||||
const right = document.createElement('div');
|
||||
if(action?.docsUrl){
|
||||
const a = document.createElement('a');
|
||||
a.href = action.docsUrl;
|
||||
a.target = '_blank'; a.rel='noopener noreferrer';
|
||||
a.className = 'btn';
|
||||
a.textContent = '📖 Docs';
|
||||
right.appendChild(a);
|
||||
}
|
||||
header.appendChild(right);
|
||||
builder.appendChild(header);
|
||||
|
||||
// Presets
|
||||
const hasPresets = Array.isArray(action?.examples) && action.examples.length>0;
|
||||
if(hasPresets){
|
||||
const chipbar = document.createElement('div');
|
||||
chipbar.style.display = 'flex';
|
||||
chipbar.style.flexWrap = 'wrap';
|
||||
chipbar.style.gap = '8px';
|
||||
chipbar.style.margin = '2px 0 10px 0';
|
||||
|
||||
action.examples.forEach((preset, idx)=>{
|
||||
const b = document.createElement('button');
|
||||
b.className = 'chip2';
|
||||
b.textContent = preset.name || preset.title || `Preset ${idx+1}`;
|
||||
b.title = 'Apply this preset';
|
||||
b.addEventListener('click', ()=>this.applyPreset(preset));
|
||||
chipbar.appendChild(b);
|
||||
});
|
||||
builder.appendChild(chipbar);
|
||||
}
|
||||
|
||||
// Fields
|
||||
if(!action.args || Object.keys(action.args).length===0){
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'sub';
|
||||
empty.textContent = 'No configurable arguments';
|
||||
builder.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
Object.entries(action.args).forEach(([key, config])=>{
|
||||
const field = document.createElement('div');
|
||||
field.className = 'field';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'label';
|
||||
label.textContent = config.label || key;
|
||||
field.appendChild(label);
|
||||
|
||||
const input = this.createInput(key, config);
|
||||
field.appendChild(input);
|
||||
|
||||
if(config.help){
|
||||
const help = document.createElement('div');
|
||||
help.className = 'sub';
|
||||
help.textContent = config.help;
|
||||
field.appendChild(help);
|
||||
}
|
||||
|
||||
builder.appendChild(field);
|
||||
});
|
||||
}
|
||||
|
||||
createInput(key, config){
|
||||
const type = config.type || 'text';
|
||||
let input;
|
||||
switch(type){
|
||||
case 'select':
|
||||
input = document.createElement('select');
|
||||
input.className = 'select';
|
||||
(config.choices || []).forEach(choice=>{
|
||||
const option = document.createElement('option');
|
||||
option.value = choice; option.textContent = choice;
|
||||
if(choice === config.default) option.selected = true;
|
||||
input.appendChild(option);
|
||||
});
|
||||
break;
|
||||
case 'checkbox':
|
||||
input = document.createElement('input');
|
||||
input.type='checkbox'; input.className='ctl';
|
||||
input.checked = config.default || false;
|
||||
break;
|
||||
case 'number':
|
||||
input = document.createElement('input');
|
||||
input.type='number'; input.className='ctl';
|
||||
if(config.min !== undefined) input.min = config.min;
|
||||
if(config.max !== undefined) input.max = config.max;
|
||||
if(config.step !== undefined) input.step = config.step;
|
||||
input.value = config.default || '';
|
||||
break;
|
||||
case 'slider':
|
||||
case 'range':
|
||||
input = document.createElement('input');
|
||||
input.type='range'; input.className='range';
|
||||
input.min = config.min || 0;
|
||||
input.max = config.max || 100;
|
||||
input.step = config.step || 1;
|
||||
input.value = config.default || input.min;
|
||||
break;
|
||||
default:
|
||||
input = document.createElement('input');
|
||||
input.type='text'; input.className='ctl';
|
||||
input.value = config.default || '';
|
||||
input.placeholder = config.placeholder || '';
|
||||
}
|
||||
input.dataset.arg = key;
|
||||
return input;
|
||||
}
|
||||
|
||||
applyPreset(preset){
|
||||
Object.entries(preset).forEach(([key,val])=>{
|
||||
if(key==='name'||key==='title') return;
|
||||
const container = document.getElementById('argBuilder');
|
||||
if(!container) return;
|
||||
const input = container.querySelector(`[data-arg="${key}"]`);
|
||||
if(!input) return;
|
||||
if(input.type==='checkbox') input.checked = !!val;
|
||||
else input.value = val;
|
||||
});
|
||||
}
|
||||
|
||||
collectArguments(){
|
||||
if(!this.activeAction) return '';
|
||||
const args = [];
|
||||
const builder = document.getElementById('argBuilder');
|
||||
if(builder){
|
||||
builder.querySelectorAll('[data-arg]').forEach(input=>{
|
||||
const key = input.dataset.arg;
|
||||
const flag = `--${key.replace(/_/g,'-')}`;
|
||||
if(input.type==='checkbox'){
|
||||
if(input.checked) args.push(flag);
|
||||
}else{
|
||||
const value = input.value.trim();
|
||||
if(value){ args.push(flag); args.push(value); }
|
||||
}
|
||||
});
|
||||
}
|
||||
const freeArgs = document.getElementById('freeArgs')?.value || '';
|
||||
if(freeArgs.trim()) args.push(...freeArgs.trim().split(' '));
|
||||
return args.join(' ');
|
||||
}
|
||||
|
||||
/* ---------- Consoles ---------- */
|
||||
renderConsoles(){
|
||||
const container = document.getElementById('multiConsole');
|
||||
const effectiveSplit = isMobile() ? 1 : this.split;
|
||||
container.className = `multiConsole split-${effectiveSplit}`;
|
||||
container.innerHTML = '';
|
||||
|
||||
const rows = (effectiveSplit===4) ? 2 : 1;
|
||||
container.style.setProperty('--rows', rows);
|
||||
|
||||
for(let i=0;i<effectiveSplit;i++){
|
||||
const actionId = this.panes[i];
|
||||
const action = actionId ? this.actions.find(a=>a.id===actionId) : null;
|
||||
|
||||
const pane = document.createElement('div');
|
||||
pane.className='pane'; pane.dataset.index=i;
|
||||
|
||||
const statusColor = this.getStatusColor(action?.status);
|
||||
const iconUrl = action?.icon || '/web/images/attack.png';
|
||||
const hasMeta = !!(action && (action.docsUrl || action.author || action.version));
|
||||
|
||||
pane.innerHTML = `
|
||||
<div class="paneHeader">
|
||||
<!-- Left: status dot + icon + title/meta -->
|
||||
<div class="paneTitle" title="${action ? (action.description||'') : ''}">
|
||||
<span class="dot" style="background:${statusColor}"></span>
|
||||
${action ? `<img class="paneIcon" src="${iconUrl}" alt="" onerror="this.style.display='none'">` : ''}
|
||||
<div class="titleBlock">
|
||||
<div class="titleLine">
|
||||
<strong>${action ? action.name : '— Empty Pane —'}</strong>
|
||||
</div>
|
||||
${
|
||||
hasMeta
|
||||
? `<div class="metaLine">
|
||||
${action?.version ? `<span class="chip">v${action.version}</span>` : ''}
|
||||
${action?.author ? `<span class="chip">by ${action.author}</span>` : ''}
|
||||
</div>`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: actions -->
|
||||
<div class="paneBtns">
|
||||
${
|
||||
action
|
||||
? `
|
||||
${action?.docsUrl ? `<a class="btn" href="${action.docsUrl}" target="_blank" rel="noopener">Docs</a>` : ''}
|
||||
<button class="btn" data-action="run" data-idx="${i}">Run</button>
|
||||
<button class="btn warn" data-action="stop" data-idx="${i}">Stop</button>
|
||||
<button class="btn" data-action="clear" data-idx="${i}">Clear</button>
|
||||
<button class="btn" data-action="export" data-idx="${i}">⬇ Export</button>
|
||||
<button class="btn ${this.autoClearPane[i] ? 'on':''}" data-action="toggleAuto" data-idx="${i}">
|
||||
${this.autoClearPane[i] ? 'Auto-clear ON' : 'Auto-clear OFF'}
|
||||
</button>
|
||||
`
|
||||
: `<button class="btn" data-action="assign" data-idx="${i}">Assign</button>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="paneLog" id="paneLog-${i}">
|
||||
${this.renderLogs(actionId)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
pane.querySelectorAll('[data-action]').forEach(btn=>{
|
||||
btn.addEventListener('click', ()=>{
|
||||
const idx = parseInt(btn.dataset.idx ?? i, 10);
|
||||
switch(btn.dataset.action){
|
||||
case 'run': this.activeAction = action; this.runAction(idx); break;
|
||||
case 'stop': this.stopAction(actionId); break;
|
||||
case 'clear': this.clearActionLogs(actionId); break;
|
||||
case 'export':this.exportPaneLogs(idx); break;
|
||||
case 'toggleAuto':
|
||||
this.autoClearPane[idx] = !this.autoClearPane[idx];
|
||||
btn.classList.toggle('on', this.autoClearPane[idx]);
|
||||
btn.textContent = this.autoClearPane[idx] ? 'Auto-clear ON' : 'Auto-clear OFF';
|
||||
break;
|
||||
case 'assign': this.assignActionToPane(idx); break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Drag-to-assign
|
||||
pane.addEventListener('dragover', e=>{ e.preventDefault(); pane.classList.add('paneHighlight'); });
|
||||
pane.addEventListener('dragleave', ()=>pane.classList.remove('paneHighlight'));
|
||||
pane.addEventListener('drop', e=>{
|
||||
e.preventDefault(); pane.classList.remove('paneHighlight');
|
||||
const droppedId = e.dataTransfer.getData('text/plain'); if(!droppedId) return;
|
||||
this.panes[i] = droppedId; this.renderConsoles();
|
||||
});
|
||||
|
||||
container.appendChild(pane);
|
||||
pane.querySelector(`#paneLog-${i}`)?.addEventListener('click', ()=>this.setAssignTarget(i));
|
||||
}
|
||||
}
|
||||
|
||||
renderLogs(actionId){
|
||||
if(!actionId) return '<div class="logline dim">Select an action to see logs</div>';
|
||||
const logs = this.logs.get(actionId) || [];
|
||||
if(logs.length===0) return '<div class="logline dim">Waiting for logs...</div>';
|
||||
return logs.map(log=>this.formatLogLine(log)).join('');
|
||||
}
|
||||
formatLogLine(log){
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const cssClass = this.getLogClass(log);
|
||||
return `<div class="logline ${cssClass}">[${timestamp}] ${this.escapeHtml(log)}</div>`;
|
||||
}
|
||||
getLogClass(log){
|
||||
const lower = String(log).toLowerCase();
|
||||
if(lower.includes('error') || lower.includes('failed')) return 'err';
|
||||
if(lower.includes('warning') || lower.includes('warn')) return 'warn';
|
||||
if(lower.includes('success') || lower.includes('complete')) return 'ok';
|
||||
if(lower.includes('info')) return 'info';
|
||||
return 'dim';
|
||||
}
|
||||
escapeHtml(text){
|
||||
const map = { '&':'&','<':'<','>':'>','"':'"',"'":''' };
|
||||
return String(text).replace(/[&<>"']/g, m=>map[m]);
|
||||
}
|
||||
getStatusColor(status){
|
||||
switch(status){
|
||||
case 'running': return 'var(--acid)';
|
||||
case 'success': return 'var(--ok)';
|
||||
case 'error': return 'var(--danger)';
|
||||
default: return 'var(--acid-2)';
|
||||
}
|
||||
}
|
||||
|
||||
clearActionLogs(actionId){
|
||||
this.logs.set(actionId, []); this.renderConsoles();
|
||||
}
|
||||
|
||||
exportPaneLogs(paneIndex){
|
||||
const actionId = this.panes[paneIndex]; if(!actionId) return;
|
||||
const action = this.actions.find(a=>a.id===actionId);
|
||||
const logs = this.logs.get(actionId) || [];
|
||||
const blob = new Blob([logs.join('\n')], {type:'text/plain'});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${(action?.name || 'pane')}_logs_${Date.now()}.txt`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
highlightPane(index){
|
||||
const pane = document.querySelector(`.pane[data-index="${index}"]`);
|
||||
if(pane){ pane.classList.add('paneHighlight'); setTimeout(()=>pane.classList.remove('paneHighlight'), 900); }
|
||||
}
|
||||
|
||||
setAssignTarget(paneIndex){
|
||||
this.assignTargetPaneIndex = paneIndex;
|
||||
document.querySelectorAll('.pane').forEach(el=>el.classList.remove('paneHighlight'));
|
||||
const pane = document.querySelector(`.pane[data-index="${paneIndex}"]`);
|
||||
if(pane) pane.classList.add('paneHighlight');
|
||||
}
|
||||
clearAssignTarget(){
|
||||
this.assignTargetPaneIndex = null;
|
||||
document.querySelectorAll('.pane').forEach(el=>el.classList.remove('paneHighlight'));
|
||||
}
|
||||
|
||||
/* ---------- Run / Stop / Poll ---------- */
|
||||
async runAction(paneIndex=null){
|
||||
if(!this.activeAction) return;
|
||||
const args = this.collectArguments();
|
||||
const action = this.activeAction;
|
||||
|
||||
if(paneIndex!==null && this.autoClearPane[paneIndex]){
|
||||
this.logs.set(action.id, []);
|
||||
const logEl = document.getElementById(`paneLog-${paneIndex}`);
|
||||
if(logEl) logEl.innerHTML = '';
|
||||
}
|
||||
|
||||
action.status = 'running';
|
||||
this.runningActions.set(action.id, {status:'running', process:null});
|
||||
this.renderActions(); this.renderConsoles();
|
||||
|
||||
if(!this.logs.has(action.id)) this.logs.set(action.id, []);
|
||||
this.logs.get(action.id).push(`Starting ${action.name}...`);
|
||||
|
||||
try{
|
||||
const response = await fetch('/run_script', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ script_name: action.module || action.id, args })
|
||||
});
|
||||
const data = await response.json();
|
||||
if(data.status==='success'){ this.startOutputPolling(action.id); }
|
||||
else{ throw new Error(data.message || 'Run failed'); }
|
||||
}catch(err){
|
||||
action.status = 'error';
|
||||
this.logs.get(action.id).push(`Error: ${err.message}`);
|
||||
this.runningActions.delete(action.id);
|
||||
this.renderActions(); this.renderConsoles();
|
||||
}
|
||||
}
|
||||
|
||||
async stopAction(actionId){
|
||||
const id = actionId || this.activeAction?.id; if(!id) return;
|
||||
try{
|
||||
const action = this.actions.find(a=>a.id===id); if(!action) return;
|
||||
const response = await fetch('/stop_script', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ script_name: action.path })
|
||||
});
|
||||
const data = await response.json();
|
||||
if(data.status==='success'){
|
||||
action.status = 'ready';
|
||||
this.runningActions.delete(id);
|
||||
this.stopOutputPolling(id);
|
||||
this.logs.get(id)?.push('Script stopped by user');
|
||||
this.renderActions(); this.renderConsoles();
|
||||
}
|
||||
}catch(err){ console.error('Failed to stop action:', err); }
|
||||
}
|
||||
|
||||
startOutputPolling(actionId){
|
||||
const poll = async ()=>{
|
||||
try{
|
||||
const action = this.actions.find(a=>a.id===actionId); if(!action) return;
|
||||
const response = await fetch(`/get_script_output/${encodeURIComponent(action.path)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if(data.status==='success'){
|
||||
const output = data.data.output || [];
|
||||
if(output.length>0){
|
||||
if(!this.logs.has(actionId)) this.logs.set(actionId, []);
|
||||
this.logs.set(actionId, output);
|
||||
this.updatePaneLog(actionId);
|
||||
}
|
||||
if(data.data.is_running){
|
||||
this.pollingIntervals.set(actionId, setTimeout(poll, 1000));
|
||||
}else{
|
||||
action.status = 'success';
|
||||
this.runningActions.delete(actionId);
|
||||
this.stopOutputPolling(actionId);
|
||||
this.logs.get(actionId)?.push('Script completed successfully');
|
||||
this.renderActions(); this.renderConsoles();
|
||||
}
|
||||
}
|
||||
}catch(err){
|
||||
console.error('Polling error:', err);
|
||||
this.stopOutputPolling(actionId);
|
||||
}
|
||||
};
|
||||
poll();
|
||||
}
|
||||
|
||||
stopOutputPolling(actionId){
|
||||
const t = this.pollingIntervals.get(actionId);
|
||||
if(t){ clearTimeout(t); this.pollingIntervals.delete(actionId); }
|
||||
}
|
||||
|
||||
updatePaneLog(actionId){
|
||||
const paneIndex = this.panes.findIndex(p=>p===actionId);
|
||||
if(paneIndex===-1) return;
|
||||
const logEl = document.getElementById(`paneLog-${paneIndex}`);
|
||||
if(logEl){
|
||||
logEl.innerHTML = this.renderLogs(actionId);
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Boot */
|
||||
document.addEventListener('DOMContentLoaded', ()=>{
|
||||
window.actionsLauncher = new ActionsLauncher();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1492
web/attacks.html
@@ -1,376 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Bjorn Cyberviking - Update and Backup Management</title>
|
||||
<link rel="icon" href="web/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="web/css/global.css">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="web/images/icon-192x192.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#333">
|
||||
<script src="web/js/global.js"></script>
|
||||
|
||||
<style>
|
||||
/* ======================================================================
|
||||
Page-scoped CSS (kept minimal) — uses your global tokens
|
||||
====================================================================== */
|
||||
.main-container{display:flex;height:calc(100vh - 60px);width:100%;position:relative}
|
||||
.section-list{list-style-type:none;padding:0;margin:0;flex-grow:1}
|
||||
.list-item{display:flex;align-items:center;padding:12px;cursor:pointer;border-radius:var(--radius);margin-bottom:12px;transition:box-shadow .3s, background-color .3s, border-color .3s;background:var(--grad-card);border:1px solid var(--c-border);box-shadow:var(--shadow)}
|
||||
.list-item:hover{box-shadow:var(--shadow-hover)}
|
||||
.list-item.selected{border:1px solid #00e764}
|
||||
.list-item img{margin-right:10px}
|
||||
@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
|
||||
.right-panel{flex:1;display:flex;flex-direction:column;padding:20px;overflow-y:auto;box-sizing:border-box;background-color:#1e1e1e}
|
||||
.content-section{display:none}
|
||||
.content-section.active{display:block}
|
||||
form{margin-top:20px}
|
||||
form label{display:block;margin-bottom:5px;color:white}
|
||||
form input[type="text"]{width:100%;padding:8px;margin-bottom:10px;border:1px solid #555;border-radius:4px;background-color:#07422f40;color:#fff;cursor:text;pointer-events:auto}
|
||||
form input[type="text"]:focus{outline:none;border-color:#007acc;background-color:#3d3d3d}
|
||||
form input[type="text"]:hover{border-color:#666}
|
||||
.default-badge{display:inline-block;padding:2px 8px;margin-left:8px;background-color:#007acc;color:white;border-radius:12px;font-size:.85em;font-weight:700}
|
||||
/* ===== Bjorn-scoped Modal (avoid conflicts with global.js) ===== */
|
||||
.bj-modal{display:none;position:fixed;z-index:1000;inset:0;overflow:auto;background-color:rgba(0,0,0,.5)}
|
||||
.bj-modal__content{background-color:#2d2d2d;margin:10% auto;padding:20px;border:1px solid #888;width:80%;max-width:fit-content;border-radius:8px;z-index:1001;color:#fff}
|
||||
.bj-modal__close{color:#aaa;float:right;font-size:28px;font-weight:700;cursor:pointer}
|
||||
.bj-modal__close:hover,.bj-modal__close:focus{color:#fff;text-decoration:none}
|
||||
/* ===== Bjorn-scoped Loading Overlay ===== */
|
||||
.bj-loading-overlay{display:none;position:fixed;z-index:1100;inset:0;background-color:rgba(0,0,0,.7);justify-content:center;align-items:center}
|
||||
.bj-rotating-arrow{width:50px;height:50px;border:5px solid transparent;border-top:5px solid #007acc;border-right:5px solid #007acc;border-radius:50%;animation:spin 1.5s linear infinite,bj-pulse 1.5s ease-in-out infinite}
|
||||
@keyframes bj-pulse{0%{box-shadow:0 0 0 0 rgba(0,122,204,.7)}70%{box-shadow:0 0 0 20px rgba(0,122,204,0)}100%{box-shadow:0 0 0 0 rgba(0,122,204,0)}}
|
||||
/* Update message bubble */
|
||||
#bj-update-message{background-color:#28a745;color:#fff;padding:12px 20px;border-radius:25px;display:inline-block;margin-bottom:15px;box-shadow:0 4px 6px rgba(0,0,0,.1);font-size:16px;max-width:100%;word-wrap:break-word}
|
||||
#bj-update-message.fade-in{animation:bjFadeIn .5s ease-in-out}
|
||||
@keyframes bjFadeIn{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}
|
||||
/* Responsive */
|
||||
@media (max-width:768px){.main-container{flex-direction:column}}
|
||||
@media (min-width:769px){.menu-icon{display:none}.side-menu{transform:translateX(0);position:relative;height:98%;z-index:10000}}
|
||||
.form-control{cursor:text;pointer-events:auto;background-color:#2d2d2d;color:#ffffff}
|
||||
.backups-table button.loading{position:relative;pointer-events:none;opacity:.6;background-color:#2d2d2d;color:#fff;border:#007acc}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sidehead">
|
||||
<div class="spacer"></div>
|
||||
<button class="btn" id="hideSidebar"><span class="icon">⟵</span><span class="label">Hide</span></button>
|
||||
</div>
|
||||
|
||||
<li class="list-item" data-section="backup-section"><img src="/web/images/backuprestore.png" alt="Icon_backup" style="height:72px;"><span>Backup / Restore</span></li>
|
||||
<li class="list-item" data-section="update-section"><img src="/web/images/update.png" alt="Icon_update" style="height:72px;"><span>Update</span></li>
|
||||
|
||||
<div class="sidecontent" id="sidecontent">
|
||||
<div class="content-section" id="logs-section-content">
|
||||
<h2>Clear Logs</h2>
|
||||
<button class="btn danger" onclick="clear_logs()">Clear Logs</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="empty-list-hint" style="display:none;opacity:.8;margin-top:8px;font-size:.95em">No attacks found. Import a .py attack with “Add Attack”.</div>
|
||||
</aside>
|
||||
|
||||
<div class="main" id="main">
|
||||
<!-- Backup and Restore Section -->
|
||||
<div class="content-section" id="backup-section-content">
|
||||
<h2>Backup and Restore</h2>
|
||||
<form id="backup-form">
|
||||
<label for="backup-description">Backup Description:</label>
|
||||
<input type="text" id="backup-description" class="form-control" name="description" required>
|
||||
<button type="submit" class="btn">Create Backup</button>
|
||||
</form>
|
||||
|
||||
<h3>Backup List</h3>
|
||||
<table id="backups-table" class="backups-table">
|
||||
<thead><tr><th>Date</th><th>Description</th><th>Actions</th></tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Update Application Section -->
|
||||
<div class="content-section" id="update-section-content">
|
||||
<div id="bj-update-message" style="margin-bottom:10px;"></div>
|
||||
<h2>Update Application (From Github)</h2>
|
||||
<button class="btn" onclick="bj_checkUpdate()">Check for Updates</button>
|
||||
<button class="btn" onclick="bj_update_application('upgrade')">Upgrade (With options to keep your data)</button>
|
||||
<button class="btn danger" onclick="bj_update_application('fresh_start')">Fresh Start (Replace everything)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bjorn-scoped Loading Overlay -->
|
||||
<div id="bj-loading-overlay" class="bj-loading-overlay"><div class="bj-rotating-arrow"></div></div>
|
||||
|
||||
<!-- Bjorn-scoped Restore/Update Modal -->
|
||||
<div id="bj-restore-modal" class="bj-modal" aria-hidden="true" role="dialog">
|
||||
<div class="bj-modal__content" role="document">
|
||||
<span class="bj-modal__close" id="bj-modal-close" aria-label="Close">×</span>
|
||||
<h2>Restore Options</h2>
|
||||
<form id="bj-restore-form">
|
||||
<p>Please select the folders to keep during restoration:</p>
|
||||
<label><input type="checkbox" name="keep" value="data"> Keep the <strong>data</strong> folder (/home/bjorn/Bjorn/data)</label><br>
|
||||
<label><input type="checkbox" name="keep" value="resources"> Keep the <strong>resources</strong> folder (/home/bjorn/Bjorn/resources)</label><br>
|
||||
<label><input type="checkbox" name="keep" value="actions"> Keep the <strong>actions</strong> folder (/home/bjorn/Bjorn/actions)</label><br>
|
||||
<label><input type="checkbox" name="keep" value="config"> Keep the <strong>config</strong> folder (/home/bjorn/Bjorn/config)</label><br><br>
|
||||
<button type="submit" class="btn">Restore Backup</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ======================================================================
|
||||
// Bjorn UI helpers — non-blocking toast + confirm over your global.js
|
||||
// ======================================================================
|
||||
const t = (msg, ms=2600) => (typeof window.toast === 'function' ? window.toast(msg, ms) : console.log(msg));
|
||||
|
||||
// Lightweight, non-blocking confirmation; auto-cleans DOM; returns Promise<boolean>
|
||||
async function toastConfirm(message, { okText='Proceed', cancelText='Cancel', timeout=0 } = {}) {
|
||||
return new Promise(resolve => {
|
||||
const box = document.createElement('div');
|
||||
Object.assign(box.style, { position:'fixed', right:'16px', bottom:'16px', zIndex:99999, maxWidth:'460px', background:'rgba(10,16,16,.96)', color:'#eafff6', border:'1px solid rgba(0,255,154,.35)', borderRadius:'12px', padding:'12px 14px', boxShadow:'0 10px 24px rgba(0,0,0,.35)', font:'14px/1.45 system-ui' });
|
||||
box.innerHTML = `
|
||||
<div style="margin-bottom:10px">${message}</div>
|
||||
<div style="display:flex; gap:8px; justify-content:flex-end">
|
||||
<button data-x style="background:#333;border:1px solid #555;color:#fff;padding:6px 10px;border-radius:8px;cursor:pointer">${cancelText}</button>
|
||||
<button data-ok style="background:#00ff9a;border:1px solid #00ff9a33;color:#001b11;padding:6px 10px;border-radius:8px;cursor:pointer;font-weight:700">${okText}</button>
|
||||
</div>`;
|
||||
document.body.appendChild(box);
|
||||
const done = v => { try { box.remove(); } catch {} resolve(v); };
|
||||
box.querySelector('[data-ok]').addEventListener('click', () => done(true));
|
||||
box.querySelector('[data-x]').addEventListener('click', () => done(false));
|
||||
if (timeout > 0) setTimeout(() => done(false), timeout);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
/* ===== Sections wiring ===== */
|
||||
const sections = {
|
||||
'logs-section': document.getElementById('logs-section-content'),
|
||||
'backup-section': document.getElementById('backup-section-content'),
|
||||
'update-section': document.getElementById('update-section-content'),
|
||||
};
|
||||
|
||||
const defaultSection = 'backup-section';
|
||||
const defaultSectionElement = document.querySelector(`[data-section="${defaultSection}"]`);
|
||||
if (defaultSectionElement) {
|
||||
defaultSectionElement.classList.add('selected');
|
||||
if (sections[defaultSection]) {
|
||||
sections[defaultSection].classList.add('active');
|
||||
loadBackups();
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Scoped modal/overlay helpers ===== */
|
||||
const bjModal = document.getElementById('bj-restore-modal');
|
||||
const bjModalClose = document.getElementById('bj-modal-close');
|
||||
|
||||
function bj_showLoading(){const overlay=document.getElementById('bj-loading-overlay'); if(overlay) overlay.style.display='flex'}
|
||||
function bj_hideLoading(){const overlay=document.getElementById('bj-loading-overlay'); if(overlay) overlay.style.display='none'}
|
||||
|
||||
function hideAllContents(){for (let key in sections) sections[key].classList.remove('active'); document.querySelectorAll('.list-item').forEach(item => item.classList.remove('selected'))}
|
||||
|
||||
function selectSection(event){
|
||||
const clickedItem = event.currentTarget;
|
||||
const section = clickedItem.getAttribute('data-section');
|
||||
hideAllContents();
|
||||
if (sections[section]) {
|
||||
sections[section].classList.add('active');
|
||||
clickedItem.classList.add('selected');
|
||||
if (section === 'backup-section') { loadBackups(); }
|
||||
else if (section === 'update-section') { bj_checkUpdate(); }
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('.list-item').forEach(item => item.addEventListener('click', selectSection));
|
||||
|
||||
/* ===== API: Update Check (shows status in green bubble + toast) ===== */
|
||||
window.bj_checkUpdate = function(){
|
||||
fetch('/check_update', { method:'GET', headers:{ 'Content-Type':'application/json' } })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const messageDiv = document.getElementById('bj-update-message');
|
||||
if (data.update_available) {
|
||||
messageDiv.innerHTML = `New update available: <strong>${data.latest_version}</strong> (currently on: <strong>${data.current_version}</strong>)`;
|
||||
t('⬆️ Update available');
|
||||
} else {
|
||||
messageDiv.innerHTML = `You are on the latest version: <strong>${data.current_version}</strong>`;
|
||||
t('✅ Latest version already installed');
|
||||
}
|
||||
messageDiv.classList.remove('fade-in'); void messageDiv.offsetWidth; messageDiv.classList.add('fade-in');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error checking updates:', err);
|
||||
const messageDiv = document.getElementById('bj-update-message');
|
||||
messageDiv.innerHTML = `Error checking updates.`;
|
||||
messageDiv.classList.remove('fade-in'); void messageDiv.offsetWidth; messageDiv.classList.add('fade-in');
|
||||
t('⛔ Error while checking updates — see console');
|
||||
});
|
||||
};
|
||||
|
||||
/* ===== Backups: list/load (toast feedback) ===== */
|
||||
function loadBackups(){
|
||||
fetch('/list_backups', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({}) })
|
||||
.then(response => { if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); })
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
const tbody = document.querySelector('#backups-table tbody'); tbody.innerHTML = '';
|
||||
data.backups.forEach(backup => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${backup.date}</td>
|
||||
<td>${backup.description}${backup.is_default ? ' <span class="default-badge">default</span>' : ''}</td>
|
||||
<td style="display:flex;gap:5px;flex-wrap:wrap">
|
||||
<button class="btn" onclick="bj_openRestoreModal('${backup.filename}')">Restore</button>
|
||||
${!backup.is_default ? `
|
||||
<button class="btn danger" onclick="bj_deleteBackup('${backup.filename}', event)">Delete</button>
|
||||
<button class="btn" onclick="bj_setAsDefault('${backup.filename}', event)">Set as Default</button>
|
||||
` : ''}
|
||||
</td>`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
t('✅ Backups loaded');
|
||||
} else {
|
||||
t('⚠️ ' + (data.message || 'Error while loading backups'));
|
||||
}
|
||||
})
|
||||
.catch(error => { console.error('Error loading backups:', error); t('⛔ Failed to load backups — see console'); });
|
||||
}
|
||||
|
||||
/* ===== Create backup (toast feedback, no blocking alert) ===== */
|
||||
const backupForm = document.getElementById('backup-form');
|
||||
backupForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const description = document.getElementById('backup-description').value;
|
||||
const button = backupForm.querySelector('button');
|
||||
bj_showLoading(); button.classList.add('loading');
|
||||
|
||||
fetch('/create_backup', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ description }) })
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||
.then(data => {
|
||||
button.classList.remove('loading'); bj_hideLoading();
|
||||
if (data.status === 'success') { t('✅ ' + (data.message || 'Backup created')); loadBackups(); backupForm.reset(); }
|
||||
else { t('⚠️ ' + (data.message || 'Backup failed')); }
|
||||
})
|
||||
.catch(error => { button.classList.remove('loading'); bj_hideLoading(); console.error('Error creating backup:', error); t('⛔ Error while creating the backup — see console'); });
|
||||
});
|
||||
|
||||
/* ===== Set default backup ===== */
|
||||
window.bj_setAsDefault = function(filename, ev){
|
||||
const button = ev?.target; if (button) button.classList.add('loading');
|
||||
fetch('/set_default_backup', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ filename }) })
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||
.then(data => {
|
||||
if (button) button.classList.remove('loading');
|
||||
if (data.status === 'success') { t('✅ Default backup set'); loadBackups(); }
|
||||
else { t('⚠️ ' + (data.message || 'Could not set default')); }
|
||||
})
|
||||
.catch(error => { if (button) button.classList.remove('loading'); console.error('Error setting default backup:', error); t('⛔ Failed to set default — see console'); });
|
||||
};
|
||||
|
||||
/* ===== Open modal for restore ===== */
|
||||
window.bj_openRestoreModal = function (filename){
|
||||
window.bj_filenameToRestore = filename;
|
||||
window.bj_updateMode = null;
|
||||
document.querySelector('#bj-restore-modal .bj-modal__content h2').textContent = 'Restore Options';
|
||||
document.querySelector('#bj-restore-modal .bj-modal__content button').textContent = 'Restore Backup';
|
||||
bjModal.style.display = 'block';
|
||||
bjModal.setAttribute('aria-hidden', 'false');
|
||||
};
|
||||
|
||||
/* ===== Update (shared) ===== */
|
||||
function proceedWithUpdate(mode, keeps, ev){
|
||||
const button = ev?.target; if (button) button.classList.add('loading');
|
||||
const bodyData = { mode, keeps };
|
||||
fetch('/update_application', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify(bodyData) })
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||
.then(data => {
|
||||
if (button) button.classList.remove('loading');
|
||||
if (data.status === 'success') { t('✅ ' + (data.message || 'Application updated')); bjModal.style.display='none'; bjModal.setAttribute('aria-hidden','true'); }
|
||||
else { t('⚠️ ' + (data.message || 'Update failed')); }
|
||||
})
|
||||
.catch(error => { if (button) button.classList.remove('loading'); console.error('Error updating application:', error); t('⛔ Update failed — see console'); });
|
||||
}
|
||||
|
||||
/* ===== Update application (toastConfirm instead of confirm) ===== */
|
||||
window.bj_update_application = async function (mode){
|
||||
const msg = mode === 'upgrade' ? 'Proceed with application upgrade?' : 'Fresh Start will delete all data. Continue?';
|
||||
const ok = await toastConfirm(msg, { okText:'Proceed', cancelText:'Cancel' });
|
||||
if (!ok) { t('ℹ️ Cancelled'); return; }
|
||||
|
||||
if (mode === 'upgrade') {
|
||||
window.bj_updateMode = mode;
|
||||
window.bj_filenameToRestore = null;
|
||||
document.querySelector('#bj-restore-modal .bj-modal__content h2').textContent = 'Update Options';
|
||||
document.querySelector('#bj-restore-modal .bj-modal__content button').textContent = 'Update Application';
|
||||
document.getElementById('bj-restore-form').reset();
|
||||
bjModal.style.display = 'block';
|
||||
bjModal.setAttribute('aria-hidden', 'false');
|
||||
} else {
|
||||
bj_showLoading();
|
||||
proceedWithUpdate(mode, []);
|
||||
bj_hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
/* ===== Restore form submit (handles restore or upgrade) ===== */
|
||||
document.getElementById('bj-restore-form').addEventListener('submit', function(e){
|
||||
e.preventDefault();
|
||||
const keeps = Array.from(this.querySelectorAll('input[name="keep"]:checked')).map(cb => cb.value);
|
||||
|
||||
if (window.bj_updateMode === 'upgrade') {
|
||||
proceedWithUpdate('upgrade', keeps, e);
|
||||
} else {
|
||||
const filename = window.bj_filenameToRestore;
|
||||
if (!filename) { t('⚠️ No backup file selected'); return; }
|
||||
|
||||
const mode = keeps.length > 0 ? 'selective_restore' : 'full_restore';
|
||||
const bodyData = { filename, mode, keeps };
|
||||
|
||||
const button = this.querySelector('button');
|
||||
button.classList.add('loading'); bj_showLoading();
|
||||
|
||||
fetch('/restore_backup', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify(bodyData) })
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||
.then(data => {
|
||||
button.classList.remove('loading'); bj_hideLoading();
|
||||
if (data.status === 'success') { t('✅ ' + (data.message || 'Backup restored')); loadBackups(); this.reset(); bjModal.style.display='none'; bjModal.setAttribute('aria-hidden','true'); }
|
||||
else { t('⚠️ ' + (data.message || 'Restore failed')); }
|
||||
})
|
||||
.catch(error => { button.classList.remove('loading'); bj_hideLoading(); console.error('Error restoring backup:', error); t('⛔ Restore failed — see console'); });
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== Delete backup (toastConfirm instead of confirm) ===== */
|
||||
window.bj_deleteBackup = async function (filename, ev){
|
||||
const ok = await toastConfirm('Delete this backup?', { okText:'Delete', cancelText:'Cancel' });
|
||||
if (!ok) { t('ℹ️ Deletion cancelled'); return; }
|
||||
|
||||
const button = ev?.target; if (button) button.classList.add('loading');
|
||||
bj_showLoading();
|
||||
|
||||
fetch('/delete_backup', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ filename }) })
|
||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||
.then(data => {
|
||||
if (button) button.classList.remove('loading'); bj_hideLoading();
|
||||
if (data.status === 'success') { t('✅ ' + (data.message || 'Backup deleted')); loadBackups(); }
|
||||
else { t('⚠️ ' + (data.message || 'Delete failed')); }
|
||||
})
|
||||
.catch(error => { if (button) button.classList.remove('loading'); bj_hideLoading(); console.error('Error deleting backup:', error); t('⛔ Delete failed — see console'); });
|
||||
};
|
||||
|
||||
/* ===== Modal close handlers (scoped) ===== */
|
||||
bjModalClose.addEventListener('click', function(){
|
||||
bjModal.style.display = 'none'; bjModal.setAttribute('aria-hidden', 'true'); window.bj_updateMode = null; window.bj_filenameToRestore = null;
|
||||
});
|
||||
window.addEventListener('click', function(event){
|
||||
if (event.target === bjModal) {
|
||||
bjModal.style.display = 'none'; bjModal.setAttribute('aria-hidden','true'); window.bj_updateMode = null; window.bj_filenameToRestore = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
164
web/bjorn.html
@@ -1,164 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Bjorn Cyberviking - Bjorn</title>
|
||||
<link rel="icon" href="web/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="web/css/global.css">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/web/images/icon-192x192.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#333">
|
||||
<script src="web/js/global.js"></script>
|
||||
<style>
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: calc(100vh - 70px); /* Adjust height to fit with mobile */
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
height: -webkit-fill-available;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.image-container img:active {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.topbar.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-container.fullscreen img {
|
||||
height: 100vh;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
|
||||
.image-container {
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<script src="web/js/global.js"></script>
|
||||
|
||||
<script defer>
|
||||
var delay = 5000; // Default value in case the fetch fails
|
||||
var intervalId;
|
||||
|
||||
function fetchWebDelay() {
|
||||
fetch('/get_web_delay')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
delay = data.web_delay;
|
||||
startLiveview(); // Start live view after setting the delay
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching web delay:', error);
|
||||
startLiveview(); // Start live view even if fetch fails
|
||||
});
|
||||
}
|
||||
|
||||
function updateImage() {
|
||||
var image = document.getElementById("screenImage_Home");
|
||||
var newImage = new Image();
|
||||
newImage.onload = function() {
|
||||
image.src = newImage.src; // Update only if the new image loads successfully
|
||||
};
|
||||
newImage.onerror = function() {
|
||||
console.warn("New image could not be loaded, keeping the previous image.");
|
||||
};
|
||||
newImage.src = "screen.png?t=" + new Date().getTime(); // Prevent caching
|
||||
}
|
||||
|
||||
function startLiveview() {
|
||||
updateImage(); // Immediately update the image
|
||||
intervalId = setInterval(updateImage, delay); // Then update at the specified interval
|
||||
}
|
||||
|
||||
function stopLiveview() {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
|
||||
function resizeImage(event) {
|
||||
var image = document.getElementById("screenImage_Home");
|
||||
var height = parseFloat(window.getComputedStyle(image).height);
|
||||
if (event.deltaY < 0) {
|
||||
// Scroll up, increase height
|
||||
height *= 1.1;
|
||||
} else {
|
||||
// Scroll down, decrease height
|
||||
height /= 1.1;
|
||||
}
|
||||
image.style.height = height + 'px';
|
||||
image.style.width = 'auto'; // Ensure aspect ratio is maintained
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
var topbar = document.querySelector('.topbar');
|
||||
var bottombar = document.querySelector('.bottombar');
|
||||
var console = document.querySelector('.console');
|
||||
var imageContainer = document.querySelector('.image-container');
|
||||
if (topbar.style.display === 'flex') {
|
||||
topbar.style.display = 'none';
|
||||
imageContainer.style.width = '100%'; // Adjust width when toolbar is hidden
|
||||
} else {
|
||||
topbar.style.display = 'flex';
|
||||
imageContainer.style.width = 'calc(100%)'; // Adjust width when toolbar is visible
|
||||
}
|
||||
if (bottombar) {
|
||||
if (bottombar.style.display === 'grid') {
|
||||
bottombar.style.display = 'none';
|
||||
} else {
|
||||
bottombar.style.display = 'grid';
|
||||
}
|
||||
}
|
||||
if (console) {
|
||||
if (console.style.display === 'grid') {
|
||||
console.style.display = 'none';
|
||||
} else {
|
||||
console.style.display = 'grid';
|
||||
imageContainer.style.width = 'calc(100%)'; // Adjust width when toolbar is visible
|
||||
}
|
||||
}
|
||||
adjustImageHeight(); // Adjust image height after toggling toolbar
|
||||
}
|
||||
|
||||
function adjustImageHeight() {
|
||||
var windowHeight = window.innerHeight;
|
||||
var image = document.getElementById("screenImage_Home");
|
||||
image.style.height = windowHeight + 'px';
|
||||
image.style.width = 'auto'; // Maintain aspect ratio
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
fetchWebDelay(); // Fetch web delay on load
|
||||
adjustImageHeight();
|
||||
});
|
||||
window.addEventListener('beforeunload', stopLiveview);
|
||||
window.addEventListener('wheel', resizeImage);
|
||||
window.addEventListener('resize', adjustImageHeight); // Adjust height on window resize
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="image-container">
|
||||
<img id="screenImage_Home" src="screen.png" onclick="toggleMenu()" alt="Bjorn">
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,726 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Bjorn Cyberviking - Credentials</title>
|
||||
<link rel="icon" href="web/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="web/css/global.css">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/web/images/icon-192x192.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#333">
|
||||
<script src="web/js/global.js"></script>
|
||||
|
||||
<style>
|
||||
/* ========= Styles alignés sur global.css (pas de nouvelle palette) ========= */
|
||||
:root{
|
||||
/* aucun override agressif : seulement des fallbacks doux */
|
||||
--_bg: var(--bg, #0b0c0f);
|
||||
--_panel: var(--c-panel-2, rgba(16,22,22,.55));
|
||||
--_border: var(--c-border, rgba(255,255,255,.08));
|
||||
--_ink: var(--ink, #e9ecef);
|
||||
--_muted: var(--muted, #a5adb6);
|
||||
--_acid1: var(--acid, #00ff9a);
|
||||
--_acid2: var(--acid-2, #18f0ff);
|
||||
--_shadow: var(--shadow, 0 10px 26px rgba(0,0,0,.35));
|
||||
}
|
||||
|
||||
/* fond + typographie harmonisés */
|
||||
body{
|
||||
background: var(--_bg);
|
||||
color: var(--_ink);
|
||||
font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','Inter',system-ui,sans-serif;
|
||||
min-height:100vh; overflow-x:hidden;
|
||||
}
|
||||
|
||||
/* conteneur principal */
|
||||
.main{ padding:16px; }
|
||||
|
||||
/* barre de stats */
|
||||
.stats-bar{
|
||||
display:flex; gap:12px; flex-wrap:wrap;
|
||||
padding:12px;
|
||||
background: color-mix(in oklab, var(--_panel) 88%, transparent);
|
||||
border:1px solid var(--_border);
|
||||
border-radius:12px;
|
||||
box-shadow: var(--_shadow);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
.stat-item{
|
||||
display:flex; align-items:center; gap:8px;
|
||||
padding:8px 12px;
|
||||
border:1px solid var(--_border);
|
||||
border-radius:10px;
|
||||
background: color-mix(in oklab, var(--_panel) 70%, transparent);
|
||||
}
|
||||
.stat-icon{ font-size:1.1rem; opacity:.9 }
|
||||
.stat-value{
|
||||
font-weight:800;
|
||||
background: linear-gradient(135deg, var(--_acid1), var(--_acid2));
|
||||
-webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
|
||||
}
|
||||
.stat-label{ color: var(--_muted); font-size:.8rem }
|
||||
|
||||
/* recherche globale */
|
||||
.global-search-container{ position:relative }
|
||||
.global-search-input{
|
||||
width:100%; padding:10px 14px; border-radius:12px;
|
||||
border:1px solid var(--_border);
|
||||
background: color-mix(in oklab, var(--_panel) 90%, transparent);
|
||||
color: var(--_ink);
|
||||
}
|
||||
.global-search-input:focus{
|
||||
outline:none;
|
||||
border-color: color-mix(in oklab, var(--_acid2) 40%, var(--_border));
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--_acid2) 18%, transparent);
|
||||
}
|
||||
.clear-global-button{
|
||||
position:absolute; right:10px; top:50%; transform:translateY(-50%);
|
||||
background:none; border:1px solid var(--_border);
|
||||
color:#ef4444; border-radius:8px; padding:2px 6px; display:none;
|
||||
}
|
||||
.clear-global-button.show{ display:block }
|
||||
|
||||
/* tabs collants */
|
||||
.tabs-container{
|
||||
position:sticky; top:0; z-index:20;
|
||||
display:flex; align-items:center; gap:8px;
|
||||
padding:8px 12px; min-height:44px;
|
||||
overflow-x:auto; -webkit-overflow-scrolling:touch;
|
||||
background: color-mix(in oklab, var(--_panel) 92%, transparent);
|
||||
border:1px solid var(--_border); border-radius:12px;
|
||||
box-shadow: var(--_shadow);
|
||||
}
|
||||
.tabs-container::-webkit-scrollbar{ height:0 }
|
||||
.tab{
|
||||
padding:10px 18px; border-radius:10px; cursor:pointer;
|
||||
color: var(--_muted); font-weight:700; font-size:.9rem;
|
||||
border:1px solid transparent; white-space:nowrap; flex:0 0 auto;
|
||||
}
|
||||
.tab:hover{ background: rgba(255,255,255,.05); color: var(--_ink); border-color: var(--_border) }
|
||||
.tab.active{
|
||||
color: var(--_ink);
|
||||
background: linear-gradient(135deg, color-mix(in oklab, var(--_acid2) 18%, transparent), color-mix(in oklab, var(--_acid1) 14%, transparent));
|
||||
border-color: color-mix(in oklab, var(--_acid2) 28%, var(--_border));
|
||||
}
|
||||
.tab-badge{
|
||||
margin-left:8px; padding:2px 6px; border-radius:999px;
|
||||
background: rgba(255,255,255,.1); border:1px solid var(--_border);
|
||||
font-size:.75rem;
|
||||
}
|
||||
|
||||
/* grille & cartes services */
|
||||
.credentials-container{ display:flex; flex-direction:column; gap:12px; scroll-padding-top:56px; }
|
||||
.services-grid{ display:flex; flex-direction:column; gap:12px }
|
||||
|
||||
.service-card{
|
||||
background: color-mix(in oklab, var(--_panel) 88%, transparent);
|
||||
border:1px solid var(--_border);
|
||||
border-radius:16px; overflow:hidden;
|
||||
box-shadow: var(--_shadow);
|
||||
}
|
||||
.service-header{
|
||||
display:flex; align-items:center; gap:8px; padding:12px;
|
||||
cursor:pointer; user-select:none;
|
||||
border-bottom:1px solid color-mix(in oklab, var(--_border) 65%, transparent);
|
||||
}
|
||||
.service-header:hover{ background: rgba(255,255,255,.04) }
|
||||
.service-title{
|
||||
flex:1; font-weight:800; letter-spacing:.2px; font-size:.95rem; text-transform:uppercase;
|
||||
background: linear-gradient(135deg, var(--_acid1), var(--_acid2));
|
||||
-webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
|
||||
white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
|
||||
}
|
||||
.service-count{
|
||||
font-weight:800; font-size:.8rem; padding:4px 8px; border-radius:10px;
|
||||
background: rgba(255,255,255,.08); color: var(--_ink); border:1px solid var(--_border);
|
||||
}
|
||||
.service-card[data-credentials]:not([data-credentials="0"]) .service-count{
|
||||
background: linear-gradient(135deg,#2e2e2e,#4CAF50);
|
||||
box-shadow: inset 0 0 0 1px rgba(76,175,80,.35);
|
||||
}
|
||||
.search-container{ position:relative }
|
||||
.search-input{
|
||||
padding:6px 24px 6px 8px; border:none; border-radius:10px;
|
||||
background: rgba(255,255,255,.06); color: var(--_ink); font-size:.82rem;
|
||||
}
|
||||
.search-input:focus{ outline:none; background: rgba(255,255,255,.1) }
|
||||
.clear-button{
|
||||
position:absolute; right:4px; top:50%; transform:translateY(-50%);
|
||||
border:none; background:none; color:#ef4444; cursor:pointer; display:none;
|
||||
}
|
||||
.clear-button.show{ display:block }
|
||||
.download-button{
|
||||
border:1px solid var(--_border); background: rgba(255,255,255,.04);
|
||||
color: var(--_muted); border-radius:8px; padding:4px 8px; cursor:pointer;
|
||||
}
|
||||
.download-button:hover{ color:#e99f00; filter:brightness(1.06) }
|
||||
|
||||
.collapse-indicator{ color: var(--_muted) }
|
||||
.service-card.collapsed .service-content{ max-height:0; overflow:hidden }
|
||||
|
||||
.service-content{ padding:8px 12px }
|
||||
|
||||
/* éléments d’identifiants */
|
||||
.credential-item{
|
||||
border:1px solid var(--_border); border-radius:10px; margin-bottom:6px; padding:8px;
|
||||
background: rgba(255,255,255,.02);
|
||||
display:grid; grid-template-columns: repeat(auto-fit, minmax(120px,1fr)); gap:8px;
|
||||
}
|
||||
.credential-field{ display:flex; align-items:center; gap:6px }
|
||||
.field-label{ font-size:.78rem; color: var(--_muted) }
|
||||
.field-value{
|
||||
flex:1; padding:2px 6px; border-radius:8px; cursor:pointer;
|
||||
white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
|
||||
border:1px solid transparent;
|
||||
}
|
||||
.field-value:hover{ background: rgba(255,255,255,.06); border-color: var(--_border) }
|
||||
|
||||
/* bulles */
|
||||
.bubble-blue{ background: linear-gradient(135deg,#1d2a32,#00c4d6); color:#fff }
|
||||
.bubble-green{ background: linear-gradient(135deg,#1e2a24,#00b894); color:#fff }
|
||||
.bubble-orange{ background: linear-gradient(135deg,#3b2f1a,#e7951a); color:#fff }
|
||||
|
||||
/* toast */
|
||||
.copied-feedback{
|
||||
position:fixed; left:50%; bottom:20px; transform:translateX(-50%);
|
||||
padding:8px 12px; background:#4CAF50; color:#fff; border-radius:10px;
|
||||
box-shadow: var(--_shadow); opacity:0; transition:opacity .25s; z-index:9999;
|
||||
}
|
||||
.copied-feedback.show{ opacity:1 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="main" id="main">
|
||||
<div class="credentials-container">
|
||||
|
||||
<div class="stats-bar">
|
||||
<div class="stat-item"><span class="stat-icon">🧩</span><span class="stat-value" id="stat-services">0</span><span class="stat-label">services</span></div>
|
||||
<div class="stat-item"><span class="stat-icon">🔐</span><span class="stat-value" id="stat-creds">0</span><span class="stat-label">credentials</span></div>
|
||||
<div class="stat-item"><span class="stat-icon">🖥️</span><span class="stat-value" id="stat-hosts">0</span><span class="stat-label">unique hosts</span></div>
|
||||
</div>
|
||||
|
||||
<div class="global-search-container">
|
||||
<input type="text" id="global-search-input" class="global-search-input" placeholder="Search Credentials..." oninput="filterAllServices()" />
|
||||
<button class="clear-global-button" onclick="clearGlobalSearch()">✖</button>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="tabs-container" id="cred-tabs"></div>
|
||||
|
||||
<div class="services-grid" id="credentials-grid"></div>
|
||||
</div>
|
||||
|
||||
<div class="copied-feedback">Copied to clipboard!</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
let fontSize = 12;
|
||||
|
||||
/* =========================
|
||||
Global state
|
||||
========================= */
|
||||
let currentCategory = 'all';
|
||||
let searchGlobal = '';
|
||||
let serviceData = []; // [{ service, category, credentials:{headers,rows} }]
|
||||
|
||||
/* =========================
|
||||
Helpers
|
||||
========================= */
|
||||
function toCaps(s){ return (s||'').toUpperCase(); }
|
||||
function slugify(s){ return (s||'').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,''); }
|
||||
function normalizeMac(v){
|
||||
if (!v) return null;
|
||||
const raw = String(v).toLowerCase().replace(/[^0-9a-f]/g,'');
|
||||
if (raw.length !== 12) return null;
|
||||
return raw.match(/.{2}/g).join(':');
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Persistence (cards & tabs)
|
||||
========================= */
|
||||
const LS_CARD_PREFIX = 'cred:card:collapsed:'; // per service
|
||||
const LS_TAB_PREFIX = 'cred:tab:autoexpand:'; // per category (tab)
|
||||
|
||||
function setCardCollapsed(service, collapsed){
|
||||
try { localStorage.setItem(LS_CARD_PREFIX+service, collapsed ? '1' : '0'); } catch {}
|
||||
}
|
||||
function getCardCollapsed(service){
|
||||
try {
|
||||
const v = localStorage.getItem(LS_CARD_PREFIX+service);
|
||||
return v === null ? null : (v === '1');
|
||||
} catch { return null; }
|
||||
}
|
||||
function setTabAutoExpand(cat, on){ try { localStorage.setItem(LS_TAB_PREFIX+cat, on?'1':'0'); } catch {} }
|
||||
function isTabAutoExpand(cat){ try { return localStorage.getItem(LS_TAB_PREFIX+cat) === '1'; } catch { return false; } }
|
||||
|
||||
/** Apply persisted collapse states (card-by-card) and tab auto-expand. */
|
||||
function applyPersistedCollapse(){
|
||||
document.querySelectorAll('.service-card').forEach(card=>{
|
||||
const svc = card.dataset.service;
|
||||
const st = getCardCollapsed(svc); // null => no explicit user choice yet
|
||||
if (st === true) card.classList.add('collapsed');
|
||||
if (st === false) card.classList.remove('collapsed');
|
||||
});
|
||||
|
||||
// Auto-expand for the active tab: only open cards without an explicit preference
|
||||
if (isTabAutoExpand(currentCategory)){
|
||||
document.querySelectorAll('.service-card').forEach(card=>{
|
||||
const svc = card.dataset.service;
|
||||
if (getCardCollapsed(svc) === null){
|
||||
card.classList.remove('collapsed');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Touch-friendly horizontal drag (no pointer capture)
|
||||
========================= */
|
||||
function enableTabsDragScroll(el){
|
||||
let isDown = false, startX = 0, startLeft = 0, moved = false;
|
||||
|
||||
const down = (e) => {
|
||||
isDown = true;
|
||||
moved = false;
|
||||
startX = e.pageX || (e.touches && e.touches[0].pageX) || 0;
|
||||
startLeft = el.scrollLeft;
|
||||
};
|
||||
const move = (e) => {
|
||||
if (!isDown) return;
|
||||
const x = e.pageX || (e.touches && e.touches[0].pageX) || 0;
|
||||
const dx = x - startX;
|
||||
if (Math.abs(dx) > 3) moved = true; // small threshold to distinguish click
|
||||
el.scrollLeft = startLeft - dx;
|
||||
};
|
||||
const up = () => { isDown = false; };
|
||||
|
||||
el.addEventListener('pointerdown', down, {passive:true});
|
||||
window.addEventListener('pointermove', move, {passive:true});
|
||||
window.addEventListener('pointerup', up, {passive:true});
|
||||
window.addEventListener('pointercancel', up, {passive:true});
|
||||
|
||||
// If a drag happened, swallow the synthetic click
|
||||
el.addEventListener('click', (e) => {
|
||||
if (moved) { e.preventDefault(); e.stopPropagation(); moved = false; }
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tabsEl = document.getElementById('cred-tabs');
|
||||
if (tabsEl) enableTabsDragScroll(tabsEl);
|
||||
});
|
||||
|
||||
/* =========================
|
||||
Categories / Tabs
|
||||
========================= */
|
||||
function getCategories(){
|
||||
const set = new Set();
|
||||
serviceData.forEach(s => set.add(s.category));
|
||||
return Array.from(set);
|
||||
}
|
||||
|
||||
/** Count credentials (rows) that match current global search, per category and total. */
|
||||
function computeBadgeCounts(){
|
||||
const map = { all: 0 };
|
||||
getCategories().forEach(cat => map[cat] = 0);
|
||||
|
||||
const needle = (searchGlobal || '').toLowerCase();
|
||||
|
||||
serviceData.forEach(svc => {
|
||||
const rows = svc.credentials.rows || [];
|
||||
let matchedCount;
|
||||
|
||||
if (!needle) {
|
||||
matchedCount = rows.length;
|
||||
} else {
|
||||
matchedCount = rows.reduce((acc, row) => {
|
||||
const text = Object.values(row).join(' ').toLowerCase();
|
||||
return acc + (text.includes(needle) ? 1 : 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
map.all += matchedCount;
|
||||
map[svc.category] = (map[svc.category] || 0) + matchedCount;
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function renderTabs(){
|
||||
const tabs = document.getElementById('cred-tabs');
|
||||
const counts = computeBadgeCounts();
|
||||
const cats = ['all', ...getCategories()];
|
||||
|
||||
tabs.innerHTML = cats.map(cat=>{
|
||||
const label = (cat==='all'?'All':toCaps(cat));
|
||||
const count = counts[cat] || 0;
|
||||
const active = (cat===currentCategory) ? 'active':'';
|
||||
return `<div class="tab ${active}" data-cat="${cat}">
|
||||
${label} <span class="tab-badge">${count}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// Click via delegation (robust with drag)
|
||||
tabs.onclick = (e) => {
|
||||
const tab = e.target.closest('.tab');
|
||||
if (!tab) return;
|
||||
|
||||
tabs.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
currentCategory = tab.dataset.cat;
|
||||
|
||||
// Keep the active tab centered (mobile nicety)
|
||||
tab.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
||||
|
||||
// Auto-expand for the selected tab (persist)
|
||||
setTabAutoExpand(currentCategory, true);
|
||||
|
||||
renderServices(); // rebuild cards for this tab
|
||||
applyPersistedCollapse(); // reapply collapse + tab auto-expand
|
||||
updateBadges();
|
||||
};
|
||||
}
|
||||
|
||||
function updateBadges(){
|
||||
const counts = computeBadgeCounts();
|
||||
document.querySelectorAll('#cred-tabs .tab').forEach(tab=>{
|
||||
const cat = tab.getAttribute('data-cat');
|
||||
const badge = tab.querySelector('.tab-badge');
|
||||
if (badge) badge.textContent = counts[cat] || 0;
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Stats bar (incl. unique hosts by MAC)
|
||||
========================= */
|
||||
function updateStatsBar(){
|
||||
const totalServices = serviceData.length;
|
||||
const totalCreds = serviceData.reduce((a,s)=>a + (s.credentials.rows?.length || 0), 0);
|
||||
|
||||
const macSet = new Set();
|
||||
serviceData.forEach(s=>{
|
||||
(s.credentials.rows||[]).forEach(r=>{
|
||||
// look for a MAC-looking field
|
||||
let macVal = null;
|
||||
for (const [k,v] of Object.entries(r)) {
|
||||
const key = (k||'').toLowerCase();
|
||||
if (key === 'mac' || key === 'mac address' || key === 'mac_address' || key.includes('mac')) {
|
||||
macVal = v; break;
|
||||
}
|
||||
}
|
||||
const norm = normalizeMac(macVal);
|
||||
if (norm) macSet.add(norm);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('stat-services').textContent = totalServices;
|
||||
document.getElementById('stat-creds').textContent = totalCreds;
|
||||
document.getElementById('stat-hosts').textContent = macSet.size;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Cards / Rows
|
||||
========================= */
|
||||
function createCredentialCard(service, credentials) {
|
||||
const credCount = credentials.rows.length;
|
||||
const borderColor = credCount > 0 ? '#4CAF50' : '#d3d3d3';
|
||||
|
||||
return `
|
||||
<div class="service-card collapsed"
|
||||
data-service="${service}"
|
||||
data-credentials="${credCount}"
|
||||
style="border-color: ${borderColor}">
|
||||
<div class="service-header" onclick="toggleServiceCollapse(this)">
|
||||
<span class="service-title">${toCaps(service)}</span>
|
||||
<span class="service-count" style="background:${credCount>0?'linear-gradient(135deg,#2e2e2e,#4CAF50)':'none'};font-weight:bold;">
|
||||
Credentials: ${credCount}
|
||||
</span>
|
||||
<div class="search-container">
|
||||
<input type="text" class="search-input"
|
||||
data-service="${service}"
|
||||
placeholder="Search..."
|
||||
oninput="filterCredentials(this, '${service}')"
|
||||
onclick="event.stopPropagation()"
|
||||
onkeyup="toggleClearButton(this)" />
|
||||
<button class="clear-button" onclick="clearSearch(this)">✖</button>
|
||||
</div>
|
||||
<button class="download-button" onclick='downloadCredentials(event, "${service}", ${JSON.stringify(credentials).replace(/"/g, '"')})'>💾</button>
|
||||
<span class="collapse-indicator">▼</span>
|
||||
</div>
|
||||
<div class="service-content">
|
||||
${createCredentialsContent(credentials)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function createCredentialsContent(credentials) {
|
||||
return credentials.rows.map(row =>
|
||||
`<div class="credential-item">
|
||||
${Object.entries(row).map(([key, value]) => {
|
||||
const bubbleClass = getBubbleClass(key);
|
||||
const val = (value ?? '').toString();
|
||||
return `
|
||||
<div class="credential-field">
|
||||
<span class="field-label">${key}</span>
|
||||
<div class="field-value ${val.trim()?bubbleClass:''}"
|
||||
data-value="${val.replace(/"/g,'"')}" onclick="copyToClipboard(this)"
|
||||
title="Click to copy">${val}</div>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
function getBubbleClass(key) {
|
||||
const k = (key||'').toLowerCase();
|
||||
if (k === 'port') return 'bubble-orange';
|
||||
if (['ip address','ip','map','hostname','mac address','mac'].includes(k)) return 'bubble-blue';
|
||||
return 'bubble-green';
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Parse backend HTML (/list_credentials)
|
||||
========================= */
|
||||
function parseTable(table) {
|
||||
const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent.trim());
|
||||
const rows = Array.from(table.querySelectorAll('tr')).slice(1).map(row => {
|
||||
const cells = Array.from(row.querySelectorAll('td'));
|
||||
return Object.fromEntries(headers.map((header, index) => [
|
||||
header,
|
||||
(cells[index]?.textContent || '').trim()
|
||||
]));
|
||||
});
|
||||
return { headers, rows };
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Fetch + Render (with sticky tabs)
|
||||
========================= */
|
||||
function fetchCredentials(){
|
||||
const globalSearchValue = document.getElementById('global-search-input').value.toLowerCase();
|
||||
searchGlobal = globalSearchValue;
|
||||
|
||||
fetch('/list_credentials')
|
||||
.then(r=>r.text())
|
||||
.then(html=>{
|
||||
const doc = new DOMParser().parseFromString(html,'text/html');
|
||||
const tables = doc.querySelectorAll('table');
|
||||
|
||||
serviceData = [];
|
||||
tables.forEach(table=>{
|
||||
const titleEl = table.previousElementSibling;
|
||||
if (titleEl && titleEl.textContent) {
|
||||
const raw = titleEl.textContent.toLowerCase().replace('.csv','').trim();
|
||||
const credentials = parseTable(table);
|
||||
serviceData.push({
|
||||
service: raw,
|
||||
category: raw, // category == service name (dynamic)
|
||||
credentials
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// sort by most credentials first
|
||||
serviceData.sort((a,b)=> (b.credentials.rows?.length||0) - (a.credentials.rows?.length||0));
|
||||
|
||||
updateStatsBar();
|
||||
renderTabs();
|
||||
renderServices();
|
||||
applyPersistedCollapse(); // survive periodic refresh
|
||||
attachSearchListeners();
|
||||
})
|
||||
.catch(err=>console.error('Error:',err));
|
||||
}
|
||||
|
||||
function renderServices(){
|
||||
const grid = document.getElementById('credentials-grid');
|
||||
const needle = (searchGlobal||'').toLowerCase();
|
||||
|
||||
// Filter services by global search (title OR any row content)
|
||||
const searched = serviceData.filter(svc=>{
|
||||
if (!needle) return true;
|
||||
const titleMatch = svc.service.includes(needle);
|
||||
const rowMatch = svc.credentials.rows.some(r => Object.values(r).join(' ').toLowerCase().includes(needle));
|
||||
return titleMatch || rowMatch;
|
||||
});
|
||||
|
||||
// Filter by active category (tab)
|
||||
const byCat = searched.filter(svc => currentCategory==='all' || svc.category===currentCategory);
|
||||
|
||||
if (byCat.length === 0) {
|
||||
grid.innerHTML = `<div style="text-align:center;color:var(--_muted);padding:40px;">
|
||||
<div style="font-size:3rem;margin-bottom:16px;opacity:.5;">🔍</div>No credentials</div>`;
|
||||
updateBadges();
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = byCat.map(s => createCredentialCard(s.service, s.credentials)).join('');
|
||||
|
||||
// If global search active, only show matching rows inside cards and auto-open them
|
||||
if (needle) {
|
||||
document.querySelectorAll('.service-card').forEach(card=>{
|
||||
card.classList.remove('collapsed');
|
||||
card.querySelectorAll('.credential-item').forEach(it=>{
|
||||
const t = it.textContent.toLowerCase();
|
||||
it.style.display = t.includes(needle)?'':'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateBadges();
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Global search
|
||||
========================= */
|
||||
function updateGlobalClearButton(){
|
||||
const btn = document.querySelector('.clear-global-button');
|
||||
if (searchGlobal && searchGlobal.length>0) btn.classList.add('show'); else btn.classList.remove('show');
|
||||
}
|
||||
|
||||
function filterAllServices() {
|
||||
searchGlobal = document.getElementById('global-search-input').value.toLowerCase();
|
||||
renderServices();
|
||||
applyPersistedCollapse();
|
||||
updateGlobalClearButton();
|
||||
}
|
||||
function clearGlobalSearch() {
|
||||
document.getElementById('global-search-input').value = '';
|
||||
searchGlobal = '';
|
||||
renderServices();
|
||||
applyPersistedCollapse();
|
||||
updateGlobalClearButton();
|
||||
document.querySelectorAll('.service-card').forEach(card => card.classList.add('collapsed'));
|
||||
}
|
||||
|
||||
/* =========================
|
||||
Per-card search
|
||||
========================= */
|
||||
let searchTerms = {};
|
||||
let initialCollapsedState = {};
|
||||
|
||||
function filterCredentials(input, service) {
|
||||
const filter = input.value.toLowerCase();
|
||||
searchTerms[service] = filter;
|
||||
|
||||
const card = document.querySelector(`.service-card[data-service="${service}"]`);
|
||||
if (!card) return;
|
||||
const items = card.querySelectorAll('.credential-item');
|
||||
|
||||
if (!(service in initialCollapsedState)) {
|
||||
initialCollapsedState[service] = card.classList.contains('collapsed');
|
||||
}
|
||||
if (filter.length > 0) card.classList.remove('collapsed');
|
||||
|
||||
items.forEach(item=>{
|
||||
const text = item.textContent.toLowerCase();
|
||||
item.style.display = text.includes(filter) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
function reapplySearchFilters(){
|
||||
Object.keys(searchTerms).forEach(service=>{
|
||||
const filter = searchTerms[service] || '';
|
||||
const card = document.querySelector(`.service-card[data-service="${service}"]`);
|
||||
if (!card) return;
|
||||
const items = card.querySelectorAll('.credential-item');
|
||||
items.forEach(item=>{
|
||||
const text = item.textContent.toLowerCase();
|
||||
item.style.display = text.includes(filter) ? '' : 'none';
|
||||
});
|
||||
const searchInput = card.querySelector('.search-input');
|
||||
if (searchInput) searchInput.value = filter;
|
||||
});
|
||||
}
|
||||
function attachSearchListeners(){
|
||||
document.querySelectorAll('.service-card').forEach(card=>{
|
||||
const service = card.dataset.service;
|
||||
const searchInput = card.querySelector('.search-input');
|
||||
if (searchInput) {
|
||||
searchInput.value = searchTerms[service] || '';
|
||||
searchInput.addEventListener('input', ()=>filterCredentials(searchInput, service));
|
||||
}
|
||||
});
|
||||
}
|
||||
function toggleClearButton(input) {
|
||||
const clearButton = input.nextElementSibling;
|
||||
if (input.value.trim().length > 0) clearButton.classList.add('show'); else clearButton.classList.remove('show');
|
||||
}
|
||||
function clearSearch(button) {
|
||||
const input = button.previousElementSibling;
|
||||
const service = input.getAttribute('data-service');
|
||||
input.value = '';
|
||||
filterCredentials(input, service);
|
||||
|
||||
if (service in initialCollapsedState) {
|
||||
const card = document.querySelector(`.service-card[data-service="${service}"]`);
|
||||
if (card) {
|
||||
if (initialCollapsedState[service]) card.classList.add('collapsed');
|
||||
else card.classList.remove('collapsed');
|
||||
}
|
||||
delete initialCollapsedState[service];
|
||||
}
|
||||
toggleClearButton(input);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
UX bits
|
||||
========================= */
|
||||
|
||||
function toggleServiceCollapse(header) {
|
||||
const card = header.closest('.service-card');
|
||||
const nowCollapsed = !card.classList.contains('collapsed');
|
||||
card.classList.toggle('collapsed');
|
||||
const svc = card.dataset.service;
|
||||
if (svc) setCardCollapsed(svc, nowCollapsed); // remember user's choice
|
||||
}
|
||||
function downloadCredentials(event, service, credentials) {
|
||||
event.stopPropagation();
|
||||
if (!credentials.rows || credentials.rows.length===0) return;
|
||||
const headers = Object.keys(credentials.rows[0]);
|
||||
let csv = headers.join(',') + '\n';
|
||||
credentials.rows.forEach(row=>{
|
||||
const values = headers.map(h=>{
|
||||
const v = (row[h] ?? '').toString();
|
||||
return v.includes(',') ? `"${v.replace(/"/g,'""')}"` : v;
|
||||
});
|
||||
csv += values.join(',') + '\n';
|
||||
});
|
||||
const blob = new Blob([csv], {type:'text/csv'});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url; a.download = `${service}_credentials.csv`;
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
function showCopyFeedback() {
|
||||
const feedback = document.querySelector('.copied-feedback');
|
||||
feedback.classList.add('show');
|
||||
setTimeout(() => feedback.classList.remove('show'), 1500);
|
||||
}
|
||||
function copyToClipboard(el) {
|
||||
const text = el.getAttribute('data-value') || '';
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text; document.body.appendChild(ta);
|
||||
ta.select(); document.execCommand('copy'); document.body.removeChild(ta);
|
||||
showCopyFeedback();
|
||||
const bg = el.style.background;
|
||||
el.style.background='#4CAF50'; setTimeout(()=>el.style.background=bg, 500);
|
||||
}
|
||||
|
||||
|
||||
/* =========================
|
||||
Boot
|
||||
========================= */
|
||||
document.addEventListener('DOMContentLoaded', ()=>{
|
||||
document.getElementById('global-search-input').addEventListener('input', filterAllServices);
|
||||
fetchCredentials();
|
||||
setInterval(fetchCredentials, 30000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
63
web/css/all.min.css
vendored
@@ -1,63 +0,0 @@
|
||||
/* Font Awesome Base Styles */
|
||||
.fa, .fas {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
text-rendering: auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Icon Definitions */
|
||||
.fa-th-list:before {
|
||||
content: "\f00b"; /* Icon for toggle between list and grid view */
|
||||
}
|
||||
.fa-object-group:before {
|
||||
content: "\f247"; /* Icon for multi-selection */
|
||||
}
|
||||
.fa-folder-plus:before {
|
||||
content: "\f65e"; /* Icon for adding a new folder */
|
||||
}
|
||||
.fa-edit:before {
|
||||
content: "\f044"; /* Icon for renaming */
|
||||
}
|
||||
.fa-arrows-alt:before {
|
||||
content: "\f0b2"; /* Icon for moving */
|
||||
}
|
||||
.fa-trash:before {
|
||||
content: "\f1f8"; /* Icon for deletion */
|
||||
}
|
||||
.fa-folder:before {
|
||||
content: "\f07b"; /* Icon for folder */
|
||||
}
|
||||
.fa-times:before {
|
||||
content: "\f00d"; /* Icon for cancel in modals */
|
||||
}
|
||||
.fa-check:before {
|
||||
content: "\f00c"; /* Icon for confirmation in modals */
|
||||
}
|
||||
|
||||
/* Font Faces */
|
||||
@font-face {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url(../css/fonts/fa-regular-400.woff2) format("woff2"),
|
||||
url(../css/fonts/fa-regular-400.woff) format("woff"),
|
||||
url(../css/fonts/fa-regular-400.ttf) format("truetype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: block;
|
||||
src: url(../css/fonts/fa-solid-900.woff2) format("woff2"),
|
||||
url(../css/fonts/fa-solid-900.woff) format("woff"),
|
||||
url(../css/fonts/fa-solid-900.ttf) format("truetype");
|
||||
}
|
||||
.fas {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
}
|
||||
2456
web/css/global.css
10646
web/css/pages.css
Normal file
2174
web/css/shell.css
Normal file
@@ -1,770 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Bjorn Cyberviking — DB Manager</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<link rel="icon" href="/web/images/favicon.ico" />
|
||||
<link rel="stylesheet" href="/web/css/global.css" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/web/images/icon-192x192.png" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="theme-color" content="#050709" />
|
||||
<style>
|
||||
:root{--topbar-h:var(--h-topbar,56px);--bottombar-h:var(--h-bottombar,56px);--db-row-hover:rgba(0,255,154,.06);--db-row-selected:rgba(0,255,154,.12);--db-cell-edited:rgba(24,240,255,.18);--db-cell-focus:rgba(0,255,154,.22);--sidebar-w:280px}
|
||||
body{padding:0;overflow-x:hidden}
|
||||
.db-header{position:sticky;top:0;z-index:20;background:var(--grad-topbar);border:1px solid var(--c-border);border-radius:12px;padding:12px;box-shadow:var(--shadow);margin-bottom:12px}
|
||||
.sticky-actions{position:sticky;bottom:0;z-index:15;display:flex;gap:8px;justify-content:flex-end;padding:8px;background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.4));border-top:1px solid var(--c-border);border-radius:12px;backdrop-filter:blur(4px)}
|
||||
.db-tree{display:grid;gap:6px}
|
||||
.tree-head{display:flex;gap:8px;align-items:center;margin-bottom:8px}
|
||||
.tree-search{display:flex;gap:6px;align-items:center;background:var(--c-panel);border:1px solid var(--c-border-strong);border-radius:10px;padding:6px 8px}
|
||||
.tree-search input{all:unset;flex:1;color:var(--ink)}
|
||||
.tree-group{margin-top:10px}
|
||||
.tree-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--c-border);border-radius:10px;background:var(--c-panel-2);cursor:pointer;transition:.18s}
|
||||
.tree-item:hover{box-shadow:0 0 0 1px var(--c-border-hi) inset,0 8px 22px var(--glow-weak);transform:translateX(2px)}
|
||||
.tree-item.active{background:linear-gradient(180deg,#0b151c,#091219);outline:2px solid color-mix(in oklab,var(--acid) 55%,transparent)}
|
||||
.tree-item .count{margin-left:auto;padding:2px 8px;border-radius:999px;background:var(--c-chip-bg);border:1px solid var(--c-border-hi);font-size:11px;color:var(--muted)}
|
||||
.db-title{display:flex;align-items:center;gap:10px;font-weight:700;color:var(--acid);letter-spacing:.08em}
|
||||
.db-controls{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
|
||||
.db-search{display:flex;align-items:center;gap:8px;background:var(--c-panel);border:1px solid var(--c-border-strong);border-radius:10px;padding:0 10px;min-width:220px;flex:1}
|
||||
.db-search input{all:unset;color:var(--ink);height:34px;flex:1}
|
||||
.db-opts{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
||||
.hint{color:var(--muted);font-size:12px}
|
||||
.sep{width:1px;height:24px;background:var(--c-border);margin:0 4px;opacity:.6}
|
||||
.db-container{min-height:100%;display:flex;flex-direction:column}
|
||||
.db-wrap{display:flex;flex-direction:column;gap:12px;min-height:0;flex:1}
|
||||
.db-table-wrap{position:relative;overflow:auto;border:1px solid var(--c-border);border-radius:12px;background:var(--grad-card);box-shadow:var(--shadow);flex:1;min-height:0}
|
||||
table.db{width:100%;border-collapse:separate;border-spacing:0}
|
||||
.db-table-wrap table.db thead th{position:sticky;top:0;z-index:5;background:var(--c-panel);border-bottom:1px solid var(--c-border-strong);text-align:left;padding:10px;font-weight:700;color:var(--acid);user-select:none;cursor:pointer;white-space:nowrap}
|
||||
.db tbody td{padding:8px 10px;border-bottom:1px dashed var(--c-border-muted);vertical-align:middle;background:var(--grad-card)}
|
||||
.db tbody tr:hover{background:var(--db-row-hover)}
|
||||
.db tbody tr.selected{background:var(--db-row-selected);outline:1px solid var(--c-border-hi)}
|
||||
.cell{display:block;min-width:80px;max-width:520px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.cell[contenteditable="true"]{outline:0;border-radius:6px;transition:.12s;padding:2px 6px}
|
||||
.cell[contenteditable="true"]:focus{background:var(--db-cell-focus);box-shadow:0 0 0 1px var(--c-border-hi) inset}
|
||||
.cell.edited{background:var(--db-cell-edited)}
|
||||
.pk{color:var(--muted);font-size:12px}
|
||||
.cols-drawer{display:none}
|
||||
.cols-drawer.open{display:block}
|
||||
.db-page{display:grid;grid-template-columns:1fr}
|
||||
.sidebar{width:auto}
|
||||
.main{position:fixed;left:var(--sidebar-w);right:0;top:var(--topbar-h);bottom:var(--bottombar-h);overflow:auto;padding:16px;transition:.25s}
|
||||
.sidebar.hidden + .main{left:0 !important}@keyframes blinkChange{from{box-shadow:0 0 0 0 var(--acid-22)}to{box-shadow:0 0 0 6px transparent}}
|
||||
.value-changed{animation:blinkChange .66s ease}
|
||||
.sticky-col-cell{position:sticky;z-index:3;background:var(--grad-card);box-shadow:1px 0 0 0 var(--c-border-strong),-1px 0 0 0 var(--c-border)}
|
||||
.sticky-col-head{position:sticky;z-index:3;background:var(--grad-card);box-shadow:1px 0 0 0 var(--c-border-strong),-1px 0 0 0 var(--c-border)}
|
||||
.sticky-check,.sticky-col-head.sticky-check{z-index:4}
|
||||
th.is-sticky .sticky-dot::after{content:"●";margin-left:6px;font-size:10px;color:var(--acid);opacity:.9}
|
||||
th[data-col]{position:relative}
|
||||
th[data-col]::after{content:"⏱️ 1.5s pour fixer";position:absolute;right:8px;top:50%;transform:translateY(-50%);font-size:11px;color:var(--muted);opacity:0;pointer-events:none;transition:opacity .15s ease}
|
||||
@media (hover:hover){th[data-col]:hover::after{opacity:.7}}
|
||||
.main{position:fixed;right:0;top:var(--topbar-h);bottom:var(--bottombar-h);overflow:auto;padding:16px;transition:.25s}
|
||||
.sidebar.hidden + .main{left:0 !important}
|
||||
body:not(:has(#sidebar)) .main{left:0 !important}
|
||||
@media (max-width:1100px){.db-controls{gap:6px}.db-search{min-width:160px}.cell{max-width:60vw}}
|
||||
@media (max-width:1100px){body{padding:0}}
|
||||
@media (max-width:900px){.main{left:240px}}
|
||||
@media (max-width:700px){.main{left:0}}
|
||||
|
||||
</style>
|
||||
<script src="/web/js/global.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar" id="sidebar" aria-label="Navigation base de données">
|
||||
<div class="sidehead">
|
||||
<div class="sidetitle">Database</div>
|
||||
<div class="spacer"></div>
|
||||
<button class="btn" id="hideSidebar"><span class="icon">⟵</span><span class="label">Hide</span></button>
|
||||
</div>
|
||||
<div class="sidecontent" id="sidecontent">
|
||||
<div class="card">
|
||||
<div class="tree-head">
|
||||
<div class="pill">Tables</div>
|
||||
<div class="spacer"></div>
|
||||
<button class="btn" id="refreshTree">Refresh</button>
|
||||
</div>
|
||||
<div class="tree-search">
|
||||
<span aria-hidden="true">🔎</span>
|
||||
<input id="treeFilter" placeholder="Filter tables…" />
|
||||
</div>
|
||||
<div id="dbTree" class="db-tree"></div>
|
||||
<div class="hint" id="treeHint" style="margin-top:8px"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="head">
|
||||
<div class="title">Utilities</div>
|
||||
</div>
|
||||
<div class="chips">
|
||||
<button class="chip" id="newTableBtn">➕ New table</button>
|
||||
<button class="chip" id="exportAllBtn">⬇ Export DB</button>
|
||||
<button class="chip is-danger" id="vacuumBtn">🧹 Vacuum</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-btn" style="display:none"></div>
|
||||
<div id="empty-list-hint" style="display:none;opacity:.8;margin-top:8px;font-size:.95em"></div>
|
||||
</aside>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main" id="main">
|
||||
<div class="db-container">
|
||||
<div class="db-wrap">
|
||||
<!-- Header (collé sous la topbar globale) -->
|
||||
<div class="db-header">
|
||||
<div class="db-title">
|
||||
<img class="sig" src="/web/images/bjornwebicon.png" alt="Bjorn" width="24" height="24" />
|
||||
<span id="titleTable">Select a table</span>
|
||||
<span class="pk" id="titleMeta"></span>
|
||||
</div>
|
||||
<div class="db-controls">
|
||||
<div class="db-search" title="Search in current table">
|
||||
<input id="q" placeholder="Search values, e.g. mac:AA:BB, port>80, text…" />
|
||||
<button class="btn" id="searchBtn">Search</button>
|
||||
</div>
|
||||
<div class="db-opts">
|
||||
<select class="select" id="sortSelect" title="Sort">
|
||||
<option value="">Sort: auto</option>
|
||||
</select>
|
||||
<select class="select" id="limitSelect" title="Rows per page">
|
||||
<option>50</option><option>100</option><option>250</option><option>500</option>
|
||||
</select>
|
||||
<div class="row-toggle" style="padding:6px 10px">
|
||||
<label for="liveToggle" style="margin-right:8px">Live</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="liveToggle" />
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-number">
|
||||
<span>Every</span>
|
||||
<input type="number" id="liveSec" value="5" min="2" max="120" />
|
||||
<span>sec</span>
|
||||
</div>
|
||||
<button class="btn" id="refreshBtn">↻ Refresh</button>
|
||||
<span class="sep" aria-hidden="true"></span>
|
||||
<button class="btn primary" id="saveBtn" disabled>💾 Save edits</button>
|
||||
<button class="btn" id="discardBtn" disabled>⟲ Discard</button>
|
||||
<span class="sep" aria-hidden="true"></span>
|
||||
<button class="btn" id="addRowBtn">➕ Row</button>
|
||||
<button class="btn" id="deleteSelBtn" disabled>🗑 Delete selected</button>
|
||||
<div class="chips">
|
||||
<button class="chip" id="exportCsvBtn">CSV</button>
|
||||
<button class="chip" id="exportJsonBtn">JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="db-table-wrap" id="tableWrap" aria-label="Table data scroller">
|
||||
<table class="db" id="dataTable" aria-live="polite">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-check" style="width:38px; left:0"><input type="checkbox" id="checkAll" aria-label="Select all rows"/></th>
|
||||
<th>—</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tbody">
|
||||
<tr><td colspan="99" class="hint" style="padding:14px">Pick a table on the left.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Actions bas (au-dessus de la bottombar) -->
|
||||
<div class="sticky-actions">
|
||||
<div class="hint" id="statusHint">Ready.</div>
|
||||
<div class="spacer"></div>
|
||||
<div class="chips">
|
||||
<button class="chip is-warn" id="truncateBtn" disabled>Danger: Truncate</button>
|
||||
<button class="chip is-danger" id="dropBtn" disabled>Danger: Drop table</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drawer colonnes -->
|
||||
<div class="cols-drawer" id="colsDrawer">
|
||||
<div class="card">
|
||||
<div class="head">
|
||||
<div class="title">Columns</div>
|
||||
<div class="spacer"></div>
|
||||
<button class="btn" id="hideCols">Close</button>
|
||||
</div>
|
||||
<div class="chips" id="colsChips"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const $ = (s, r=document)=> r.querySelector(s);
|
||||
const $$ = (s, r=document)=> [...r.querySelectorAll(s)];
|
||||
const toast = (msg)=> (window.AcidBurn?.toast ? window.AcidBurn.toast(msg) : alert(msg));
|
||||
|
||||
/* ========= API ADAPTER ========= */
|
||||
const API = (function(){
|
||||
const base = '/api/db';
|
||||
const j = (r)=> { if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); };
|
||||
const t = (r)=> { if(!r.ok) throw new Error('HTTP '+r.status); return r.text(); };
|
||||
return {
|
||||
listCatalog: ()=> fetch(`${base}/catalog`).then(j),
|
||||
listTables: ()=> fetch(`${base}/tables`).then(j),
|
||||
getTable: (name, opts={})=>{
|
||||
const p = new URLSearchParams({limit: opts.limit||50, offset: opts.offset||0, q: opts.q||'', sort: opts.sort||''});
|
||||
return fetch(`${base}/table/${encodeURIComponent(name)}?`+p.toString()).then(j);
|
||||
},
|
||||
updateCells: (payload)=> fetch(`${base}/update`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)}).then(j),
|
||||
deleteRows: (payload)=> fetch(`${base}/delete`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)}).then(j),
|
||||
insertRow: (payload)=> fetch(`${base}/insert`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)}).then(j),
|
||||
exportTable: (name, fmt='csv')=> fetch(`${base}/export/${encodeURIComponent(name)}?format=${fmt}`).then(t),
|
||||
exportAll: (fmt='csv')=> fetch(`${base}/export_all?format=${fmt}`),
|
||||
vacuum: ()=> fetch(`${base}/vacuum`, {method:'POST'}).then(j),
|
||||
dropTable: (name)=> fetch(`${base}/drop/${encodeURIComponent(name)}`, {method:'POST'}).then(j),
|
||||
dropView: (name)=> fetch(`${base}/drop_view/${encodeURIComponent(name)}`, {method:'POST'}).then(j),
|
||||
truncateTable: (name)=> fetch(`${base}/truncate/${encodeURIComponent(name)}`, {method:'POST'}).then(j),
|
||||
schema: (name)=> fetch(`${base}/schema/${encodeURIComponent(name)}`).then(j),
|
||||
createTable: (payload)=> fetch(`${base}/create_table`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)}).then(j),
|
||||
renameTable: (from,to)=> fetch(`${base}/rename_table`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({from,to})}).then(j),
|
||||
addColumn: (table, column)=> fetch(`${base}/add_column`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({table, column})}).then(j),
|
||||
};
|
||||
})();
|
||||
|
||||
/* ========= STATE ========= */
|
||||
const state = {
|
||||
table: null, kind: 'table', // 'table' | 'view'
|
||||
columns: [], pk: null, rows: [],
|
||||
dirty: new Map(),
|
||||
selected: new Set(),
|
||||
timers: { live: null },
|
||||
sort: '', q: '', limit: 50, offset: 0,
|
||||
lastHash: '', hiddenCols: new Set(),
|
||||
stickyCols: new Set(), // ← colonnes “collées”
|
||||
};
|
||||
|
||||
/* ========= SIDEBAR: TABLE + VIEWS TREE ========= */
|
||||
const dbTree = $('#dbTree');
|
||||
const treeHint = $('#treeHint');
|
||||
const treeFilter = $('#treeFilter');
|
||||
|
||||
async function loadTree(){
|
||||
$('#refreshTree')?.setAttribute('disabled','disabled');
|
||||
try{
|
||||
const data = await API.listCatalog();
|
||||
const q = (treeFilter.value||'').toLowerCase().trim();
|
||||
dbTree.innerHTML = '';
|
||||
let shown = 0;
|
||||
|
||||
function section(title, list, icon, kind){
|
||||
if(!list?.length) return 0;
|
||||
const head = document.createElement('div');
|
||||
head.className = 'hint';
|
||||
head.textContent = title;
|
||||
dbTree.appendChild(head);
|
||||
(list||[]).forEach(t=>{
|
||||
if(q && !t.name.toLowerCase().includes(q)) return;
|
||||
shown++;
|
||||
const item = document.createElement('div');
|
||||
item.className = 'tree-item';
|
||||
if(t.name === state.table) item.classList.add('active');
|
||||
item.dataset.name = t.name;
|
||||
item.dataset.kind = kind;
|
||||
item.innerHTML = `<span class="icon">${icon}</span><span>${t.name}</span><span class="count">${t.count ?? '—'}</span>`;
|
||||
item.addEventListener('click', ()=>{
|
||||
// toggle visuel immédiat
|
||||
dbTree.querySelector('.tree-item.active')?.classList.remove('active');
|
||||
item.classList.add('active');
|
||||
// logique
|
||||
selectTable(t.name, t.pk || 'id', kind);
|
||||
});
|
||||
dbTree.appendChild(item);
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
|
||||
section('Tables', data.tables, '🗂', 'table');
|
||||
section('Views', data.views, '📄', 'view');
|
||||
|
||||
treeHint.textContent = shown ? `${shown} item(s)` : 'No tables/views.';
|
||||
}catch(e){
|
||||
toast('Failed to load catalog');
|
||||
}finally{
|
||||
$('#refreshTree')?.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
/* ========= MAIN: TABLE VIEW ========= */
|
||||
const titleTable = $('#titleTable');
|
||||
const titleMeta = $('#titleMeta');
|
||||
const sortSelect = $('#sortSelect');
|
||||
const limitSelect = $('#limitSelect');
|
||||
const qInput = $('#q');
|
||||
const searchBtn = $('#searchBtn');
|
||||
const tableWrap = $('#tableWrap');
|
||||
const tbody = $('#tbody');
|
||||
const dataTable = $('#dataTable');
|
||||
const saveBtn = $('#saveBtn');
|
||||
const discardBtn = $('#discardBtn');
|
||||
const refreshBtn = $('#refreshBtn');
|
||||
const addRowBtn = $('#addRowBtn');
|
||||
const deleteSelBtn = $('#deleteSelBtn');
|
||||
const exportCsvBtn = $('#exportCsvBtn');
|
||||
const exportJsonBtn = $('#exportJsonBtn');
|
||||
const truncateBtn = $('#truncateBtn');
|
||||
const dropBtn = $('#dropBtn');
|
||||
const statusHint = $('#statusHint');
|
||||
const liveToggle = $('#liveToggle');
|
||||
const liveSec = $('#liveSec');
|
||||
const colsDrawer = $('#colsDrawer');
|
||||
const colsChips = $('#colsChips');
|
||||
const hideCols = $('#hideCols');
|
||||
|
||||
function setStatus(s){ statusHint.textContent = s; }
|
||||
|
||||
function hashRows(rows){
|
||||
try{ return JSON.stringify(rows).slice(0, 1024); }catch{ return String(rows?.length||0) }
|
||||
}
|
||||
|
||||
async function selectTable(name, pkFallback='id', kind='table'){
|
||||
state.table = name;
|
||||
state.kind = kind;
|
||||
state.q = ''; state.sort = ''; state.offset = 0;
|
||||
state.dirty.clear(); state.selected.clear();
|
||||
state.stickyCols.clear();
|
||||
qInput.value = '';
|
||||
titleTable.textContent = name + (kind==='view'?' (view)':'');
|
||||
setButtons();
|
||||
await refresh(true, {pkFallback});
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
const hasItem = !!state.table;
|
||||
const hasDirty = state.dirty.size > 0;
|
||||
const hasSel = state.selected.size > 0;
|
||||
const readOnly = state.kind === 'view';
|
||||
|
||||
saveBtn.disabled = !hasDirty || readOnly;
|
||||
discardBtn.disabled = !hasDirty;
|
||||
deleteSelBtn.disabled = !hasSel || readOnly;
|
||||
truncateBtn.disabled = !hasItem || readOnly;
|
||||
dropBtn.disabled = !hasItem;
|
||||
addRowBtn.disabled = readOnly;
|
||||
}
|
||||
|
||||
function renderSortOptions(){
|
||||
sortSelect.innerHTML = '<option value="">Sort: auto</option>';
|
||||
state.columns.forEach(c=>{
|
||||
const o1 = document.createElement('option');
|
||||
o1.value = c+':asc'; o1.textContent = `▲ ${c}`;
|
||||
const o2 = document.createElement('option');
|
||||
o2.value = c+':desc'; o2.textContent = `▼ ${c}`;
|
||||
sortSelect.append(o1,o2);
|
||||
});
|
||||
}
|
||||
|
||||
function renderColsChips(){
|
||||
colsChips.innerHTML = '';
|
||||
state.columns.forEach(c=>{
|
||||
const chip = document.createElement('button');
|
||||
chip.className = 'chip'+(state.hiddenCols.has(c)?' is-ghost':'');
|
||||
chip.textContent = state.hiddenCols.has(c) ? `👁️🗨️ ${c}` : `👁️ ${c}`;
|
||||
chip.addEventListener('click', ()=>{
|
||||
if(state.hiddenCols.has(c)) state.hiddenCols.delete(c); else state.hiddenCols.add(c);
|
||||
renderTable();
|
||||
});
|
||||
colsChips.appendChild(chip);
|
||||
});
|
||||
}
|
||||
(function(){
|
||||
const sb=document.getElementById('sidebar');
|
||||
if(!sb) return;
|
||||
const apply=()=>{const w=sb.classList.contains('hidden')?0:Math.round(sb.getBoundingClientRect().width);document.documentElement.style.setProperty('--sidebar-w',w+'px');};
|
||||
const ro=new ResizeObserver(apply);
|
||||
ro.observe(sb);
|
||||
window.addEventListener('resize',apply,{passive:true});
|
||||
new MutationObserver(apply).observe(sb,{attributes:true,attributeFilter:['class']});
|
||||
apply();
|
||||
})();
|
||||
|
||||
function pkOf(row){ return row[state.pk]; }
|
||||
|
||||
/* ===== Long-press + gestion colonnes collées ===== */
|
||||
function onLongPress(el, ms, onFire){
|
||||
let t=null;
|
||||
const clear=()=>{ if(t){ clearTimeout(t); t=null; } };
|
||||
const down=(ev)=>{ t = setTimeout(()=>{ t=null; onFire(ev); }, ms); };
|
||||
const up=()=> clear();
|
||||
const leave=()=> clear();
|
||||
el.addEventListener('mousedown', down);
|
||||
el.addEventListener('touchstart', down, {passive:true});
|
||||
el.addEventListener('mouseup', up);
|
||||
el.addEventListener('mouseleave', leave);
|
||||
el.addEventListener('touchend', up);
|
||||
el.addEventListener('touchcancel', up);
|
||||
}
|
||||
|
||||
function toggleSticky(colName){
|
||||
if(!colName) return;
|
||||
if(state.stickyCols.has(colName)) state.stickyCols.delete(colName);
|
||||
else state.stickyCols.add(colName);
|
||||
renderTable(false);
|
||||
requestAnimationFrame(applyStickyPositions);
|
||||
}
|
||||
|
||||
function applyStickyPositions(){
|
||||
const table = document.getElementById('dataTable');
|
||||
if(!table) return;
|
||||
|
||||
// largeur de la colonne checkbox (left = 0 pour le th)
|
||||
const checkHead = table.querySelector('thead th.sticky-check');
|
||||
let left = (checkHead?.offsetWidth || 38);
|
||||
|
||||
const heads = [...table.querySelectorAll('thead th[data-col]')];
|
||||
|
||||
for(const th of heads){
|
||||
const col = th.dataset.col;
|
||||
const isSticky = th.classList.contains('is-sticky');
|
||||
const cells = [...table.querySelectorAll(`tbody td[data-col="${CSS.escape(col)}"]`)];
|
||||
if(isSticky){
|
||||
th.style.left = left + 'px';
|
||||
th.classList.add('sticky-col-head');
|
||||
cells.forEach(td=>{
|
||||
td.style.left = left + 'px';
|
||||
td.classList.add('sticky-col-cell');
|
||||
});
|
||||
const w = th.offsetWidth || cells[0]?.offsetWidth || 120;
|
||||
left += w;
|
||||
}else{
|
||||
th.style.left = '';
|
||||
th.classList.remove('sticky-col-head');
|
||||
cells.forEach(td=>{
|
||||
td.style.left = '';
|
||||
td.classList.remove('sticky-col-cell');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', ()=> requestAnimationFrame(applyStickyPositions));
|
||||
|
||||
function renderTable(diffAnimate=true){
|
||||
// THEAD
|
||||
const thead = dataTable.tHead || dataTable.createTHead();
|
||||
thead.innerHTML = '';
|
||||
const trh = thead.insertRow();
|
||||
|
||||
// Col sélection (checkbox) — sticky à gauche
|
||||
const thSel = document.createElement('th');
|
||||
thSel.style.width='38px';
|
||||
thSel.classList.add('sticky-check');
|
||||
thSel.style.left = '0';
|
||||
thSel.innerHTML = `<input type="checkbox" id="checkAll">`;
|
||||
trh.appendChild(thSel);
|
||||
$('#checkAll', thead)?.addEventListener('change', (e)=>{
|
||||
if(e.target.checked){ state.rows.forEach(r=> state.selected.add(pkOf(r))); }
|
||||
else { state.selected.clear(); }
|
||||
renderTable(false);
|
||||
setButtons();
|
||||
});
|
||||
|
||||
// Colonnes data
|
||||
state.columns.forEach(col=>{
|
||||
if(state.hiddenCols.has(col)) return;
|
||||
const th = document.createElement('th');
|
||||
th.textContent = col + (col===state.pk ? ' (pk)' : '');
|
||||
th.title = 'Click: trier • Long-press: fixer/relâcher';
|
||||
th.dataset.col = col;
|
||||
|
||||
// tri au clic court
|
||||
th.addEventListener('click', ()=>{
|
||||
const cur = state.sort;
|
||||
const asc = `${col}:asc`, desc = `${col}:desc`;
|
||||
state.sort = cur===asc ? desc : asc;
|
||||
sortSelect.value = state.sort;
|
||||
refresh();
|
||||
});
|
||||
|
||||
// long-press (1,5 s) -> toggle sticky
|
||||
onLongPress(th, 1500, ()=> toggleSticky(col));
|
||||
|
||||
// marque visuelle si sticky
|
||||
if(state.stickyCols.has(col)) th.classList.add('is-sticky'); else th.classList.remove('is-sticky');
|
||||
|
||||
// point indicateur
|
||||
const dot = document.createElement('span');
|
||||
dot.className = 'sticky-dot';
|
||||
th.appendChild(dot);
|
||||
|
||||
trh.appendChild(th);
|
||||
});
|
||||
|
||||
// TBODY
|
||||
tbody.innerHTML = '';
|
||||
if(!state.rows.length){
|
||||
const tr = document.createElement('tr');
|
||||
const td = document.createElement('td');
|
||||
td.colSpan = 1 + (state.columns.filter(c=>!state.hiddenCols.has(c)).length);
|
||||
td.className = 'hint';
|
||||
td.style.padding = '14px';
|
||||
td.textContent = 'No rows.';
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
}else{
|
||||
for(const row of state.rows){
|
||||
const tr = document.createElement('tr');
|
||||
const tdSel = document.createElement('td');
|
||||
const pk = pkOf(row);
|
||||
tdSel.classList.add('sticky-check');
|
||||
tdSel.style.left = '0';
|
||||
tdSel.innerHTML = `<input type="checkbox" ${state.selected.has(pk)?'checked':''} data-pk="${String(pk)}">`;
|
||||
tr.appendChild(tdSel);
|
||||
tdSel.querySelector('input').addEventListener('change',(e)=>{
|
||||
if(e.target.checked) state.selected.add(pk);
|
||||
else state.selected.delete(pk);
|
||||
setButtons();
|
||||
});
|
||||
|
||||
for(const col of state.columns){
|
||||
if(state.hiddenCols.has(col)) continue;
|
||||
const td = document.createElement('td');
|
||||
td.dataset.col = col; /* ← nécessaire pour le sticky left */
|
||||
|
||||
const isPK = (col === state.pk);
|
||||
const val = row[col] ?? '';
|
||||
const safe = (val === null || val === undefined) ? '' : String(val);
|
||||
td.innerHTML = `<span class="cell ${isPK?'pk':''}" ${isPK?'':'contenteditable="true"'} data-col="${col}" data-pk="${String(pk)}"></span>`;
|
||||
const span = td.firstElementChild;
|
||||
span.textContent = safe;
|
||||
|
||||
if(!isPK && state.kind !== 'view'){
|
||||
span.addEventListener('input', ()=>{
|
||||
span.classList.add('edited');
|
||||
const changes = state.dirty.get(pk) || {};
|
||||
changes[col] = span.textContent;
|
||||
state.dirty.set(pk, changes);
|
||||
setButtons();
|
||||
});
|
||||
span.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='Enter'){ e.preventDefault(); span.blur(); }
|
||||
});
|
||||
}
|
||||
tr.appendChild(td);
|
||||
}
|
||||
|
||||
if(tr && tr.animate && diffAnimate){ tr.classList.add('value-changed'); setTimeout(()=>tr.classList.remove('value-changed'), 700); }
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
// recalcul des positions “left” des colonnes sticky
|
||||
requestAnimationFrame(applyStickyPositions);
|
||||
}
|
||||
|
||||
async function refresh(initial=false, opts={}){
|
||||
if(!state.table){ return; }
|
||||
setStatus('Loading…');
|
||||
try{
|
||||
const res = await API.getTable(state.table, { limit: state.limit, offset: state.offset, q: state.q, sort: state.sort });
|
||||
state.columns = res.columns || [];
|
||||
state.rows = res.rows || [];
|
||||
state.pk = res.pk || opts.pkFallback || state.pk || 'id';
|
||||
|
||||
if(initial){ renderSortOptions(); renderColsChips(); }
|
||||
|
||||
const curSet = new Set(state.rows.map(r=> pkOf(r)));
|
||||
[...state.selected].forEach(k=>{ if(!curSet.has(k)) state.selected.delete(k); });
|
||||
|
||||
const newHash = hashRows(state.rows);
|
||||
const doAnim = state.lastHash && state.lastHash !== newHash;
|
||||
state.lastHash = newHash;
|
||||
|
||||
titleMeta.textContent = ` • pk: ${state.pk} • ${res.total ?? state.rows.length} rows`;
|
||||
renderTable(doAnim);
|
||||
setButtons();
|
||||
setStatus(`Loaded ${state.rows.length}${res.total ? ' / '+res.total : ''}`);
|
||||
}catch(e){
|
||||
toast('Failed to load data');
|
||||
setStatus('Error.');
|
||||
}
|
||||
}
|
||||
|
||||
/* ========= EDIT / SAVE ========= */
|
||||
async function saveEdits(){
|
||||
if(!state.table || state.dirty.size===0 || state.kind==='view') return;
|
||||
saveBtn.disabled = true;
|
||||
try{
|
||||
const rows = [...state.dirty.entries()].map(([pk, changes])=>({ pk, changes }));
|
||||
await API.updateCells({ table: state.table, pk: state.pk, rows });
|
||||
toast('Saved ✔');
|
||||
state.dirty.clear();
|
||||
setButtons();
|
||||
await refresh();
|
||||
}catch(e){
|
||||
toast('Save failed');
|
||||
}finally{
|
||||
saveBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
function discardEdits(){
|
||||
state.dirty.clear();
|
||||
$$('[contenteditable].edited', tbody).forEach(el=> el.classList.remove('edited'));
|
||||
setButtons();
|
||||
refresh();
|
||||
}
|
||||
|
||||
/* ========= ROW OPS ========= */
|
||||
async function addRow(){
|
||||
if(!state.table || state.kind==='view') return;
|
||||
const values = {};
|
||||
state.columns.forEach(c=> { if(c!==state.pk) values[c]=''; });
|
||||
try{
|
||||
const res = await API.insertRow({ table: state.table, values });
|
||||
toast('Row added');
|
||||
await refresh();
|
||||
if(res?.pk !== undefined){
|
||||
const el = $(`.cell[data-pk="${String(res.pk)}"][data-col]`, tbody);
|
||||
el?.focus();
|
||||
}
|
||||
}catch(e){ toast('Insert failed'); }
|
||||
}
|
||||
|
||||
async function deleteSelected(){
|
||||
if(!state.table || state.selected.size===0 || state.kind==='view') return;
|
||||
if(!confirm(`Delete ${state.selected.size} selected row(s)?`)) return;
|
||||
try{
|
||||
await API.deleteRows({ table: state.table, pk: state.pk, pks: [...state.selected] });
|
||||
toast('Deleted');
|
||||
state.selected.clear();
|
||||
setButtons();
|
||||
await refresh();
|
||||
}catch(e){ toast('Delete failed'); }
|
||||
}
|
||||
|
||||
/* ========= EXPORT ========= */
|
||||
async function exportTable(fmt){
|
||||
if(!state.table) return;
|
||||
try{
|
||||
const data = await API.exportTable(state.table, fmt);
|
||||
const blob = new Blob([data], {type: fmt==='csv'?'text/csv':'application/json'});
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${state.table}.${fmt}`;
|
||||
document.body.appendChild(a); a.click(); a.remove();
|
||||
toast(`Exported ${fmt.toUpperCase()}`);
|
||||
}catch(e){ toast('Export failed'); }
|
||||
}
|
||||
|
||||
/* ========= DANGEROUS OPS ========= */
|
||||
async function truncateTable(){
|
||||
if(!state.table || state.kind==='view') return;
|
||||
if(!confirm(`TRUNCATE ${state.table}? This removes all data.`)) return;
|
||||
try{ await API.truncateTable(state.table); toast('Table truncated'); await refresh(); }
|
||||
catch(e){ toast('Truncate failed'); }
|
||||
}
|
||||
async function dropCurrent(){
|
||||
if(!state.table) return;
|
||||
const what = state.kind==='view' ? 'VIEW' : 'TABLE';
|
||||
if(!confirm(`DROP ${what} ${state.table}? This removes it.`)) return;
|
||||
try{
|
||||
if(state.kind==='view') await API.dropView(state.table);
|
||||
else await API.dropTable(state.table);
|
||||
toast(`${what} dropped`);
|
||||
state.table = null;
|
||||
titleTable.textContent = 'Select a table';
|
||||
dataTable.tHead.innerHTML = '';
|
||||
tbody.innerHTML = `<tr><td colspan="99" class="hint" style="padding:14px">Pick a table on the left.</td></tr>`;
|
||||
setButtons();
|
||||
await loadTree();
|
||||
}catch(e){ toast('Drop failed'); }
|
||||
}
|
||||
async function vacuum(){ try{ await API.vacuum(); toast('VACUUM done'); }catch(e){ toast('VACUUM failed'); } }
|
||||
|
||||
/* ========= LIVE REFRESH ========= */
|
||||
function startLive(){
|
||||
stopLive();
|
||||
const sec = clamp(parseInt(liveSec.value||'5',10), 2, 120);
|
||||
state.timers.live = setInterval(()=> refresh(), sec*1000);
|
||||
setStatus(`Live: ${sec}s`);
|
||||
}
|
||||
function stopLive(){
|
||||
if(state.timers.live){ clearInterval(state.timers.live); state.timers.live=null; }
|
||||
setStatus('Live: off');
|
||||
}
|
||||
function clamp(n,min,max){ return Math.max(min, Math.min(max,n)); }
|
||||
|
||||
|
||||
/* ========= EVENTS ========= */
|
||||
$('#refreshTree')?.addEventListener('click', loadTree);
|
||||
treeFilter?.addEventListener('input', loadTree);
|
||||
|
||||
qInput?.addEventListener('keydown', e=>{ if(e.key==='Enter') searchBtn.click(); });
|
||||
searchBtn?.addEventListener('click', ()=>{ state.q = qInput.value.trim(); state.offset=0; refresh(); });
|
||||
|
||||
sortSelect?.addEventListener('change', ()=>{ state.sort = sortSelect.value; refresh(); });
|
||||
limitSelect?.addEventListener('change', ()=>{ state.limit = parseInt(limitSelect.value,10)||50; refresh(); });
|
||||
|
||||
refreshBtn?.addEventListener('click', ()=> refresh());
|
||||
saveBtn?.addEventListener('click', saveEdits);
|
||||
discardBtn?.addEventListener('click', discardEdits);
|
||||
addRowBtn?.addEventListener('click', addRow);
|
||||
deleteSelBtn?.addEventListener('click', deleteSelected);
|
||||
exportCsvBtn?.addEventListener('click', ()=> exportTable('csv'));
|
||||
exportJsonBtn?.addEventListener('click', ()=> exportTable('json'));
|
||||
truncateBtn?.addEventListener('click', truncateTable);
|
||||
dropBtn?.addEventListener('click', dropCurrent);
|
||||
|
||||
$('#newTableBtn')?.addEventListener('click', async ()=>{
|
||||
const name = prompt('New table name (letters, digits, underscore):');
|
||||
if(!name) return;
|
||||
try{
|
||||
await API.createTable({
|
||||
name,
|
||||
if_not_exists: true,
|
||||
columns: [{name:'id', type:'INTEGER', pk:true}, {name:'created_at', type:'TEXT'}]
|
||||
});
|
||||
toast('Table created'); await loadTree();
|
||||
}catch{ toast('Create failed'); }
|
||||
});
|
||||
|
||||
$('#exportAllBtn')?.addEventListener('click', async ()=>{
|
||||
try{
|
||||
const res = await API.exportAll('csv');
|
||||
const blob = await res.blob();
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `database_export.zip`;
|
||||
document.body.appendChild(a); a.click(); a.remove();
|
||||
toast('Exported all');
|
||||
}catch{ toast('Export all failed'); }
|
||||
});
|
||||
|
||||
$('#vacuumBtn')?.addEventListener('click', vacuum);
|
||||
|
||||
liveToggle?.addEventListener('change', (e)=> e.target.checked ? startLive() : stopLive());
|
||||
liveSec?.addEventListener('change', ()=>{ if(liveToggle.checked) startLive(); });
|
||||
|
||||
// Column drawer toggle with keyboard (Shift+C)
|
||||
document.addEventListener('keydown', (e)=>{
|
||||
if(e.shiftKey && e.key.toLowerCase()==='c'){
|
||||
colsDrawer.classList.toggle('open');
|
||||
if(colsDrawer.classList.contains('open')) renderColsChips();
|
||||
}
|
||||
});
|
||||
hideCols?.addEventListener('click', ()=> colsDrawer.classList.remove('open'));
|
||||
|
||||
// Preserve vertical scroll of table area on refresh
|
||||
let preserveScrollY = 0;
|
||||
tableWrap.addEventListener('scroll', ()=> preserveScrollY = tableWrap.scrollTop);
|
||||
const origRefresh = refresh;
|
||||
refresh = async function(initial=false, opts={}){
|
||||
await origRefresh(initial, opts);
|
||||
tableWrap.scrollTop = preserveScrollY;
|
||||
}
|
||||
|
||||
// Init
|
||||
loadTree();
|
||||
setButtons();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,758 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<title>Bjorn Cyberviking - Files Explorer</title>
|
||||
<link rel="icon" href="web/images/favicon.ico" type="image/x-icon" />
|
||||
<link rel="stylesheet" href="web/css/global.css" />
|
||||
<link rel="stylesheet" href="web/css/all.min.css" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="web/images/icon-192x192.png" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="theme-color" content="#333" />
|
||||
<script src="web/js/global.js"></script>
|
||||
|
||||
<!-- Integrated CSS (theming only; no logic changes) -->
|
||||
<style>
|
||||
/* ========= bridge variables to global.css tokens ========= */
|
||||
:root{
|
||||
--_bg: var(--bg, #0b0c0f);
|
||||
--_panel: var(--c-panel-2, rgba(16,22,22,.6));
|
||||
--_border: var(--c-border, rgba(255,255,255,.08));
|
||||
--_ink: var(--ink, #e6fff7);
|
||||
--_muted: var(--muted, #8affc1cc);
|
||||
--_acid: var(--acid, #00ff9a);
|
||||
--_acid2: var(--acid-2, #18f0ff);
|
||||
--_shadow: var(--shadow, 0 10px 26px rgba(0,0,0,.35));
|
||||
}
|
||||
|
||||
body{ background:var(--_bg); color:var(--_ink); }
|
||||
.main{ padding:0 !important; }
|
||||
|
||||
/* ===== Layout ===== */
|
||||
.loot-container{
|
||||
display:flex; flex-direction:column;
|
||||
height:calc(100vh - 120px);
|
||||
padding:12px;
|
||||
gap:12px;
|
||||
}
|
||||
|
||||
.file-explorer{
|
||||
flex:1; display:flex; flex-direction:column; overflow:hidden; padding:10px;
|
||||
color:var(--_ink);
|
||||
background: color-mix(in oklab, var(--_panel) 92%, transparent);
|
||||
border:1px solid var(--_border);
|
||||
border-radius:14px;
|
||||
backdrop-filter: blur(18px);
|
||||
box-shadow: var(--_shadow);
|
||||
}
|
||||
|
||||
/* ===== View containers ===== */
|
||||
.files-grid{
|
||||
overflow-y:auto;
|
||||
display:grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
||||
gap:8px; padding:8px; border-radius:8px;
|
||||
}
|
||||
.files-list{ overflow-y:auto; padding:4px; }
|
||||
|
||||
/* ===== Upload section ===== */
|
||||
.upload-container{
|
||||
padding:10px; margin-bottom:10px;
|
||||
display:flex; justify-content:center; align-items:center;
|
||||
}
|
||||
.drop-zone{
|
||||
width:100%; max-width:800px;
|
||||
padding:16px;
|
||||
border:2px dashed var(--_border);
|
||||
border-radius:12px; text-align:center; font-size:14px;
|
||||
color: var(--_muted);
|
||||
cursor:pointer; transition:.25s ease;
|
||||
background: color-mix(in oklab, var(--_panel) 88%, transparent);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.drop-zone:hover{ background: color-mix(in oklab, var(--_panel) 96%, transparent); }
|
||||
.drop-zone.dragover{
|
||||
border-color: color-mix(in oklab, var(--_acid) 50%, var(--_border));
|
||||
background: color-mix(in oklab, var(--_acid) 12%, var(--_panel));
|
||||
color: var(--_ink);
|
||||
}
|
||||
|
||||
/* ===== Items (grid & list) ===== */
|
||||
.grid-item, .list-item{
|
||||
border-radius:10px; padding:8px; cursor:pointer; transition:.15s ease;
|
||||
display:flex; align-items:center; position:relative;
|
||||
border:1px solid transparent;
|
||||
background: color-mix(in oklab, var(--_panel) 86%, transparent);
|
||||
}
|
||||
.grid-item{ flex-direction:column; text-align:center; }
|
||||
.list-item{ flex-direction:row; gap:12px; }
|
||||
|
||||
.grid-item:hover, .list-item:hover{
|
||||
transform: translateY(-2px);
|
||||
border-color: color-mix(in oklab, var(--_acid2) 35%, var(--_border));
|
||||
box-shadow: 0 4px 14px rgba(0,0,0,.25);
|
||||
background: color-mix(in oklab, var(--_panel) 96%, transparent);
|
||||
}
|
||||
|
||||
.grid-item img, .list-item img{ width:28px; height:28px; margin-bottom:4px; }
|
||||
.list-item img{ margin-bottom:0; }
|
||||
|
||||
.item-name{
|
||||
color:var(--_ink); font-size:14px; line-height:1.3;
|
||||
word-break: break-word; pointer-events:none;
|
||||
}
|
||||
.folder .item-name{ color:var(--_ink); font-weight:700; }
|
||||
|
||||
.item-meta{
|
||||
font-size:11px; color:var(--_muted); margin-top:4px; pointer-events:none;
|
||||
}
|
||||
|
||||
/* selected state */
|
||||
.multi-select-mode{ background: color-mix(in oklab, var(--_acid) 6%, transparent); }
|
||||
.item-selected{
|
||||
background: color-mix(in oklab, var(--_acid) 18%, var(--_panel)) !important;
|
||||
border:2px solid color-mix(in oklab, var(--_acid) 55%, var(--_border)) !important;
|
||||
}
|
||||
|
||||
/* ===== Context menu ===== */
|
||||
.context-menu{
|
||||
position:absolute; z-index:1000;
|
||||
background: color-mix(in oklab, var(--_panel) 98%, transparent);
|
||||
border:1px solid var(--_border);
|
||||
border-radius:10px; padding:6px 8px; min-width:160px;
|
||||
color:var(--_ink); box-shadow: var(--_shadow);
|
||||
}
|
||||
.context-menu > div{
|
||||
padding:8px 10px; border-radius:8px; cursor:pointer;
|
||||
}
|
||||
.context-menu > div:hover{
|
||||
background: color-mix(in oklab, var(--_acid2) 12%, transparent);
|
||||
}
|
||||
|
||||
/* ===== Search ===== */
|
||||
.search-container{
|
||||
position:relative; margin-bottom:10px; display:flex; align-items:center;
|
||||
}
|
||||
.search-input{
|
||||
width:100%; padding:10px 40px 10px 12px; font-size:14px;
|
||||
border-radius:10px; border:1px solid var(--_border);
|
||||
background: color-mix(in oklab, var(--_panel) 90%, transparent);
|
||||
color:var(--_ink); box-sizing:border-box; transition:.2s;
|
||||
}
|
||||
.search-input:focus{
|
||||
outline:none;
|
||||
border-color: color-mix(in oklab, var(--_acid2) 35%, var(--_border));
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--_acid2) 18%, transparent);
|
||||
background: color-mix(in oklab, var(--_panel) 96%, transparent);
|
||||
}
|
||||
.search-input::placeholder{ color: color-mix(in oklab, var(--_muted) 70%, transparent); }
|
||||
.clear-button{
|
||||
position:absolute; right:12px; background:none; border:none;
|
||||
color: color-mix(in oklab, var(--_acid) 55%, var(--_ink));
|
||||
font-size:16px; cursor:pointer; display:none;
|
||||
}
|
||||
.clear-button.show{ display:block; }
|
||||
|
||||
/* ===== Toolbar ===== */
|
||||
.toolbar-buttons{ display:flex; gap:8px; margin-bottom:10px; flex-wrap:wrap; }
|
||||
.action-button{
|
||||
background: color-mix(in oklab, var(--_panel) 90%, transparent);
|
||||
border:1px solid var(--_border);
|
||||
color: var(--_muted);
|
||||
padding:8px 10px; border-radius:10px; cursor:pointer; font-size:14px; font-weight:700;
|
||||
display:flex; align-items:center; gap:6px; transition:.2s; backdrop-filter: blur(10px);
|
||||
}
|
||||
.action-button:hover{
|
||||
background: color-mix(in oklab, var(--_panel) 96%, transparent);
|
||||
color: var(--_ink); transform: translateY(-2px);
|
||||
}
|
||||
.action-button.active{
|
||||
background: linear-gradient(135deg, color-mix(in oklab, var(--_acid) 18%, transparent), color-mix(in oklab, var(--_acid2) 10%, transparent));
|
||||
color: var(--_ink);
|
||||
border-color: color-mix(in oklab, var(--_acid2) 28%, var(--_border));
|
||||
}
|
||||
.action-button.delete{
|
||||
background: color-mix(in oklab, var(--_acid) 14%, var(--_panel));
|
||||
color: var(--_ink); display:none; border-color: color-mix(in oklab, var(--_acid) 40%, var(--_border));
|
||||
}
|
||||
.action-button.delete.show{ display:flex; }
|
||||
|
||||
/* ===== Modal ===== */
|
||||
.modal{
|
||||
display:block; position:fixed; inset:0; z-index:1000;
|
||||
background: rgba(0,0,0,.5);
|
||||
}
|
||||
.modal-content{
|
||||
background: color-mix(in oklab, var(--_panel) 98%, transparent);
|
||||
color: var(--_ink);
|
||||
margin: 12vh auto; padding:20px; width: min(500px, 92vw);
|
||||
border:1px solid var(--_border); border-radius:14px; box-shadow: var(--_shadow);
|
||||
}
|
||||
.modal-buttons{ margin-top:18px; text-align:right; display:flex; gap:8px; justify-content:flex-end; }
|
||||
.modal-buttons button{
|
||||
margin-left:0; padding:8px 14px; border-radius:10px; border:1px solid var(--_border);
|
||||
cursor:pointer; background: color-mix(in oklab, var(--_panel) 92%, transparent); color:var(--_ink);
|
||||
}
|
||||
.modal-buttons button:hover{ background: color-mix(in oklab, var(--_panel) 98%, transparent); }
|
||||
.modal-buttons .primary{
|
||||
background: linear-gradient(135deg, color-mix(in oklab, var(--_acid) 18%, transparent), color-mix(in oklab, var(--_acid2) 10%, transparent));
|
||||
border-color: color-mix(in oklab, var(--_acid2) 35%, var(--_border));
|
||||
color: var(--_ink);
|
||||
}
|
||||
|
||||
#folder-tree{
|
||||
border:1px solid var(--_border);
|
||||
border-radius:10px; padding:8px; margin:10px 0; max-height:320px; overflow-y:auto;
|
||||
background: color-mix(in oklab, var(--_panel) 92%, transparent);
|
||||
}
|
||||
.folder-item{
|
||||
padding:8px 10px; cursor:pointer; display:flex; align-items:center; gap:8px; border-radius:8px;
|
||||
}
|
||||
.folder-item:hover{ background: color-mix(in oklab, var(--_panel) 98%, transparent); }
|
||||
.folder-item.selected{
|
||||
background: color-mix(in oklab, var(--_acid2) 16%, transparent);
|
||||
outline: 1px solid color-mix(in oklab, var(--_acid2) 35%, var(--_border));
|
||||
}
|
||||
.folder-item i{ color: var(--_muted); }
|
||||
|
||||
/* ===== Path navigator ===== */
|
||||
.path-navigator{
|
||||
padding:8px; margin-bottom:8px; border-radius:10px;
|
||||
display:flex; align-items:center; gap:8px;
|
||||
background: color-mix(in oklab, var(--_panel) 90%, transparent);
|
||||
border:1px solid var(--_border);
|
||||
}
|
||||
.nav-buttons{ display:flex; gap:8px; }
|
||||
.back-button{
|
||||
background: color-mix(in oklab, var(--_panel) 92%, transparent);
|
||||
border:1px solid var(--_border);
|
||||
color: var(--_muted);
|
||||
padding:8px 12px; border-radius:10px; cursor:pointer; font-weight:700;
|
||||
display:flex; align-items:center; gap:6px; min-width:40px; min-height:40px; justify-content:center;
|
||||
transition:.2s;
|
||||
}
|
||||
.back-button:hover{ background: color-mix(in oklab, var(--_panel) 98%, transparent); color: var(--_ink); }
|
||||
|
||||
.current-path{ display:flex; align-items:center; gap:6px; overflow:hidden; flex-wrap:wrap; }
|
||||
.path-segment{
|
||||
background: linear-gradient(135deg, color-mix(in oklab, var(--_acid) 16%, transparent), color-mix(in oklab, var(--_acid2) 10%, transparent));
|
||||
color: var(--_ink); padding:6px 10px; border-radius:10px; cursor:pointer; transition:.2s;
|
||||
border:1px solid color-mix(in oklab, var(--_acid2) 28%, var(--_border));
|
||||
}
|
||||
.path-segment:hover{ filter: brightness(1.08); }
|
||||
|
||||
/* ===== Responsive ===== */
|
||||
@media (max-width:420px){
|
||||
.loot-container{ height:80vh; }
|
||||
.file-explorer{ max-height:40vh; }
|
||||
.files-grid{ max-height:40vh; }
|
||||
.drop-zone{ padding:18px; font-size:15px; }
|
||||
.toolbar-buttons{ padding:4px; gap:6px; }
|
||||
.search-container, .path-navigator{ padding:4px; }
|
||||
.grid-item{ min-height:74px; font-size:12px; }
|
||||
.item-name{ font-size:13px; margin-top:2px; }
|
||||
.item-meta{ font-size:10px; margin-top:2px; }
|
||||
.grid-item img, .list-item img{ width:28px; height:28px; }
|
||||
}
|
||||
@media (max-width:768px){
|
||||
.files-grid{ grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap:8px; }
|
||||
#file-list{ max-height:fit-content; overflow-y:auto; }
|
||||
.toolbar-buttons{ flex-direction:row; flex-wrap:wrap; gap:8px; }
|
||||
.files-list{ padding:8px; max-height:50vh; overflow-y:auto; }
|
||||
.grid-item{ padding:8px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main" id="main">
|
||||
<div class="loot-container">
|
||||
<!-- File Explorer Section -->
|
||||
<div class="file-explorer">
|
||||
<div class="toolbar-buttons">
|
||||
<button class="action-button" onclick="toggleView()">
|
||||
<i class="fas fa-th-list"></i>
|
||||
</button>
|
||||
<button class="action-button" id="multiSelectBtn" onclick="toggleMultiSelect()">
|
||||
<i class="fas fa-object-group"></i>Select
|
||||
</button>
|
||||
<button class="action-button" id="newFolderBtn" onclick="createNewFolder()">
|
||||
<i class="fas fa-folder-plus"></i> New Folder
|
||||
</button>
|
||||
<button class="action-button" onclick="renameSelected()" id="renameBtn" style="display: none;">
|
||||
<i class="fas fa-edit"></i> Rename
|
||||
</button>
|
||||
<button class="action-button" onclick="moveSelected()" id="moveBtn" disabled>
|
||||
<i class="fas fa-arrows-alt"></i> Move
|
||||
</button>
|
||||
<button class="action-button delete" id="deleteBtn" onclick="deleteSelectedItems()" disabled>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="search-container">
|
||||
<input type="text" class="search-input" id="search-input" placeholder="Search files..." oninput="filterFiles()" />
|
||||
<button class="clear-button" id="clear-button" onclick="clearSearch()">✖</button>
|
||||
</div>
|
||||
|
||||
<div class="path-navigator">
|
||||
<div class="nav-buttons">
|
||||
<button class="back-button" onclick="navigateUp()" title="Go to parent directory">
|
||||
← Back
|
||||
</button>
|
||||
</div>
|
||||
<div class="current-path" id="currentPath"></div>
|
||||
</div>
|
||||
|
||||
<div class="files-grid" id="file-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Container Fixed at Bottom -->
|
||||
<div class="upload-container">
|
||||
<input id="file-upload" type="file" multiple style="display: none;" onchange="handle_file_upload(event)" />
|
||||
<div id="drop-zone" class="drop-zone" onclick="document.getElementById('file-upload').click()">
|
||||
Drag files or folders here or click to upload
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Embedded JavaScript (UNCHANGED) -->
|
||||
<script>
|
||||
let currentPath = [];
|
||||
let fontSize = 14;
|
||||
let allFiles = [];
|
||||
let isGridView = true; // Grid view by default
|
||||
|
||||
function formatBytes(bytes, decimals = 1) {
|
||||
if (!bytes) return '0 Bytes';
|
||||
const k = 1024; const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
function toggleView() { isGridView = !isGridView; loadCurrentFolder(); }
|
||||
function navigateUp() { if (currentPath.length > 0) { currentPath.pop(); loadCurrentFolder(); } }
|
||||
function navigateToFolder(folderName) { currentPath.push(folderName); loadCurrentFolder(); }
|
||||
|
||||
function loadAllFiles() {
|
||||
fetch('/list_files')
|
||||
.then(r => r.json())
|
||||
.then(data => { allFiles = data; loadCurrentFolder(); })
|
||||
.catch(err => console.error('Error fetching files:', err));
|
||||
}
|
||||
function loadCurrentFolder() {
|
||||
const currentContent = findFolderContents(allFiles, currentPath);
|
||||
displayFiles(currentContent);
|
||||
updateCurrentPathDisplay();
|
||||
const v = document.getElementById('search-input').value.toLowerCase();
|
||||
if (v) { filterFiles(); }
|
||||
}
|
||||
function findFolderContents(data, path) {
|
||||
if (path.length === 0) return data;
|
||||
let current = data;
|
||||
for (let folder of path) {
|
||||
const found = current.find(item => item.is_directory && item.name === folder);
|
||||
if (!found || !found.children) return [];
|
||||
current = found.children;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
function displayFiles(currentContent) {
|
||||
const container = document.getElementById('file-list');
|
||||
container.innerHTML = '';
|
||||
container.className = isGridView ? 'files-grid' : 'files-list';
|
||||
|
||||
const sorted = [...currentContent].sort((a,b)=>{
|
||||
if (a.is_directory && !b.is_directory) return -1;
|
||||
if (!a.is_directory && b.is_directory) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
sorted.forEach(item => {
|
||||
const el = document.createElement('div');
|
||||
el.className = (isGridView ? 'grid-item ' : 'list-item ') + (item.is_directory ? 'folder' : 'file');
|
||||
|
||||
item.path = buildCompletePath(item.name);
|
||||
el.dataset.path = item.path;
|
||||
if (selectedItems.has(item.path)) el.classList.add('item-selected');
|
||||
|
||||
el.innerHTML = `
|
||||
<img src="/web/images/${item.is_directory ? 'mainfolder' : 'file'}.png" alt="${item.is_directory ? 'Folder' : 'File'}">
|
||||
<div>
|
||||
<div class="item-name">${item.name}</div>
|
||||
<div class="item-meta">${item.is_directory ? 'Folder' : formatBytes(item.size || 0)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
el.addEventListener('click', (e)=>{
|
||||
e.preventDefault();
|
||||
if (isMultiSelectMode) {
|
||||
toggleItemSelection(el, item);
|
||||
} else {
|
||||
if (item.is_directory) navigateToFolder(item.name);
|
||||
else window.location.href = `/download_file?path=${encodeURIComponent(item.path)}`;
|
||||
}
|
||||
});
|
||||
|
||||
el.addEventListener('contextmenu', (e)=>{ e.preventDefault(); showContextMenu(e, item); });
|
||||
container.appendChild(el);
|
||||
});
|
||||
|
||||
updateSelectionCount();
|
||||
updateButtonStates();
|
||||
}
|
||||
function navigateToRoot(){ currentPath = []; loadCurrentFolder(); }
|
||||
|
||||
function showMultiContextMenu(event){
|
||||
const existing = document.querySelector('.context-menu'); if (existing) existing.remove();
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'context-menu';
|
||||
menu.style.top = `${event.clientY}px`; menu.style.left = `${event.clientX}px`;
|
||||
const del = document.createElement('div');
|
||||
del.textContent = `Delete (${selectedItems.size} items)`;
|
||||
del.onclick = ()=>{ deleteMultipleItems(Array.from(selectedItems)); menu.remove(); };
|
||||
menu.appendChild(del); document.body.appendChild(menu);
|
||||
document.addEventListener('click', ()=>{ if (menu.parentElement) menu.remove(); }, { once:true });
|
||||
}
|
||||
|
||||
async function deleteSelectedItems(){
|
||||
if (selectedItems.size === 0) return;
|
||||
if (confirm(`Are you sure you want to delete ${selectedItems.size} items?`)){
|
||||
const items = Array.from(selectedItems.keys());
|
||||
for (const path of items) {
|
||||
try{
|
||||
const r = await fetch('/delete_file', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ file_path: path })
|
||||
});
|
||||
if (!r.ok) console.error(`Failed to delete ${path}`);
|
||||
} catch(err){ console.error(`Error deleting ${path}:`, err); }
|
||||
}
|
||||
await loadAllFiles(); clearSelection(); toggleMultiSelect();
|
||||
}
|
||||
}
|
||||
|
||||
function updateCurrentPathDisplay(){
|
||||
const wrap = document.getElementById('currentPath'); wrap.innerHTML='';
|
||||
const root = document.createElement('span'); root.className='path-segment'; root.textContent='/'; root.onclick = ()=>navigateToRoot(); wrap.appendChild(root);
|
||||
currentPath.forEach((folder, idx)=>{
|
||||
const seg = document.createElement('span'); seg.className='path-segment'; seg.textContent=folder;
|
||||
seg.onclick = ()=>{ currentPath = currentPath.slice(0, idx+1); loadCurrentFolder(); };
|
||||
wrap.appendChild(seg);
|
||||
});
|
||||
}
|
||||
|
||||
function buildCompletePath(fileName){
|
||||
const basePath = '/home/bjorn/';
|
||||
return basePath + currentPath.join('/') + '/' + fileName;
|
||||
}
|
||||
|
||||
function deleteFileOrFolder(item){
|
||||
if (!item || !item.path){ console.error('Invalid item or missing path:', item); alert('Error: Invalid file information'); return; }
|
||||
if (confirm(`Are you sure you want to delete "${item.name}"?`)){
|
||||
fetch('/delete_file', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ file_path: item.path })
|
||||
})
|
||||
.then(r=>{ if (!r.ok) return r.json().then(d=>{ throw new Error(d.message || 'Failed to delete the item.');}); return r.json(); })
|
||||
.then(d=>{ if (d.status==='success') loadAllFiles(); else throw new Error(d.message || 'Unknown error occurred'); })
|
||||
.catch(err=>{ console.error('Error in delete operation:', err); alert(`An error occurred: ${err.message}`); });
|
||||
}
|
||||
}
|
||||
|
||||
function showContextMenu(event, item){
|
||||
event.preventDefault();
|
||||
const existing = document.querySelector('.context-menu'); if (existing) existing.remove();
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'context-menu';
|
||||
menu.style.top = `${event.clientY}px`; menu.style.left = `${event.clientX}px`;
|
||||
|
||||
const rename = document.createElement('div'); rename.textContent='Rename';
|
||||
rename.onclick = ()=>{ renameItem(item); menu.remove(); };
|
||||
const dup = document.createElement('div'); dup.textContent='Duplicate';
|
||||
dup.onclick = ()=>{ duplicateItem(item); menu.remove(); };
|
||||
const move = document.createElement('div'); move.textContent='Move to...';
|
||||
move.onclick = ()=>{ showMoveToDialog(item); menu.remove(); };
|
||||
const del = document.createElement('div'); del.textContent='Delete';
|
||||
del.onclick = ()=>{ deleteFileOrFolder({ name:item.name, path:buildCompletePath(item.name), is_directory:item.is_directory }); menu.remove(); };
|
||||
|
||||
[rename, dup, move, del].forEach(x=>menu.appendChild(x));
|
||||
document.body.appendChild(menu);
|
||||
document.addEventListener('click', ()=>{ if (menu.parentElement) menu.remove(); }, { once:true });
|
||||
}
|
||||
|
||||
function filterFiles(){
|
||||
const v = document.getElementById('search-input').value.toLowerCase();
|
||||
const clear = document.getElementById('clear-button');
|
||||
if (v.length > 0){
|
||||
clear.classList.add('show');
|
||||
displayFiles( filterAllFiles(allFiles, v) );
|
||||
} else {
|
||||
clear.classList.remove('show');
|
||||
loadCurrentFolder();
|
||||
}
|
||||
}
|
||||
function filterAllFiles(files, v){
|
||||
let res = [];
|
||||
files.forEach(it=>{
|
||||
if (it.name.toLowerCase().includes(v)) res.push(it);
|
||||
if (it.is_directory && it.children) res = res.concat(filterAllFiles(it.children, v));
|
||||
});
|
||||
return res;
|
||||
}
|
||||
function clearSearch(){ const i = document.getElementById('search-input'); i.value=''; filterFiles(); }
|
||||
|
||||
document.addEventListener('DOMContentLoaded', ()=>{
|
||||
const fi = document.getElementById('file-upload');
|
||||
fi.removeAttribute('webkitdirectory'); fi.removeAttribute('directory'); fi.removeAttribute('mozdirectory'); fi.setAttribute('multiple','');
|
||||
loadAllFiles();
|
||||
if (/Mobi|Android/i.test(navigator.userAgent)){ fontSize = 12; adjustFontSize(0); }
|
||||
const filesGrid = document.querySelector('.files-grid'); if (filesGrid){ filesGrid.addEventListener('contextmenu', showEmptySpaceContextMenu); }
|
||||
});
|
||||
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
dropZone.addEventListener('dragover', (e)=>{ e.preventDefault(); dropZone.classList.add('dragover'); });
|
||||
dropZone.addEventListener('dragleave', ()=>{ dropZone.classList.remove('dragover'); });
|
||||
dropZone.addEventListener('drop', (e)=>{
|
||||
e.preventDefault(); dropZone.classList.remove('dragover');
|
||||
const items = e.dataTransfer.items;
|
||||
if (items){ handleDirectoryUpload(items); } else { handleFiles(e.dataTransfer.files); }
|
||||
});
|
||||
|
||||
function handle_file_upload(e){ handleFiles(e.target.files); }
|
||||
function handleFiles(files){
|
||||
const formData = new FormData();
|
||||
Array.from(files).forEach(file=>{
|
||||
const rel = file.webkitRelativePath || file.name;
|
||||
formData.append('files[]', file, rel);
|
||||
});
|
||||
formData.append('currentPath', JSON.stringify(currentPath));
|
||||
fetch('/upload_files', { method:'POST', body: formData })
|
||||
.then(r=>r.json())
|
||||
.then(()=>{ loadAllFiles(); })
|
||||
.catch(err=>{ alert('Error uploading files: ' + err.message); });
|
||||
}
|
||||
|
||||
let isMultiSelectMode = false;
|
||||
const selectedItems = new Map();
|
||||
function toggleMultiSelect(){
|
||||
isMultiSelectMode = !isMultiSelectMode;
|
||||
const container = document.querySelector('.file-explorer');
|
||||
const btn = document.querySelector('#multiSelectBtn');
|
||||
if (isMultiSelectMode){ container.classList.add('multi-select-mode'); btn.classList.add('active'); }
|
||||
else { container.classList.remove('multi-select-mode'); btn.classList.remove('active'); clearSelection(); }
|
||||
updateButtonStates();
|
||||
}
|
||||
function updateSelectionCount(){
|
||||
const del = document.getElementById('deleteBtn');
|
||||
const n = selectedItems.size;
|
||||
del.innerHTML = n>0 ? `<i class="fas fa-trash"></i> ${n}` : `<i class="fas fa-trash"></i>`;
|
||||
}
|
||||
function clearSelection(){
|
||||
selectedItems.clear();
|
||||
document.querySelectorAll('.grid-item, .list-item').forEach(i=>i.classList.remove('item-selected'));
|
||||
updateButtonStates();
|
||||
}
|
||||
function toggleItemSelection(el, item){
|
||||
if (!isMultiSelectMode) return;
|
||||
const p = item.path;
|
||||
if (selectedItems.has(p)){ selectedItems.delete(p); el.classList.remove('item-selected'); }
|
||||
else { selectedItems.set(p, item); el.classList.add('item-selected'); }
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
async function handleDirectoryUpload(items){
|
||||
const files = []; const entries = Array.from(items).map(i=>i.webkitGetAsEntry());
|
||||
async function traverseEntry(entry, path=''){
|
||||
if (entry.isFile){
|
||||
const file = await new Promise(res=>entry.file(res));
|
||||
Object.defineProperty(file, 'webkitRelativePath', { value: path + entry.name });
|
||||
files.push(file);
|
||||
} else if (entry.isDirectory){
|
||||
const reader = entry.createReader();
|
||||
const kids = await new Promise(res=>{
|
||||
const acc=[]; (function read(){ reader.readEntries((ents)=>{ if(ents.length){ acc.push(...ents); read(); } else { res(acc); } }); })();
|
||||
});
|
||||
const next = path + entry.name + '/';
|
||||
for (const k of kids){ await traverseEntry(k, next); }
|
||||
}
|
||||
}
|
||||
await Promise.all(entries.map(e=>traverseEntry(e)));
|
||||
handleFiles(files);
|
||||
}
|
||||
|
||||
function showEmptySpaceContextMenu(event){
|
||||
if (event.target.classList.contains('files-grid')){
|
||||
event.preventDefault();
|
||||
const existing = document.querySelector('.context-menu'); if (existing) existing.remove();
|
||||
const menu = document.createElement('div'); menu.className='context-menu';
|
||||
menu.style.top = `${event.clientY}px`; menu.style.left = `${event.clientX}px`;
|
||||
const nf = document.createElement('div'); nf.textContent='New Folder'; nf.onclick = ()=>{ createNewFolder(); menu.remove(); };
|
||||
menu.appendChild(nf); document.body.appendChild(menu);
|
||||
document.addEventListener('click', ()=>{ if (menu.parentElement) menu.remove(); }, { once:true });
|
||||
}
|
||||
}
|
||||
|
||||
async function renameItem(item){
|
||||
const newName = prompt(`Rename ${item.is_directory ? 'folder' : 'file'} "${item.name}" to:`, item.name);
|
||||
if (newName && newName !== item.name){
|
||||
const oldPath = buildCompletePath(item.name);
|
||||
const newPath = buildCompletePath(newName);
|
||||
try{
|
||||
const r = await fetch('/rename_file', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ old_path: oldPath, new_path: newPath })
|
||||
});
|
||||
if (!r.ok) throw new Error(await r.text());
|
||||
loadAllFiles();
|
||||
} catch(err){ alert(`Error renaming item: ${err.message}`); }
|
||||
}
|
||||
}
|
||||
|
||||
async function duplicateItem(item){
|
||||
const base = item.name;
|
||||
const ext = item.is_directory ? '' : (base.includes('.') ? '.' + base.split('.').pop() : '');
|
||||
const nameNoExt = item.is_directory ? base : base.split('.')[0];
|
||||
const newName = `${nameNoExt} (copy)${ext}`;
|
||||
try{
|
||||
const sourcePath = buildCompletePath(item.name);
|
||||
const targetPath = buildCompletePath(newName);
|
||||
const r = await fetch('/duplicate_file', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ source_path: sourcePath, target_path: targetPath })
|
||||
});
|
||||
if (!r.ok) throw new Error(await r.text());
|
||||
loadAllFiles();
|
||||
} catch(err){ alert(`Error duplicating item: ${err.message}`); }
|
||||
}
|
||||
|
||||
async function createNewFolder(){
|
||||
const folderName = prompt('Enter new folder name:', 'New Folder');
|
||||
if (folderName){
|
||||
try{
|
||||
const folderPath = buildCompletePath(folderName);
|
||||
const r = await fetch('/create_folder', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ folder_path: folderPath })
|
||||
});
|
||||
if (!r.ok) throw new Error(await r.text());
|
||||
loadAllFiles();
|
||||
} catch(err){ alert(`Error creating folder: ${err.message}`); }
|
||||
}
|
||||
}
|
||||
|
||||
async function showMoveToDialog(items){
|
||||
const itemsArr = Array.isArray(items) ? items : [items];
|
||||
const modal = document.createElement('div');
|
||||
modal.className='modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<h2>Move ${itemsArr.length} ${itemsArr.length>1 ? 'items' : 'item'} to...</h2>
|
||||
<div id="folder-tree"></div>
|
||||
<div class="modal-buttons">
|
||||
<button class="action-button" id="cancelButton"><i class="fas fa-times"></i> Cancel</button>
|
||||
<button class="action-button primary" id="moveConfirmButton"><i class="fas fa-check"></i> Move</button>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(modal);
|
||||
document.getElementById('cancelButton').addEventListener('click', closeModal);
|
||||
document.getElementById('moveConfirmButton').addEventListener('click', ()=>{ processMove(itemsArr); });
|
||||
modal.addEventListener('click', (e)=>{ if (e.target === modal) closeModal(); });
|
||||
await loadFolderTree();
|
||||
}
|
||||
|
||||
async function loadFolderTree(){
|
||||
try{
|
||||
const r = await fetch('/list_directories'); if (!r.ok) throw new Error('Failed to load directory structure');
|
||||
const dirs = await r.json();
|
||||
const tree = document.getElementById('folder-tree');
|
||||
tree.innerHTML = buildFolderTreeHTML(dirs);
|
||||
addFolderTreeListeners();
|
||||
} catch(err){ alert('Error loading folder structure: ' + err.message); }
|
||||
}
|
||||
|
||||
function buildFolderTreeHTML(directories, level=0){
|
||||
let html=''; const pad = level * 20;
|
||||
directories.forEach(dir=>{
|
||||
if (dir.is_directory){
|
||||
html += `<div class="folder-item" data-path="${dir.path}" style="padding-left:${pad}px"><i class="fas fa-folder"></i><span>${dir.name}</span></div>`;
|
||||
if (dir.children && dir.children.length>0) html += buildFolderTreeHTML(dir.children, level+1);
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
let selectedTargetPath = null;
|
||||
function addFolderTreeListeners(){
|
||||
document.querySelectorAll('.folder-item').forEach(it=>{
|
||||
it.addEventListener('click', (e)=>{
|
||||
e.stopPropagation();
|
||||
document.querySelectorAll('.folder-item.selected').forEach(x=>x.classList.remove('selected'));
|
||||
it.classList.add('selected'); selectedTargetPath = it.dataset.path;
|
||||
});
|
||||
});
|
||||
}
|
||||
function closeModal(){ const m = document.querySelector('.modal'); if (m) m.remove(); selectedTargetPath = null; }
|
||||
|
||||
async function processMove(items){
|
||||
if (!selectedTargetPath){ alert('Please select a destination folder'); return; }
|
||||
const errors = []; const arr = Array.isArray(items)?items:[items];
|
||||
for (const item of arr){
|
||||
try{
|
||||
const r = await fetch('/move_file', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ source_path:item.path, target_path:`${selectedTargetPath}/${item.name}` })
|
||||
});
|
||||
if (!r.ok){ const e = await r.text(); errors.push(`Failed to move ${item.name}: ${e}`); }
|
||||
} catch(err){ errors.push(`Error moving ${item.name}: ${err.message}`); }
|
||||
}
|
||||
if (errors.length>0) alert('Some errors occurred:\n' + errors.join('\n'));
|
||||
closeModal(); loadAllFiles();
|
||||
}
|
||||
|
||||
function renameSelected(){
|
||||
const el = document.querySelector('.item-selected'); if (!el){ alert('Please select an item to rename'); return; }
|
||||
const name = el.querySelector('.item-name').textContent; const path = el.dataset.path; const isDir = el.classList.contains('folder');
|
||||
renameItem({ name, path, is_directory:isDir });
|
||||
}
|
||||
|
||||
function moveSelected(){
|
||||
const els = document.querySelectorAll('.item-selected');
|
||||
if (els.length===0){ alert('Please select items to move'); return; }
|
||||
const items = Array.from(els).map(el=>({ name: el.querySelector('.item-name').textContent, path: el.dataset.path, is_directory: el.classList.contains('folder') }));
|
||||
showMoveToDialog(items);
|
||||
}
|
||||
|
||||
function updateButtonStates(){
|
||||
const n = selectedItems.size;
|
||||
const renameBtn = document.getElementById('renameBtn');
|
||||
const moveBtn = document.getElementById('moveBtn');
|
||||
const deleteBtn = document.getElementById('deleteBtn');
|
||||
const newFolderBtn = document.getElementById('newFolderBtn');
|
||||
|
||||
if (renameBtn){
|
||||
if (isMultiSelectMode && n===1){ renameBtn.style.display='inline-block'; renameBtn.disabled=false; }
|
||||
else { renameBtn.style.display='none'; renameBtn.disabled=true; }
|
||||
}
|
||||
if (moveBtn){
|
||||
if (isMultiSelectMode && n>0){ moveBtn.style.display='inline-block'; moveBtn.disabled=false; }
|
||||
else { moveBtn.style.display='none'; moveBtn.disabled=true; }
|
||||
}
|
||||
if (deleteBtn){
|
||||
deleteBtn.style.display = isMultiSelectMode ? 'inline-block' : 'none';
|
||||
deleteBtn.disabled = n===0;
|
||||
deleteBtn.innerHTML = n>0 ? `<i class="fas fa-trash"></i> ${n}` : `<i class="fas fa-trash"></i>`;
|
||||
}
|
||||
if (newFolderBtn){ newFolderBtn.style.display = isMultiSelectMode ? 'none' : 'inline-block'; }
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
781
web/i18n/de.json
Normal file
@@ -0,0 +1,781 @@
|
||||
{
|
||||
"nav.dashboard": "Dashboard",
|
||||
"nav.bjorn": "Bjorn",
|
||||
"nav.netkb": "Netzwerkwissen",
|
||||
"nav.network": "Netzwerk",
|
||||
"nav.credentials": "Anmeldedaten",
|
||||
"nav.vulnerabilities": "Schwachstellen",
|
||||
"nav.attacks": "Angriff",
|
||||
"nav.scheduler": "Zeitplaner",
|
||||
"nav.database": "Datenbank",
|
||||
"nav.files": "Dateien",
|
||||
"nav.loot": "Beute",
|
||||
"nav.actions": "Aktionen",
|
||||
"nav.actionsStudio": "Aktions-Studio",
|
||||
"nav.backup": "Backup & Update",
|
||||
"nav.webEnum": "Web-Enum",
|
||||
"nav.zombieland": "Zombieland",
|
||||
"nav.settings": "Einstellungen",
|
||||
"nav.shortcuts": "Tastenkürzel",
|
||||
"nav.pages": "Seiten",
|
||||
"status.initializing": "Initialisierung...",
|
||||
"status.online": "Online",
|
||||
"status.offline": "Offline",
|
||||
"console.title": "Konsole",
|
||||
"console.clear": "Löschen",
|
||||
"console.sseOn": "SSE Ein",
|
||||
"console.sseOff": "SSE Aus",
|
||||
"console.newLogs": "{{count}} neue Protokolle",
|
||||
"settings.theme": "Design",
|
||||
"settings.language": "Sprache",
|
||||
"settings.general": "Allgemein",
|
||||
"settings.toggles": "Optionen",
|
||||
"settings.editValue": "Wert bearbeiten",
|
||||
"settings.addValues": "Werte hinzufügen (kommagetrennt)...",
|
||||
"settings.setValue": "Wert setzen...",
|
||||
"settings.errorLoading": "Fehler beim Laden der Konfiguration",
|
||||
"settings.configSaved": "Konfiguration gespeichert",
|
||||
"settings.errorSaving": "Fehler beim Speichern der Konfiguration",
|
||||
"settings.defaultsRestored": "Standardwerte wiederhergestellt",
|
||||
"settings.errorRestoring": "Fehler beim Wiederherstellen der Standardwerte",
|
||||
"theme.group.colors": "Farben",
|
||||
"theme.group.surfaces": "Oberflächen",
|
||||
"theme.group.layout": "Layout",
|
||||
"theme.token.bg": "Hintergrund",
|
||||
"theme.token.ink": "Textfarbe",
|
||||
"theme.token.accent1": "Akzent 1 (Acid)",
|
||||
"theme.token.accent2": "Akzent 2 (Cyan)",
|
||||
"theme.token.danger": "Gefahr",
|
||||
"theme.token.warning": "Warnung",
|
||||
"theme.token.ok": "Erfolg",
|
||||
"theme.token.panel": "Panel",
|
||||
"theme.token.panel2": "Panel Alt",
|
||||
"theme.token.ctrlPanel": "Steuerpanel",
|
||||
"theme.token.border": "Rahmen",
|
||||
"theme.token.radius": "Rahmenradius",
|
||||
"theme.advanced": "Erweitertes CSS",
|
||||
"theme.applyRaw": "Anwenden",
|
||||
"theme.reset": "Zurücksetzen",
|
||||
"dash.title": "Dashboard",
|
||||
"dash.battery": "Batterie",
|
||||
"dash.internet": "Internet",
|
||||
"dash.cpu": "CPU",
|
||||
"dash.ram": "RAM",
|
||||
"dash.disk": "Festplatte",
|
||||
"dash.temp": "Temp",
|
||||
"dash.uptime": "Laufzeit",
|
||||
"dash.hostsAlive": "Aktive Hosts",
|
||||
"dash.totalHosts": "Hosts gesamt",
|
||||
"dash.openPorts": "Offene Ports",
|
||||
"dash.credentials": "Anmeldedaten",
|
||||
"dash.vulnerabilities": "Schwachstellen",
|
||||
"dash.actions": "Aktionen",
|
||||
"dash.connected": "Verbunden",
|
||||
"dash.disconnected": "Getrennt",
|
||||
"dash.charging": "Lädt",
|
||||
"dash.discharging": "Entlädt",
|
||||
"dash.full": "Voll",
|
||||
"dash.connectivity": "Konnektivität",
|
||||
"dash.liveOps": "Live-Operationen",
|
||||
"dash.tapRefresh": "Zum Aktualisieren tippen",
|
||||
"dash.wifi": "WLAN",
|
||||
"dash.ethernet": "Ethernet",
|
||||
"dash.usb": "USB",
|
||||
"dash.bluetooth": "Bluetooth",
|
||||
"dash.mode": "Modus",
|
||||
"dash.gps": "GPS",
|
||||
"dash.age": "Alter von Bjorn",
|
||||
"dash.plugged": "Eingesteckt",
|
||||
"dash.noBattery": "Keine Batterie",
|
||||
"dash.sinceScan": "seit letztem Scan",
|
||||
"dash.wifiKnown": "Bekannte WLANs",
|
||||
"dash.dataFiles": "Gesammelte Daten/Dateien",
|
||||
"dash.fileDescriptors": "Dateideskriptoren",
|
||||
"dash.attackScripts": "Angriffsskripte",
|
||||
"dash.system": "System",
|
||||
"dash.zombies": "Zombies",
|
||||
"netkb.title": "Netzwerkwissensdatenbank",
|
||||
"netkb.showOffline": "Offline anzeigen",
|
||||
"netkb.gridView": "Gitter",
|
||||
"netkb.listView": "Liste",
|
||||
"netkb.hostname": "Hostname",
|
||||
"netkb.ip": "IP-Adresse",
|
||||
"netkb.mac": "MAC-Adresse",
|
||||
"netkb.vendor": "Hersteller",
|
||||
"netkb.ports": "Ports",
|
||||
"netkb.essid": "ESSID",
|
||||
"netkb.lastSeen": "Zuletzt gesehen",
|
||||
"netkb.firstSeen": "Zuerst gesehen",
|
||||
"netkb.online": "Online",
|
||||
"netkb.offline": "Offline",
|
||||
"netkb.openPorts": "Offene Ports",
|
||||
"netkb.noHosts": "Keine Hosts gefunden",
|
||||
"network.title": "Netzwerk-Visualisierung",
|
||||
"network.tableView": "Tabelle",
|
||||
"network.mapView": "Karte",
|
||||
"network.hostname": "Hostname",
|
||||
"network.ip": "IP-Adresse",
|
||||
"network.mac": "MAC",
|
||||
"network.ports": "Ports",
|
||||
"network.status": "Status",
|
||||
"network.searchPlaceholder": "Hosts suchen...",
|
||||
"network.noData": "Keine Netzwerkdaten",
|
||||
"creds.title": "Anmeldedaten",
|
||||
"creds.total": "Gesamt",
|
||||
"creds.unique": "Eindeutig",
|
||||
"creds.types": "Typen",
|
||||
"creds.username": "Benutzername",
|
||||
"creds.password": "Passwort",
|
||||
"creds.service": "Dienst",
|
||||
"creds.host": "Host",
|
||||
"creds.port": "Port",
|
||||
"creds.type": "Typ",
|
||||
"creds.timestamp": "Zeitstempel",
|
||||
"creds.showPassword": "Passwort anzeigen",
|
||||
"creds.hidePassword": "Passwort ausblenden",
|
||||
"creds.copyPassword": "Kopieren",
|
||||
"creds.exportAll": "Alle exportieren",
|
||||
"creds.noCredentials": "Keine Anmeldedaten gefunden",
|
||||
"vulns.title": "Schwachstellen-Board",
|
||||
"vulns.total": "Gesamt",
|
||||
"vulns.critical": "Kritisch",
|
||||
"vulns.high": "Hoch",
|
||||
"vulns.medium": "Mittel",
|
||||
"vulns.low": "Niedrig",
|
||||
"vulns.infoLevel": "Info",
|
||||
"vulns.host": "Host",
|
||||
"vulns.port": "Port",
|
||||
"vulns.service": "Dienst",
|
||||
"vulns.severity": "Schweregrad",
|
||||
"vulns.description": "Beschreibung",
|
||||
"vulns.cve": "CVE",
|
||||
"vulns.scanDate": "Scan-Datum",
|
||||
"vulns.details": "Details",
|
||||
"vulns.noVulns": "Keine Schwachstellen gefunden",
|
||||
"vulns.byHost": "Nach Host",
|
||||
"vulns.bySeverity": "Nach Schweregrad",
|
||||
"vulns.byService": "Nach Dienst",
|
||||
"attacks.title": "Angriffs-Manager",
|
||||
"attacks.running": "Läuft",
|
||||
"attacks.completed": "Abgeschlossen",
|
||||
"attacks.failed": "Fehlgeschlagen",
|
||||
"attacks.queued": "Warteschlange",
|
||||
"attacks.start": "Starten",
|
||||
"attacks.stop": "Stoppen",
|
||||
"attacks.restart": "Neustarten",
|
||||
"attacks.status": "Status",
|
||||
"attacks.target": "Ziel",
|
||||
"attacks.action": "Aktion",
|
||||
"attacks.duration": "Dauer",
|
||||
"attacks.progress": "Fortschritt",
|
||||
"attacks.noAttacks": "Keine laufenden Angriffe",
|
||||
"sched.title": "Aktions-Planer",
|
||||
"sched.pending": "Ausstehend",
|
||||
"sched.running": "Läuft",
|
||||
"sched.done": "Erledigt",
|
||||
"sched.failed": "Fehlgeschlagen",
|
||||
"sched.all": "Alle",
|
||||
"sched.searchPlaceholder": "Aufgaben suchen...",
|
||||
"sched.noTasks": "Keine Aufgaben gefunden",
|
||||
"sched.stats": "{{running}} laufend / {{pending}} ausstehend / {{done}} erledigt",
|
||||
"db.title": "Datenbank-Manager",
|
||||
"db.tables": "Tabellen",
|
||||
"db.rows": "Zeilen",
|
||||
"db.columns": "Spalten",
|
||||
"db.search": "Tabellen suchen...",
|
||||
"db.searchRows": "Zeilen suchen...",
|
||||
"db.export": "Exportieren",
|
||||
"db.import": "Importieren",
|
||||
"db.addRow": "Zeile hinzufügen",
|
||||
"db.deleteRow": "Zeile löschen",
|
||||
"db.deleteSelected": "Ausgewählte löschen",
|
||||
"db.saveChanges": "Speichern",
|
||||
"db.discardChanges": "Verwerfen",
|
||||
"db.confirmDelete": "Löschen bestätigen?",
|
||||
"db.noTables": "Keine Tabellen gefunden",
|
||||
"db.noData": "Keine Daten in dieser Tabelle",
|
||||
"db.hide": "Ausblenden",
|
||||
"db.showSidebar": "Seitenleiste anzeigen",
|
||||
"files.title": "Datei-Explorer",
|
||||
"files.gridView": "Gitter",
|
||||
"files.listView": "Liste",
|
||||
"files.size": "Größe",
|
||||
"files.modified": "Geändert",
|
||||
"files.name": "Name",
|
||||
"files.type": "Typ",
|
||||
"files.download": "Herunterladen",
|
||||
"files.preview": "Vorschau",
|
||||
"files.noFiles": "Keine Dateien gefunden",
|
||||
"files.parentDir": "Übergeordnetes Verzeichnis",
|
||||
"files.searchPlaceholder": "Dateien suchen...",
|
||||
"loot.title": "Beute",
|
||||
"loot.directories": "Verzeichnisse",
|
||||
"loot.totalFiles": "Dateien gesamt",
|
||||
"loot.totalSize": "Gesamtgröße",
|
||||
"loot.download": "Herunterladen",
|
||||
"loot.downloadAll": "Alle herunterladen",
|
||||
"loot.noLoot": "Keine Beute gefunden",
|
||||
"loot.explore": "Erkunden",
|
||||
"actions.title": "Aktions-Manager",
|
||||
"actions.available": "Verfügbar",
|
||||
"actions.enabled": "Aktiviert",
|
||||
"actions.disabled": "Deaktiviert",
|
||||
"actions.category": "Kategorie",
|
||||
"actions.enableAll": "Alle aktivieren",
|
||||
"actions.disableAll": "Alle deaktivieren",
|
||||
"actions.import": "Importieren",
|
||||
"actions.export": "Exportieren",
|
||||
"actions.noActions": "Keine Aktionen gefunden",
|
||||
"actions.description": "Beschreibung",
|
||||
"actions.menu.restartService": "Bjorn-Dienst neustarten",
|
||||
"actions.menu.deleteActionStatus": "Alle Aktions-Stati löschen",
|
||||
"actions.menu.clearOutput": "Output-Ordner leeren",
|
||||
"actions.menu.clearLogs": "Protokolle löschen",
|
||||
"actions.menu.reloadImages": "Bilder neu laden (experimentell)",
|
||||
"actions.menu.reloadFonts": "Schriftarten neu laden",
|
||||
"actions.menu.reloadActionsJson": "Aktions-JSON neu laden",
|
||||
"actions.menu.initializeCsv": "CSV-Dateien initialisieren",
|
||||
"actions.menu.clearLivestatus": "Livestatus-Datei löschen",
|
||||
"actions.menu.refreshActionsFile": "Aktions-Datei aktualisieren",
|
||||
"actions.menu.clearNetkb": "Netzwerkwissen leeren",
|
||||
"actions.menu.clearSharedConfig": "Geteilte Konfigurations-JSON löschen",
|
||||
"actions.menu.eraseMemories": "Bjorns Gedächtnis löschen",
|
||||
"actions.menu.reboot": "System neustarten",
|
||||
"actions.menu.shutdown": "System herunterfahren",
|
||||
"actions.tip.restartService": "Startet den Bjorn-Dienst neu, um seinen Zustand zu aktualisieren.",
|
||||
"actions.tip.deleteActionStatus": "Löscht alle Erfolgs- und Fehlerstati von Aktionen/Angriffen in netkb.csv.",
|
||||
"actions.tip.clearOutput": "Löscht alle Dateien in den Output-Ordnern und Unterordnern.",
|
||||
"actions.tip.clearLogs": "Löscht alle Systemprotokolldateien.",
|
||||
"actions.tip.reloadImages": "Lädt die vom System verwendeten Bilder neu.",
|
||||
"actions.tip.reloadFonts": "Lädt die Anwendungsschriftarten neu.",
|
||||
"actions.tip.reloadActionsJson": "Lädt die generierte Aktions-JSON-Datei neu.",
|
||||
"actions.tip.initializeCsv": "Erstellt CSV- und JSON-Dateien neu.",
|
||||
"actions.tip.clearLivestatus": "Löscht die Livestatus-Datei.",
|
||||
"actions.tip.refreshActionsFile": "Aktualisiert die Aktionsdatei, um neue Aktionen einzubeziehen.",
|
||||
"actions.tip.clearNetkb": "Löscht alle in der Netzwerkwissensdatenbank gespeicherten Informationen.",
|
||||
"actions.tip.clearSharedConfig": "Löscht die geteilte Konfigurations-JSON-Datei.",
|
||||
"actions.tip.eraseMemories": "Löscht Bjorns Gedächtnis und Einstellungen vollständig.",
|
||||
"actions.tip.reboot": "Startet das gesamte System neu.",
|
||||
"actions.tip.shutdown": "Fährt das System vollständig herunter.",
|
||||
"actions.confirm.restartRecommended": "Neustart des Dienstes empfohlen. Jetzt neustarten?",
|
||||
"actions.confirm.restartService": "Bjorn-Dienst neustarten?",
|
||||
"actions.confirm.deleteActionStatus": "Alle gespeicherten Aktionsstati löschen?",
|
||||
"actions.confirm.clearOutput": "Gesamten Output-Ordner leeren?",
|
||||
"actions.confirm.clearLogs": "Alle Protokolldateien löschen?",
|
||||
"actions.confirm.clearNetkb": "Netzwerkwissen leeren? Dies kann nicht rückgängig gemacht werden.",
|
||||
"actions.confirm.clearLivestatus": "Livestatus-Datei löschen?",
|
||||
"actions.confirm.refreshActionsFile": "Aktionsdatei aktualisieren?",
|
||||
"actions.confirm.clearSharedConfig": "Geteilte Konfigurations-JSON löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"actions.confirm.eraseMemories": "Ganzes Gedächtnis und Einstellungen von Bjorn löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"actions.confirm.reboot": "Ganzes System neustarten?",
|
||||
"actions.confirm.shutdown": "System herunterfahren?",
|
||||
"actions.msg.restartingService": "Bjorn-Dienst startet neu...",
|
||||
"actions.msg.restartFailed": "Neustart des Dienstes fehlgeschlagen",
|
||||
"actions.msg.actionStatusDeleted": "Alle Aktionsstati wurden gelöscht.",
|
||||
"actions.msg.outputCleared": "Output-Ordner wurde geleert.",
|
||||
"actions.msg.logsCleared": "Protokolle wurden gelöscht.",
|
||||
"actions.msg.netkbCleared": "Netzwerkwissen wurde geleert.",
|
||||
"actions.msg.livestatusDeleted": "Livestatus-Datei wurde gelöscht.",
|
||||
"actions.msg.actionsFileRefreshed": "Aktionsdatei wurde aktualisiert.",
|
||||
"actions.msg.sharedConfigDeleted": "Geteilte Konfigurations-JSON wurde gelöscht.",
|
||||
"actions.msg.memoriesErased": "Bjorns Gedächtnis wurde gelöscht.",
|
||||
"actions.msg.rebooting": "System startet neu...",
|
||||
"actions.msg.shuttingDown": "System fährt herunter...",
|
||||
"actions.msg.csvInitialized": "CSV-Dateien wurden initialisiert.",
|
||||
"actions.msg.actionsJsonReloaded": "Aktions-JSON wurde neu geladen.",
|
||||
"actions.msg.imagesReloaded": "Bilder wurden neu geladen.",
|
||||
"actions.msg.fontsReloaded": "Schriftarten wurden neu geladen.",
|
||||
"actions.msg.unknownAction": "Unbekannte Aktion",
|
||||
"actions.msg.actionFailed": "Aktion fehlgeschlagen",
|
||||
"studio.title": "Aktions-Studio",
|
||||
"studio.palette": "Palette",
|
||||
"studio.canvas": "Leinwand",
|
||||
"studio.inspector": "Inspektor",
|
||||
"studio.actionsTab": "Aktionen",
|
||||
"studio.hostsTab": "Hosts",
|
||||
"studio.globalTab": "Global",
|
||||
"studio.save": "Speichern",
|
||||
"studio.load": "Laden",
|
||||
"studio.run": "Ausführen",
|
||||
"studio.clear": "Leeren",
|
||||
"studio.addNode": "Knoten hinzufügen",
|
||||
"studio.removeNode": "Knoten entfernen",
|
||||
"studio.search": "Aktionen suchen...",
|
||||
"backup.title": "Backup & Update",
|
||||
"backup.backupRestore": "Backup / Wiederherstellung",
|
||||
"backup.update": "Update",
|
||||
"backup.createBackup": "Backup erstellen",
|
||||
"backup.restoreBackup": "Wiederherstellen",
|
||||
"backup.downloadBackup": "Herunterladen",
|
||||
"backup.deleteBackup": "Backup löschen",
|
||||
"backup.lastBackup": "Letztes Backup",
|
||||
"backup.checkUpdates": "Updates prüfen",
|
||||
"backup.installUpdate": "Update installieren",
|
||||
"backup.currentVersion": "Aktuelle Version",
|
||||
"backup.latestVersion": "Neueste Version",
|
||||
"backup.upToDate": "Aktuell",
|
||||
"backup.updateAvailable": "Update verfügbar",
|
||||
"backup.clearLogs": "Protokolle löschen",
|
||||
"backup.noBackups": "Keine Backups gefunden",
|
||||
"backup.restoring": "Wiederherstellung...",
|
||||
"backup.creating": "Erstelle Backup...",
|
||||
"webenum.title": "Web-Enumeration",
|
||||
"webenum.totalResults": "Ergebnisse gesamt",
|
||||
"webenum.uniqueHosts": "Eindeutige Hosts",
|
||||
"webenum.successCount": "Erfolgreich (2xx)",
|
||||
"webenum.errorCount": "Fehler (4xx/5xx)",
|
||||
"webenum.host": "Host",
|
||||
"webenum.ip": "IP",
|
||||
"webenum.port": "Port",
|
||||
"webenum.directory": "Verzeichnis",
|
||||
"webenum.status": "Status",
|
||||
"webenum.size": "Größe",
|
||||
"webenum.scanDate": "Scan-Datum",
|
||||
"webenum.link": "Link",
|
||||
"webenum.exportJson": "JSON exportieren",
|
||||
"webenum.exportCsv": "CSV exportieren",
|
||||
"webenum.noResults": "Keine Ergebnisse gefunden",
|
||||
"webenum.details": "Ergebnisdetails",
|
||||
"webenum.openUrl": "URL öffnen",
|
||||
"webenum.copyUrl": "URL kopieren",
|
||||
"webenum.showing": "Zeige {{start}}-{{end}} von {{total}} Ergebnissen",
|
||||
"webenum.itemsPerPage": "Elemente pro Seite",
|
||||
"webenum.refreshData": "Daten aktualisieren",
|
||||
"webenum.responseTime": "Antwortzeit",
|
||||
"webenum.contentType": "Inhaltstyp",
|
||||
"webenum.fullUrl": "Vollständige URL",
|
||||
"zombie.title": "Zombieland C2C",
|
||||
"zombie.agents": "Agenten",
|
||||
"zombie.terminal": "Terminal",
|
||||
"zombie.commands": "Befehle",
|
||||
"zombie.totalAgents": "Agenten gesamt",
|
||||
"zombie.onlineAgents": "Online",
|
||||
"zombie.offlineAgents": "Offline",
|
||||
"zombie.idleAgents": "Untätig",
|
||||
"zombie.sendCommand": "Befehl senden",
|
||||
"zombie.broadcast": "Rundruf",
|
||||
"zombie.selectAgent": "Agent auswählen",
|
||||
"zombie.os": "BS",
|
||||
"zombie.lastSeen": "Zuletzt gesehen",
|
||||
"zombie.status": "Status",
|
||||
"zombie.noAgents": "Keine verbundenen Agenten",
|
||||
"zombie.quickCommands": "Schnellbefehle",
|
||||
"zombie.files": "Dateien",
|
||||
"quick.autoScan": "Auto-Scan",
|
||||
"quick.connectWifi": "Mit WLAN verbinden",
|
||||
"quick.knownNetworks": "Bekannte Netzwerke",
|
||||
"quick.importPotfiles": "Potfiles importieren",
|
||||
"quick.subtitle": "WLAN & Bluetooth",
|
||||
"quick.pair": "Koppeln",
|
||||
"quick.trust": "Vertrauen",
|
||||
"quick.forgetDevice": "Gerät vergessen",
|
||||
"quick.forgetDevicePrompt": "{{name}} vergessen?",
|
||||
"quick.forgetNetworkPrompt": "Sind Sie sicher, dass Sie dieses Netzwerk vergessen möchten?",
|
||||
"bjorn.title": "Bjorn EPD-Bildschirm",
|
||||
"bjorn.epdScreen": "e-Paper Display",
|
||||
"bjorn.refreshInterval": "Aktualisierungsintervall",
|
||||
"bjorn.autoRefresh": "Auto-Aktualisierung",
|
||||
"bjorn.manualRefresh": "Jetzt aktualisieren",
|
||||
"bjorn.seconds": "Sekunden",
|
||||
"common.search": "Suchen",
|
||||
"common.filter": "Filtern",
|
||||
"common.refresh": "Aktualisieren",
|
||||
"common.save": "Speichern",
|
||||
"common.cancel": "Abbrechen",
|
||||
"common.delete": "Löschen",
|
||||
"common.edit": "Bearbeiten",
|
||||
"common.close": "Schließen",
|
||||
"common.loading": "Laden...",
|
||||
"common.noData": "Keine Daten verfügbar",
|
||||
"common.error": "Fehler",
|
||||
"common.success": "Erfolg",
|
||||
"common.confirm": "Bestätigen",
|
||||
"common.yes": "Ja",
|
||||
"common.no": "Nein",
|
||||
"common.export": "Exportieren",
|
||||
"common.import": "Importieren",
|
||||
"common.download": "Herunterladen",
|
||||
"common.upload": "Hochladen",
|
||||
"common.copy": "Kopieren",
|
||||
"common.start": "Starten",
|
||||
"common.stop": "Stoppen",
|
||||
"common.restart": "Neustarten",
|
||||
"common.status": "Status",
|
||||
"common.name": "Name",
|
||||
"common.value": "Wert",
|
||||
"common.type": "Typ",
|
||||
"common.host": "Host",
|
||||
"common.port": "Port",
|
||||
"common.target": "Ziel",
|
||||
"common.date": "Datum",
|
||||
"common.time": "Uhrzeit",
|
||||
"common.size": "Größe",
|
||||
"common.actions": "Aktionen",
|
||||
"common.details": "Details",
|
||||
"common.back": "Zurück",
|
||||
"common.next": "Weiter",
|
||||
"common.previous": "Zurück",
|
||||
"common.first": "Erster",
|
||||
"common.last": "Letzter",
|
||||
"common.all": "Alle",
|
||||
"common.none": "Keine",
|
||||
"common.showing": "Zeige",
|
||||
"common.of": "von",
|
||||
"common.results": "Ergebnissen",
|
||||
"common.items": "Elemente",
|
||||
"common.page": "Seite",
|
||||
"common.perPage": "pro Seite",
|
||||
"common.sortBy": "Sortieren nach",
|
||||
"common.ascending": "Aufsteigend",
|
||||
"common.descending": "Absteigend",
|
||||
"common.view": "Ansicht",
|
||||
"common.table": "Tabelle",
|
||||
"common.grid": "Gitter",
|
||||
"common.list": "Liste",
|
||||
"common.map": "Karte",
|
||||
"common.enabled": "Aktiviert",
|
||||
"common.disabled": "Deaktiviert",
|
||||
"common.on": "An",
|
||||
"common.off": "Aus",
|
||||
"common.version": "Version",
|
||||
"common.hide": "Ausblenden",
|
||||
"common.show": "Anzeigen",
|
||||
"common.add": "Hinzufügen",
|
||||
"common.remove": "Entfernen",
|
||||
"common.clear": "Leeren",
|
||||
"common.reset": "Zurücksetzen",
|
||||
"common.apply": "Anwenden",
|
||||
"common.run": "Ausführen",
|
||||
"common.send": "Senden",
|
||||
"common.connect": "Verbinden",
|
||||
"common.disconnect": "Trennen",
|
||||
"common.selectAll": "Alle auswählen",
|
||||
"common.deselectAll": "Alle abwählen",
|
||||
"common.copied": "Kopiert!",
|
||||
"common.notFound": "Nicht gefunden",
|
||||
"backup.checkUpdatesHint": "Klicken Sie auf \"Updates prüfen\", um Versionen anzuzeigen.",
|
||||
"backup.checkingUpdates": "Prüfe auf Updates...",
|
||||
"backup.confirmFreshStart": "Frischstart bestätigen? ",
|
||||
"backup.createdSuccessfully": "Backup erfolgreich erstellt.",
|
||||
"backup.defaultUpdated": "Standard-Backup aktualisiert.",
|
||||
"backup.deleted": "Backup gelöscht.",
|
||||
"backup.descriptionPlaceholder": "Backup-Beschreibung...",
|
||||
"backup.enterDescription": "Bitte eine Backup-Beschreibung eingeben.",
|
||||
"backup.failedCheckUpdates": "Fehler beim Prüfen auf Updates",
|
||||
"backup.failedCreate": "Fehler beim Erstellen des Backups",
|
||||
"backup.failedDelete": "Fehler beim Löschen des Backups",
|
||||
"backup.failedLoadBackups": "Fehler beim Laden der Backups",
|
||||
"backup.failedSetDefault": "Fehler beim Festlegen des Standards",
|
||||
"backup.freshStart": "Frischstart",
|
||||
"backup.freshStartFailed": "Frischstart fehlgeschlagen",
|
||||
"backup.freshStartInitiated": "Frischstart eingeleitet.",
|
||||
"backup.github": "github",
|
||||
"backup.keepActions": "Aktions-Ordner behalten",
|
||||
"backup.keepConfig": "Konfig-Ordner behalten",
|
||||
"backup.keepData": "Daten-Ordner behalten",
|
||||
"backup.keepResources": "Ressourcen-Ordner behalten",
|
||||
"backup.noBackupsCreateAbove": "Keine Backups gefunden. Oben eines erstellen.",
|
||||
"backup.restoreCompleted": "Wiederherstellung abgeschlossen.",
|
||||
"backup.restoreOptions": "Wiederherstellungsoptionen",
|
||||
"backup.restorePoint": "wiederherstellungspunkt",
|
||||
"backup.selectKeepFolders": "Wählen Sie Ordner, die während des Vorgangs behalten werden sollen:",
|
||||
"backup.setDefault": "Als Standard festlegen",
|
||||
"backup.unnamedBackup": "Unbenanntes Backup",
|
||||
"backup.updateInitiated": "Update eingeleitet.",
|
||||
"backup.updateOptions": "Update-Optionen",
|
||||
"common.confirmDiscardUnsaved": "Ungespeicherte Änderungen verwerfen?",
|
||||
"common.confirmQuestion": "Bestätigen?",
|
||||
"common.default": "standard",
|
||||
"common.deleteFailed": "Löschen fehlgeschlagen",
|
||||
"common.deleted": "Gelöscht",
|
||||
"common.description": "Beschreibung",
|
||||
"common.directory": "verzeichnis",
|
||||
"common.duplicate": "Duplizieren",
|
||||
"common.exportJson": "JSON exportieren",
|
||||
"common.failed": "fehlgeschlagen",
|
||||
"common.file": "datei",
|
||||
"common.importJson": "JSON importieren",
|
||||
"common.new": "Neu",
|
||||
"common.noMatches": "Keine Treffer",
|
||||
"common.options": "Optionen",
|
||||
"common.processingPleaseWait": "Verarbeitung läuft, bitte warten...",
|
||||
"common.refreshed": "Aktualisiert",
|
||||
"common.rename": "Umbenennen",
|
||||
"common.saving": "Speichere...",
|
||||
"common.unknown": "unbekannt",
|
||||
"common.unsavedChanges": "Ungespeicherte Änderungen",
|
||||
"db.autoRefresh": "Auto-Aktualisierung",
|
||||
"db.changesDiscarded": "Änderungen verworfen",
|
||||
"db.changesSaved": "Änderungen gespeichert",
|
||||
"db.confirmDrop": "Tabelle \"{{table}}\" LÖSCHEN? Dies kann nicht rückgängig gemacht werden!",
|
||||
"db.confirmTruncate": "Alle Zeilen in \"{{table}}\" leeren?",
|
||||
"db.dangerZone": "Gefahrenzone",
|
||||
"db.deletingRowsCount": "Lösche {{count}} Zeile(n)...",
|
||||
"db.dropFailed": "Tabelle löschen fehlgeschlagen",
|
||||
"db.droppedTable": "Tabelle {{table}} gelöscht",
|
||||
"db.dropping": "Lösche...",
|
||||
"db.emptyTable": "Leere Tabelle",
|
||||
"db.errorLoadingData": "Fehler beim Laden der Daten",
|
||||
"db.failedLoadCatalog": "Laden des Katalogs fehlgeschlagen",
|
||||
"db.failedLoadTable": "Laden der Tabelle fehlgeschlagen",
|
||||
"db.filterTables": "Tabellen filtern...",
|
||||
"db.insertFailed": "Einfügen fehlgeschlagen",
|
||||
"db.insertingRow": "Füge Zeile ein...",
|
||||
"db.noRowsSelected": "Keine Zeilen ausgewählt",
|
||||
"db.rowInserted": "Zeile eingefügt",
|
||||
"db.rowsDeleted": "Zeilen gelöscht",
|
||||
"db.runningVacuum": "VACUUM wird ausgeführt...",
|
||||
"db.saveFailed": "Speichern fehlgeschlagen",
|
||||
"db.selectTableFromSidebar": "Wählen Sie eine Tabelle aus der Seitenleiste",
|
||||
"db.tableDropped": "Tabelle gelöscht",
|
||||
"db.tableTruncated": "Tabelle geleert",
|
||||
"db.truncateFailed": "Leeren fehlgeschlagen",
|
||||
"db.truncating": "Leere...",
|
||||
"db.vacuumComplete": "VACUUM abgeschlossen",
|
||||
"db.vacuumDone": "VACUUM erledigt",
|
||||
"db.vacuumFailed": "VACUUM fehlgeschlagen",
|
||||
"files.confirmDelete": "{{label}} \"{{name}}\" löschen?",
|
||||
"files.downloadFile": "Datei herunterladen",
|
||||
"files.duplicateFailed": "Duplizieren fehlgeschlagen",
|
||||
"files.duplicated": "Dupliziert",
|
||||
"files.emptyDirectory": "Leeres Verzeichnis",
|
||||
"files.errorLoading": "Fehler beim Laden der Dateien",
|
||||
"files.failedLoadDir": "Laden des Verzeichnisses fehlgeschlagen",
|
||||
"files.filterPlaceholder": "Dateien filtern...",
|
||||
"files.itemsCount": "{{count}} Element(s)",
|
||||
"files.newNamePrompt": "Neuer Name:",
|
||||
"files.noMatch": "Keine passenden Dateien",
|
||||
"files.openDirectory": "Verzeichnis öffnen",
|
||||
"files.parent": ".. (übergeordnet)",
|
||||
"files.renameFailed": "Umbenennen fehlgeschlagen",
|
||||
"files.renamed": "Umbenannt",
|
||||
"files.root": "Wurzel",
|
||||
"files.uploadComplete": "Hochladen abgeschlossen",
|
||||
"files.uploadFailed": "Hochladen fehlgeschlagen",
|
||||
"files.uploadingCount": "Lade {{count}} Datei(en) hoch...",
|
||||
"studio.actionNotFound": "Aktion nicht gefunden",
|
||||
"studio.classNameRequired": "Klassenname erforderlich",
|
||||
"studio.confirmDeleteAction": "Aktion \"{{name}}\" löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"studio.deletedName": "Gelöscht: {{name}}",
|
||||
"studio.exportedFile": "Exportiert: {{name}}",
|
||||
"studio.filterActions": "Aktionen filtern...",
|
||||
"studio.importFailed": "Import fehlgeschlagen",
|
||||
"studio.importedFile": "Importiert: {{name}}",
|
||||
"studio.loadFailed": "Laden fehlgeschlagen",
|
||||
"studio.loadedFromCacheName": "Aus Cache geladen: {{name}}",
|
||||
"studio.loadedName": "Geladen: {{name}}",
|
||||
"studio.newActionCreated": "Neue Aktion erstellt",
|
||||
"studio.noActionLoaded": "Keine Aktion geladen",
|
||||
"studio.saveFailedBackedUp": "Speichern fehlgeschlagen (lokales Backup erstellt)",
|
||||
"studio.savedName": "Gespeichert: {{name}}",
|
||||
"studio.setClassBeforeExport": "Klasse vor Export festlegen",
|
||||
"zombie.agentRemoved": "Agent {{name}} entfernt",
|
||||
"zombie.agentsPurged": "{{count}} Agenten bereinigt",
|
||||
"zombie.allAgents": "Alle Agenten",
|
||||
"zombie.c2StartedOnPort": "C2-Server auf Port {{port}} gestartet",
|
||||
"zombie.c2Stopped": "C2-Server gestoppt",
|
||||
"zombie.clearConsole": "Konsole leeren",
|
||||
"zombie.clearLogs": "Protokolle leeren",
|
||||
"zombie.commandBroadcasted": "Befehl per Rundruf gesendet",
|
||||
"zombie.commandSentToAgents": "Befehl an {{count}} Agent(en) gesendet",
|
||||
"zombie.confirmPurgeStale": "Alle Agenten bereinigen, die länger als 24h inaktiv sind?",
|
||||
"zombie.confirmRemoveAgent": "Agent {{name}} entfernen?",
|
||||
"zombie.confirmStopC2": "C2-Server stoppen?",
|
||||
"zombie.consoleCleared": "Konsole geleert",
|
||||
"zombie.enterC2Port": "C2-Port eingeben:",
|
||||
"zombie.enterCommand": "Befehl eingeben...",
|
||||
"zombie.failedPurgeStale": "Bereinigung inaktiver Agenten fehlgeschlagen",
|
||||
"zombie.failedRemoveAgent": "Entfernen von Agent {{name}} fehlgeschlagen",
|
||||
"zombie.failedSendCommand": "Senden des Befehls fehlgeschlagen",
|
||||
"zombie.failedStartC2": "Starten von C2 fehlgeschlagen",
|
||||
"zombie.failedStopC2": "Stoppen von C2 fehlgeschlagen",
|
||||
"zombie.noAgentsConnected": "Keine Agenten verbunden",
|
||||
"zombie.noAgentsMatchSearch": "Keine Agenten entsprechen Ihrer Suche",
|
||||
"zombie.purgeStale": "Inaktive bereinigen",
|
||||
"zombie.purgeStaleHint": "Bereinige Agenten, die >24h inaktiv sind",
|
||||
"zombie.removeAgent": "Agent entfernen",
|
||||
"zombie.startC2": "C2 starten",
|
||||
"zombie.stopC2": "C2 stoppen",
|
||||
"zombie.systemLogs": "Systemprotokolle",
|
||||
"zombieland.alive": "Lebendig",
|
||||
"zombieland.c2Status": "C2 Status",
|
||||
"zombieland.dead": "Tot",
|
||||
"zombieland.totalAgents": "Agenten gesamt",
|
||||
"greeting": "Hallo",
|
||||
"start": "Start",
|
||||
"tick": "Tick",
|
||||
"common.ip": "IP",
|
||||
"common.mac": "MAC",
|
||||
"common.os": "BS",
|
||||
"zombie.never": "Nie",
|
||||
"zombie.openInConsole": "In Konsole öffnen",
|
||||
"common.saved": "Gespeichert",
|
||||
"attacks.tabs.attacks": "Angriffe",
|
||||
"attacks.tabs.comments": "Kommentare",
|
||||
"attacks.tabs.images": "Bilder",
|
||||
"attacks.btn.addAttack": "Angriff hinzufügen",
|
||||
"attacks.btn.removeAttack": "Angriff entfernen",
|
||||
"attacks.btn.deleteAction": "Aktion löschen",
|
||||
"attacks.btn.restoreDefaultsBundle": "Standardwerte wiederherstellen",
|
||||
"attacks.btn.addSection": "Abschnitt hinzufügen",
|
||||
"attacks.btn.deleteSection": "Abschnitt löschen",
|
||||
"attacks.btn.restoreDefault": "Standard wiederherstellen",
|
||||
"attacks.btn.createCharacter": "Charakter erstellen",
|
||||
"attacks.btn.deleteCharacter": "Charakter löschen",
|
||||
"attacks.section.characters": "Charakter",
|
||||
"attacks.section.statusImages": "Status-Bilder",
|
||||
"attacks.section.staticImages": "Statische Bilder",
|
||||
"attacks.section.webImages": "Web-Bilder",
|
||||
"attacks.section.actionIcons": "Aktions-Icons",
|
||||
"attacks.editor.selectAttack": "Angriff auswählen",
|
||||
"attacks.empty.noAttacks": "Keine Angriffe gefunden.",
|
||||
"attacks.empty.noComments": "Keine Kommentare gefunden.",
|
||||
"attacks.comments.placeholder": "Kommentare werden hier angezeigt...",
|
||||
"attacks.images.enterEditMode": "Bearbeitungsmodus aktivieren",
|
||||
"attacks.images.exitEditMode": "Bearbeitungsmodus verlassen",
|
||||
"attacks.images.sortName": "Sortierung: Name",
|
||||
"attacks.images.sortDimensions": "Sortierung: Größe",
|
||||
"attacks.images.search": "Bilder suchen...",
|
||||
"attacks.images.rename": "Bild umbenennen",
|
||||
"attacks.images.replace": "Bild ersetzen",
|
||||
"attacks.images.resizeSelected": "Auswahl skalieren",
|
||||
"attacks.images.addCharacters": "Charakter-Bilder hinzufügen",
|
||||
"attacks.images.deleteSelected": "Auswahl löschen",
|
||||
"attacks.images.addStatus": "Status-Bild hinzufügen",
|
||||
"attacks.images.addStatic": "Statisches Bild hinzufügen",
|
||||
"attacks.images.addWeb": "Web-Bild hinzufügen",
|
||||
"attacks.images.addIcon": "Aktions-Icon hinzufügen",
|
||||
"attacks.errors.loadAttacks": "Fehler beim Laden der Angriffe.",
|
||||
"attacks.errors.loadImages": "Fehler beim Laden der Bilder.",
|
||||
"attacks.confirm.switchCharacter": "Zu Charakter \"{{name}}\" wechseln?",
|
||||
"attacks.confirm.removeAttack": "Angriff \"{{name}}\" löschen?",
|
||||
"attacks.confirm.deleteAction": "Aktion \"{{name}}\" löschen?",
|
||||
"attacks.confirm.restoreAttack": "\"{{name}}\" auf Standard zurücksetzen?",
|
||||
"attacks.confirm.restoreDefaultsBundle": "ALLE Standardwerte (Aktionen, Bilder, Kommentare) wiederherstellen?",
|
||||
"attacks.confirm.deleteCharacter": "Charakter \"{{name}}\" löschen?",
|
||||
"attacks.confirm.deleteSection": "Abschnitt \"{{name}}\" löschen?",
|
||||
"attacks.confirm.restoreDefaultComments": "Standardkommentare wiederherstellen?",
|
||||
"attacks.confirm.deleteSelectedImages": "Ausgewählte Bilder löschen?",
|
||||
"attacks.prompt.newCharacterName": "Neuer Charaktername:",
|
||||
"attacks.prompt.characterToDelete": "Zu löschender Charakter:",
|
||||
"attacks.prompt.newSectionName": "Neuer Abschnittsname:",
|
||||
"attacks.prompt.newImageName": "Neuer Name:",
|
||||
"attacks.prompt.resizeWidth": "Breite skalieren:",
|
||||
"attacks.prompt.resizeHeight": "Höhe skalieren:",
|
||||
"attacks.toast.characterSwitched": "Charakter gewechselt",
|
||||
"attacks.toast.attackImported": "Angriff importiert",
|
||||
"attacks.toast.selectAttackFirst": "Zuerst einen Angriff auswählen",
|
||||
"attacks.toast.actionDeleted": "Aktion gelöscht",
|
||||
"attacks.toast.defaultsRestored": "Standardwerte wiederherstellen",
|
||||
"attacks.toast.characterCreated": "Charakter erstellt",
|
||||
"attacks.toast.noDeletableCharacters": "Keine löschbaren Charaktere",
|
||||
"attacks.toast.characterDeleted": "Charakter gelöscht",
|
||||
"attacks.toast.commentsRestored": "Kommentare wiederhergestellt",
|
||||
"attacks.toast.selectSectionFirst": "Zuerst einen Abschnitt auswählen",
|
||||
"attacks.toast.commentsSaved": "Kommentare gespeichert",
|
||||
"attacks.toast.selectExactlyOneImage": "Genau ein Bild auswählen",
|
||||
"attacks.toast.selectAtLeastOneImage": "Mindestens ein Bild auswählen",
|
||||
"attacks.toast.imagesResized": "Bilder skaliert",
|
||||
"attacks.toast.characterImagesUploaded": "Charakter-Bilder hochgeladen",
|
||||
"attacks.toast.selectStatusActionFirst": "Zuerst eine Status-Aktion auswählen",
|
||||
"actions.toast.presetApplied": "Voreinstellung angewendet",
|
||||
"actions.toast.startingAction": "Starte {{name}}...",
|
||||
"actions.toast.actionStarted": "Aktion gestartet",
|
||||
"actions.toast.stoppedByUser": "Vom Benutzer gestoppt",
|
||||
"actions.toast.actionStopped": "Aktion gestoppt",
|
||||
"actions.toast.stopFailed": "Stoppen fehlgeschlagen",
|
||||
"actions.toast.failedToStop": "Konnte nicht gestoppt werden",
|
||||
"actions.toast.consoleCleared": "Konsole geleert",
|
||||
"actions.toast.noLogsToExport": "Keine Protokolle zum Exportieren",
|
||||
"actions.toast.logsExported": "Protokolle exportiert",
|
||||
"netkb.confirmRemoveAction": "Aktion \"{{action}}\" für IP \"{{ip}}\" entfernen?",
|
||||
"netkb.actionRemoved": "Aktion entfernt",
|
||||
"actions.running": "Läuft",
|
||||
"attacks.btn.syncMissing": "Fehlende Elemente synchronisieren",
|
||||
"attacks.images.gridDensity": "Gitterdichte",
|
||||
"attacks.images.density": "Dichte",
|
||||
"attacks.sync.defaultComment": "Kommentar für diese Aktion hinzufügen",
|
||||
"attacks.sync.none": "Keine Angriffe zum Synchronisieren.",
|
||||
"attacks.sync.done": "Synchronisierung abgeschlossen. Neue Kommentare: {{comments}}, Status-Bilder: {{status}}, Charakter-Bilder: {{characters}}.",
|
||||
"attacks.sync.failed": "Synchronisierung fehlender Elemente fehlgeschlagen",
|
||||
"actions.args.free": "Freie Argumente",
|
||||
"actions.args.none": "Keine konfigurierbaren Argumente",
|
||||
"actions.args.subtitle": "Automatisch aus Aktionsdefinitionen generiert",
|
||||
"actions.args.title": "Argumente",
|
||||
"actions.assign": "Zuweisen",
|
||||
"actions.emptyPane": "Keine Aktion ausgewählt",
|
||||
"actions.logs.completed": "Abgeschlossen",
|
||||
"actions.logs.empty": "Noch keine Protokolle",
|
||||
"actions.logs.waiting": "Warten...",
|
||||
"actions.searchPlaceholder": "Aktionen suchen...",
|
||||
"actions.tabs.actions": "Aktionen",
|
||||
"actions.tabs.arguments": "Argumente",
|
||||
"actions.toast.selectActionFirst": "Zuerst eine Aktion auswählen",
|
||||
"common.move": "Verschieben",
|
||||
"common.ready": "Bereit",
|
||||
"common.menu": "Menü",
|
||||
"common.browse": "Durchsuchen...",
|
||||
"common.platform": "Plattform",
|
||||
"common.generate": "Generieren",
|
||||
"common.vendor": "Hersteller",
|
||||
"common.hostname": "Hostname",
|
||||
"common.ports": "Ports",
|
||||
"zombie.generateClient": "Client generieren",
|
||||
"zombie.checkStale": "Inaktive prüfen",
|
||||
"zombie.selectedAgents": "ausgewählte Agenten",
|
||||
"zombie.clientId": "Client-ID",
|
||||
"zombie.labCreds": "Lab-Anmeldedaten",
|
||||
"zombie.deployOptions": "Bereitstellungsoptionen",
|
||||
"zombie.deployViaSSH": "Über SSH bereitstellen",
|
||||
"zombie.fileBrowser": "Datei-Browser",
|
||||
"dash.lastUpdate": "Zuletzte Aktualisierung",
|
||||
"netkb.searchPlaceholder": "Host, IP, Hersteller, Port suchen...",
|
||||
"netkb.searchHint": "Tipp: 'port:80' oder 'vendor:intel' eingeben",
|
||||
"files.dropzoneHint": "Dateien hier ablegen oder zum Hochladen klicken",
|
||||
"files.moveToTitle": "Verschieben nach...",
|
||||
"files.selectDestinationFolder": "Zielordner auswählen",
|
||||
"attacks.sidebar.management": "Verwendung",
|
||||
"sched.upcoming": "Anstehend",
|
||||
"sched.success": "Erfolg",
|
||||
"sched.cancelled": "Abgebrochen",
|
||||
"sched.history": "Historie",
|
||||
"sched.historyMsg": "Historien-Protokolle",
|
||||
"creds.searchPlaceholder": "Dienste, Benutzer suchen...",
|
||||
"creds.uniqueHosts": "Eindeutige Hosts",
|
||||
"creds.totalCredentials": "Gesamte Anmeldedaten",
|
||||
"console.maxReconnect": "Konsole: Maximale Anzahl an Wiederverbindungsversuchen erreicht",
|
||||
"console.scrollToBottom": "Nach unten scrollen",
|
||||
"console.manual": "Manuell",
|
||||
"console.auto": "Auto",
|
||||
"console.turnOnAuto": "Auto-Modus einschalten",
|
||||
"console.turnOnManual": "Manuellen Modus einschalten",
|
||||
"console.noTarget": "Kein Ziel",
|
||||
"console.noAction": "Keine Aktion",
|
||||
"console.scanStarted": "Manueller Scan gestartet",
|
||||
"console.scanFailed": "Manueller Scan fehlgeschlagen",
|
||||
"console.attackStarted": "Manueller Angriff gestartet",
|
||||
"console.attackFailed": "Manueller Angriff fehlgeschlagen",
|
||||
"console.failedToggleMode": "Moduswechsel fehlgeschlagen",
|
||||
"console.reconnectAttempt": "Verbinde neu (Versuch {{count}})...",
|
||||
"quick.close": "Panel schließen",
|
||||
"quick.connectingTo": "Verbinde mit {{ssid}}...",
|
||||
"quick.connectedTo": "Verbunden mit {{ssid}}",
|
||||
"quick.connectionFailed": "Verbindung fehlgeschlagen",
|
||||
"quick.loadKnownFailed": "Laden bekannter Netzwerke fehlgeschlagen",
|
||||
"quick.priorityUpdated": "Priorität aktualisiert",
|
||||
"quick.priorityUpdateFailed": "Aktualisierung der Priorität fehlgeschlagen",
|
||||
"quick.networkRemoved": "Netzwerk entfernt",
|
||||
"quick.importingPotfiles": "Potfiles werden importiert...",
|
||||
"quick.importedCount": "{{count}} Anmeldedaten importiert",
|
||||
"quick.btScanFailed": "Bluetooth-Scan fehlgeschlagen",
|
||||
"quick.btActioning": "{{action}} von {{name}}...",
|
||||
"quick.btActionDone": "{{name}} {{action}} abgeschlossen",
|
||||
"quick.btActionFailed": "{{action}} fehlgeschlagen",
|
||||
"quick.btForgotten": "{{name}} vergessen",
|
||||
"sidebar.close": "Seitenleiste schließen",
|
||||
"api.aborted": "Abgebrochen",
|
||||
"api.timeout": "Anfrage Zeitüberschreitung",
|
||||
"api.failed": "Anfrage fehlgeschlagen",
|
||||
"router.notFound": "Seite nicht gefunden: {{path}}",
|
||||
"router.errorLoading": "Fehler beim Laden der Seite: {{message}}"
|
||||
}
|
||||
823
web/i18n/en.json
Normal file
@@ -0,0 +1,823 @@
|
||||
{
|
||||
"nav.dashboard": "Dashboard",
|
||||
"nav.bjorn": "Bjorn",
|
||||
"nav.netkb": "NetKB",
|
||||
"nav.network": "Network",
|
||||
"nav.credentials": "Credentials",
|
||||
"nav.vulnerabilities": "Vulnerabilities",
|
||||
"nav.attacks": "Attacks",
|
||||
"nav.scheduler": "Scheduler",
|
||||
"nav.database": "Database",
|
||||
"nav.files": "Files",
|
||||
"nav.loot": "Loot",
|
||||
"nav.actions": "Actions",
|
||||
"nav.actionsStudio": "Actions Studio",
|
||||
"nav.backup": "Backup & Update",
|
||||
"nav.webEnum": "Web Enum",
|
||||
"nav.zombieland": "Zombieland",
|
||||
"nav.ai_dashboard": "AI Dashboard",
|
||||
"nav.settings": "Settings",
|
||||
"nav.shortcuts": "Shortcuts",
|
||||
"nav.pages": "Pages",
|
||||
"status.initializing": "Initializing...",
|
||||
"status.online": "Online",
|
||||
"status.offline": "Offline",
|
||||
"console.title": "Console",
|
||||
"console.clear": "Clear",
|
||||
"console.sseOn": "SSE On",
|
||||
"console.sseOff": "SSE Off",
|
||||
"console.newLogs": "{{count}} new logs",
|
||||
"settings.theme": "Theme",
|
||||
"settings.language": "Language",
|
||||
"settings.general": "General",
|
||||
"settings.toggles": "Toggles",
|
||||
"settings.editValue": "Edit value",
|
||||
"settings.addValues": "Add values (comma separated)...",
|
||||
"settings.setValue": "Set value...",
|
||||
"settings.errorLoading": "Error loading config",
|
||||
"settings.configSaved": "Configuration saved",
|
||||
"settings.errorSaving": "Error saving config",
|
||||
"settings.defaultsRestored": "Defaults restored",
|
||||
"settings.errorRestoring": "Error restoring defaults",
|
||||
"settings.tooltip.manual_mode": "When enabled, Bjorn runs only manual actions from the UI.",
|
||||
"settings.tooltip.ai_mode": "Enables AI decision mode. Disable to run heuristic AUTO mode.",
|
||||
"settings.tooltip.learn_in_auto": "Collect AI training data while running in AUTO mode.",
|
||||
"settings.tooltip.debug_mode": "Enables extra diagnostic logs and debug behavior.",
|
||||
"settings.tooltip.websrv": "Start the embedded web server at startup.",
|
||||
"settings.tooltip.webauth": "Require authentication to access the web interface.",
|
||||
"settings.tooltip.bjorn_debug_enabled": "Expose the Bjorn Debug page and debug API endpoints.",
|
||||
"settings.tooltip.retry_success_actions": "Allow retrying actions that already succeeded for a target.",
|
||||
"settings.tooltip.retry_failed_actions": "Retry actions after a previous failure on a target.",
|
||||
"settings.tooltip.ai_server_url": "Base URL of the AI server used for model sync and uploads.",
|
||||
"settings.tooltip.ai_exploration_rate": "Probability of exploration in AI mode (higher = more random tries).",
|
||||
"settings.tooltip.ai_sync_interval": "Seconds between AI synchronization attempts.",
|
||||
"settings.tooltip.ai_server_max_failures_before_auto": "Switch to AUTO mode after this many consecutive AI server failures.",
|
||||
"settings.tooltip.startup_delay": "Delay in seconds before startup workflows begin.",
|
||||
"settings.tooltip.web_delay": "Polling interval for some web UI refresh operations.",
|
||||
"settings.tooltip.screen_delay": "Delay between display refresh cycles.",
|
||||
"settings.tooltip.livestatus_delay": "Interval for live status updates.",
|
||||
"settings.tooltip.epd_enabled": "Enable e-paper display output. Disable for headless mode.",
|
||||
"settings.tooltip.showiponscreen": "Show current IP address on the display.",
|
||||
"settings.tooltip.shared_update_interval": "Seconds between shared runtime state updates.",
|
||||
"settings.tooltip.vuln_update_interval": "Seconds between vulnerability counters refresh.",
|
||||
"settings.tooltip.semaphore_slots": "Maximum number of concurrent action slots.",
|
||||
"settings.tooltip.runtime_tick_s": "Runtime updater loop tick interval in seconds.",
|
||||
"settings.tooltip.runtime_gc_interval_s": "Optional forced GC interval in seconds (0 disables).",
|
||||
"settings.tooltip.default_network_interface": "Preferred network interface for scans and connectivity checks.",
|
||||
"settings.tooltip.use_custom_network": "Use custom CIDR network instead of auto-detected network.",
|
||||
"settings.tooltip.custom_network": "Custom target network in CIDR format (example: 192.168.1.0/24).",
|
||||
"settings.tooltip.portlist": "Default port list used by scanners and schedulers.",
|
||||
"settings.tooltip.portstart": "Start of port range for range-based scans.",
|
||||
"settings.tooltip.portend": "End of port range for range-based scans.",
|
||||
"settings.tooltip.scan_max_host_threads": "Maximum parallel host worker threads during scans.",
|
||||
"settings.tooltip.scan_max_port_threads": "Maximum parallel port scan threads per host.",
|
||||
"settings.tooltip.mac_scan_blacklist": "MAC addresses excluded from automated targeting.",
|
||||
"settings.tooltip.ip_scan_blacklist": "IP addresses excluded from automated targeting.",
|
||||
"settings.tooltip.hostname_scan_blacklist": "Hostnames excluded from automated targeting.",
|
||||
"settings.tooltip.vuln_fast": "Use faster vulnerability scan profile.",
|
||||
"settings.tooltip.nse_vulners": "Enable Nmap NSE vulners scripts during vulnerability scans.",
|
||||
"settings.tooltip.vuln_max_ports": "Maximum number of ports evaluated per host for vuln scans.",
|
||||
"settings.tooltip.use_actions_studio": "Load and use action graph definitions from Actions Studio.",
|
||||
"settings.tooltip.bruteforce_exhaustive_enabled": "Enable exhaustive bruteforce fallback after dictionary attempts.",
|
||||
"settings.tooltip.bruteforce_exhaustive_max_candidates": "Maximum generated candidates for exhaustive bruteforce.",
|
||||
"theme.group.colors": "Colors",
|
||||
"theme.group.surfaces": "Surfaces",
|
||||
"theme.group.layout": "Layout",
|
||||
"theme.token.bg": "Background",
|
||||
"theme.token.ink": "Text Color",
|
||||
"theme.token.accent1": "Accent 1 (Acid)",
|
||||
"theme.token.accent2": "Accent 2 (Cyan)",
|
||||
"theme.token.danger": "Danger",
|
||||
"theme.token.warning": "Warning",
|
||||
"theme.token.ok": "Success",
|
||||
"theme.token.panel": "Panel",
|
||||
"theme.token.panel2": "Panel Alt",
|
||||
"theme.token.ctrlPanel": "Control Panel",
|
||||
"theme.token.border": "Border",
|
||||
"theme.token.radius": "Border Radius",
|
||||
"theme.advanced": "Advanced CSS",
|
||||
"theme.applyRaw": "Apply",
|
||||
"theme.reset": "Reset to Default",
|
||||
"dash.title": "Dashboard",
|
||||
"dash.battery": "Battery",
|
||||
"dash.internet": "Internet",
|
||||
"dash.cpu": "CPU",
|
||||
"dash.ram": "RAM",
|
||||
"dash.disk": "Disk",
|
||||
"dash.temp": "Temp",
|
||||
"dash.uptime": "Uptime",
|
||||
"dash.hostsAlive": "Hosts Alive",
|
||||
"dash.totalHosts": "Total Hosts",
|
||||
"dash.openPorts": "Open Ports",
|
||||
"dash.credentials": "Credentials",
|
||||
"dash.vulnerabilities": "Vulnerabilities",
|
||||
"dash.actions": "Actions",
|
||||
"dash.connected": "Connected",
|
||||
"dash.disconnected": "Disconnected",
|
||||
"dash.charging": "Charging",
|
||||
"dash.discharging": "Discharging",
|
||||
"dash.full": "Full",
|
||||
"dash.connectivity": "Connectivity",
|
||||
"dash.liveOps": "Live Operations",
|
||||
"dash.tapRefresh": "Tap to refresh",
|
||||
"dash.wifi": "Wi-Fi",
|
||||
"dash.ethernet": "Ethernet",
|
||||
"dash.usb": "USB",
|
||||
"dash.bluetooth": "Bluetooth",
|
||||
"dash.mode": "Mode",
|
||||
"dash.gps": "GPS",
|
||||
"dash.age": "Bjorn age",
|
||||
"dash.plugged": "Plugged",
|
||||
"dash.noBattery": "No battery",
|
||||
"dash.sinceScan": "since last scan",
|
||||
"dash.wifiKnown": "Known Wi-Fi",
|
||||
"dash.dataFiles": "Data / Files collected",
|
||||
"dash.fileDescriptors": "File Descriptors",
|
||||
"dash.attackScripts": "Attack scripts",
|
||||
"dash.system": "System",
|
||||
"dash.zombies": "Zombies",
|
||||
"netkb.title": "Network Knowledge Base",
|
||||
"netkb.showOffline": "Show offline",
|
||||
"netkb.gridView": "Grid",
|
||||
"netkb.listView": "List",
|
||||
"netkb.hostname": "Hostname",
|
||||
"netkb.ip": "IP Address",
|
||||
"netkb.mac": "MAC Address",
|
||||
"netkb.vendor": "Vendor",
|
||||
"netkb.ports": "Ports",
|
||||
"netkb.essid": "ESSID",
|
||||
"netkb.lastSeen": "Last seen",
|
||||
"netkb.firstSeen": "First seen",
|
||||
"netkb.online": "Online",
|
||||
"netkb.offline": "Offline",
|
||||
"netkb.openPorts": "Open ports",
|
||||
"netkb.noHosts": "No hosts found",
|
||||
"network.title": "Network Visualization",
|
||||
"network.tableView": "Table",
|
||||
"network.mapView": "Map",
|
||||
"network.hostname": "Hostname",
|
||||
"network.ip": "IP Address",
|
||||
"network.mac": "MAC",
|
||||
"network.ports": "Ports",
|
||||
"network.status": "Status",
|
||||
"network.searchPlaceholder": "Search hosts...",
|
||||
"network.noData": "No network data",
|
||||
"creds.title": "Credentials",
|
||||
"creds.total": "Total",
|
||||
"creds.unique": "Unique",
|
||||
"creds.types": "Types",
|
||||
"creds.username": "Username",
|
||||
"creds.password": "Password",
|
||||
"creds.service": "Service",
|
||||
"creds.host": "Host",
|
||||
"creds.port": "Port",
|
||||
"creds.type": "Type",
|
||||
"creds.timestamp": "Timestamp",
|
||||
"creds.showPassword": "Show password",
|
||||
"creds.hidePassword": "Hide password",
|
||||
"creds.copyPassword": "Copy",
|
||||
"creds.exportAll": "Export all",
|
||||
"creds.noCredentials": "No credentials found",
|
||||
"vulns.title": "Vulnerability Dashboard",
|
||||
"vulns.total": "Total",
|
||||
"vulns.critical": "Critical",
|
||||
"vulns.high": "High",
|
||||
"vulns.medium": "Medium",
|
||||
"vulns.low": "Low",
|
||||
"vulns.infoLevel": "Info",
|
||||
"vulns.host": "Host",
|
||||
"vulns.port": "Port",
|
||||
"vulns.service": "Service",
|
||||
"vulns.severity": "Severity",
|
||||
"vulns.description": "Description",
|
||||
"vulns.cve": "CVE",
|
||||
"vulns.scanDate": "Scan Date",
|
||||
"vulns.details": "Details",
|
||||
"vulns.noVulns": "No vulnerabilities found",
|
||||
"vulns.byHost": "By Host",
|
||||
"vulns.bySeverity": "By Severity",
|
||||
"vulns.byService": "By Service",
|
||||
"attacks.title": "Attack Manager",
|
||||
"attacks.running": "Running",
|
||||
"attacks.completed": "Completed",
|
||||
"attacks.failed": "Failed",
|
||||
"attacks.queued": "Queued",
|
||||
"attacks.start": "Start",
|
||||
"attacks.stop": "Stop",
|
||||
"attacks.restart": "Restart",
|
||||
"attacks.status": "Status",
|
||||
"attacks.target": "Target",
|
||||
"attacks.action": "Action",
|
||||
"attacks.duration": "Duration",
|
||||
"attacks.progress": "Progress",
|
||||
"attacks.noAttacks": "No attacks running",
|
||||
"sched.title": "Action Scheduler",
|
||||
"sched.pending": "Pending",
|
||||
"sched.running": "Running",
|
||||
"sched.done": "Done",
|
||||
"sched.failed": "Failed",
|
||||
"sched.all": "All",
|
||||
"sched.searchPlaceholder": "Search tasks...",
|
||||
"sched.noTasks": "No tasks found",
|
||||
"sched.stats": "{{running}} running / {{pending}} pending / {{done}} done",
|
||||
"db.title": "Database Manager",
|
||||
"db.tables": "Tables",
|
||||
"db.rows": "Rows",
|
||||
"db.columns": "Columns",
|
||||
"db.search": "Search tables...",
|
||||
"db.searchRows": "Search rows...",
|
||||
"db.export": "Export",
|
||||
"db.import": "Import",
|
||||
"db.addRow": "Add row",
|
||||
"db.deleteRow": "Delete row",
|
||||
"db.deleteSelected": "Delete selected",
|
||||
"db.saveChanges": "Save changes",
|
||||
"db.discardChanges": "Discard",
|
||||
"db.confirmDelete": "Confirm deletion?",
|
||||
"db.noTables": "No tables found",
|
||||
"db.noData": "No data in this table",
|
||||
"db.hide": "Hide",
|
||||
"db.showSidebar": "Show sidebar",
|
||||
"files.title": "Files Explorer",
|
||||
"files.gridView": "Grid",
|
||||
"files.listView": "List",
|
||||
"files.size": "Size",
|
||||
"files.modified": "Modified",
|
||||
"files.name": "Name",
|
||||
"files.type": "Type",
|
||||
"files.download": "Download",
|
||||
"files.preview": "Preview",
|
||||
"files.noFiles": "No files found",
|
||||
"files.parentDir": "Parent directory",
|
||||
"files.searchPlaceholder": "Search files...",
|
||||
"loot.title": "Loot",
|
||||
"loot.directories": "Directories",
|
||||
"loot.totalFiles": "Total files",
|
||||
"loot.totalSize": "Total size",
|
||||
"loot.download": "Download",
|
||||
"loot.downloadAll": "Download all",
|
||||
"loot.noLoot": "No loot found",
|
||||
"loot.explore": "Explore",
|
||||
"actions.title": "Actions Manager",
|
||||
"actions.available": "Available",
|
||||
"actions.enabled": "Enabled",
|
||||
"actions.disabled": "Disabled",
|
||||
"actions.category": "Category",
|
||||
"actions.enableAll": "Enable all",
|
||||
"actions.disableAll": "Disable all",
|
||||
"actions.import": "Import",
|
||||
"actions.export": "Export",
|
||||
"actions.noActions": "No actions found",
|
||||
"actions.description": "Description",
|
||||
"actions.menu.restartService": "Restart Bjorn Service",
|
||||
"actions.menu.deleteActionStatus": "Delete all actions status",
|
||||
"actions.menu.clearOutput": "Clear Output folder",
|
||||
"actions.menu.clearLogs": "Clear Logs",
|
||||
"actions.menu.reloadImages": "Reload Images (Experimental Buggy)",
|
||||
"actions.menu.reloadFonts": "Reload Fonts",
|
||||
"actions.menu.reloadActionsJson": "Reload Generate Actions JSON",
|
||||
"actions.menu.initializeCsv": "Initialize CSV files",
|
||||
"actions.menu.clearLivestatus": "Delete Livestatus file",
|
||||
"actions.menu.refreshActionsFile": "Refresh Actions file",
|
||||
"actions.menu.clearNetkb": "Clear Network Knowledge Base",
|
||||
"actions.menu.clearSharedConfig": "Delete Shared Config JSON",
|
||||
"actions.menu.eraseMemories": "Erase Bjorn Memories",
|
||||
"actions.menu.reboot": "Reboot System",
|
||||
"actions.menu.shutdown": "Shutdown System",
|
||||
"actions.tip.restartService": "Restart the Bjorn service to refresh its state.",
|
||||
"actions.tip.deleteActionStatus": "Delete all recorded success and failed actions statuses in netkb.csv.",
|
||||
"actions.tip.clearOutput": "Erase all files from the output folders and subdirectories.",
|
||||
"actions.tip.clearLogs": "Delete all log files from the system.",
|
||||
"actions.tip.reloadImages": "Reload images used by the system.",
|
||||
"actions.tip.reloadFonts": "Reload font assets for the application.",
|
||||
"actions.tip.reloadActionsJson": "Reload the Generate Actions JSON file.",
|
||||
"actions.tip.initializeCsv": "Recreate the CSV and JSON files.",
|
||||
"actions.tip.clearLivestatus": "Delete the current live status file.",
|
||||
"actions.tip.refreshActionsFile": "Refresh the actions file to account for new actions.",
|
||||
"actions.tip.clearNetkb": "Clear all saved network knowledge base information.",
|
||||
"actions.tip.clearSharedConfig": "Delete the shared configuration JSON file.",
|
||||
"actions.tip.eraseMemories": "Completely erase Bjorn memories and settings.",
|
||||
"actions.tip.reboot": "Restart the entire system.",
|
||||
"actions.tip.shutdown": "Power down the system.",
|
||||
"actions.confirm.restartRecommended": "Service restart is recommended. Restart now?",
|
||||
"actions.confirm.restartService": "Restart the Bjorn service?",
|
||||
"actions.confirm.deleteActionStatus": "Delete all recorded action statuses?",
|
||||
"actions.confirm.clearOutput": "Clear the entire output folder?",
|
||||
"actions.confirm.clearLogs": "Delete all log files?",
|
||||
"actions.confirm.clearNetkb": "Clear the Network Knowledge Base? This cannot be undone.",
|
||||
"actions.confirm.clearLivestatus": "Delete the livestatus file?",
|
||||
"actions.confirm.refreshActionsFile": "Refresh the actions file?",
|
||||
"actions.confirm.clearSharedConfig": "Delete the shared config JSON? This cannot be undone.",
|
||||
"actions.confirm.eraseMemories": "Erase all Bjorn memories and settings? This cannot be undone.",
|
||||
"actions.confirm.reboot": "Reboot the entire system?",
|
||||
"actions.confirm.shutdown": "Shut down the system?",
|
||||
"actions.msg.restartingService": "Bjorn service is restarting...",
|
||||
"actions.msg.restartFailed": "Failed to restart service",
|
||||
"actions.msg.actionStatusDeleted": "All action statuses deleted.",
|
||||
"actions.msg.outputCleared": "Output folder cleared.",
|
||||
"actions.msg.logsCleared": "Logs cleared.",
|
||||
"actions.msg.netkbCleared": "Network Knowledge Base cleared.",
|
||||
"actions.msg.livestatusDeleted": "Livestatus file deleted.",
|
||||
"actions.msg.actionsFileRefreshed": "Actions file refreshed.",
|
||||
"actions.msg.sharedConfigDeleted": "Shared config JSON deleted.",
|
||||
"actions.msg.memoriesErased": "Bjorn memories erased.",
|
||||
"actions.msg.rebooting": "System is rebooting...",
|
||||
"actions.msg.shuttingDown": "System is shutting down...",
|
||||
"actions.msg.csvInitialized": "CSV files initialized.",
|
||||
"actions.msg.actionsJsonReloaded": "Actions JSON reloaded.",
|
||||
"actions.msg.imagesReloaded": "Images reloaded.",
|
||||
"actions.msg.fontsReloaded": "Fonts reloaded.",
|
||||
"actions.msg.unknownAction": "Unknown action",
|
||||
"actions.msg.actionFailed": "Action failed",
|
||||
"studio.title": "Actions Studio",
|
||||
"studio.palette": "Palette",
|
||||
"studio.canvas": "Canvas",
|
||||
"studio.inspector": "Inspector",
|
||||
"studio.actionsTab": "Actions",
|
||||
"studio.hostsTab": "Hosts",
|
||||
"studio.globalTab": "Global",
|
||||
"studio.save": "Save",
|
||||
"studio.load": "Load",
|
||||
"studio.run": "Run",
|
||||
"studio.clear": "Clear",
|
||||
"studio.addNode": "Add node",
|
||||
"studio.removeNode": "Remove node",
|
||||
"studio.search": "Search actions...",
|
||||
"backup.title": "Backup & Update",
|
||||
"backup.backupRestore": "Backup / Restore",
|
||||
"backup.update": "Update",
|
||||
"backup.createBackup": "Create backup",
|
||||
"backup.restoreBackup": "Restore backup",
|
||||
"backup.downloadBackup": "Download",
|
||||
"backup.deleteBackup": "Delete backup",
|
||||
"backup.lastBackup": "Last backup",
|
||||
"backup.checkUpdates": "Check for updates",
|
||||
"backup.installUpdate": "Install update",
|
||||
"backup.currentVersion": "Current version",
|
||||
"backup.latestVersion": "Latest version",
|
||||
"backup.upToDate": "Up to date",
|
||||
"backup.updateAvailable": "Update available",
|
||||
"backup.clearLogs": "Clear logs",
|
||||
"backup.noBackups": "No backups found",
|
||||
"backup.restoring": "Restoring...",
|
||||
"backup.creating": "Creating backup...",
|
||||
"webenum.title": "Web Enumeration",
|
||||
"webenum.totalResults": "Total Results",
|
||||
"webenum.uniqueHosts": "Unique Hosts",
|
||||
"webenum.successCount": "Success (2xx)",
|
||||
"webenum.errorCount": "Errors (4xx/5xx)",
|
||||
"webenum.host": "Host",
|
||||
"webenum.ip": "IP",
|
||||
"webenum.port": "Port",
|
||||
"webenum.directory": "Directory",
|
||||
"webenum.status": "Status",
|
||||
"webenum.size": "Size",
|
||||
"webenum.scanDate": "Scan Date",
|
||||
"webenum.link": "Link",
|
||||
"webenum.exportJson": "Export JSON",
|
||||
"webenum.exportCsv": "Export CSV",
|
||||
"webenum.noResults": "No results found",
|
||||
"webenum.details": "Result Details",
|
||||
"webenum.openUrl": "Open URL",
|
||||
"webenum.copyUrl": "Copy URL",
|
||||
"webenum.showing": "Showing {{start}}-{{end}} of {{total}} results",
|
||||
"webenum.itemsPerPage": "Items per page",
|
||||
"webenum.refreshData": "Refresh data",
|
||||
"webenum.responseTime": "Response Time",
|
||||
"webenum.contentType": "Content Type",
|
||||
"webenum.fullUrl": "Full URL",
|
||||
"zombie.title": "Zombieland C2C",
|
||||
"zombie.agents": "Agents",
|
||||
"zombie.terminal": "Terminal",
|
||||
"zombie.commands": "Commands",
|
||||
"zombie.totalAgents": "Total Agents",
|
||||
"zombie.onlineAgents": "Online",
|
||||
"zombie.offlineAgents": "Offline",
|
||||
"zombie.idleAgents": "Idle",
|
||||
"zombie.sendCommand": "Send command",
|
||||
"zombie.broadcast": "Broadcast",
|
||||
"zombie.selectAgent": "Select an agent",
|
||||
"zombie.os": "OS",
|
||||
"zombie.lastSeen": "Last seen",
|
||||
"zombie.status": "Status",
|
||||
"zombie.noAgents": "No agents connected",
|
||||
"zombie.quickCommands": "Quick Commands",
|
||||
"zombie.files": "Files",
|
||||
"quick.autoScan": "Auto-scan",
|
||||
"quick.connectWifi": "Connect to WiFi",
|
||||
"quick.knownNetworks": "Known Networks",
|
||||
"quick.importPotfiles": "Import Potfiles",
|
||||
"quick.subtitle": "WiFi & Bluetooth",
|
||||
"quick.pair": "Pair",
|
||||
"quick.trust": "Trust",
|
||||
"quick.forgetDevice": "Forget Device",
|
||||
"quick.forgetDevicePrompt": "Forget {{name}}?",
|
||||
"quick.forgetNetworkPrompt": "Are you sure you want to forget this network?",
|
||||
"bjorn.title": "Bjorn EPD Screen",
|
||||
"bjorn.epdScreen": "E-Paper Display",
|
||||
"bjorn.refreshInterval": "Refresh interval",
|
||||
"bjorn.autoRefresh": "Auto refresh",
|
||||
"bjorn.manualRefresh": "Refresh now",
|
||||
"bjorn.seconds": "seconds",
|
||||
"common.search": "Search",
|
||||
"common.filter": "Filter",
|
||||
"common.refresh": "Refresh",
|
||||
"common.save": "Save",
|
||||
"common.cancel": "Cancel",
|
||||
"common.delete": "Delete",
|
||||
"common.edit": "Edit",
|
||||
"common.close": "Close",
|
||||
"common.loading": "Loading...",
|
||||
"common.noData": "No data available",
|
||||
"common.error": "Error",
|
||||
"common.success": "Success",
|
||||
"common.confirm": "Confirm",
|
||||
"common.yes": "Yes",
|
||||
"common.no": "No",
|
||||
"common.export": "Export",
|
||||
"common.import": "Import",
|
||||
"common.download": "Download",
|
||||
"common.upload": "Upload",
|
||||
"common.copy": "Copy",
|
||||
"common.start": "Start",
|
||||
"common.stop": "Stop",
|
||||
"common.restart": "Restart",
|
||||
"common.status": "Status",
|
||||
"common.name": "Name",
|
||||
"common.value": "Value",
|
||||
"common.type": "Type",
|
||||
"common.host": "Host",
|
||||
"common.port": "Port",
|
||||
"common.target": "Target",
|
||||
"common.date": "Date",
|
||||
"common.time": "Time",
|
||||
"common.size": "Size",
|
||||
"common.actions": "Actions",
|
||||
"common.details": "Details",
|
||||
"common.back": "Back",
|
||||
"common.next": "Next",
|
||||
"common.previous": "Previous",
|
||||
"common.first": "First",
|
||||
"common.last": "Last",
|
||||
"common.all": "All",
|
||||
"common.none": "None",
|
||||
"common.showing": "Showing",
|
||||
"common.of": "of",
|
||||
"common.results": "results",
|
||||
"common.items": "items",
|
||||
"common.page": "Page",
|
||||
"common.perPage": "per page",
|
||||
"common.sortBy": "Sort by",
|
||||
"common.ascending": "Ascending",
|
||||
"common.descending": "Descending",
|
||||
"common.view": "View",
|
||||
"common.table": "Table",
|
||||
"common.grid": "Grid",
|
||||
"common.list": "List",
|
||||
"common.map": "Map",
|
||||
"common.enabled": "Enabled",
|
||||
"common.disabled": "Disabled",
|
||||
"common.on": "On",
|
||||
"common.off": "Off",
|
||||
"common.version": "Version",
|
||||
"common.hide": "Hide",
|
||||
"common.show": "Show",
|
||||
"common.add": "Add",
|
||||
"common.remove": "Remove",
|
||||
"common.clear": "Clear",
|
||||
"common.reset": "Reset",
|
||||
"common.apply": "Apply",
|
||||
"common.run": "Run",
|
||||
"common.send": "Send",
|
||||
"common.connect": "Connect",
|
||||
"common.disconnect": "Disconnect",
|
||||
"common.selectAll": "Select all",
|
||||
"common.deselectAll": "Deselect all",
|
||||
"common.copied": "Copied!",
|
||||
"common.notFound": "Not found",
|
||||
"backup.checkUpdatesHint": "Click \"Check for updates\" to see version information.",
|
||||
"backup.checkingUpdates": "Checking for updates...",
|
||||
"backup.confirmFreshStart": "Confirm Fresh Start?",
|
||||
"backup.createdSuccessfully": "Backup created successfully.",
|
||||
"backup.defaultUpdated": "Default backup updated.",
|
||||
"backup.deleted": "Backup deleted.",
|
||||
"backup.descriptionPlaceholder": "Backup description...",
|
||||
"backup.enterDescription": "Please enter a backup description.",
|
||||
"backup.failedCheckUpdates": "Failed to check for updates",
|
||||
"backup.failedCreate": "Failed to create backup",
|
||||
"backup.failedDelete": "Failed to delete backup",
|
||||
"backup.failedLoadBackups": "Failed to load backups",
|
||||
"backup.failedSetDefault": "Failed to set default",
|
||||
"backup.freshStart": "Fresh Start",
|
||||
"backup.freshStartFailed": "Fresh start failed",
|
||||
"backup.freshStartInitiated": "Fresh start initiated.",
|
||||
"backup.github": "github",
|
||||
"backup.keepActions": "Keep actions folder",
|
||||
"backup.keepConfig": "Keep config folder",
|
||||
"backup.keepData": "Keep data folder",
|
||||
"backup.keepResources": "Keep resources folder",
|
||||
"backup.noBackupsCreateAbove": "No backups found. Create one above.",
|
||||
"backup.restoreCompleted": "Restore completed.",
|
||||
"backup.restoreOptions": "Restore Options",
|
||||
"backup.restorePoint": "restore-point",
|
||||
"backup.selectKeepFolders": "Select folders to keep during the operation:",
|
||||
"backup.setDefault": "Set Default",
|
||||
"backup.unnamedBackup": "Unnamed backup",
|
||||
"backup.updateInitiated": "Update initiated.",
|
||||
"backup.updateOptions": "Update Options",
|
||||
"common.confirmDiscardUnsaved": "Discard unsaved changes?",
|
||||
"common.confirmQuestion": "Confirm?",
|
||||
"common.default": "default",
|
||||
"common.deleteFailed": "Delete failed",
|
||||
"common.deleted": "Deleted",
|
||||
"common.description": "Description",
|
||||
"common.directory": "directory",
|
||||
"common.duplicate": "Duplicate",
|
||||
"common.exportJson": "Export JSON",
|
||||
"common.failed": "failed",
|
||||
"common.file": "file",
|
||||
"common.importJson": "Import JSON",
|
||||
"common.new": "New",
|
||||
"common.noMatches": "No matches",
|
||||
"common.options": "Options",
|
||||
"common.processingPleaseWait": "Processing, please wait...",
|
||||
"common.refreshed": "Refreshed",
|
||||
"common.rename": "Rename",
|
||||
"common.saving": "Saving...",
|
||||
"common.unknown": "unknown",
|
||||
"common.unsavedChanges": "Unsaved changes",
|
||||
"db.autoRefresh": "Auto-refresh",
|
||||
"db.changesDiscarded": "Changes discarded",
|
||||
"db.changesSaved": "Changes saved",
|
||||
"db.confirmDrop": "DROP \"{{table}}\"? This cannot be undone!",
|
||||
"db.confirmTruncate": "Truncate all rows from \"{{table}}\"?",
|
||||
"db.dangerZone": "Danger Zone",
|
||||
"db.deletingRowsCount": "Deleting {{count}} row(s)...",
|
||||
"db.dropFailed": "Drop failed",
|
||||
"db.droppedTable": "Dropped {{table}}",
|
||||
"db.dropping": "Dropping...",
|
||||
"db.emptyTable": "Empty table",
|
||||
"db.errorLoadingData": "Error loading data",
|
||||
"db.failedLoadCatalog": "Failed to load catalog",
|
||||
"db.failedLoadTable": "Failed to load table",
|
||||
"db.filterTables": "Filter tables...",
|
||||
"db.insertFailed": "Insert failed",
|
||||
"db.insertingRow": "Inserting row...",
|
||||
"db.noRowsSelected": "No rows selected",
|
||||
"db.rowInserted": "Row inserted",
|
||||
"db.rowsDeleted": "Rows deleted",
|
||||
"db.runningVacuum": "Running VACUUM...",
|
||||
"db.saveFailed": "Save failed",
|
||||
"db.selectTableFromSidebar": "Select a table from the sidebar",
|
||||
"db.tableDropped": "Table dropped",
|
||||
"db.tableTruncated": "Table truncated",
|
||||
"db.truncateFailed": "Truncate failed",
|
||||
"db.truncating": "Truncating...",
|
||||
"db.vacuumComplete": "VACUUM complete",
|
||||
"db.vacuumDone": "VACUUM done",
|
||||
"db.vacuumFailed": "VACUUM failed",
|
||||
"files.confirmDelete": "Delete {{label}} \"{{name}}\"?",
|
||||
"files.downloadFile": "Download file",
|
||||
"files.duplicateFailed": "Duplicate failed",
|
||||
"files.duplicated": "Duplicated",
|
||||
"files.emptyDirectory": "Empty directory",
|
||||
"files.errorLoading": "Error loading files",
|
||||
"files.failedLoadDir": "Failed to load directory",
|
||||
"files.filterPlaceholder": "Filter files...",
|
||||
"files.itemsCount": "{{count}} item(s)",
|
||||
"files.newNamePrompt": "New name:",
|
||||
"files.noMatch": "No matching files",
|
||||
"files.openDirectory": "Open directory",
|
||||
"files.parent": ".. (parent)",
|
||||
"files.renameFailed": "Rename failed",
|
||||
"files.renamed": "Renamed",
|
||||
"files.root": "Root",
|
||||
"files.uploadComplete": "Upload complete",
|
||||
"files.uploadFailed": "Upload failed",
|
||||
"files.uploadingCount": "Uploading {{count}} file(s)...",
|
||||
"studio.actionNotFound": "Action not found",
|
||||
"studio.classNameRequired": "Class name is required",
|
||||
"studio.confirmDeleteAction": "Delete action \"{{name}}\"? This cannot be undone.",
|
||||
"studio.deletedName": "Deleted: {{name}}",
|
||||
"studio.exportedFile": "Exported: {{name}}",
|
||||
"studio.filterActions": "Filter actions...",
|
||||
"studio.importFailed": "Import failed",
|
||||
"studio.importedFile": "Imported: {{name}}",
|
||||
"studio.loadFailed": "Load failed",
|
||||
"studio.loadedFromCacheName": "Loaded from cache: {{name}}",
|
||||
"studio.loadedName": "Loaded: {{name}}",
|
||||
"studio.newActionCreated": "New action created",
|
||||
"studio.noActionLoaded": "No action loaded",
|
||||
"studio.saveFailedBackedUp": "Save failed (backed up locally)",
|
||||
"studio.savedName": "Saved: {{name}}",
|
||||
"studio.setClassBeforeExport": "Set a class name before exporting",
|
||||
"zombie.agentRemoved": "Agent {{name}} removed",
|
||||
"zombie.agentsPurged": "{{count}} agent(s) purged",
|
||||
"zombie.allAgents": "All Agents",
|
||||
"zombie.c2StartedOnPort": "C2 server started on port {{port}}",
|
||||
"zombie.c2Stopped": "C2 server stopped",
|
||||
"zombie.clearConsole": "Clear console",
|
||||
"zombie.clearLogs": "Clear logs",
|
||||
"zombie.commandBroadcasted": "Command broadcasted",
|
||||
"zombie.commandSentToAgents": "Command sent to {{count}} agent(s)",
|
||||
"zombie.confirmPurgeStale": "Purge all agents inactive > 24 hours?",
|
||||
"zombie.confirmRemoveAgent": "Remove agent {{name}}?",
|
||||
"zombie.confirmStopC2": "Stop the C2 server?",
|
||||
"zombie.consoleCleared": "Console cleared",
|
||||
"zombie.enterC2Port": "Enter C2 port:",
|
||||
"zombie.enterCommand": "Enter command...",
|
||||
"zombie.failedPurgeStale": "Failed to purge stale agents",
|
||||
"zombie.failedRemoveAgent": "Failed to remove agent {{name}}",
|
||||
"zombie.failedSendCommand": "Failed to send command",
|
||||
"zombie.failedStartC2": "Failed to start C2",
|
||||
"zombie.failedStopC2": "Failed to stop C2",
|
||||
"zombie.noAgentsConnected": "No agents connected",
|
||||
"zombie.noAgentsMatchSearch": "No agents match your search",
|
||||
"zombie.purgeStale": "Purge Stale",
|
||||
"zombie.purgeStaleHint": "Purge agents inactive >24h",
|
||||
"zombie.removeAgent": "Remove agent",
|
||||
"zombie.startC2": "Start C2",
|
||||
"zombie.stopC2": "Stop C2",
|
||||
"zombie.systemLogs": "System Logs",
|
||||
"zombieland.alive": "Alive",
|
||||
"zombieland.c2Status": "C2 Status",
|
||||
"zombieland.dead": "Dead",
|
||||
"zombieland.totalAgents": "Total Agents",
|
||||
"greeting": "Hello",
|
||||
"start": "Start",
|
||||
"tick": "Tick",
|
||||
"common.ip": "IP",
|
||||
"common.mac": "MAC",
|
||||
"common.os": "OS",
|
||||
"zombie.never": "Never",
|
||||
"zombie.openInConsole": "Open in console",
|
||||
"common.saved": "Saved",
|
||||
"attacks.tabs.attacks": "Attacks",
|
||||
"attacks.tabs.comments": "Comments",
|
||||
"attacks.tabs.images": "Images",
|
||||
"attacks.btn.addAttack": "Add Attack",
|
||||
"attacks.btn.removeAttack": "Remove Attack",
|
||||
"attacks.btn.deleteAction": "Delete Action",
|
||||
"attacks.btn.restoreDefaultsBundle": "Restore Defaults",
|
||||
"attacks.btn.addSection": "Add Section",
|
||||
"attacks.btn.deleteSection": "Delete Section",
|
||||
"attacks.btn.restoreDefault": "Restore Default",
|
||||
"attacks.btn.createCharacter": "Create Character",
|
||||
"attacks.btn.deleteCharacter": "Delete Character",
|
||||
"attacks.section.characters": "Characters",
|
||||
"attacks.section.statusImages": "Status Images",
|
||||
"attacks.section.staticImages": "Static Images",
|
||||
"attacks.section.webImages": "Web Images",
|
||||
"attacks.section.actionIcons": "Action Icons",
|
||||
"attacks.editor.selectAttack": "Select an Attack",
|
||||
"attacks.empty.noAttacks": "No attacks found.",
|
||||
"attacks.empty.noComments": "No comments found.",
|
||||
"attacks.comments.placeholder": "Comments will be displayed here...",
|
||||
"attacks.images.enterEditMode": "Enter Edit Mode",
|
||||
"attacks.images.exitEditMode": "Exit Edit Mode",
|
||||
"attacks.images.sortName": "Sort: Name",
|
||||
"attacks.images.sortDimensions": "Sort: Dimensions",
|
||||
"attacks.images.search": "Search images...",
|
||||
"attacks.images.rename": "Rename Image",
|
||||
"attacks.images.replace": "Replace Image",
|
||||
"attacks.images.resizeSelected": "Resize Selected",
|
||||
"attacks.images.addCharacters": "Add Character Images",
|
||||
"attacks.images.deleteSelected": "Delete Selected",
|
||||
"attacks.images.addStatus": "Add Status Image",
|
||||
"attacks.images.addStatic": "Add Static Image",
|
||||
"attacks.images.addWeb": "Add Web Image",
|
||||
"attacks.images.addIcon": "Add Action Icon",
|
||||
"attacks.errors.loadAttacks": "Failed to load attacks.",
|
||||
"attacks.errors.loadImages": "Failed to load images.",
|
||||
"attacks.confirm.switchCharacter": "Switch to character '{{name}}'?",
|
||||
"attacks.confirm.removeAttack": "Remove attack \"{{name}}\"?",
|
||||
"attacks.confirm.deleteAction": "Delete action \"{{name}}\"?",
|
||||
"attacks.confirm.restoreAttack": "Restore \"{{name}}\" to default?",
|
||||
"attacks.confirm.restoreDefaultsBundle": "Restore ALL defaults (actions, images, comments)?",
|
||||
"attacks.confirm.deleteCharacter": "Delete character '{{name}}'?",
|
||||
"attacks.confirm.deleteSection": "Delete section '{{name}}'?",
|
||||
"attacks.confirm.restoreDefaultComments": "Restore default comments?",
|
||||
"attacks.confirm.deleteSelectedImages": "Delete selected images?",
|
||||
"attacks.prompt.newCharacterName": "Enter a name for the new character:",
|
||||
"attacks.prompt.characterToDelete": "Character to delete:",
|
||||
"attacks.prompt.newSectionName": "Enter the name of the new section:",
|
||||
"attacks.prompt.newImageName": "New name:",
|
||||
"attacks.prompt.resizeWidth": "Resize width:",
|
||||
"attacks.prompt.resizeHeight": "Resize height:",
|
||||
"attacks.toast.characterSwitched": "Character switched",
|
||||
"attacks.toast.attackImported": "Attack imported",
|
||||
"attacks.toast.selectAttackFirst": "Select an attack first",
|
||||
"attacks.toast.actionDeleted": "Action deleted",
|
||||
"attacks.toast.defaultsRestored": "Defaults restored",
|
||||
"attacks.toast.characterCreated": "Character created",
|
||||
"attacks.toast.noDeletableCharacters": "No deletable characters",
|
||||
"attacks.toast.characterDeleted": "Character deleted",
|
||||
"attacks.toast.commentsRestored": "Comments restored",
|
||||
"attacks.toast.selectSectionFirst": "Select a section first",
|
||||
"attacks.toast.commentsSaved": "Comments saved",
|
||||
"attacks.toast.selectExactlyOneImage": "Select exactly one image",
|
||||
"attacks.toast.selectAtLeastOneImage": "Select at least one image",
|
||||
"attacks.toast.imagesResized": "Images resized",
|
||||
"attacks.toast.characterImagesUploaded": "Character images uploaded",
|
||||
"attacks.toast.selectStatusActionFirst": "Select a status action first",
|
||||
"actions.toast.presetApplied": "Preset applied",
|
||||
"actions.toast.startingAction": "Starting {{name}}...",
|
||||
"actions.toast.actionStarted": "Action started",
|
||||
"actions.toast.stoppedByUser": "Stopped by user",
|
||||
"actions.toast.actionStopped": "Action stopped",
|
||||
"actions.toast.stopFailed": "Stop failed",
|
||||
"actions.toast.failedToStop": "Failed to stop",
|
||||
"actions.toast.consoleCleared": "Console cleared",
|
||||
"actions.toast.noLogsToExport": "No logs to export",
|
||||
"actions.toast.logsExported": "Logs exported",
|
||||
"netkb.confirmRemoveAction": "Remove action \"{{action}}\" for IP \"{{ip}}\"?",
|
||||
"netkb.actionRemoved": "Action removed",
|
||||
"actions.running": "Running",
|
||||
"attacks.btn.syncMissing": "Sync Missing",
|
||||
"attacks.images.gridDensity": "Grid density",
|
||||
"attacks.images.density": "Density",
|
||||
"attacks.sync.defaultComment": "Add comment for this action",
|
||||
"attacks.sync.none": "No attacks to sync.",
|
||||
"attacks.sync.done": "Sync done. New comments: {{comments}}, status images: {{status}}, character images: {{characters}}.",
|
||||
"attacks.sync.failed": "Sync Missing failed",
|
||||
"actions.args.free": "Free command args",
|
||||
"actions.args.none": "No configurable arguments",
|
||||
"actions.args.subtitle": "Auto-generated from action definitions",
|
||||
"actions.args.title": "Arguments",
|
||||
"actions.assign": "Assign",
|
||||
"actions.emptyPane": "No action selected",
|
||||
"actions.logs.completed": "Completed",
|
||||
"actions.logs.empty": "No logs yet",
|
||||
"actions.logs.waiting": "Waiting...",
|
||||
"actions.searchPlaceholder": "Search actions...",
|
||||
"actions.tabs.actions": "Actions",
|
||||
"actions.tabs.arguments": "Arguments",
|
||||
"actions.toast.selectActionFirst": "Select an action first",
|
||||
"common.move": "Move",
|
||||
"common.ready": "Ready",
|
||||
"common.menu": "Menu",
|
||||
"common.browse": "Browse...",
|
||||
"common.platform": "Platform",
|
||||
"common.generate": "Generate",
|
||||
"common.vendor": "Vendor",
|
||||
"common.hostname": "Hostname",
|
||||
"common.ports": "Ports",
|
||||
"zombie.generateClient": "Generate Client",
|
||||
"zombie.checkStale": "Check for stale agents",
|
||||
"zombie.selectedAgents": "selected agents",
|
||||
"zombie.clientId": "Client ID",
|
||||
"zombie.labCreds": "Lab Credentials",
|
||||
"zombie.deployOptions": "Deployment Options",
|
||||
"zombie.deployViaSSH": "Deploy via SSH",
|
||||
"zombie.fileBrowser": "File Browser",
|
||||
"dash.lastUpdate": "Last update",
|
||||
"netkb.searchPlaceholder": "Search host, IP, vendor, port...",
|
||||
"netkb.searchHint": "Tip: type 'port:80' or 'vendor:intel'",
|
||||
"files.dropzoneHint": "Drop files here or click to upload",
|
||||
"files.moveToTitle": "Move to...",
|
||||
"files.selectDestinationFolder": "Select destination folder",
|
||||
"attacks.sidebar.management": "Management",
|
||||
"sched.upcoming": "Upcoming",
|
||||
"sched.success": "Success",
|
||||
"sched.cancelled": "Cancelled",
|
||||
"sched.history": "History",
|
||||
"sched.historyMsg": "History logs",
|
||||
"creds.searchPlaceholder": "Search services, usernames...",
|
||||
"creds.uniqueHosts": "Unique Hosts",
|
||||
"creds.totalCredentials": "Total Credentials",
|
||||
"console.maxReconnect": "Console: max reconnect attempts reached",
|
||||
"console.scrollToBottom": "Scroll to bottom",
|
||||
"console.manual": "Manual",
|
||||
"console.auto": "Auto",
|
||||
"console.turnOnAuto": "Turn on Auto",
|
||||
"console.turnOnManual": "Turn on Manual",
|
||||
"console.noTarget": "No target",
|
||||
"console.noAction": "No action",
|
||||
"console.scanStarted": "Manual scan started",
|
||||
"console.scanFailed": "Manual scan failed",
|
||||
"console.attackStarted": "Manual attack started",
|
||||
"console.attackFailed": "Manual attack failed",
|
||||
"console.failedToggleMode": "Failed to toggle mode",
|
||||
"console.reconnectAttempt": "Reconnecting (attempt {{count}})...",
|
||||
"quick.close": "Close panel",
|
||||
"quick.connectingTo": "Connecting to {{ssid}}...",
|
||||
"quick.connectedTo": "Connected to {{ssid}}",
|
||||
"quick.connectionFailed": "Connection failed",
|
||||
"quick.loadKnownFailed": "Failed to load known networks",
|
||||
"quick.priorityUpdated": "Priority updated",
|
||||
"quick.priorityUpdateFailed": "Priority update failed",
|
||||
"quick.networkRemoved": "Network removed",
|
||||
"quick.importingPotfiles": "Importing potfiles...",
|
||||
"quick.importedCount": "Imported {{count}} credentials",
|
||||
"quick.btScanFailed": "Bluetooth scan failed",
|
||||
"quick.btActioning": "{{action}}ing {{name}}...",
|
||||
"quick.btActionDone": "{{name}} {{action}}ed",
|
||||
"quick.btActionFailed": "{{action}} failed",
|
||||
"quick.btForgotten": "{{name}} forgotten",
|
||||
"sidebar.close": "Close sidebar",
|
||||
"api.aborted": "Aborted",
|
||||
"api.timeout": "Request timed out",
|
||||
"api.failed": "Request failed",
|
||||
"router.notFound": "Page not found: {{path}}",
|
||||
"router.errorLoading": "Error loading page: {{message}}"
|
||||
}
|
||||
781
web/i18n/es.json
Normal file
@@ -0,0 +1,781 @@
|
||||
{
|
||||
"nav.dashboard": "Panel de control",
|
||||
"nav.bjorn": "Bjorn",
|
||||
"nav.netkb": "Base Red",
|
||||
"nav.network": "Red",
|
||||
"nav.credentials": "Credenciales",
|
||||
"nav.vulnerabilities": "Vulnerabilidades",
|
||||
"nav.attacks": "Ataque",
|
||||
"nav.scheduler": "Planificador",
|
||||
"nav.database": "Base de datos",
|
||||
"nav.files": "Archivos",
|
||||
"nav.loot": "Botín",
|
||||
"nav.actions": "Acciones",
|
||||
"nav.actionsStudio": "Estudio Acciones",
|
||||
"nav.backup": "Copia y Act.",
|
||||
"nav.webEnum": "Enum Web",
|
||||
"nav.zombieland": "Zombieland",
|
||||
"nav.settings": "Ajustes",
|
||||
"nav.shortcuts": "Atajos",
|
||||
"nav.pages": "Páginas",
|
||||
"status.initializing": "Inicializando...",
|
||||
"status.online": "En línea",
|
||||
"status.offline": "Desconectado",
|
||||
"console.title": "Consola",
|
||||
"console.clear": "Limpiar",
|
||||
"console.sseOn": "SSE Activo",
|
||||
"console.sseOff": "SSE Inactivo",
|
||||
"console.newLogs": "{{count}} nuevos registros",
|
||||
"settings.theme": "Tema",
|
||||
"settings.language": "Idioma",
|
||||
"settings.general": "General",
|
||||
"settings.toggles": "Opciones",
|
||||
"settings.editValue": "Editar valor",
|
||||
"settings.addValues": "Añadir valores (separados por comas)...",
|
||||
"settings.setValue": "Establecer valor...",
|
||||
"settings.errorLoading": "Error al cargar la configuración",
|
||||
"settings.configSaved": "Configuración guardada",
|
||||
"settings.errorSaving": "Error al guardar la configuración",
|
||||
"settings.defaultsRestored": "Valores predeterminados restaurados",
|
||||
"settings.errorRestoring": "Error al restaurar valores predeterminados",
|
||||
"theme.group.colors": "Colores",
|
||||
"theme.group.surfaces": "Superficies",
|
||||
"theme.group.layout": "Diseño",
|
||||
"theme.token.bg": "Fondo",
|
||||
"theme.token.ink": "Color de texto",
|
||||
"theme.token.accent1": "Acento 1 (Ácido)",
|
||||
"theme.token.accent2": "Acento 2 (Cian)",
|
||||
"theme.token.danger": "Peligro",
|
||||
"theme.token.warning": "Advertencia",
|
||||
"theme.token.ok": "Éxito",
|
||||
"theme.token.panel": "Panel",
|
||||
"theme.token.panel2": "Panel Alt",
|
||||
"theme.token.ctrlPanel": "Panel control",
|
||||
"theme.token.border": "Borde",
|
||||
"theme.token.radius": "Radio de borde",
|
||||
"theme.advanced": "CSS avanzado",
|
||||
"theme.applyRaw": "Aplicar",
|
||||
"theme.reset": "Restablecer",
|
||||
"dash.title": "Panel de control",
|
||||
"dash.battery": "Batería",
|
||||
"dash.internet": "Internet",
|
||||
"dash.cpu": "CPU",
|
||||
"dash.ram": "RAM",
|
||||
"dash.disk": "Disco",
|
||||
"dash.temp": "Temp",
|
||||
"dash.uptime": "Tiempo",
|
||||
"dash.hostsAlive": "Hosts activos",
|
||||
"dash.totalHosts": "Total hosts",
|
||||
"dash.openPorts": "Puertos abiertos",
|
||||
"dash.credentials": "Credenciales",
|
||||
"dash.vulnerabilities": "Vulnerabilidades",
|
||||
"dash.actions": "Acciones",
|
||||
"dash.connected": "Conectado",
|
||||
"dash.disconnected": "Desconectado",
|
||||
"dash.charging": "Cargando",
|
||||
"dash.discharging": "Descargando",
|
||||
"dash.full": "Lleno",
|
||||
"dash.connectivity": "Conectividad",
|
||||
"dash.liveOps": "Op. en vivo",
|
||||
"dash.tapRefresh": "Tocar para actualizar",
|
||||
"dash.wifi": "Wi-Fi",
|
||||
"dash.ethernet": "Ethernet",
|
||||
"dash.usb": "USB",
|
||||
"dash.bluetooth": "Bluetooth",
|
||||
"dash.mode": "Modo",
|
||||
"dash.gps": "GPS",
|
||||
"dash.age": "Edad de Bjorn",
|
||||
"dash.plugged": "Enchufado",
|
||||
"dash.noBattery": "Sin batería",
|
||||
"dash.sinceScan": "desde el último escaneo",
|
||||
"dash.wifiKnown": "Wi-Fi conocidos",
|
||||
"dash.dataFiles": "Datos / Archivos recogidos",
|
||||
"dash.fileDescriptors": "Descriptores de archivos",
|
||||
"dash.attackScripts": "Scripts de ataque",
|
||||
"dash.system": "Sistema",
|
||||
"dash.zombies": "Zombies",
|
||||
"netkb.title": "Base de conocimiento de red",
|
||||
"netkb.showOffline": "Mostrar desconectados",
|
||||
"netkb.gridView": "Cuadrícula",
|
||||
"netkb.listView": "Lista",
|
||||
"netkb.hostname": "Nombre de host",
|
||||
"netkb.ip": "Dirección IP",
|
||||
"netkb.mac": "Dirección MAC",
|
||||
"netkb.vendor": "Fabricante",
|
||||
"netkb.ports": "Puertos",
|
||||
"netkb.essid": "ESSID",
|
||||
"netkb.lastSeen": "Última vez visto",
|
||||
"netkb.firstSeen": "Primera vez visto",
|
||||
"netkb.online": "En línea",
|
||||
"netkb.offline": "Desconectado",
|
||||
"netkb.openPorts": "Puertos abiertos",
|
||||
"netkb.noHosts": "No se encontraron hosts",
|
||||
"network.title": "Visualización de red",
|
||||
"network.tableView": "Tabla",
|
||||
"network.mapView": "Mapa",
|
||||
"network.hostname": "Nombre de host",
|
||||
"network.ip": "Dirección IP",
|
||||
"network.mac": "MAC",
|
||||
"network.ports": "Puertos",
|
||||
"network.status": "Estado",
|
||||
"network.searchPlaceholder": "Buscar hosts...",
|
||||
"network.noData": "No hay datos de red",
|
||||
"creds.title": "Credenciales",
|
||||
"creds.total": "Total",
|
||||
"creds.unique": "Únicas",
|
||||
"creds.types": "Tipos",
|
||||
"creds.username": "Usuario",
|
||||
"creds.password": "Contraseña",
|
||||
"creds.service": "Servicio",
|
||||
"creds.host": "Host",
|
||||
"creds.port": "Puerto",
|
||||
"creds.type": "Tipo",
|
||||
"creds.timestamp": "Marca de tiempo",
|
||||
"creds.showPassword": "Mostrar contraseña",
|
||||
"creds.hidePassword": "Ocultar contraseña",
|
||||
"creds.copyPassword": "Copiar",
|
||||
"creds.exportAll": "Exportar todo",
|
||||
"creds.noCredentials": "No se encontraron credenciales",
|
||||
"vulns.title": "Tablero de vulnerabilidades",
|
||||
"vulns.total": "Total",
|
||||
"vulns.critical": "Crítica",
|
||||
"vulns.high": "Alta",
|
||||
"vulns.medium": "Media",
|
||||
"vulns.low": "Baja",
|
||||
"vulns.infoLevel": "Info",
|
||||
"vulns.host": "Host",
|
||||
"vulns.port": "Puerto",
|
||||
"vulns.service": "Servicio",
|
||||
"vulns.severity": "Severidad",
|
||||
"vulns.description": "Descripción",
|
||||
"vulns.cve": "CVE",
|
||||
"vulns.scanDate": "Fecha de escaneo",
|
||||
"vulns.details": "Detalles",
|
||||
"vulns.noVulns": "No se encontraron vulnerabilidades",
|
||||
"vulns.byHost": "Por host",
|
||||
"vulns.bySeverity": "Por severidad",
|
||||
"vulns.byService": "Por servicio",
|
||||
"attacks.title": "Gestor de ataques",
|
||||
"attacks.running": "En curso",
|
||||
"attacks.completed": "Completado",
|
||||
"attacks.failed": "Fallido",
|
||||
"attacks.queued": "En cola",
|
||||
"attacks.start": "Iniciar",
|
||||
"attacks.stop": "Detener",
|
||||
"attacks.restart": "Reiniciar",
|
||||
"attacks.status": "Estado",
|
||||
"attacks.target": "Objetivo",
|
||||
"attacks.action": "Acción",
|
||||
"attacks.duration": "Duración",
|
||||
"attacks.progress": "Progreso",
|
||||
"attacks.noAttacks": "No hay ataques en curso",
|
||||
"sched.title": "Planificador de acciones",
|
||||
"sched.pending": "Pendiente",
|
||||
"sched.running": "En curso",
|
||||
"sched.done": "Hecho",
|
||||
"sched.failed": "Fallido",
|
||||
"sched.all": "Todo",
|
||||
"sched.searchPlaceholder": "Buscar tareas...",
|
||||
"sched.noTasks": "No se encontraron tareas",
|
||||
"sched.stats": "{{running}} en curso / {{pending}} pendientes / {{done}} hechas",
|
||||
"db.title": "Gestor de base de datos",
|
||||
"db.tables": "Tablas",
|
||||
"db.rows": "Filas",
|
||||
"db.columns": "Columnas",
|
||||
"db.search": "Buscar tablas...",
|
||||
"db.searchRows": "Buscar filas...",
|
||||
"db.export": "Exportar",
|
||||
"db.import": "Importar",
|
||||
"db.addRow": "Añadir fila",
|
||||
"db.deleteRow": "Eliminar fila",
|
||||
"db.deleteSelected": "Eliminar selección",
|
||||
"db.saveChanges": "Guardar",
|
||||
"db.discardChanges": "Descartar",
|
||||
"db.confirmDelete": "¿Confirmar eliminación?",
|
||||
"db.noTables": "No se encontraron tablas",
|
||||
"db.noData": "No hay datos en esta tabla",
|
||||
"db.hide": "Ocultar",
|
||||
"db.showSidebar": "Mostrar barra lateral",
|
||||
"files.title": "Explorador de archivos",
|
||||
"files.gridView": "Cuadrícula",
|
||||
"files.listView": "Lista",
|
||||
"files.size": "Tamaño",
|
||||
"files.modified": "Modificado",
|
||||
"files.name": "Nombre",
|
||||
"files.type": "Tipo",
|
||||
"files.download": "Descargar",
|
||||
"files.preview": "Previsualizar",
|
||||
"files.noFiles": "No se encontraron archivos",
|
||||
"files.parentDir": "Directorio superior",
|
||||
"files.searchPlaceholder": "Buscar archivos...",
|
||||
"loot.title": "Botín",
|
||||
"loot.directories": "Directorios",
|
||||
"loot.totalFiles": "Total archivos",
|
||||
"loot.totalSize": "Tamaño total",
|
||||
"loot.download": "Descargar",
|
||||
"loot.downloadAll": "Descargar todo",
|
||||
"loot.noLoot": "No se encontró botín",
|
||||
"loot.explore": "Explorar",
|
||||
"actions.title": "Gestor de acciones",
|
||||
"actions.available": "Disponibles",
|
||||
"actions.enabled": "Habilitadas",
|
||||
"actions.disabled": "Deshabilitadas",
|
||||
"actions.category": "Categoría",
|
||||
"actions.enableAll": "Habilitar todas",
|
||||
"actions.disableAll": "Deshabilitar todas",
|
||||
"actions.import": "Importar",
|
||||
"actions.export": "Exportar",
|
||||
"actions.noActions": "No se encontraron acciones",
|
||||
"actions.description": "Descripción",
|
||||
"actions.menu.restartService": "Reiniciar servicio Bjorn",
|
||||
"actions.menu.deleteActionStatus": "Eliminar todos los estados de acción",
|
||||
"actions.menu.clearOutput": "Vaciar carpeta Output",
|
||||
"actions.menu.clearLogs": "Limpiar registros",
|
||||
"actions.menu.reloadImages": "Recargar imágenes (experimental)",
|
||||
"actions.menu.reloadFonts": "Recargar fuentes",
|
||||
"actions.menu.reloadActionsJson": "Recargar JSON de acciones",
|
||||
"actions.menu.initializeCsv": "Inicializar archivos CSV",
|
||||
"actions.menu.clearLivestatus": "Eliminar archivo Livestatus",
|
||||
"actions.menu.refreshActionsFile": "Actualizar archivo de acciones",
|
||||
"actions.menu.clearNetkb": "Vaciar conocimiento de red",
|
||||
"actions.menu.clearSharedConfig": "Eliminar JSON de config compartida",
|
||||
"actions.menu.eraseMemories": "Borrar memorias de Bjorn",
|
||||
"actions.menu.reboot": "Reiniciar sistema",
|
||||
"actions.menu.shutdown": "Apagar sistema",
|
||||
"actions.tip.restartService": "Reinicia el servicio Bjorn para actualizar su estado.",
|
||||
"actions.tip.deleteActionStatus": "Elimina todos los estados de éxito y fallo de acciones/ataques en netkb.csv.",
|
||||
"actions.tip.clearOutput": "Elimina todos los archivos en las carpetas de salida y subcarpetas.",
|
||||
"actions.tip.clearLogs": "Elimina todos los archivos de registros del sistema.",
|
||||
"actions.tip.reloadImages": "Recarga las imágenes utilizadas por el sistema.",
|
||||
"actions.tip.reloadFonts": "Recarga las fuentes de la aplicación.",
|
||||
"actions.tip.reloadActionsJson": "Recarga el archivo JSON de acciones generadas.",
|
||||
"actions.tip.initializeCsv": "Vuelve a crear los archivos CSV y JSON.",
|
||||
"actions.tip.clearLivestatus": "Elimina el archivo de estado en vivo.",
|
||||
"actions.tip.refreshActionsFile": "Actualiza el archivo de acciones para incluir nuevas acciones.",
|
||||
"actions.tip.clearNetkb": "Elimina toda la información guardada en la base de conocimiento de red.",
|
||||
"actions.tip.clearSharedConfig": "Elimina el archivo JSON de configuración compartida.",
|
||||
"actions.tip.eraseMemories": "Borra completamente la memoria y ajustes de Bjorn.",
|
||||
"actions.tip.reboot": "Reinicia todo el sistema.",
|
||||
"actions.tip.shutdown": "Apaga el sistema completamente.",
|
||||
"actions.confirm.restartRecommended": "Se recomienda reiniciar el servicio. ¿Reiniciar ahora?",
|
||||
"actions.confirm.restartService": "¿Reiniciar servicio Bjorn?",
|
||||
"actions.confirm.deleteActionStatus": "¿Eliminar todos los estados de acción guardados?",
|
||||
"actions.confirm.clearOutput": "¿Vaciar toda la carpeta output?",
|
||||
"actions.confirm.clearLogs": "¿Eliminar todos los archivos de registros?",
|
||||
"actions.confirm.clearNetkb": "¿Vaciar conocimiento de red? Esta acción es irreversible.",
|
||||
"actions.confirm.clearLivestatus": "¿Eliminar archivo livestatus?",
|
||||
"actions.confirm.refreshActionsFile": "¿Actualizar archivo de acciones?",
|
||||
"actions.confirm.clearSharedConfig": "¿Eliminar JSON de config compartida? Esta acción es irreversible.",
|
||||
"actions.confirm.eraseMemories": "¿Borrar toda la memoria y ajustes de Bjorn? Esta acción es irreversible.",
|
||||
"actions.confirm.reboot": "¿Reiniciar todo el sistema?",
|
||||
"actions.confirm.shutdown": "¿Apagar el sistema?",
|
||||
"actions.msg.restartingService": "El servicio Bjorn se está reiniciando...",
|
||||
"actions.msg.restartFailed": "Fallo al reiniciar el servicio",
|
||||
"actions.msg.actionStatusDeleted": "Se eliminaron todos los estados de acción.",
|
||||
"actions.msg.outputCleared": "Se vació la carpeta output.",
|
||||
"actions.msg.logsCleared": "Se limpiaron los registros.",
|
||||
"actions.msg.netkbCleared": "Se vació el conocimiento de red.",
|
||||
"actions.msg.livestatusDeleted": "Se eliminó el archivo livestatus.",
|
||||
"actions.msg.actionsFileRefreshed": "Se actualizó el archivo de acciones.",
|
||||
"actions.msg.sharedConfigDeleted": "Se eliminó el JSON de config compartida.",
|
||||
"actions.msg.memoriesErased": "Se borraron las memorias de Bjorn.",
|
||||
"actions.msg.rebooting": "El sistema se está reiniciando...",
|
||||
"actions.msg.shuttingDown": "El sistema se está apagando...",
|
||||
"actions.msg.csvInitialized": "Se inicializaron los archivos CSV.",
|
||||
"actions.msg.actionsJsonReloaded": "Se recargó el JSON de acciones.",
|
||||
"actions.msg.imagesReloaded": "Se recargaron las imágenes.",
|
||||
"actions.msg.fontsReloaded": "Se recargaron las fuentes.",
|
||||
"actions.msg.unknownAction": "Acción desconocida",
|
||||
"actions.msg.actionFailed": "Acción fallida",
|
||||
"studio.title": "Estudio Acciones",
|
||||
"studio.palette": "Paleta",
|
||||
"studio.canvas": "Lienzo",
|
||||
"studio.inspector": "Inspector",
|
||||
"studio.actionsTab": "Acciones",
|
||||
"studio.hostsTab": "Hosts",
|
||||
"studio.globalTab": "Global",
|
||||
"studio.save": "Guardar",
|
||||
"studio.load": "Cargar",
|
||||
"studio.run": "Ejecutar",
|
||||
"studio.clear": "Limpiar",
|
||||
"studio.addNode": "Añadir nodo",
|
||||
"studio.removeNode": "Eliminar nodo",
|
||||
"studio.search": "Buscar acciones...",
|
||||
"backup.title": "Copia y Act.",
|
||||
"backup.backupRestore": "Copia / Restauración",
|
||||
"backup.update": "Actualización",
|
||||
"backup.createBackup": "Crear copia",
|
||||
"backup.restoreBackup": "Restaurar",
|
||||
"backup.downloadBackup": "Descargar",
|
||||
"backup.deleteBackup": "Eliminar copia",
|
||||
"backup.lastBackup": "Última copia",
|
||||
"backup.checkUpdates": "Buscar act.",
|
||||
"backup.installUpdate": "Instalar act.",
|
||||
"backup.currentVersion": "Versión actual",
|
||||
"backup.latestVersion": "Última versión",
|
||||
"backup.upToDate": "Al día",
|
||||
"backup.updateAvailable": "Act. disponible",
|
||||
"backup.clearLogs": "Limpiar registros",
|
||||
"backup.noBackups": "No se encontraron copias",
|
||||
"backup.restoring": "Restaurando...",
|
||||
"backup.creating": "Creando copia...",
|
||||
"webenum.title": "Enumeración Web",
|
||||
"webenum.totalResults": "Total resultados",
|
||||
"webenum.uniqueHosts": "Hosts únicos",
|
||||
"webenum.successCount": "Éxito (2xx)",
|
||||
"webenum.errorCount": "Errores (4xx/5xx)",
|
||||
"webenum.host": "Host",
|
||||
"webenum.ip": "IP",
|
||||
"webenum.port": "Puerto",
|
||||
"webenum.directory": "Directorio",
|
||||
"webenum.status": "Estado",
|
||||
"webenum.size": "Tamaño",
|
||||
"webenum.scanDate": "Fecha escaneo",
|
||||
"webenum.link": "Enlace",
|
||||
"webenum.exportJson": "Exportar JSON",
|
||||
"webenum.exportCsv": "Exportar CSV",
|
||||
"webenum.noResults": "No se encontraron resultados",
|
||||
"webenum.details": "Detalles del resultado",
|
||||
"webenum.openUrl": "Abrir URL",
|
||||
"webenum.copyUrl": "Copiar URL",
|
||||
"webenum.showing": "Mostrando {{start}}-{{end}} de {{total}} resultados",
|
||||
"webenum.itemsPerPage": "Elementos por página",
|
||||
"webenum.refreshData": "Actualizar datos",
|
||||
"webenum.responseTime": "Tiempo respuesta",
|
||||
"webenum.contentType": "Tipo contenido",
|
||||
"webenum.fullUrl": "URL completa",
|
||||
"zombie.title": "Zombieland C2C",
|
||||
"zombie.agents": "Agentes",
|
||||
"zombie.terminal": "Terminal",
|
||||
"zombie.commands": "Comandos",
|
||||
"zombie.totalAgents": "Total agentes",
|
||||
"zombie.onlineAgents": "En línea",
|
||||
"zombie.offlineAgents": "Desconectados",
|
||||
"zombie.idleAgents": "Inactivos",
|
||||
"zombie.sendCommand": "Enviar comando",
|
||||
"zombie.broadcast": "Difusión",
|
||||
"zombie.selectAgent": "Seleccionar agente",
|
||||
"zombie.os": "SO",
|
||||
"zombie.lastSeen": "Última vez visto",
|
||||
"zombie.status": "Estado",
|
||||
"zombie.noAgents": "No hay agentes conectados",
|
||||
"zombie.quickCommands": "Comandos rápidos",
|
||||
"zombie.files": "Archivos",
|
||||
"quick.autoScan": "Auto-escaneo",
|
||||
"quick.connectWifi": "Conectar WiFi",
|
||||
"quick.knownNetworks": "Redes conocidas",
|
||||
"quick.importPotfiles": "Importar Potfiles",
|
||||
"quick.subtitle": "WiFi & Bluetooth",
|
||||
"quick.pair": "Emparejar",
|
||||
"quick.trust": "Confiar",
|
||||
"quick.forgetDevice": "Olvidar dispositivo",
|
||||
"quick.forgetDevicePrompt": "¿Olvidar {{name}}?",
|
||||
"quick.forgetNetworkPrompt": "¿Seguro que quieres olvidar esta red?",
|
||||
"bjorn.title": "Pantalla EPD Bjorn",
|
||||
"bjorn.epdScreen": "Pantalla e-Paper",
|
||||
"bjorn.refreshInterval": "Intervalo de refresco",
|
||||
"bjorn.autoRefresh": "Refresco auto",
|
||||
"bjorn.manualRefresh": "Refrescar ahora",
|
||||
"bjorn.seconds": "segundos",
|
||||
"common.search": "Buscar",
|
||||
"common.filter": "Filtrar",
|
||||
"common.refresh": "Refrescar",
|
||||
"common.save": "Guardar",
|
||||
"common.cancel": "Cancelar",
|
||||
"common.delete": "Eliminar",
|
||||
"common.edit": "Editar",
|
||||
"common.close": "Cerrar",
|
||||
"common.loading": "Cargando...",
|
||||
"common.noData": "No hay datos disponibles",
|
||||
"common.error": "Error",
|
||||
"common.success": "Éxito",
|
||||
"common.confirm": "Confirmar",
|
||||
"common.yes": "Sí",
|
||||
"common.no": "No",
|
||||
"common.export": "Exportar",
|
||||
"common.import": "Importar",
|
||||
"common.download": "Descargar",
|
||||
"common.upload": "Subir",
|
||||
"common.copy": "Copiar",
|
||||
"common.start": "Iniciar",
|
||||
"common.stop": "Detener",
|
||||
"common.restart": "Reiniciar",
|
||||
"common.status": "Estado",
|
||||
"common.name": "Nombre",
|
||||
"common.value": "Valor",
|
||||
"common.type": "Tipo",
|
||||
"common.host": "Host",
|
||||
"common.port": "Puerto",
|
||||
"common.target": "Objetivo",
|
||||
"common.date": "Fecha",
|
||||
"common.time": "Hora",
|
||||
"common.size": "Tamaño",
|
||||
"common.actions": "Acciones",
|
||||
"common.details": "Detalles",
|
||||
"common.back": "Atrás",
|
||||
"common.next": "Siguiente",
|
||||
"common.previous": "Anterior",
|
||||
"common.first": "Primero",
|
||||
"common.last": "Último",
|
||||
"common.all": "Todo",
|
||||
"common.none": "Ninguno",
|
||||
"common.showing": "Mostrando",
|
||||
"common.of": "de",
|
||||
"common.results": "resultados",
|
||||
"common.items": "elementos",
|
||||
"common.page": "Página",
|
||||
"common.perPage": "por página",
|
||||
"common.sortBy": "Ordenar por",
|
||||
"common.ascending": "Ascendente",
|
||||
"common.descending": "Descendente",
|
||||
"common.view": "Vista",
|
||||
"common.table": "Tabla",
|
||||
"common.grid": "Cuadrícula",
|
||||
"common.list": "Lista",
|
||||
"common.map": "Mapa",
|
||||
"common.enabled": "Habilitado",
|
||||
"common.disabled": "Deshabilitado",
|
||||
"common.on": "On",
|
||||
"common.off": "Off",
|
||||
"common.version": "Versión",
|
||||
"common.hide": "Ocultar",
|
||||
"common.show": "Mostrar",
|
||||
"common.add": "Añadir",
|
||||
"common.remove": "Eliminar",
|
||||
"common.clear": "Limpiar",
|
||||
"common.reset": "Restablecer",
|
||||
"common.apply": "Aplicar",
|
||||
"common.run": "Ejecutar",
|
||||
"common.send": "Enviar",
|
||||
"common.connect": "Conectar",
|
||||
"common.disconnect": "Desconectar",
|
||||
"common.selectAll": "Seleccionar todo",
|
||||
"common.deselectAll": "Deseleccionar todo",
|
||||
"common.copied": "¡Copiado!",
|
||||
"common.notFound": "No encontrado",
|
||||
"backup.checkUpdatesHint": "Haz clic en \"Buscar act.\" para ver las versiones.",
|
||||
"backup.checkingUpdates": "Buscando actualizaciones...",
|
||||
"backup.confirmFreshStart": "¿Confirmar inicio fresco? ",
|
||||
"backup.createdSuccessfully": "Copia creada con éxito.",
|
||||
"backup.defaultUpdated": "Copia predeterminada actualizada.",
|
||||
"backup.deleted": "Copia eliminada.",
|
||||
"backup.descriptionPlaceholder": "Descripción de la copia...",
|
||||
"backup.enterDescription": "Por favor, introduce una descripción.",
|
||||
"backup.failedCheckUpdates": "Error al buscar actualizaciones",
|
||||
"backup.failedCreate": "Error al crear la copia",
|
||||
"backup.failedDelete": "Error al eliminar la copia",
|
||||
"backup.failedLoadBackups": "Error al cargar las copias",
|
||||
"backup.failedSetDefault": "Error al establecer predeterminada",
|
||||
"backup.freshStart": "Inicio fresco",
|
||||
"backup.freshStartFailed": "Error en inicio fresco",
|
||||
"backup.freshStartInitiated": "Inicio fresco iniciado.",
|
||||
"backup.github": "github",
|
||||
"backup.keepActions": "Mantener carpeta actions",
|
||||
"backup.keepConfig": "Mantener carpeta config",
|
||||
"backup.keepData": "Mantener carpeta data",
|
||||
"backup.keepResources": "Mantener carpeta resources",
|
||||
"backup.noBackupsCreateAbove": "No hay copias. Crea una arriba.",
|
||||
"backup.restoreCompleted": "Restauración completada.",
|
||||
"backup.restoreOptions": "Opciones de restauración",
|
||||
"backup.restorePoint": "punto-de-restauración",
|
||||
"backup.selectKeepFolders": "Selecciona las carpetas a mantener:",
|
||||
"backup.setDefault": "Establecer predeterminada",
|
||||
"backup.unnamedBackup": "Copia sin nombre",
|
||||
"backup.updateInitiated": "Actualización iniciada.",
|
||||
"backup.updateOptions": "Opciones de actualización",
|
||||
"common.confirmDiscardUnsaved": "¿Descartar cambios no guardados?",
|
||||
"common.confirmQuestion": "¿Confirmar?",
|
||||
"common.default": "predeterminado",
|
||||
"common.deleteFailed": "Error al eliminar",
|
||||
"common.deleted": "Eliminado",
|
||||
"common.description": "Descripción",
|
||||
"common.directory": "directorio",
|
||||
"common.duplicate": "Duplicar",
|
||||
"common.exportJson": "Exportar JSON",
|
||||
"common.failed": "fallido",
|
||||
"common.file": "archivo",
|
||||
"common.importJson": "Importar JSON",
|
||||
"common.new": "Nuevo",
|
||||
"common.noMatches": "Sin coincidencias",
|
||||
"common.options": "Opciones",
|
||||
"common.processingPleaseWait": "Procesando, espera por favor...",
|
||||
"common.refreshed": "Actualizado",
|
||||
"common.rename": "Renombrar",
|
||||
"common.saving": "Guardando...",
|
||||
"common.unknown": "desconocido",
|
||||
"common.unsavedChanges": "Cambios no guardados",
|
||||
"db.autoRefresh": "Refresco auto",
|
||||
"db.changesDiscarded": "Cambios descartados",
|
||||
"db.changesSaved": "Cambios guardados",
|
||||
"db.confirmDrop": "¿ELIMINAR tabla \"{{table}}\"? ¡Esto es irreversible!",
|
||||
"db.confirmTruncate": "¿Vaciar todas las filas de \"{{table}}\"?",
|
||||
"db.dangerZone": "Zona de peligro",
|
||||
"db.deletingRowsCount": "Eliminando {{count}} fila(s)...",
|
||||
"db.dropFailed": "Error al eliminar la tabla",
|
||||
"db.droppedTable": "Tabla {{table}} eliminada",
|
||||
"db.dropping": "Eliminando...",
|
||||
"db.emptyTable": "Tabla vacía",
|
||||
"db.errorLoadingData": "Error al cargar los datos",
|
||||
"db.failedLoadCatalog": "Error al cargar el catálogo",
|
||||
"db.failedLoadTable": "Error al cargar la tabla",
|
||||
"db.filterTables": "Filtrar tablas...",
|
||||
"db.insertFailed": "Error al insertar",
|
||||
"db.insertingRow": "Insertando fila...",
|
||||
"db.noRowsSelected": "No hay filas seleccionadas",
|
||||
"db.rowInserted": "Fila insertada",
|
||||
"db.rowsDeleted": "Filas eliminadas",
|
||||
"db.runningVacuum": "Ejecutando VACUUM...",
|
||||
"db.saveFailed": "Error al guardar",
|
||||
"db.selectTableFromSidebar": "Selecciona una tabla en la barra lateral",
|
||||
"db.tableDropped": "Tabla eliminada",
|
||||
"db.tableTruncated": "Tabla vaciada",
|
||||
"db.truncateFailed": "Error al vaciar",
|
||||
"db.truncating": "Vaciando...",
|
||||
"db.vacuumComplete": "VACUUM completado",
|
||||
"db.vacuumDone": "VACUUM hecho",
|
||||
"db.vacuumFailed": "Error en VACUUM",
|
||||
"files.confirmDelete": "¿Eliminar {{label}} \"{{name}}\"?",
|
||||
"files.downloadFile": "Descargar archivo",
|
||||
"files.duplicateFailed": "Error al duplicar",
|
||||
"files.duplicated": "Duplicado",
|
||||
"files.emptyDirectory": "Directorio vacío",
|
||||
"files.errorLoading": "Error al cargar archivos",
|
||||
"files.failedLoadDir": "Error al cargar directorio",
|
||||
"files.filterPlaceholder": "Filtrar archivos...",
|
||||
"files.itemsCount": "{{count}} elemento(s)",
|
||||
"files.newNamePrompt": "Nuevo nombre:",
|
||||
"files.noMatch": "No hay archivos coincidentes",
|
||||
"files.openDirectory": "Abrir directorio",
|
||||
"files.parent": ".. (superior)",
|
||||
"files.renameFailed": "Error al renombrar",
|
||||
"files.renamed": "Renombrado",
|
||||
"files.root": "Raíz",
|
||||
"files.uploadComplete": "Subida completada",
|
||||
"files.uploadFailed": "La subida falló",
|
||||
"files.uploadingCount": "Subiendo {{count}} archivo(s)...",
|
||||
"studio.actionNotFound": "Acción no encontrada",
|
||||
"studio.classNameRequired": "Nombre de clase requerido",
|
||||
"studio.confirmDeleteAction": "¿Eliminar acción \"{{name}}\"? Esto es irreversible.",
|
||||
"studio.deletedName": "Eliminado: {{name}}",
|
||||
"studio.exportedFile": "Exportado: {{name}}",
|
||||
"studio.filterActions": "Filtrar acciones...",
|
||||
"studio.importFailed": "Fallo al importar",
|
||||
"studio.importedFile": "Importado: {{name}}",
|
||||
"studio.loadFailed": "Fallo al cargar",
|
||||
"studio.loadedFromCacheName": "Cargado desde caché: {{name}}",
|
||||
"studio.loadedName": "Cargado: {{name}}",
|
||||
"studio.newActionCreated": "Nueva acción creada",
|
||||
"studio.noActionLoaded": "No hay acción cargada",
|
||||
"studio.saveFailedBackedUp": "Fallo al guardar (copia local creada)",
|
||||
"studio.savedName": "Guardado: {{name}}",
|
||||
"studio.setClassBeforeExport": "Define una clase antes de exportar",
|
||||
"zombie.agentRemoved": "Agente {{name}} eliminado",
|
||||
"zombie.agentsPurged": "{{count}} agente(s) purgados",
|
||||
"zombie.allAgents": "Todos los agentes",
|
||||
"zombie.c2StartedOnPort": "Servidor C2 iniciado en puerto {{port}}",
|
||||
"zombie.c2Stopped": "Servidor C2 detenido",
|
||||
"zombie.clearConsole": "Limpiar consola",
|
||||
"zombie.clearLogs": "Limpiar registros",
|
||||
"zombie.commandBroadcasted": "Comando difundido",
|
||||
"zombie.commandSentToAgents": "Comando enviado a {{count}} agente(s)",
|
||||
"zombie.confirmPurgeStale": "¿Purgar agentes inactivos por más de 24h?",
|
||||
"zombie.confirmRemoveAgent": "¿Eliminar agente {{name}}?",
|
||||
"zombie.confirmStopC2": "¿Detener servidor C2?",
|
||||
"zombie.consoleCleared": "Consola limpiada",
|
||||
"zombie.enterC2Port": "Introduce puerto C2:",
|
||||
"zombie.enterCommand": "Introduce comando...",
|
||||
"zombie.failedPurgeStale": "Fallo al purgar agentes inactivos",
|
||||
"zombie.failedRemoveAgent": "Fallo al eliminar agente {{name}}",
|
||||
"zombie.failedSendCommand": "Fallo al enviar comando",
|
||||
"zombie.failedStartC2": "Fallo al iniciar C2",
|
||||
"zombie.failedStopC2": "Fallo al detener C2",
|
||||
"zombie.noAgentsConnected": "No hay agentes conectados",
|
||||
"zombie.noAgentsMatchSearch": "Ningún agente coincide con tu búsqueda",
|
||||
"zombie.purgeStale": "Purgar inactivos",
|
||||
"zombie.purgeStaleHint": "Purgar agentes inactivos >24h",
|
||||
"zombie.removeAgent": "Eliminar agente",
|
||||
"zombie.startC2": "Iniciar C2",
|
||||
"zombie.stopC2": "Detener C2",
|
||||
"zombie.systemLogs": "Registros sistema",
|
||||
"zombieland.alive": "Vivo",
|
||||
"zombieland.c2Status": "Estado C2",
|
||||
"zombieland.dead": "Muerto",
|
||||
"zombieland.totalAgents": "Total agentes",
|
||||
"greeting": "Hola",
|
||||
"start": "Iniciar",
|
||||
"tick": "Tick",
|
||||
"common.ip": "IP",
|
||||
"common.mac": "MAC",
|
||||
"common.os": "SO",
|
||||
"zombie.never": "Nunca",
|
||||
"zombie.openInConsole": "Abrir en consola",
|
||||
"common.saved": "Guardado",
|
||||
"attacks.tabs.attacks": "Ataques",
|
||||
"attacks.tabs.comments": "Comentarios",
|
||||
"attacks.tabs.images": "Imágenes",
|
||||
"attacks.btn.addAttack": "Añadir ataque",
|
||||
"attacks.btn.removeAttack": "Eliminar ataque",
|
||||
"attacks.btn.deleteAction": "Eliminar acción",
|
||||
"attacks.btn.restoreDefaultsBundle": "Restaurar predeterminados",
|
||||
"attacks.btn.addSection": "Añadir sección",
|
||||
"attacks.btn.deleteSection": "Eliminar sección",
|
||||
"attacks.btn.restoreDefault": "Restaurar por defecto",
|
||||
"attacks.btn.createCharacter": "Crear personaje",
|
||||
"attacks.btn.deleteCharacter": "Eliminar personaje",
|
||||
"attacks.section.characters": "Personaje",
|
||||
"attacks.section.statusImages": "Imágenes estado",
|
||||
"attacks.section.staticImages": "Imágenes estáticas",
|
||||
"attacks.section.webImages": "Imágenes web",
|
||||
"attacks.section.actionIcons": "Iconos acción",
|
||||
"attacks.editor.selectAttack": "Seleccionar ataque",
|
||||
"attacks.empty.noAttacks": "No se encontraron ataques.",
|
||||
"attacks.empty.noComments": "No se encontraron comentarios.",
|
||||
"attacks.comments.placeholder": "Los comentarios se mostrarán aquí...",
|
||||
"attacks.images.enterEditMode": "Activar modo edición",
|
||||
"attacks.images.exitEditMode": "Salir modo edición",
|
||||
"attacks.images.sortName": "Orden: nombre",
|
||||
"attacks.images.sortDimensions": "Orden: tamaño",
|
||||
"attacks.images.search": "Buscar imágenes...",
|
||||
"attacks.images.rename": "Renombrar imagen",
|
||||
"attacks.images.replace": "Reemplazar imagen",
|
||||
"attacks.images.resizeSelected": "Redimensionar selección",
|
||||
"attacks.images.addCharacters": "Añadir imágenes personaje",
|
||||
"attacks.images.deleteSelected": "Eliminar selección",
|
||||
"attacks.images.addStatus": "Añadir imagen estado",
|
||||
"attacks.images.addStatic": "Añadir imagen estática",
|
||||
"attacks.images.addWeb": "Añadir imagen web",
|
||||
"attacks.images.addIcon": "Añadir icono acción",
|
||||
"attacks.errors.loadAttacks": "Fallo al cargar ataques.",
|
||||
"attacks.errors.loadImages": "Fallo al cargar imágenes.",
|
||||
"attacks.confirm.switchCharacter": "¿Cambiar al personaje \"{{name}}\"?",
|
||||
"attacks.confirm.removeAttack": "¿Eliminar ataque \"{{name}}\"?",
|
||||
"attacks.confirm.deleteAction": "¿Eliminar acción \"{{name}}\"?",
|
||||
"attacks.confirm.restoreAttack": "¿Restaurar \"{{name}}\" por defecto?",
|
||||
"attacks.confirm.restoreDefaultsBundle": "¿Restaurar TODOS los valores por defecto (acciones, imágenes, comentarios)?",
|
||||
"attacks.confirm.deleteCharacter": "¿Eliminar personaje \"{{name}}\"?",
|
||||
"attacks.confirm.deleteSection": "¿Eliminar sección \"{{name}}\"?",
|
||||
"attacks.confirm.restoreDefaultComments": "¿Restaurar comentarios por defecto?",
|
||||
"attacks.confirm.deleteSelectedImages": "¿Eliminar imágenes seleccionadas?",
|
||||
"attacks.prompt.newCharacterName": "Nombre nuevo personaje:",
|
||||
"attacks.prompt.characterToDelete": "Personaje a eliminar:",
|
||||
"attacks.prompt.newSectionName": "Nombre nueva sección:",
|
||||
"attacks.prompt.newImageName": "Nuevo nombre:",
|
||||
"attacks.prompt.resizeWidth": "Ancho redimensión:",
|
||||
"attacks.prompt.resizeHeight": "Alto redimensión:",
|
||||
"attacks.toast.characterSwitched": "Personaje cambiado",
|
||||
"attacks.toast.attackImported": "Ataque importado",
|
||||
"attacks.toast.selectAttackFirst": "Selecciona un ataque primero",
|
||||
"attacks.toast.actionDeleted": "Acción eliminada",
|
||||
"attacks.toast.defaultsRestored": "Valores por defecto restaurados",
|
||||
"attacks.toast.characterCreated": "Personaje creado",
|
||||
"attacks.toast.noDeletableCharacters": "No hay personajes eliminables",
|
||||
"attacks.toast.characterDeleted": "Personaje eliminado",
|
||||
"attacks.toast.commentsRestored": "Comentarios restaurados",
|
||||
"attacks.toast.selectSectionFirst": "Selecciona una sección primero",
|
||||
"attacks.toast.commentsSaved": "Comentarios guardados",
|
||||
"attacks.toast.selectExactlyOneImage": "Selecciona exactamente una imagen",
|
||||
"attacks.toast.selectAtLeastOneImage": "Selecciona al menos una imagen",
|
||||
"attacks.toast.imagesResized": "Imágenes redimensionadas",
|
||||
"attacks.toast.characterImagesUploaded": "Imágenes personaje subidas",
|
||||
"attacks.toast.selectStatusActionFirst": "Selecciona acción estado primero",
|
||||
"actions.toast.presetApplied": "Ajuste aplicado",
|
||||
"actions.toast.startingAction": "Iniciando {{name}}...",
|
||||
"actions.toast.actionStarted": "Acción iniciada",
|
||||
"actions.toast.stoppedByUser": "Detenido por usuario",
|
||||
"actions.toast.actionStopped": "Acción detenida",
|
||||
"actions.toast.stopFailed": "Fallo al detener",
|
||||
"actions.toast.failedToStop": "Incapaz de detener",
|
||||
"actions.toast.consoleCleared": "Consola limpiada",
|
||||
"actions.toast.noLogsToExport": "Sin registros para exportar",
|
||||
"actions.toast.logsExported": "Registros exportados",
|
||||
"netkb.confirmRemoveAction": "¿Eliminar acción \"{{action}}\" para IP \"{{ip}}\"?",
|
||||
"netkb.actionRemoved": "Acción eliminada",
|
||||
"actions.running": "En curso",
|
||||
"attacks.btn.syncMissing": "Sincronizar elementos faltantes",
|
||||
"attacks.images.gridDensity": "Densidad cuadrícula",
|
||||
"attacks.images.density": "Densidad",
|
||||
"attacks.sync.defaultComment": "Añadir comentario para esta acción",
|
||||
"attacks.sync.none": "No hay ataques que sincronizar.",
|
||||
"attacks.sync.done": "Sincronización terminada. Nuevos comentarios: {{comments}}, imágenes estado: {{status}}, imágenes personaje: {{characters}}.",
|
||||
"attacks.sync.failed": "Fallo al sincronizar elementos faltantes",
|
||||
"actions.args.free": "Argumentos libres",
|
||||
"actions.args.none": "Sin argumentos configurables",
|
||||
"actions.args.subtitle": "Generado auto desde definiciones",
|
||||
"actions.args.title": "Argumentos",
|
||||
"actions.assign": "Asignar",
|
||||
"actions.emptyPane": "Sin acción seleccionada",
|
||||
"actions.logs.completed": "Hecho",
|
||||
"actions.logs.empty": "Sin registros aún",
|
||||
"actions.logs.waiting": "Esperando...",
|
||||
"actions.searchPlaceholder": "Buscar acciones...",
|
||||
"actions.tabs.actions": "Acciones",
|
||||
"actions.tabs.arguments": "Argumentos",
|
||||
"actions.toast.selectActionFirst": "Selecciona una acción primero",
|
||||
"common.move": "Mover",
|
||||
"common.ready": "Listo",
|
||||
"common.menu": "Menú",
|
||||
"common.browse": "Examinar...",
|
||||
"common.platform": "Plataforma",
|
||||
"common.generate": "Generar",
|
||||
"common.vendor": "Fabricante",
|
||||
"common.hostname": "Nombre host",
|
||||
"common.ports": "Puertos",
|
||||
"zombie.generateClient": "Generar cliente",
|
||||
"zombie.checkStale": "Buscar inactivos",
|
||||
"zombie.selectedAgents": "agentes seleccionados",
|
||||
"zombie.clientId": "ID Cliente",
|
||||
"zombie.labCreds": "Credenciales Lab",
|
||||
"zombie.deployOptions": "Opciones despliegue",
|
||||
"zombie.deployViaSSH": "Desplegar vía SSH",
|
||||
"zombie.fileBrowser": "Explorador archivos",
|
||||
"dash.lastUpdate": "Última actualización",
|
||||
"netkb.searchPlaceholder": "Buscar host, IP, fabricante, puerto...",
|
||||
"netkb.searchHint": "Tip: escribe 'port:80' o 'vendor:intel'",
|
||||
"files.dropzoneHint": "Suelta archivos aquí o haz clic para subir",
|
||||
"files.moveToTitle": "Mover a...",
|
||||
"files.selectDestinationFolder": "Seleccionar destino",
|
||||
"attacks.sidebar.management": "Gestión",
|
||||
"sched.upcoming": "Próximos",
|
||||
"sched.success": "Éxito",
|
||||
"sched.cancelled": "Cancelado",
|
||||
"sched.history": "Historial",
|
||||
"sched.historyMsg": "Registros historial",
|
||||
"creds.searchPlaceholder": "Buscar servicios, usuarios...",
|
||||
"creds.uniqueHosts": "Hosts únicos",
|
||||
"creds.totalCredentials": "Total credenciales",
|
||||
"console.maxReconnect": "Consola: alcanzado el máximo de intentos de reconexión",
|
||||
"console.scrollToBottom": "Desplazarse al final",
|
||||
"console.manual": "Manual",
|
||||
"console.auto": "Auto",
|
||||
"console.turnOnAuto": "Activar modo Auto",
|
||||
"console.turnOnManual": "Activar modo Manual",
|
||||
"console.noTarget": "Sin objetivo",
|
||||
"console.noAction": "Sin acción",
|
||||
"console.scanStarted": "Escaneo manual iniciado",
|
||||
"console.scanFailed": "Fallo en escaneo manual",
|
||||
"console.attackStarted": "Ataque manual iniciado",
|
||||
"console.attackFailed": "Fallo en ataque manual",
|
||||
"console.failedToggleMode": "Fallo al cambiar modo",
|
||||
"console.reconnectAttempt": "Reconectando (intento {{count}})...",
|
||||
"quick.close": "Cerrar panel",
|
||||
"quick.connectingTo": "Conectando a {{ssid}}...",
|
||||
"quick.connectedTo": "Conectado a {{ssid}}",
|
||||
"quick.connectionFailed": "Fallo en la conexión",
|
||||
"quick.loadKnownFailed": "Fallo al cargar redes conocidas",
|
||||
"quick.priorityUpdated": "Prioridad actualizada",
|
||||
"quick.priorityUpdateFailed": "Fallo al actualizar prioridad",
|
||||
"quick.networkRemoved": "Red eliminada",
|
||||
"quick.importingPotfiles": "Importando potfiles...",
|
||||
"quick.importedCount": "{{count}} credenciales importadas",
|
||||
"quick.btScanFailed": "Fallo en escaneo Bluetooth",
|
||||
"quick.btActioning": "{{action}} de {{name}}...",
|
||||
"quick.btActionDone": "{{name}} {{action}} hecho",
|
||||
"quick.btActionFailed": "Fallo en {{action}}",
|
||||
"quick.btForgotten": "{{name}} olvidado",
|
||||
"sidebar.close": "Cerrar barra lateral",
|
||||
"api.aborted": "Abortado",
|
||||
"api.timeout": "La solicitud ha expirado",
|
||||
"api.failed": "La solicitud ha fallado",
|
||||
"router.notFound": "Página no encontrada: {{path}}",
|
||||
"router.errorLoading": "Error al cargar la página: {{message}}"
|
||||
}
|
||||
782
web/i18n/fr.json
Normal file
@@ -0,0 +1,782 @@
|
||||
{
|
||||
"nav.dashboard": "Tableau de bord",
|
||||
"nav.bjorn": "Bjorn",
|
||||
"nav.netkb": "Base Réseau",
|
||||
"nav.network": "Réseau",
|
||||
"nav.credentials": "Identifiants",
|
||||
"nav.vulnerabilities": "Vulnérabilités",
|
||||
"nav.attacks": "Attaques",
|
||||
"nav.scheduler": "Planificateur",
|
||||
"nav.database": "Base de données",
|
||||
"nav.files": "Fichiers",
|
||||
"nav.loot": "Butin",
|
||||
"nav.actions": "Actions",
|
||||
"nav.actionsStudio": "Studio Actions",
|
||||
"nav.backup": "Sauvegarde & MAJ",
|
||||
"nav.webEnum": "Enum Web",
|
||||
"nav.zombieland": "Zombieland",
|
||||
"nav.ai_dashboard": "Tableau de bord IA",
|
||||
"nav.settings": "Paramètres",
|
||||
"nav.shortcuts": "Raccourcis",
|
||||
"nav.pages": "Pages",
|
||||
"status.initializing": "Initialisation...",
|
||||
"status.online": "En ligne",
|
||||
"status.offline": "Hors ligne",
|
||||
"console.title": "Console",
|
||||
"console.clear": "Effacer",
|
||||
"console.sseOn": "SSE Actif",
|
||||
"console.sseOff": "SSE Inactif",
|
||||
"console.newLogs": "{{count}} nouveaux logs",
|
||||
"settings.theme": "Thème",
|
||||
"settings.language": "Langue",
|
||||
"settings.general": "Général",
|
||||
"settings.toggles": "Options",
|
||||
"settings.editValue": "Modifier la valeur",
|
||||
"settings.addValues": "Ajouter des valeurs (séparées par des virgules)...",
|
||||
"settings.setValue": "Définir la valeur...",
|
||||
"settings.errorLoading": "Erreur de chargement de la configuration",
|
||||
"settings.configSaved": "Configuration enregistrée",
|
||||
"settings.errorSaving": "Erreur d'enregistrement de la configuration",
|
||||
"settings.defaultsRestored": "Valeurs par défaut restaurées",
|
||||
"settings.errorRestoring": "Erreur lors de la restauration des valeurs par défaut",
|
||||
"theme.group.colors": "Couleurs",
|
||||
"theme.group.surfaces": "Surfaces",
|
||||
"theme.group.layout": "Disposition",
|
||||
"theme.token.bg": "Arrière-plan",
|
||||
"theme.token.ink": "Couleur du texte",
|
||||
"theme.token.accent1": "Accent 1 (Acide)",
|
||||
"theme.token.accent2": "Accent 2 (Cyan)",
|
||||
"theme.token.danger": "Danger",
|
||||
"theme.token.warning": "Avertissement",
|
||||
"theme.token.ok": "Succès",
|
||||
"theme.token.panel": "Panneau",
|
||||
"theme.token.panel2": "Panneau Alt",
|
||||
"theme.token.ctrlPanel": "Panneau contrôle",
|
||||
"theme.token.border": "Bordure",
|
||||
"theme.token.radius": "Rayon de bordure",
|
||||
"theme.advanced": "CSS avancé",
|
||||
"theme.applyRaw": "Appliquer",
|
||||
"theme.reset": "Réinitialiser",
|
||||
"dash.title": "Tableau de bord",
|
||||
"dash.battery": "Batterie",
|
||||
"dash.internet": "Internet",
|
||||
"dash.cpu": "CPU",
|
||||
"dash.ram": "RAM",
|
||||
"dash.disk": "Disque",
|
||||
"dash.temp": "Temp",
|
||||
"dash.uptime": "Durée",
|
||||
"dash.hostsAlive": "Hôtes actifs",
|
||||
"dash.totalHosts": "Total hôtes",
|
||||
"dash.openPorts": "Ports ouverts",
|
||||
"dash.credentials": "Identifiants",
|
||||
"dash.vulnerabilities": "Vulnérabilités",
|
||||
"dash.actions": "Actions",
|
||||
"dash.connected": "Connecté",
|
||||
"dash.disconnected": "Déconnecté",
|
||||
"dash.charging": "En charge",
|
||||
"dash.discharging": "Décharge",
|
||||
"dash.full": "Plein",
|
||||
"dash.connectivity": "Connectivité",
|
||||
"dash.liveOps": "Opérations en direct",
|
||||
"dash.tapRefresh": "Appuyer pour rafraîchir",
|
||||
"dash.wifi": "Wi-Fi",
|
||||
"dash.ethernet": "Ethernet",
|
||||
"dash.usb": "USB",
|
||||
"dash.bluetooth": "Bluetooth",
|
||||
"dash.mode": "Mode",
|
||||
"dash.gps": "GPS",
|
||||
"dash.age": "Âge de Bjorn",
|
||||
"dash.plugged": "Branché",
|
||||
"dash.noBattery": "Pas de batterie",
|
||||
"dash.sinceScan": "depuis le dernier scan",
|
||||
"dash.wifiKnown": "Wi-Fi connus",
|
||||
"dash.dataFiles": "Données / Fichiers collectés",
|
||||
"dash.fileDescriptors": "Descripteurs de fichiers",
|
||||
"dash.attackScripts": "Scripts d'attaque",
|
||||
"dash.system": "Système",
|
||||
"dash.zombies": "Zombies",
|
||||
"netkb.title": "Base de connaissances réseau",
|
||||
"netkb.showOffline": "Afficher hors ligne",
|
||||
"netkb.gridView": "Grille",
|
||||
"netkb.listView": "Liste",
|
||||
"netkb.hostname": "Nom d'hôte",
|
||||
"netkb.ip": "Adresse IP",
|
||||
"netkb.mac": "Adresse MAC",
|
||||
"netkb.vendor": "Fabricant",
|
||||
"netkb.ports": "Ports",
|
||||
"netkb.essid": "ESSID",
|
||||
"netkb.lastSeen": "Dernière vue",
|
||||
"netkb.firstSeen": "Première vue",
|
||||
"netkb.online": "En ligne",
|
||||
"netkb.offline": "Hors ligne",
|
||||
"netkb.openPorts": "Ports ouverts",
|
||||
"netkb.noHosts": "Aucun hôte trouvé",
|
||||
"network.title": "Visualisation réseau",
|
||||
"network.tableView": "Tableau",
|
||||
"network.mapView": "Carte",
|
||||
"network.hostname": "Nom d'hôte",
|
||||
"network.ip": "Adresse IP",
|
||||
"network.mac": "MAC",
|
||||
"network.ports": "Ports",
|
||||
"network.status": "Statut",
|
||||
"network.searchPlaceholder": "Rechercher des hôtes...",
|
||||
"network.noData": "Aucune donnée réseau",
|
||||
"creds.title": "Identifiants",
|
||||
"creds.total": "Total",
|
||||
"creds.unique": "Uniques",
|
||||
"creds.types": "Types",
|
||||
"creds.username": "Nom d'utilisateur",
|
||||
"creds.password": "Mot de passe",
|
||||
"creds.service": "Service",
|
||||
"creds.host": "Hôte",
|
||||
"creds.port": "Port",
|
||||
"creds.type": "Type",
|
||||
"creds.timestamp": "Horodatage",
|
||||
"creds.showPassword": "Afficher le mot de passe",
|
||||
"creds.hidePassword": "Masquer le mot de passe",
|
||||
"creds.copyPassword": "Copier",
|
||||
"creds.exportAll": "Tout exporter",
|
||||
"creds.noCredentials": "Aucun identifiant trouvé",
|
||||
"vulns.title": "Tableau des vulnérabilités",
|
||||
"vulns.total": "Total",
|
||||
"vulns.critical": "Critique",
|
||||
"vulns.high": "Élevé",
|
||||
"vulns.medium": "Moyen",
|
||||
"vulns.low": "Faible",
|
||||
"vulns.infoLevel": "Info",
|
||||
"vulns.host": "Hôte",
|
||||
"vulns.port": "Port",
|
||||
"vulns.service": "Service",
|
||||
"vulns.severity": "Sévérité",
|
||||
"vulns.description": "Description",
|
||||
"vulns.cve": "CVE",
|
||||
"vulns.scanDate": "Date de scan",
|
||||
"vulns.details": "Détails",
|
||||
"vulns.noVulns": "Aucune vulnérabilité trouvée",
|
||||
"vulns.byHost": "Par hôte",
|
||||
"vulns.bySeverity": "Par sévérité",
|
||||
"vulns.byService": "Par service",
|
||||
"attacks.title": "Gestionnaire d'attaques",
|
||||
"attacks.running": "En cours",
|
||||
"attacks.completed": "Terminé",
|
||||
"attacks.failed": "Échoué",
|
||||
"attacks.queued": "En attente",
|
||||
"attacks.start": "Démarrer",
|
||||
"attacks.stop": "Arrêter",
|
||||
"attacks.restart": "Redémarrer",
|
||||
"attacks.status": "Statut",
|
||||
"attacks.target": "Cible",
|
||||
"attacks.action": "Action",
|
||||
"attacks.duration": "Durée",
|
||||
"attacks.progress": "Progression",
|
||||
"attacks.noAttacks": "Aucune attaque en cours",
|
||||
"sched.title": "Planificateur d'actions",
|
||||
"sched.pending": "En attente",
|
||||
"sched.running": "En cours",
|
||||
"sched.done": "Terminé",
|
||||
"sched.failed": "Échoué",
|
||||
"sched.all": "Tout",
|
||||
"sched.searchPlaceholder": "Rechercher des tâches...",
|
||||
"sched.noTasks": "Aucune tâche trouvée",
|
||||
"sched.stats": "{{running}} en cours / {{pending}} en attente / {{done}} terminées",
|
||||
"db.title": "Gestionnaire de base de données",
|
||||
"db.tables": "Tables",
|
||||
"db.rows": "Lignes",
|
||||
"db.columns": "Colonnes",
|
||||
"db.search": "Rechercher des tables...",
|
||||
"db.searchRows": "Rechercher des lignes...",
|
||||
"db.export": "Exporter",
|
||||
"db.import": "Importer",
|
||||
"db.addRow": "Ajouter une ligne",
|
||||
"db.deleteRow": "Supprimer la ligne",
|
||||
"db.deleteSelected": "Supprimer la sélection",
|
||||
"db.saveChanges": "Enregistrer",
|
||||
"db.discardChanges": "Annuler",
|
||||
"db.confirmDelete": "Confirmer la suppression ?",
|
||||
"db.noTables": "Aucune table trouvée",
|
||||
"db.noData": "Aucune donnée dans cette table",
|
||||
"db.hide": "Masquer",
|
||||
"db.showSidebar": "Afficher le panneau",
|
||||
"files.title": "Explorateur de fichiers",
|
||||
"files.gridView": "Grille",
|
||||
"files.listView": "Liste",
|
||||
"files.size": "Taille",
|
||||
"files.modified": "Modifié",
|
||||
"files.name": "Nom",
|
||||
"files.type": "Type",
|
||||
"files.download": "Télécharger",
|
||||
"files.preview": "Aperçu",
|
||||
"files.noFiles": "Aucun fichier trouvé",
|
||||
"files.parentDir": "Répertoire parent",
|
||||
"files.searchPlaceholder": "Rechercher des fichiers...",
|
||||
"loot.title": "Butin",
|
||||
"loot.directories": "Répertoires",
|
||||
"loot.totalFiles": "Total fichiers",
|
||||
"loot.totalSize": "Taille totale",
|
||||
"loot.download": "Télécharger",
|
||||
"loot.downloadAll": "Tout télécharger",
|
||||
"loot.noLoot": "Aucun butin trouvé",
|
||||
"loot.explore": "Explorer",
|
||||
"actions.title": "Gestionnaire d'actions",
|
||||
"actions.available": "Disponibles",
|
||||
"actions.enabled": "Activées",
|
||||
"actions.disabled": "Désactivées",
|
||||
"actions.category": "Catégorie",
|
||||
"actions.enableAll": "Tout activer",
|
||||
"actions.disableAll": "Tout désactiver",
|
||||
"actions.import": "Importer",
|
||||
"actions.export": "Exporter",
|
||||
"actions.noActions": "Aucune action trouvée",
|
||||
"actions.description": "Description",
|
||||
"actions.menu.restartService": "Redémarrer le service Bjorn",
|
||||
"actions.menu.deleteActionStatus": "Supprimer tous les statuts d'actions",
|
||||
"actions.menu.clearOutput": "Vider le dossier Output",
|
||||
"actions.menu.clearLogs": "Effacer les logs",
|
||||
"actions.menu.reloadImages": "Recharger les images (expérimental)",
|
||||
"actions.menu.reloadFonts": "Recharger les polices",
|
||||
"actions.menu.reloadActionsJson": "Recharger le JSON des actions",
|
||||
"actions.menu.initializeCsv": "Initialiser les fichiers CSV",
|
||||
"actions.menu.clearLivestatus": "Supprimer le fichier Livestatus",
|
||||
"actions.menu.refreshActionsFile": "Rafraîchir le fichier Actions",
|
||||
"actions.menu.clearNetkb": "Vider la base de connaissances réseau",
|
||||
"actions.menu.clearSharedConfig": "Supprimer le JSON de configuration partagée",
|
||||
"actions.menu.eraseMemories": "Effacer la mémoire de Bjorn",
|
||||
"actions.menu.reboot": "Redémarrer le système",
|
||||
"actions.menu.shutdown": "Éteindre le système",
|
||||
"actions.tip.restartService": "Redémarre le service Bjorn pour rafraîchir son état.",
|
||||
"actions.tip.deleteActionStatus": "Supprime tous les statuts de succès et d'échec des actions/attaques dans netkb.csv.",
|
||||
"actions.tip.clearOutput": "Efface tous les fichiers des dossiers output et sous-dossiers.",
|
||||
"actions.tip.clearLogs": "Supprime tous les fichiers de logs du système.",
|
||||
"actions.tip.reloadImages": "Recharge les images utilisées par le système.",
|
||||
"actions.tip.reloadFonts": "Recharge les polices de l'application.",
|
||||
"actions.tip.reloadActionsJson": "Recharge le fichier JSON des actions générées.",
|
||||
"actions.tip.initializeCsv": "Recrée les fichiers CSV et JSON.",
|
||||
"actions.tip.clearLivestatus": "Supprime le fichier de statut en direct.",
|
||||
"actions.tip.refreshActionsFile": "Rafraîchit le fichier des actions pour prendre en compte les nouvelles actions.",
|
||||
"actions.tip.clearNetkb": "Supprime toutes les informations enregistrées dans la base de connaissances réseau.",
|
||||
"actions.tip.clearSharedConfig": "Supprime le fichier JSON de configuration partagée.",
|
||||
"actions.tip.eraseMemories": "Efface complètement la mémoire et les paramètres de Bjorn.",
|
||||
"actions.tip.reboot": "Redémarre tout le système.",
|
||||
"actions.tip.shutdown": "Éteint complètement le système.",
|
||||
"actions.confirm.restartRecommended": "Le redémarrage du service est recommandé. Redémarrer maintenant ?",
|
||||
"actions.confirm.restartService": "Redémarrer le service Bjorn ?",
|
||||
"actions.confirm.deleteActionStatus": "Supprimer tous les statuts d'actions enregistrés ?",
|
||||
"actions.confirm.clearOutput": "Vider entièrement le dossier output ?",
|
||||
"actions.confirm.clearLogs": "Supprimer tous les fichiers de logs ?",
|
||||
"actions.confirm.clearNetkb": "Vider la base de connaissances réseau ? Cette action est irréversible.",
|
||||
"actions.confirm.clearLivestatus": "Supprimer le fichier livestatus ?",
|
||||
"actions.confirm.refreshActionsFile": "Rafraîchir le fichier actions ?",
|
||||
"actions.confirm.clearSharedConfig": "Supprimer le JSON de configuration partagée ? Cette action est irréversible.",
|
||||
"actions.confirm.eraseMemories": "Effacer toute la mémoire et les paramètres de Bjorn ? Cette action est irréversible.",
|
||||
"actions.confirm.reboot": "Redémarrer tout le système ?",
|
||||
"actions.confirm.shutdown": "Éteindre le système ?",
|
||||
"actions.msg.restartingService": "Le service Bjorn redémarre...",
|
||||
"actions.msg.restartFailed": "Échec du redémarrage du service",
|
||||
"actions.msg.actionStatusDeleted": "Tous les statuts d'actions ont été supprimés.",
|
||||
"actions.msg.outputCleared": "Le dossier output a été vidé.",
|
||||
"actions.msg.logsCleared": "Les logs ont été effacés.",
|
||||
"actions.msg.netkbCleared": "La base de connaissances réseau a été vidée.",
|
||||
"actions.msg.livestatusDeleted": "Le fichier livestatus a été supprimé.",
|
||||
"actions.msg.actionsFileRefreshed": "Le fichier actions a été rafraîchi.",
|
||||
"actions.msg.sharedConfigDeleted": "Le JSON de configuration partagée a été supprimé.",
|
||||
"actions.msg.memoriesErased": "La mémoire de Bjorn a été effacée.",
|
||||
"actions.msg.rebooting": "Le système redémarre...",
|
||||
"actions.msg.shuttingDown": "Le système s'éteint...",
|
||||
"actions.msg.csvInitialized": "Les fichiers CSV ont été initialisés.",
|
||||
"actions.msg.actionsJsonReloaded": "Le JSON des actions a été rechargé.",
|
||||
"actions.msg.imagesReloaded": "Les images ont été rechargées.",
|
||||
"actions.msg.fontsReloaded": "Les polices ont été rechargées.",
|
||||
"actions.msg.unknownAction": "Action inconnue",
|
||||
"actions.msg.actionFailed": "Échec de l'action",
|
||||
"studio.title": "Studio Actions",
|
||||
"studio.palette": "Palette",
|
||||
"studio.canvas": "Canevas",
|
||||
"studio.inspector": "Inspecteur",
|
||||
"studio.actionsTab": "Actions",
|
||||
"studio.hostsTab": "Hôtes",
|
||||
"studio.globalTab": "Global",
|
||||
"studio.save": "Enregistrer",
|
||||
"studio.load": "Charger",
|
||||
"studio.run": "Exécuter",
|
||||
"studio.clear": "Effacer",
|
||||
"studio.addNode": "Ajouter un nœud",
|
||||
"studio.removeNode": "Supprimer le nœud",
|
||||
"studio.search": "Rechercher des actions...",
|
||||
"backup.title": "Sauvegarde & MAJ",
|
||||
"backup.backupRestore": "Sauvegarde / Restauration",
|
||||
"backup.update": "Mise à jour",
|
||||
"backup.createBackup": "Créer une sauvegarde",
|
||||
"backup.restoreBackup": "Restaurer",
|
||||
"backup.downloadBackup": "Télécharger",
|
||||
"backup.deleteBackup": "Supprimer la sauvegarde",
|
||||
"backup.lastBackup": "Dernière sauvegarde",
|
||||
"backup.checkUpdates": "Vérifier les mises à jour",
|
||||
"backup.installUpdate": "Installer la mise à jour",
|
||||
"backup.currentVersion": "Version actuelle",
|
||||
"backup.latestVersion": "Dernière version",
|
||||
"backup.upToDate": "À jour",
|
||||
"backup.updateAvailable": "Mise à jour disponible",
|
||||
"backup.clearLogs": "Effacer les logs",
|
||||
"backup.noBackups": "Aucune sauvegarde trouvée",
|
||||
"backup.restoring": "Restauration...",
|
||||
"backup.creating": "Création de la sauvegarde...",
|
||||
"webenum.title": "Énumération Web",
|
||||
"webenum.totalResults": "Total résultats",
|
||||
"webenum.uniqueHosts": "Hôtes uniques",
|
||||
"webenum.successCount": "Succès (2xx)",
|
||||
"webenum.errorCount": "Erreurs (4xx/5xx)",
|
||||
"webenum.host": "Hôte",
|
||||
"webenum.ip": "IP",
|
||||
"webenum.port": "Port",
|
||||
"webenum.directory": "Répertoire",
|
||||
"webenum.status": "Statut",
|
||||
"webenum.size": "Taille",
|
||||
"webenum.scanDate": "Date de scan",
|
||||
"webenum.link": "Lien",
|
||||
"webenum.exportJson": "Exporter JSON",
|
||||
"webenum.exportCsv": "Exporter CSV",
|
||||
"webenum.noResults": "Aucun résultat trouvé",
|
||||
"webenum.details": "Détails du résultat",
|
||||
"webenum.openUrl": "Ouvrir l'URL",
|
||||
"webenum.copyUrl": "Copier l'URL",
|
||||
"webenum.showing": "Affichage {{start}}-{{end}} sur {{total}} résultats",
|
||||
"webenum.itemsPerPage": "Éléments par page",
|
||||
"webenum.refreshData": "Rafraîchir les données",
|
||||
"webenum.responseTime": "Temps de réponse",
|
||||
"webenum.contentType": "Type de contenu",
|
||||
"webenum.fullUrl": "URL complète",
|
||||
"zombie.title": "Zombieland C2C",
|
||||
"zombie.agents": "Agents",
|
||||
"zombie.terminal": "Terminal",
|
||||
"zombie.commands": "Commandes",
|
||||
"zombie.totalAgents": "Total agents",
|
||||
"zombie.onlineAgents": "En ligne",
|
||||
"zombie.offlineAgents": "Hors ligne",
|
||||
"zombie.idleAgents": "Inactif",
|
||||
"zombie.sendCommand": "Envoyer la commande",
|
||||
"zombie.broadcast": "Diffusion",
|
||||
"zombie.selectAgent": "Sélectionner un agent",
|
||||
"zombie.os": "OS",
|
||||
"zombie.lastSeen": "Dernière vue",
|
||||
"zombie.status": "Statut",
|
||||
"zombie.noAgents": "Aucun agent connecté",
|
||||
"zombie.quickCommands": "Commandes rapides",
|
||||
"zombie.files": "Fichiers",
|
||||
"quick.autoScan": "Auto-scan",
|
||||
"quick.connectWifi": "Se connecter au WiFi",
|
||||
"quick.knownNetworks": "Réseaux connus",
|
||||
"quick.importPotfiles": "Importer Potfiles",
|
||||
"quick.subtitle": "WiFi & Bluetooth",
|
||||
"quick.pair": "Appairer",
|
||||
"quick.trust": "Faire confiance",
|
||||
"quick.forgetDevice": "Oublier l'appareil",
|
||||
"quick.forgetDevicePrompt": "Oublier {{name}} ?",
|
||||
"quick.forgetNetworkPrompt": "Êtes-vous sûr de vouloir oublier ce réseau ?",
|
||||
"bjorn.title": "Écran EPD Bjorn",
|
||||
"bjorn.epdScreen": "Écran e-Paper",
|
||||
"bjorn.refreshInterval": "Intervalle de rafraîchissement",
|
||||
"bjorn.autoRefresh": "Rafraîchissement auto",
|
||||
"bjorn.manualRefresh": "Rafraîchir maintenant",
|
||||
"bjorn.seconds": "secondes",
|
||||
"common.search": "Rechercher",
|
||||
"common.filter": "Filtrer",
|
||||
"common.refresh": "Actualiser",
|
||||
"common.save": "Enregistrer",
|
||||
"common.cancel": "Annuler",
|
||||
"common.delete": "Supprimer",
|
||||
"common.edit": "Modifier",
|
||||
"common.close": "Fermer",
|
||||
"common.loading": "Chargement...",
|
||||
"common.noData": "Aucune donnée disponible",
|
||||
"common.error": "Erreur",
|
||||
"common.success": "Succès",
|
||||
"common.confirm": "Confirmer",
|
||||
"common.yes": "Oui",
|
||||
"common.no": "Non",
|
||||
"common.export": "Exporter",
|
||||
"common.import": "Importer",
|
||||
"common.download": "Télécharger",
|
||||
"common.upload": "Téléverser",
|
||||
"common.copy": "Copier",
|
||||
"common.start": "Démarrer",
|
||||
"common.stop": "Arrêter",
|
||||
"common.restart": "Redémarrer",
|
||||
"common.status": "Statut",
|
||||
"common.name": "Nom",
|
||||
"common.value": "Valeur",
|
||||
"common.type": "Type",
|
||||
"common.host": "Hôte",
|
||||
"common.port": "Port",
|
||||
"common.target": "Cible",
|
||||
"common.date": "Date",
|
||||
"common.time": "Heure",
|
||||
"common.size": "Taille",
|
||||
"common.actions": "Actions",
|
||||
"common.details": "Détails",
|
||||
"common.back": "Retour",
|
||||
"common.next": "Suivant",
|
||||
"common.previous": "Précédent",
|
||||
"common.first": "Premier",
|
||||
"common.last": "Dernier",
|
||||
"common.all": "Tout",
|
||||
"common.none": "Aucun",
|
||||
"common.showing": "Affichage",
|
||||
"common.of": "sur",
|
||||
"common.results": "résultats",
|
||||
"common.items": "éléments",
|
||||
"common.page": "Page",
|
||||
"common.perPage": "par page",
|
||||
"common.sortBy": "Trier par",
|
||||
"common.ascending": "Croissant",
|
||||
"common.descending": "Décroissant",
|
||||
"common.view": "Vue",
|
||||
"common.table": "Tableau",
|
||||
"common.grid": "Grille",
|
||||
"common.list": "Liste",
|
||||
"common.map": "Carte",
|
||||
"common.enabled": "Activé",
|
||||
"common.disabled": "Désactivé",
|
||||
"common.on": "Activé",
|
||||
"common.off": "Désactivé",
|
||||
"common.version": "Version",
|
||||
"common.hide": "Masquer",
|
||||
"common.show": "Afficher",
|
||||
"common.add": "Ajouter",
|
||||
"common.remove": "Supprimer",
|
||||
"common.clear": "Effacer",
|
||||
"common.reset": "Réinitialiser",
|
||||
"common.apply": "Appliquer",
|
||||
"common.run": "Exécuter",
|
||||
"common.send": "Envoyer",
|
||||
"common.connect": "Connecter",
|
||||
"common.disconnect": "Déconnecter",
|
||||
"common.selectAll": "Tout sélectionner",
|
||||
"common.deselectAll": "Tout désélectionner",
|
||||
"common.copied": "Copié !",
|
||||
"common.notFound": "Non trouvé",
|
||||
"backup.checkUpdatesHint": "Cliquez sur \"Vérifier les mises à jour\" pour afficher les versions.",
|
||||
"backup.checkingUpdates": "Vérification des mises à jour...",
|
||||
"backup.confirmFreshStart": "Confirmer le Fresh Start ? ",
|
||||
"backup.createdSuccessfully": "Sauvegarde créée avec succès.",
|
||||
"backup.defaultUpdated": "Sauvegarde par défaut mise à jour.",
|
||||
"backup.deleted": "Sauvegarde supprimée.",
|
||||
"backup.descriptionPlaceholder": "Description de la sauvegarde...",
|
||||
"backup.enterDescription": "Veuillez saisir une description de sauvegarde.",
|
||||
"backup.failedCheckUpdates": "Échec de la vérification des mises à jour",
|
||||
"backup.failedCreate": "Échec de création de la sauvegarde",
|
||||
"backup.failedDelete": "Échec de suppression de la sauvegarde",
|
||||
"backup.failedLoadBackups": "Échec du chargement des sauvegardes",
|
||||
"backup.failedSetDefault": "Échec de définition par défaut",
|
||||
"backup.freshStart": "Réinitialisation complète",
|
||||
"backup.freshStartFailed": "Échec de la réinitialisation complète",
|
||||
"backup.freshStartInitiated": "Réinitialisation complète lancée.",
|
||||
"backup.github": "github",
|
||||
"backup.keepActions": "Conserver le dossier actions",
|
||||
"backup.keepConfig": "Conserver le dossier config",
|
||||
"backup.keepData": "Conserver le dossier data",
|
||||
"backup.keepResources": "Conserver le dossier resources",
|
||||
"backup.noBackupsCreateAbove": "Aucune sauvegarde trouvée. Créez-en une ci-dessus.",
|
||||
"backup.restoreCompleted": "Restauration terminée.",
|
||||
"backup.restoreOptions": "Options de restauration",
|
||||
"backup.restorePoint": "point-de-restauration",
|
||||
"backup.selectKeepFolders": "Sélectionnez les dossiers à conserver pendant l'opération :",
|
||||
"backup.setDefault": "Définir par défaut",
|
||||
"backup.unnamedBackup": "Sauvegarde sans nom",
|
||||
"backup.updateInitiated": "Mise à jour lancée.",
|
||||
"backup.updateOptions": "Options de mise à jour",
|
||||
"common.confirmDiscardUnsaved": "Ignorer les modifications non enregistrées ?",
|
||||
"common.confirmQuestion": "Confirmer ?",
|
||||
"common.default": "par défaut",
|
||||
"common.deleteFailed": "Échec de suppression",
|
||||
"common.deleted": "Supprimé",
|
||||
"common.description": "Description",
|
||||
"common.directory": "répertoire",
|
||||
"common.duplicate": "Dupliquer",
|
||||
"common.exportJson": "Exporter JSON",
|
||||
"common.failed": "échec",
|
||||
"common.file": "fichier",
|
||||
"common.importJson": "Importer JSON",
|
||||
"common.new": "Nouveau",
|
||||
"common.noMatches": "Aucun résultat",
|
||||
"common.options": "Options",
|
||||
"common.processingPleaseWait": "Traitement en cours, veuillez patienter...",
|
||||
"common.refreshed": "Actualisé",
|
||||
"common.rename": "Renommer",
|
||||
"common.saving": "Enregistrement...",
|
||||
"common.unknown": "inconnu",
|
||||
"common.unsavedChanges": "Modifications non enregistrées",
|
||||
"db.autoRefresh": "Rafraîchissement auto",
|
||||
"db.changesDiscarded": "Modifications annulées",
|
||||
"db.changesSaved": "Modifications enregistrées",
|
||||
"db.confirmDrop": "SUPPRIMER \"{{table}}\" ? Cette action est irréversible !",
|
||||
"db.confirmTruncate": "Vider toutes les lignes de \"{{table}}\" ?",
|
||||
"db.dangerZone": "Zone de danger",
|
||||
"db.deletingRowsCount": "Suppression de {{count}} ligne(s)...",
|
||||
"db.dropFailed": "Échec de suppression de la table",
|
||||
"db.droppedTable": "Table {{table}} supprimée",
|
||||
"db.dropping": "Suppression...",
|
||||
"db.emptyTable": "Table vide",
|
||||
"db.errorLoadingData": "Erreur lors du chargement des données",
|
||||
"db.failedLoadCatalog": "Échec du chargement du catalogue",
|
||||
"db.failedLoadTable": "Échec du chargement de la table",
|
||||
"db.filterTables": "Filtrer les tables...",
|
||||
"db.insertFailed": "Échec de l'insertion",
|
||||
"db.insertingRow": "Insertion de la ligne...",
|
||||
"db.noRowsSelected": "Aucune ligne sélectionnée",
|
||||
"db.rowInserted": "Ligne insérée",
|
||||
"db.rowsDeleted": "Lignes supprimées",
|
||||
"db.runningVacuum": "Exécution de VACUUM...",
|
||||
"db.saveFailed": "Échec de l'enregistrement",
|
||||
"db.selectTableFromSidebar": "Sélectionnez une table dans la barre latérale",
|
||||
"db.tableDropped": "Table supprimée",
|
||||
"db.tableTruncated": "Table vidée",
|
||||
"db.truncateFailed": "Échec du vidage",
|
||||
"db.truncating": "Vidage...",
|
||||
"db.vacuumComplete": "VACUUM terminé",
|
||||
"db.vacuumDone": "VACUUM effectué",
|
||||
"db.vacuumFailed": "Échec de VACUUM",
|
||||
"files.confirmDelete": "Supprimer {{label}} \"{{name}}\" ?",
|
||||
"files.downloadFile": "Télécharger le fichier",
|
||||
"files.duplicateFailed": "Échec de la duplication",
|
||||
"files.duplicated": "Dupliqué",
|
||||
"files.emptyDirectory": "Répertoire vide",
|
||||
"files.errorLoading": "Erreur lors du chargement des fichiers",
|
||||
"files.failedLoadDir": "Échec du chargement du répertoire",
|
||||
"files.filterPlaceholder": "Filtrer les fichiers...",
|
||||
"files.itemsCount": "{{count}} élément(s)",
|
||||
"files.newNamePrompt": "Nouveau nom :",
|
||||
"files.noMatch": "Aucun fichier correspondant",
|
||||
"files.openDirectory": "Ouvrir le répertoire",
|
||||
"files.parent": ".. (parent)",
|
||||
"files.renameFailed": "Échec du renommage",
|
||||
"files.renamed": "Renommé",
|
||||
"files.root": "Racine",
|
||||
"files.uploadComplete": "Téléversement terminé",
|
||||
"files.uploadFailed": "L'envoi a échoué",
|
||||
"files.uploadingCount": "Envoi de {{count}} fichier(s)...",
|
||||
"studio.actionNotFound": "Action non trouvée",
|
||||
"studio.classNameRequired": "Le nom de la classe est requis",
|
||||
"studio.confirmDeleteAction": "Supprimer l'action \"{{name}}\" ? Cette action est irréversible.",
|
||||
"studio.deletedName": "Supprimé : {{name}}",
|
||||
"studio.exportedFile": "Exporté : {{name}}",
|
||||
"studio.filterActions": "Filtrer les actions...",
|
||||
"studio.importFailed": "L'importation a échoué",
|
||||
"studio.importedFile": "Importé : {{name}}",
|
||||
"studio.loadFailed": "Le chargement a échoué",
|
||||
"studio.loadedFromCacheName": "Chargé depuis le cache : {{name}}",
|
||||
"studio.loadedName": "Chargé : {{name}}",
|
||||
"studio.newActionCreated": "Nouvelle action créée",
|
||||
"studio.noActionLoaded": "Aucune action chargée",
|
||||
"studio.saveFailedBackedUp": "Échec de l'enregistrement (sauvegarde locale effectuée)",
|
||||
"studio.savedName": "Enregistré : {{name}}",
|
||||
"studio.setClassBeforeExport": "Définissez une classe avant l'exportation",
|
||||
"zombie.agentRemoved": "Agent {{name}} supprimé",
|
||||
"zombie.agentsPurged": "{{count}} agent(s) purgés",
|
||||
"zombie.allAgents": "Tous les agents",
|
||||
"zombie.c2StartedOnPort": "Serveur C2 démarré sur le port {{port}}",
|
||||
"zombie.c2Stopped": "Serveur C2 arrêté",
|
||||
"zombie.clearConsole": "Effacer la console",
|
||||
"zombie.clearLogs": "Effacer les logs",
|
||||
"zombie.commandBroadcasted": "Commande diffusée",
|
||||
"zombie.commandSentToAgents": "Commande envoyée à {{count}} agent(s)",
|
||||
"zombie.confirmPurgeStale": "Purger tous les agents inactifs depuis plus de 24 heures ?",
|
||||
"zombie.confirmRemoveAgent": "Supprimer l'agent {{name}} ?",
|
||||
"zombie.confirmStopC2": "Arrêter le serveur C2 ?",
|
||||
"zombie.consoleCleared": "Console effacée",
|
||||
"zombie.enterC2Port": "Entrez le port C2 :",
|
||||
"zombie.enterCommand": "Entrez la commande...",
|
||||
"zombie.failedPurgeStale": "Échec de la purge des agents inactifs",
|
||||
"zombie.failedRemoveAgent": "Échec de la suppression de l'agent {{name}}",
|
||||
"zombie.failedSendCommand": "Échec de l'envoi de la commande",
|
||||
"zombie.failedStartC2": "Échec du démarrage du C2",
|
||||
"zombie.failedStopC2": "Échec de l'arrêt du C2",
|
||||
"zombie.noAgentsConnected": "Aucun agent connecté",
|
||||
"zombie.noAgentsMatchSearch": "Aucun agent ne correspond à votre recherche",
|
||||
"zombie.purgeStale": "Purger inactifs",
|
||||
"zombie.purgeStaleHint": "Purger les agents inactifs >24h",
|
||||
"zombie.removeAgent": "Supprimer l'agent",
|
||||
"zombie.startC2": "Démarrer C2",
|
||||
"zombie.stopC2": "Arrêter C2",
|
||||
"zombie.systemLogs": "Logs système",
|
||||
"zombieland.alive": "Vivant",
|
||||
"zombieland.c2Status": "Statut C2",
|
||||
"zombieland.dead": "Mort",
|
||||
"zombieland.totalAgents": "Total agents",
|
||||
"greeting": "Bonjour",
|
||||
"start": "Démarrer",
|
||||
"tick": "Tick",
|
||||
"common.ip": "IP",
|
||||
"common.mac": "MAC",
|
||||
"common.os": "OS",
|
||||
"zombie.never": "Jamais",
|
||||
"zombie.openInConsole": "Ouvrir dans la console",
|
||||
"common.saved": "Enregistré",
|
||||
"attacks.tabs.attacks": "Attaques",
|
||||
"attacks.tabs.comments": "Commentaires",
|
||||
"attacks.tabs.images": "Images",
|
||||
"attacks.btn.addAttack": "Ajouter une attaque",
|
||||
"attacks.btn.removeAttack": "Supprimer l'attaque",
|
||||
"attacks.btn.deleteAction": "Supprimer l'action",
|
||||
"attacks.btn.restoreDefaultsBundle": "Restaurer les valeurs par défaut",
|
||||
"attacks.btn.addSection": "Ajouter une section",
|
||||
"attacks.btn.deleteSection": "Supprimer la section",
|
||||
"attacks.btn.restoreDefault": "Restaurer par défaut",
|
||||
"attacks.btn.createCharacter": "Créer un personnage",
|
||||
"attacks.btn.deleteCharacter": "Supprimer le personnage",
|
||||
"attacks.section.characters": "Personnages",
|
||||
"attacks.section.statusImages": "Images de statut",
|
||||
"attacks.section.staticImages": "Images statiques",
|
||||
"attacks.section.webImages": "Images web",
|
||||
"attacks.section.actionIcons": "Icônes d'action",
|
||||
"attacks.editor.selectAttack": "Sélectionner une attaque",
|
||||
"attacks.empty.noAttacks": "Aucune attaque trouvée.",
|
||||
"attacks.empty.noComments": "Aucun commentaire trouvé.",
|
||||
"attacks.comments.placeholder": "Les commentaires seront affichés ici...",
|
||||
"attacks.images.enterEditMode": "Activer le mode édition",
|
||||
"attacks.images.exitEditMode": "Quitter le mode édition",
|
||||
"attacks.images.sortName": "Tri : nom",
|
||||
"attacks.images.sortDimensions": "Tri : dimensions",
|
||||
"attacks.images.search": "Rechercher des images...",
|
||||
"attacks.images.rename": "Renommer l'image",
|
||||
"attacks.images.replace": "Remplacer l'image",
|
||||
"attacks.images.resizeSelected": "Redimensionner la sélection",
|
||||
"attacks.images.addCharacters": "Ajouter des images de personnage",
|
||||
"attacks.images.deleteSelected": "Supprimer la sélection",
|
||||
"attacks.images.addStatus": "Ajouter image statut",
|
||||
"attacks.images.addStatic": "Ajouter image statique",
|
||||
"attacks.images.addWeb": "Ajouter image web",
|
||||
"attacks.images.addIcon": "Ajouter icône action",
|
||||
"attacks.errors.loadAttacks": "Échec du chargement des attaques.",
|
||||
"attacks.errors.loadImages": "Échec du chargement des images.",
|
||||
"attacks.confirm.switchCharacter": "Basculer vers le personnage \"{{name}}\" ?",
|
||||
"attacks.confirm.removeAttack": "Supprimer l'attaque \"{{name}}\" ?",
|
||||
"attacks.confirm.deleteAction": "Supprimer l'action \"{{name}}\" ?",
|
||||
"attacks.confirm.restoreAttack": "Restaurer \"{{name}}\" par défaut ?",
|
||||
"attacks.confirm.restoreDefaultsBundle": "Restaurer TOUTES les valeurs par défaut (actions, images, commentaires) ?",
|
||||
"attacks.confirm.deleteCharacter": "Supprimer le personnage \"{{name}}\" ?",
|
||||
"attacks.confirm.deleteSection": "Supprimer la section \"{{name}}\" ?",
|
||||
"attacks.confirm.restoreDefaultComments": "Restaurer les commentaires par défaut ?",
|
||||
"attacks.confirm.deleteSelectedImages": "Supprimer les images sélectionnées ?",
|
||||
"attacks.prompt.newCharacterName": "Nom du nouveau personnage :",
|
||||
"attacks.prompt.characterToDelete": "Personnage à supprimer :",
|
||||
"attacks.prompt.newSectionName": "Nom de la nouvelle section :",
|
||||
"attacks.prompt.newImageName": "Nouveau nom :",
|
||||
"attacks.prompt.resizeWidth": "Largeur de redimensionnement :",
|
||||
"attacks.prompt.resizeHeight": "Hauteur de redimensionnement :",
|
||||
"attacks.toast.characterSwitched": "Personnage changé",
|
||||
"attacks.toast.attackImported": "Attaque importée",
|
||||
"attacks.toast.selectAttackFirst": "Sélectionnez d'abord une attaque",
|
||||
"attacks.toast.actionDeleted": "Action supprimée",
|
||||
"attacks.toast.defaultsRestored": "Valeurs par défaut restaurées",
|
||||
"attacks.toast.characterCreated": "Personnage créé",
|
||||
"attacks.toast.noDeletableCharacters": "Aucun personnage supprimable",
|
||||
"attacks.toast.characterDeleted": "Personnage supprimé",
|
||||
"attacks.toast.commentsRestored": "Commentaires restaurés",
|
||||
"attacks.toast.selectSectionFirst": "Sélectionnez d'abord une section",
|
||||
"attacks.toast.commentsSaved": "Commentaires enregistrés",
|
||||
"attacks.toast.selectExactlyOneImage": "Sélectionnez exactement une image",
|
||||
"attacks.toast.selectAtLeastOneImage": "Sélectionnez au moins une image",
|
||||
"attacks.toast.imagesResized": "Images redimensionnées",
|
||||
"attacks.toast.characterImagesUploaded": "Images de personnage envoyées",
|
||||
"attacks.toast.selectStatusActionFirst": "Sélectionnez d'abord une action de statut",
|
||||
"actions.toast.presetApplied": "Préconfiguration appliquée",
|
||||
"actions.toast.startingAction": "Démarrage de {{name}}...",
|
||||
"actions.toast.actionStarted": "Action démarrée",
|
||||
"actions.toast.stoppedByUser": "Arrêté par l'utilisateur",
|
||||
"actions.toast.actionStopped": "Action arrêtée",
|
||||
"actions.toast.stopFailed": "Échec de l'arrêt",
|
||||
"actions.toast.failedToStop": "Impossible d'arrêter",
|
||||
"actions.toast.consoleCleared": "Console vidée",
|
||||
"actions.toast.noLogsToExport": "Aucun log à exporter",
|
||||
"actions.toast.logsExported": "Logs exportés",
|
||||
"netkb.confirmRemoveAction": "Supprimer l'action \"{{action}}\" pour l'IP \"{{ip}}\" ?",
|
||||
"netkb.actionRemoved": "Action supprimée",
|
||||
"actions.running": "En cours",
|
||||
"attacks.btn.syncMissing": "Synchroniser les éléments manquants",
|
||||
"attacks.images.gridDensity": "Densité de grille",
|
||||
"attacks.images.density": "Densité",
|
||||
"attacks.sync.defaultComment": "Ajouter un commentaire pour cette action",
|
||||
"attacks.sync.none": "Aucune attaque à synchroniser.",
|
||||
"attacks.sync.done": "Synchronisation terminée. Nouveaux commentaires : {{comments}}, images de statut : {{status}}, images de personnage : {{characters}}.",
|
||||
"attacks.sync.failed": "Échec de la synchronisation des éléments manquants",
|
||||
"actions.args.free": "Arguments libres",
|
||||
"actions.args.none": "Aucun argument configurable",
|
||||
"actions.args.subtitle": "Généré automatiquement depuis les définitions d'action",
|
||||
"actions.args.title": "Arguments",
|
||||
"actions.assign": "Assigner",
|
||||
"actions.emptyPane": "Aucune action sélectionnée",
|
||||
"actions.logs.completed": "Terminé",
|
||||
"actions.logs.empty": "Aucun log pour le moment",
|
||||
"actions.logs.waiting": "En attente...",
|
||||
"actions.searchPlaceholder": "Rechercher des actions...",
|
||||
"actions.tabs.actions": "Actions",
|
||||
"actions.tabs.arguments": "Arguments",
|
||||
"actions.toast.selectActionFirst": "Sélectionne d'abord une action",
|
||||
"common.move": "Déplacer",
|
||||
"common.ready": "Prêt",
|
||||
"common.menu": "Menu",
|
||||
"common.browse": "Parcourir...",
|
||||
"common.platform": "Plateforme",
|
||||
"common.generate": "Générer",
|
||||
"common.vendor": "Fabricant",
|
||||
"common.hostname": "Nom d'hôte",
|
||||
"common.ports": "Ports",
|
||||
"zombie.generateClient": "Générer le client",
|
||||
"zombie.checkStale": "Vérifier agents inactifs",
|
||||
"zombie.selectedAgents": "agents sélectionnés",
|
||||
"zombie.clientId": "ID Client",
|
||||
"zombie.labCreds": "Identifiants Lab",
|
||||
"zombie.deployOptions": "Options de déploiement",
|
||||
"zombie.deployViaSSH": "Déployer via SSH",
|
||||
"zombie.fileBrowser": "Explorateur de fichiers",
|
||||
"dash.lastUpdate": "Dernière mise à jour",
|
||||
"netkb.searchPlaceholder": "Chercher hôte, IP, fabricant, port...",
|
||||
"netkb.searchHint": "Astuce : tapez 'port:80' ou 'vendor:intel'",
|
||||
"files.dropzoneHint": "Déposez les fichiers ici ou cliquez pour envoyer",
|
||||
"files.moveToTitle": "Déplacer vers...",
|
||||
"files.selectDestinationFolder": "Sélectionner le dossier de destination",
|
||||
"attacks.sidebar.management": "Gestion",
|
||||
"sched.upcoming": "À venir",
|
||||
"sched.success": "Succès",
|
||||
"sched.cancelled": "Annulé",
|
||||
"sched.history": "Historique",
|
||||
"sched.historyMsg": "Logs d'historique",
|
||||
"creds.searchPlaceholder": "Chercher services, utilisateurs...",
|
||||
"creds.uniqueHosts": "Hôtes uniques",
|
||||
"creds.totalCredentials": "Total identifiants",
|
||||
"console.maxReconnect": "Console : nombre maximal de tentatives de reconnexion atteint",
|
||||
"console.scrollToBottom": "Défiler vers le bas",
|
||||
"console.manual": "Manuel",
|
||||
"console.auto": "Auto",
|
||||
"console.turnOnAuto": "Activer le mode Auto",
|
||||
"console.turnOnManual": "Activer le mode Manuel",
|
||||
"console.noTarget": "Aucune cible",
|
||||
"console.noAction": "Aucune action",
|
||||
"console.scanStarted": "Scan manuel démarré",
|
||||
"console.scanFailed": "Échec du scan manuel",
|
||||
"console.attackStarted": "Attaque manuelle démarrée",
|
||||
"console.attackFailed": "Échec de l'attaque manuelle",
|
||||
"console.failedToggleMode": "Échec du changement de mode",
|
||||
"console.reconnectAttempt": "Reconnexion (tentative {{count}})...",
|
||||
"quick.close": "Fermer le panneau",
|
||||
"quick.connectingTo": "Connexion à {{ssid}}...",
|
||||
"quick.connectedTo": "Connecté à {{ssid}}",
|
||||
"quick.connectionFailed": "Échec de la connexion",
|
||||
"quick.loadKnownFailed": "Échec du chargement des réseaux connus",
|
||||
"quick.priorityUpdated": "Priorité mise à jour",
|
||||
"quick.priorityUpdateFailed": "Échec de la mise à jour de la priorité",
|
||||
"quick.networkRemoved": "Réseau supprimé",
|
||||
"quick.importingPotfiles": "Importation des potfiles...",
|
||||
"quick.importedCount": "{{count}} identifiants importés",
|
||||
"quick.btScanFailed": "Échec du scan Bluetooth",
|
||||
"quick.btActioning": "{{action}} de {{name}}...",
|
||||
"quick.btActionDone": "{{name}} {{action}}é",
|
||||
"quick.btActionFailed": "Échec de {{action}}",
|
||||
"quick.btForgotten": "{{name}} oublié",
|
||||
"sidebar.close": "Fermer la barre latérale",
|
||||
"api.aborted": "Abandonné",
|
||||
"api.timeout": "La requête a expiré",
|
||||
"api.failed": "La requête a échoué",
|
||||
"router.notFound": "Page non trouvée : {{path}}",
|
||||
"router.errorLoading": "Erreur lors du chargement de la page : {{message}}"
|
||||
}
|
||||
781
web/i18n/it.json
Normal file
@@ -0,0 +1,781 @@
|
||||
{
|
||||
"nav.dashboard": "Cruscotto",
|
||||
"nav.bjorn": "Bjorn",
|
||||
"nav.netkb": "Base Rete",
|
||||
"nav.network": "Rete",
|
||||
"nav.credentials": "Credenziali",
|
||||
"nav.vulnerabilities": "Vulnerabilità",
|
||||
"nav.attacks": "Attacco",
|
||||
"nav.scheduler": "Pianificatore",
|
||||
"nav.database": "Database",
|
||||
"nav.files": "File",
|
||||
"nav.loot": "Bottino",
|
||||
"nav.actions": "Azioni",
|
||||
"nav.actionsStudio": "Studio Azioni",
|
||||
"nav.backup": "Backup & Agg.",
|
||||
"nav.webEnum": "Enum Web",
|
||||
"nav.zombieland": "Zombieland",
|
||||
"nav.settings": "Impostazioni",
|
||||
"nav.shortcuts": "Scorciatoie",
|
||||
"nav.pages": "Pagine",
|
||||
"status.initializing": "Inizializzazione...",
|
||||
"status.online": "In linea",
|
||||
"status.offline": "Non in linea",
|
||||
"console.title": "Console",
|
||||
"console.clear": "Cancella",
|
||||
"console.sseOn": "SSE Attivo",
|
||||
"console.sseOff": "SSE Inattivo",
|
||||
"console.newLogs": "{{count}} nuovi log",
|
||||
"settings.theme": "Tema",
|
||||
"settings.language": "Lingua",
|
||||
"settings.general": "Generale",
|
||||
"settings.toggles": "Opzioni",
|
||||
"settings.editValue": "Modifica valore",
|
||||
"settings.addValues": "Aggiungi valori (separati da virgola)...",
|
||||
"settings.setValue": "Imposta valore...",
|
||||
"settings.errorLoading": "Errore nel caricamento della configurazione",
|
||||
"settings.configSaved": "Configurazione salvata",
|
||||
"settings.errorSaving": "Errore nel salvataggio della configurazione",
|
||||
"settings.defaultsRestored": "Valori predefiniti ripristinati",
|
||||
"settings.errorRestoring": "Errore nel ripristino dei valori predefiniti",
|
||||
"theme.group.colors": "Colori",
|
||||
"theme.group.surfaces": "Superfici",
|
||||
"theme.group.layout": "Disposizione",
|
||||
"theme.token.bg": "Sfondo",
|
||||
"theme.token.ink": "Colore testo",
|
||||
"theme.token.accent1": "Accento 1 (Acido)",
|
||||
"theme.token.accent2": "Accento 2 (Ciano)",
|
||||
"theme.token.danger": "Pericolo",
|
||||
"theme.token.warning": "Avviso",
|
||||
"theme.token.ok": "Successo",
|
||||
"theme.token.panel": "Pannello",
|
||||
"theme.token.panel2": "Pannello Alt",
|
||||
"theme.token.ctrlPanel": "Pannello controllo",
|
||||
"theme.token.border": "Bordo",
|
||||
"theme.token.radius": "Raggio bordo",
|
||||
"theme.advanced": "CSS avanzato",
|
||||
"theme.applyRaw": "Applica",
|
||||
"theme.reset": "Ripristina",
|
||||
"dash.title": "Cruscotto",
|
||||
"dash.battery": "Batteria",
|
||||
"dash.internet": "Internet",
|
||||
"dash.cpu": "CPU",
|
||||
"dash.ram": "RAM",
|
||||
"dash.disk": "Disco",
|
||||
"dash.temp": "Temp",
|
||||
"dash.uptime": "Uptime",
|
||||
"dash.hostsAlive": "Host attivi",
|
||||
"dash.totalHosts": "Totale host",
|
||||
"dash.openPorts": "Porte aperte",
|
||||
"dash.credentials": "Credenziali",
|
||||
"dash.vulnerabilities": "Vulnerabilità",
|
||||
"dash.actions": "Azioni",
|
||||
"dash.connected": "Connesso",
|
||||
"dash.disconnected": "Disconnesso",
|
||||
"dash.charging": "In carica",
|
||||
"dash.discharging": "In scarica",
|
||||
"dash.full": "Carica",
|
||||
"dash.connectivity": "Connettività",
|
||||
"dash.liveOps": "Operazioni live",
|
||||
"dash.tapRefresh": "Tocca per aggiornare",
|
||||
"dash.wifi": "Wi-Fi",
|
||||
"dash.ethernet": "Ethernet",
|
||||
"dash.usb": "USB",
|
||||
"dash.bluetooth": "Bluetooth",
|
||||
"dash.mode": "Modalità",
|
||||
"dash.gps": "GPS",
|
||||
"dash.age": "Età di Bjorn",
|
||||
"dash.plugged": "Collegato",
|
||||
"dash.noBattery": "Nessuna batteria",
|
||||
"dash.sinceScan": "dall'ultimo scan",
|
||||
"dash.wifiKnown": "Wi-Fi noti",
|
||||
"dash.dataFiles": "Dati / File raccolti",
|
||||
"dash.fileDescriptors": "Descrittori file",
|
||||
"dash.attackScripts": "Script d'attacco",
|
||||
"dash.system": "Sistema",
|
||||
"dash.zombies": "Zombie",
|
||||
"netkb.title": "Conoscenza di rete",
|
||||
"netkb.showOffline": "Mostra offline",
|
||||
"netkb.gridView": "Griglia",
|
||||
"netkb.listView": "Lista",
|
||||
"netkb.hostname": "Hostname",
|
||||
"netkb.ip": "Indirizzo IP",
|
||||
"netkb.mac": "Indirizzo MAC",
|
||||
"netkb.vendor": "Produttore",
|
||||
"netkb.ports": "Porte",
|
||||
"netkb.essid": "ESSID",
|
||||
"netkb.lastSeen": "Ultima vista",
|
||||
"netkb.firstSeen": "Prima vista",
|
||||
"netkb.online": "In linea",
|
||||
"netkb.offline": "Non in linea",
|
||||
"netkb.openPorts": "Porte aperte",
|
||||
"netkb.noHosts": "Nessun host trovato",
|
||||
"network.title": "Visualizzazione rete",
|
||||
"network.tableView": "Tabella",
|
||||
"network.mapView": "Mappa",
|
||||
"network.hostname": "Hostname",
|
||||
"network.ip": "Indirizzo IP",
|
||||
"network.mac": "MAC",
|
||||
"network.ports": "Porte",
|
||||
"network.status": "Stato",
|
||||
"network.searchPlaceholder": "Cerca host...",
|
||||
"network.noData": "Nessun dato di rete",
|
||||
"creds.title": "Credenziali",
|
||||
"creds.total": "Totale",
|
||||
"creds.unique": "Uniche",
|
||||
"creds.types": "Tipi",
|
||||
"creds.username": "Nome utente",
|
||||
"creds.password": "Password",
|
||||
"creds.service": "Servizio",
|
||||
"creds.host": "Host",
|
||||
"creds.port": "Porta",
|
||||
"creds.type": "Tipo",
|
||||
"creds.timestamp": "Timestamp",
|
||||
"creds.showPassword": "Mostra password",
|
||||
"creds.hidePassword": "Nascondi password",
|
||||
"creds.copyPassword": "Copia",
|
||||
"creds.exportAll": "Esporta tutto",
|
||||
"creds.noCredentials": "Nessuna credenziale trovata",
|
||||
"vulns.title": "Tabellone vulnerabilità",
|
||||
"vulns.total": "Totale",
|
||||
"vulns.critical": "Critica",
|
||||
"vulns.high": "Alta",
|
||||
"vulns.medium": "Media",
|
||||
"vulns.low": "Bassa",
|
||||
"vulns.infoLevel": "Info",
|
||||
"vulns.host": "Host",
|
||||
"vulns.port": "Porta",
|
||||
"vulns.service": "Servizio",
|
||||
"vulns.severity": "Gravità",
|
||||
"vulns.description": "Descrizione",
|
||||
"vulns.cve": "CVE",
|
||||
"vulns.scanDate": "Data scan",
|
||||
"vulns.details": "Dettagli",
|
||||
"vulns.noVulns": "Nessuna vulnerabilità trovata",
|
||||
"vulns.byHost": "Per host",
|
||||
"vulns.bySeverity": "Per gravità",
|
||||
"vulns.byService": "Per servizio",
|
||||
"attacks.title": "Gestore attacchi",
|
||||
"attacks.running": "In corso",
|
||||
"attacks.completed": "Completato",
|
||||
"attacks.failed": "Fallito",
|
||||
"attacks.queued": "In coda",
|
||||
"attacks.start": "Avvia",
|
||||
"attacks.stop": "Ferma",
|
||||
"attacks.restart": "Riavvia",
|
||||
"attacks.status": "Stato",
|
||||
"attacks.target": "Target",
|
||||
"attacks.action": "Azione",
|
||||
"attacks.duration": "Durata",
|
||||
"attacks.progress": "Progresso",
|
||||
"attacks.noAttacks": "Nessun attacco in corso",
|
||||
"sched.title": "Pianificatore azioni",
|
||||
"sched.pending": "In attesa",
|
||||
"sched.running": "In corso",
|
||||
"sched.done": "Fatto",
|
||||
"sched.failed": "Fallito",
|
||||
"sched.all": "Tutti",
|
||||
"sched.searchPlaceholder": "Cerca task...",
|
||||
"sched.noTasks": "Nessun task trovato",
|
||||
"sched.stats": "{{running}} in corso / {{pending}} in attesa / {{done}} fatti",
|
||||
"db.title": "Gestore database",
|
||||
"db.tables": "Tabelle",
|
||||
"db.rows": "Righe",
|
||||
"db.columns": "Colonne",
|
||||
"db.search": "Cerca tabelle...",
|
||||
"db.searchRows": "Cerca righe...",
|
||||
"db.export": "Esporta",
|
||||
"db.import": "Importa",
|
||||
"db.addRow": "Aggiungi riga",
|
||||
"db.deleteRow": "Elimina riga",
|
||||
"db.deleteSelected": "Elimina selezione",
|
||||
"db.saveChanges": "Salva",
|
||||
"db.discardChanges": "Annulla",
|
||||
"db.confirmDelete": "Confermi eliminazione ?",
|
||||
"db.noTables": "Nessuna tabella trovata",
|
||||
"db.noData": "Nessun dato in questa tabella",
|
||||
"db.hide": "Nascondi",
|
||||
"db.showSidebar": "Mostra pannello",
|
||||
"files.title": "Esplora file",
|
||||
"files.gridView": "Griglia",
|
||||
"files.listView": "Lista",
|
||||
"files.size": "Dimensione",
|
||||
"files.modified": "Modificato",
|
||||
"files.name": "Nome",
|
||||
"files.type": "Tipo",
|
||||
"files.download": "Scarica",
|
||||
"files.preview": "Anteprima",
|
||||
"files.noFiles": "Nessun file trovato",
|
||||
"files.parentDir": "Directory superiore",
|
||||
"files.searchPlaceholder": "Cerca file...",
|
||||
"loot.title": "Bottino",
|
||||
"loot.directories": "Directory",
|
||||
"loot.totalFiles": "Totale file",
|
||||
"loot.totalSize": "Dimensione totale",
|
||||
"loot.download": "Scarica",
|
||||
"loot.downloadAll": "Scarica tutto",
|
||||
"loot.noLoot": "Nessun bottino trovato",
|
||||
"loot.explore": "Esplora",
|
||||
"actions.title": "Gestore azioni",
|
||||
"actions.available": "Disponibili",
|
||||
"actions.enabled": "Abilitate",
|
||||
"actions.disabled": "Disabilitate",
|
||||
"actions.category": "Categoria",
|
||||
"actions.enableAll": "Abilita tutte",
|
||||
"actions.disableAll": "Disabilita tutte",
|
||||
"actions.import": "Importa",
|
||||
"actions.export": "Esporta",
|
||||
"actions.noActions": "Nessuna azione trovata",
|
||||
"actions.description": "Descrizione",
|
||||
"actions.menu.restartService": "Riavvia servizio Bjorn",
|
||||
"actions.menu.deleteActionStatus": "Elimina tutti gli stati azione",
|
||||
"actions.menu.clearOutput": "Svuota cartella Output",
|
||||
"actions.menu.clearLogs": "Cancella i log",
|
||||
"actions.menu.reloadImages": "Ricarica immagini (sperimentale)",
|
||||
"actions.menu.reloadFonts": "Ricarica i font",
|
||||
"actions.menu.reloadActionsJson": "Ricarica JSON azioni",
|
||||
"actions.menu.initializeCsv": "Inizializza file CSV",
|
||||
"actions.menu.clearLivestatus": "Elimina file Livestatus",
|
||||
"actions.menu.refreshActionsFile": "Aggiorna file Azioni",
|
||||
"actions.menu.clearNetkb": "Svuota conoscenza rete",
|
||||
"actions.menu.clearSharedConfig": "Elimina JSON configurazione condivisa",
|
||||
"actions.menu.eraseMemories": "Cancella memoria di Bjorn",
|
||||
"actions.menu.reboot": "Riavvia sistema",
|
||||
"actions.menu.shutdown": "Spegni sistema",
|
||||
"actions.tip.restartService": "Riavvia il servizio Bjorn per rinfrescare lo stato.",
|
||||
"actions.tip.deleteActionStatus": "Elimina tutti gli stati di successo/fallimento delle azioni in netkb.csv.",
|
||||
"actions.tip.clearOutput": "Cancella tutti i file nelle cartelle output e sottocartelle.",
|
||||
"actions.tip.clearLogs": "Elimina tutti i file di log del sistema.",
|
||||
"actions.tip.reloadImages": "Ricarica le immagini usate dal sistema.",
|
||||
"actions.tip.reloadFonts": "Ricarica i font dell'applicazione.",
|
||||
"actions.tip.reloadActionsJson": "Ricarica il file JSON delle azioni generate.",
|
||||
"actions.tip.initializeCsv": "Ricrea i file CSV e JSON.",
|
||||
"actions.tip.clearLivestatus": "Elimina il file di stato in tempo reale.",
|
||||
"actions.tip.refreshActionsFile": "Aggiorna il file delle azioni per includere nuove azioni.",
|
||||
"actions.tip.clearNetkb": "Elimina tutte le informazioni salvate nella conoscenza di rete.",
|
||||
"actions.tip.clearSharedConfig": "Elimina il file JSON di configurazione condivisa.",
|
||||
"actions.tip.eraseMemories": "Cancella completamente la memoria e le impostazioni di Bjorn.",
|
||||
"actions.tip.reboot": "Riavvia l'intero sistema.",
|
||||
"actions.tip.shutdown": "Spegne completamente il sistema.",
|
||||
"actions.confirm.restartRecommended": "Riavvio del servizio consigliato. Riavviare ora ?",
|
||||
"actions.confirm.restartService": "Riavviare il servizio Bjorn ?",
|
||||
"actions.confirm.deleteActionStatus": "Eliminare tutti gli stati azione salvati ?",
|
||||
"actions.confirm.clearOutput": "Svuotare interamente la cartella output ?",
|
||||
"actions.confirm.clearLogs": "Eliminare tutti i file di log ?",
|
||||
"actions.confirm.clearNetkb": "Svuotare la conoscenza di rete? Azione irreversibile.",
|
||||
"actions.confirm.clearLivestatus": "Eliminare il file livestatus ?",
|
||||
"actions.confirm.refreshActionsFile": "Aggiornare il file delle azioni ?",
|
||||
"actions.confirm.clearSharedConfig": "Eliminare il JSON di configurazione condivisa? Azione irreversibile.",
|
||||
"actions.confirm.eraseMemories": "Cancellare memoria e impostazioni di Bjorn? Azione irreversibile.",
|
||||
"actions.confirm.reboot": "Riavviare l'intero sistema ?",
|
||||
"actions.confirm.shutdown": "Spegnere il sistema ?",
|
||||
"actions.msg.restartingService": "Il servizio Bjorn si sta riavviando...",
|
||||
"actions.msg.restartFailed": "Riavvio del servizio fallito",
|
||||
"actions.msg.actionStatusDeleted": "Tutti gli stati azione eliminati.",
|
||||
"actions.msg.outputCleared": "La cartella output è stata svuotata.",
|
||||
"actions.msg.logsCleared": "I log sono stati cancellati.",
|
||||
"actions.msg.netkbCleared": "Conoscenza rete svuotata.",
|
||||
"actions.msg.livestatusDeleted": "File livestatus eliminato.",
|
||||
"actions.msg.actionsFileRefreshed": "File azioni aggiornato.",
|
||||
"actions.msg.sharedConfigDeleted": "Il JSON di configurazione condivisa è stato eliminato.",
|
||||
"actions.msg.memoriesErased": "Memoria di Bjorn cancellata.",
|
||||
"actions.msg.rebooting": "Il sistema si sta riavviando...",
|
||||
"actions.msg.shuttingDown": "Il sistema si sta spegnendo...",
|
||||
"actions.msg.csvInitialized": "I file CSV sono stati inizializzati.",
|
||||
"actions.msg.actionsJsonReloaded": "Il JSON delle azioni è stato ricaricato.",
|
||||
"actions.msg.imagesReloaded": "Le immagini sono state ricaricate.",
|
||||
"actions.msg.fontsReloaded": "I font sono stati ricaricati.",
|
||||
"actions.msg.unknownAction": "Azione sconosciuta",
|
||||
"actions.msg.actionFailed": "Azione fallita",
|
||||
"studio.title": "Studio Azioni",
|
||||
"studio.palette": "Palette",
|
||||
"studio.canvas": "Tela",
|
||||
"studio.inspector": "Ispettore",
|
||||
"studio.actionsTab": "Azioni",
|
||||
"studio.hostsTab": "Host",
|
||||
"studio.globalTab": "Global",
|
||||
"studio.save": "Salva",
|
||||
"studio.load": "Carica",
|
||||
"studio.run": "Esegui",
|
||||
"studio.clear": "Pulisci",
|
||||
"studio.addNode": "Aggiungi nodo",
|
||||
"studio.removeNode": "Rimuovi nodo",
|
||||
"studio.search": "Cerca azioni...",
|
||||
"backup.title": "Backup & Agg.",
|
||||
"backup.backupRestore": "Backup / Ripristino",
|
||||
"backup.update": "Aggiornamento",
|
||||
"backup.createBackup": "Crea backup",
|
||||
"backup.restoreBackup": "Ripristina",
|
||||
"backup.downloadBackup": "Scarica",
|
||||
"backup.deleteBackup": "Elimina backup",
|
||||
"backup.lastBackup": "Ultimo backup",
|
||||
"backup.checkUpdates": "Controlla aggiornamenti",
|
||||
"backup.installUpdate": "Installa aggiornamento",
|
||||
"backup.currentVersion": "Versione attuale",
|
||||
"backup.latestVersion": "Ultima versione",
|
||||
"backup.upToDate": "Aggiornato",
|
||||
"backup.updateAvailable": "Aggiornamento disponibile",
|
||||
"backup.clearLogs": "Cancella i log",
|
||||
"backup.noBackups": "Nessun backup trovato",
|
||||
"backup.restoring": "Ripristino in corso...",
|
||||
"backup.creating": "Creazione backup...",
|
||||
"webenum.title": "Enumerazione Web",
|
||||
"webenum.totalResults": "Totale risultati",
|
||||
"webenum.uniqueHosts": "Host unici",
|
||||
"webenum.successCount": "Successi (2xx)",
|
||||
"webenum.errorCount": "Errori (4xx/5xx)",
|
||||
"webenum.host": "Host",
|
||||
"webenum.ip": "IP",
|
||||
"webenum.port": "Porta",
|
||||
"webenum.directory": "Directory",
|
||||
"webenum.status": "Stato",
|
||||
"webenum.size": "Dimensione",
|
||||
"webenum.scanDate": "Data scan",
|
||||
"webenum.link": "Link",
|
||||
"webenum.exportJson": "Esporta JSON",
|
||||
"webenum.exportCsv": "Esporta CSV",
|
||||
"webenum.noResults": "Nessun risultato trovato",
|
||||
"webenum.details": "Dettagli risultato",
|
||||
"webenum.openUrl": "Apri URL",
|
||||
"webenum.copyUrl": "Copia URL",
|
||||
"webenum.showing": "Mostra {{start}}-{{end}} di {{total}} risultati",
|
||||
"webenum.itemsPerPage": "Elementi per pagina",
|
||||
"webenum.refreshData": "Aggiorna dati",
|
||||
"webenum.responseTime": "Tempo risposta",
|
||||
"webenum.contentType": "Tipo contenuto",
|
||||
"webenum.fullUrl": "URL completa",
|
||||
"zombie.title": "Zombieland C2C",
|
||||
"zombie.agents": "Agenti",
|
||||
"zombie.terminal": "Terminal",
|
||||
"zombie.commands": "Comandi",
|
||||
"zombie.totalAgents": "Totale agenti",
|
||||
"zombie.onlineAgents": "In linea",
|
||||
"zombie.offlineAgents": "Non in linea",
|
||||
"zombie.idleAgents": "Inattivi",
|
||||
"zombie.sendCommand": "Invia comando",
|
||||
"zombie.broadcast": "Broadcast",
|
||||
"zombie.selectAgent": "Seleziona agente",
|
||||
"zombie.os": "OS",
|
||||
"zombie.lastSeen": "Ultima vista",
|
||||
"zombie.status": "Stato",
|
||||
"zombie.noAgents": "Nessun agente connesso",
|
||||
"zombie.quickCommands": "Comandi rapidi",
|
||||
"zombie.files": "File",
|
||||
"quick.autoScan": "Auto-scan",
|
||||
"quick.connectWifi": "Connettiti al WiFi",
|
||||
"quick.knownNetworks": "Reti note",
|
||||
"quick.importPotfiles": "Importa Potfile",
|
||||
"quick.subtitle": "WiFi & Bluetooth",
|
||||
"quick.pair": "Accoppia",
|
||||
"quick.trust": "Autorizza",
|
||||
"quick.forgetDevice": "Dimentica dispositivo",
|
||||
"quick.forgetDevicePrompt": "Dimenticare {{name}} ?",
|
||||
"quick.forgetNetworkPrompt": "Sei sicuro di voler dimenticare questa rete ?",
|
||||
"bjorn.title": "Schermo EPD Bjorn",
|
||||
"bjorn.epdScreen": "Schermo e-Paper",
|
||||
"bjorn.refreshInterval": "Intervallo refresh",
|
||||
"bjorn.autoRefresh": "Refresh auto",
|
||||
"bjorn.manualRefresh": "Refresh ora",
|
||||
"bjorn.seconds": "secondi",
|
||||
"common.search": "Cerca",
|
||||
"common.filter": "Filtra",
|
||||
"common.refresh": "Aggiorna",
|
||||
"common.save": "Salva",
|
||||
"common.cancel": "Annulla",
|
||||
"common.delete": "Elimina",
|
||||
"common.edit": "Modifica",
|
||||
"common.close": "Chiudi",
|
||||
"common.loading": "Caricamento...",
|
||||
"common.noData": "Nessun dato disponibile",
|
||||
"common.error": "Errore",
|
||||
"common.success": "Successo",
|
||||
"common.confirm": "Conferma",
|
||||
"common.yes": "Sì",
|
||||
"common.no": "No",
|
||||
"common.export": "Esporta",
|
||||
"common.import": "Importa",
|
||||
"common.download": "Scarica",
|
||||
"common.upload": "Carica",
|
||||
"common.copy": "Copia",
|
||||
"common.start": "Avvia",
|
||||
"common.stop": "Ferma",
|
||||
"common.restart": "Riavvia",
|
||||
"common.status": "Stato",
|
||||
"common.name": "Nome",
|
||||
"common.value": "Valore",
|
||||
"common.type": "Tipo",
|
||||
"common.host": "Host",
|
||||
"common.port": "Porta",
|
||||
"common.target": "Target",
|
||||
"common.date": "Data",
|
||||
"common.time": "Ora",
|
||||
"common.size": "Dimensione",
|
||||
"common.actions": "Azioni",
|
||||
"common.details": "Dettagli",
|
||||
"common.back": "Indietro",
|
||||
"common.next": "Avanti",
|
||||
"common.previous": "Precedente",
|
||||
"common.first": "Primo",
|
||||
"common.last": "Ultimo",
|
||||
"common.all": "Tutto",
|
||||
"common.none": "Nessuno",
|
||||
"common.showing": "Visualizzazione",
|
||||
"common.of": "di",
|
||||
"common.results": "risultati",
|
||||
"common.items": "elementi",
|
||||
"common.page": "Pagina",
|
||||
"common.perPage": "per pagina",
|
||||
"common.sortBy": "Ordina per",
|
||||
"common.ascending": "Crescente",
|
||||
"common.descending": "Decrescente",
|
||||
"common.view": "Vista",
|
||||
"common.table": "Tabella",
|
||||
"common.grid": "Griglia",
|
||||
"common.list": "Lista",
|
||||
"common.map": "Mappa",
|
||||
"common.enabled": "Abilitato",
|
||||
"common.disabled": "Disabilitato",
|
||||
"common.on": "On",
|
||||
"common.off": "Off",
|
||||
"common.version": "Versione",
|
||||
"common.hide": "Nascondi",
|
||||
"common.show": "Mostra",
|
||||
"common.add": "Aggiungi",
|
||||
"common.remove": "Rimuovi",
|
||||
"common.clear": "Pulisci",
|
||||
"common.reset": "Ripristina",
|
||||
"common.apply": "Applica",
|
||||
"common.run": "Esegui",
|
||||
"common.send": "Invia",
|
||||
"common.connect": "Connetti",
|
||||
"common.disconnect": "Disconnetti",
|
||||
"common.selectAll": "Seleziona tutto",
|
||||
"common.deselectAll": "Deseleziona tutto",
|
||||
"common.copied": "Copiato !",
|
||||
"common.notFound": "Non trovato",
|
||||
"backup.checkUpdatesHint": "Clicca \"Controlla aggiornamenti\" per visualizzare le versioni.",
|
||||
"backup.checkingUpdates": "Controllo aggiornamenti...",
|
||||
"backup.confirmFreshStart": "Confermi Fresh Start ? ",
|
||||
"backup.createdSuccessfully": "Backup creato con successo.",
|
||||
"backup.defaultUpdated": "Backup predefinito aggiornato.",
|
||||
"backup.deleted": "Backup eliminato.",
|
||||
"backup.descriptionPlaceholder": "Descrizione backup...",
|
||||
"backup.enterDescription": "Inserisci una descrizione per il backup.",
|
||||
"backup.failedCheckUpdates": "Impossibile controllare gli aggiornamenti",
|
||||
"backup.failedCreate": "Creazione backup fallita",
|
||||
"backup.failedDelete": "Eliminazione backup fallita",
|
||||
"backup.failedLoadBackups": "Impossibile caricare i backup",
|
||||
"backup.failedSetDefault": "Impossibile impostare come predefinito",
|
||||
"backup.freshStart": "Fresh Start",
|
||||
"backup.freshStartFailed": "Fresh Start fallito",
|
||||
"backup.freshStartInitiated": "Fresh Start avviato.",
|
||||
"backup.github": "github",
|
||||
"backup.keepActions": "Mantieni cartella actions",
|
||||
"backup.keepConfig": "Mantieni cartella config",
|
||||
"backup.keepData": "Mantieni cartella data",
|
||||
"backup.keepResources": "Mantieni cartella resources",
|
||||
"backup.noBackupsCreateAbove": "Nessun backup trovato. Creane uno sopra.",
|
||||
"backup.restoreCompleted": "Ripristino completato.",
|
||||
"backup.restoreOptions": "Opzioni ripristino",
|
||||
"backup.restorePoint": "punto-di-ripristino",
|
||||
"backup.selectKeepFolders": "Seleziona le cartelle da mantenere durante l'operazione :",
|
||||
"backup.setDefault": "Imposta come predefinito",
|
||||
"backup.unnamedBackup": "Backup senza nome",
|
||||
"backup.updateInitiated": "Aggiornamento avviato.",
|
||||
"backup.updateOptions": "Opzioni aggiornamento",
|
||||
"common.confirmDiscardUnsaved": "Ignorare le modifiche non salvate ?",
|
||||
"common.confirmQuestion": "Confermi ?",
|
||||
"common.default": "predefinito",
|
||||
"common.deleteFailed": "Eliminazione fallita",
|
||||
"common.deleted": "Eliminato",
|
||||
"common.description": "Descrizione",
|
||||
"common.directory": "directory",
|
||||
"common.duplicate": "Duplica",
|
||||
"common.exportJson": "Esporta JSON",
|
||||
"common.failed": "fallito",
|
||||
"common.file": "file",
|
||||
"common.importJson": "Importa JSON",
|
||||
"common.new": "Nuovo",
|
||||
"common.noMatches": "Nessun risultato",
|
||||
"common.options": "Opzioni",
|
||||
"common.processingPleaseWait": "Elaborazione in corso, attendere...",
|
||||
"common.refreshed": "Aggiornato",
|
||||
"common.rename": "Rinomina",
|
||||
"common.saving": "Salvataggio...",
|
||||
"common.unknown": "sconosciuto",
|
||||
"common.unsavedChanges": "Modifiche non salvate",
|
||||
"db.autoRefresh": "Refresh auto",
|
||||
"db.changesDiscarded": "Modifiche annullate",
|
||||
"db.changesSaved": "Modifiche salvate",
|
||||
"db.confirmDrop": "ELIMINARE la tabella \"{{table}}\" ? Azione irreversibile !",
|
||||
"db.confirmTruncate": "Svuotare tutte le righe di \"{{table}}\" ?",
|
||||
"db.dangerZone": "Zona di pericolo",
|
||||
"db.deletingRowsCount": "Eliminazione di {{count}} riga/righe...",
|
||||
"db.dropFailed": "Eliminazione tabella fallita",
|
||||
"db.droppedTable": "Tabella {{table}} eliminata",
|
||||
"db.dropping": "Eliminazione...",
|
||||
"db.emptyTable": "Tabella vuota",
|
||||
"db.errorLoadingData": "Errore nel caricamento dei dati",
|
||||
"db.failedLoadCatalog": "Caricamento catalogo fallito",
|
||||
"db.failedLoadTable": "Caricamento tabella fallito",
|
||||
"db.filterTables": "Filtra tabelle...",
|
||||
"db.insertFailed": "Inserimento fallito",
|
||||
"db.insertingRow": "Inserimento riga...",
|
||||
"db.noRowsSelected": "Nessuna riga selezionata",
|
||||
"db.rowInserted": "Riga inserita",
|
||||
"db.rowsDeleted": "Righe eliminate",
|
||||
"db.runningVacuum": "Esecuzione VACUUM...",
|
||||
"db.saveFailed": "Salvataggio fallito",
|
||||
"db.selectTableFromSidebar": "Seleziona una tabella dalla barra laterale",
|
||||
"db.tableDropped": "Tabella eliminata",
|
||||
"db.tableTruncated": "Tabella svuotata",
|
||||
"db.truncateFailed": "Svuotamento fallito",
|
||||
"db.truncating": "Svuotamento...",
|
||||
"db.vacuumComplete": "VACUUM completato",
|
||||
"db.vacuumDone": "VACUUM eseguito",
|
||||
"db.vacuumFailed": "VACUUM fallito",
|
||||
"files.confirmDelete": "Eliminare {{label}} \"{{name}}\" ?",
|
||||
"files.downloadFile": "Scarica il file",
|
||||
"files.duplicateFailed": "Duplicazione fallita",
|
||||
"files.duplicated": "Duplicato",
|
||||
"files.emptyDirectory": "Directory vuota",
|
||||
"files.errorLoading": "Errore nel caricamento file",
|
||||
"files.failedLoadDir": "Caricamento directory fallito",
|
||||
"files.filterPlaceholder": "Filtra file...",
|
||||
"files.itemsCount": "{{count}} elementi",
|
||||
"files.newNamePrompt": "Nuovo nome :",
|
||||
"files.noMatch": "Nessun file corrispondente",
|
||||
"files.openDirectory": "Apri directory",
|
||||
"files.parent": ".. (superiore)",
|
||||
"files.renameFailed": "Rinomina fallita",
|
||||
"files.renamed": "Rinominato",
|
||||
"files.root": "Root",
|
||||
"files.uploadComplete": "Caricamento completato",
|
||||
"files.uploadFailed": "Caricamento fallito",
|
||||
"files.uploadingCount": "Caricamento di {{count}} file...",
|
||||
"studio.actionNotFound": "Azione non trovata",
|
||||
"studio.classNameRequired": "Nome classe richiesto",
|
||||
"studio.confirmDeleteAction": "Eliminare l'azione \"{{name}}\" ? Azione irreversibile.",
|
||||
"studio.deletedName": "Eliminato : {{name}}",
|
||||
"studio.exportedFile": "Esportato : {{name}}",
|
||||
"studio.filterActions": "Filtra azioni...",
|
||||
"studio.importFailed": "Importazione fallita",
|
||||
"studio.importedFile": "Importato : {{name}}",
|
||||
"studio.loadFailed": "Caricamento fallito",
|
||||
"studio.loadedFromCacheName": "Caricato dalla cache : {{name}}",
|
||||
"studio.loadedName": "Caricato : {{name}}",
|
||||
"studio.newActionCreated": "Nuova azione creata",
|
||||
"studio.noActionLoaded": "Nessuna azione caricata",
|
||||
"studio.saveFailedBackedUp": "Salvataggio fallito (backup locale eseguito)",
|
||||
"studio.savedName": "Salvato : {{name}}",
|
||||
"studio.setClassBeforeExport": "Definisci una classe prima di esportare",
|
||||
"zombie.agentRemoved": "Agente {{name}} rimosso",
|
||||
"zombie.agentsPurged": "{{count}} agenti purgati",
|
||||
"zombie.allAgents": "Tutti gli agenti",
|
||||
"zombie.c2StartedOnPort": "Server C2 avviato sulla porta {{port}}",
|
||||
"zombie.c2Stopped": "Server C2 fermato",
|
||||
"zombie.clearConsole": "Pulisci console",
|
||||
"zombie.clearLogs": "Cancella i log",
|
||||
"zombie.commandBroadcasted": "Comando diffuso",
|
||||
"zombie.commandSentToAgents": "Comando inviato a {{count}} agenti",
|
||||
"zombie.confirmPurgeStale": "Purgare tutti gli agenti inattivi da più di 24 ore ?",
|
||||
"zombie.confirmRemoveAgent": "Rimuovere l'agente {{name}} ?",
|
||||
"zombie.confirmStopC2": "Fermare il server C2 ?",
|
||||
"zombie.consoleCleared": "Console pulita",
|
||||
"zombie.enterC2Port": "Inserisci porta C2 :",
|
||||
"zombie.enterCommand": "Inserisci comando...",
|
||||
"zombie.failedPurgeStale": "Purga agenti inattivi fallita",
|
||||
"zombie.failedRemoveAgent": "Eliminazione agente {{name}} fallita",
|
||||
"zombie.failedSendCommand": "Invio comando fallito",
|
||||
"zombie.failedStartC2": "Avvio C2 fallito",
|
||||
"zombie.failedStopC2": "Arresto C2 fallito",
|
||||
"zombie.noAgentsConnected": "Nessun agente connesso",
|
||||
"zombie.noAgentsMatchSearch": "Nessun agente corrisponde alla ricerca",
|
||||
"zombie.purgeStale": "Purga inattivi",
|
||||
"zombie.purgeStaleHint": "Purga agenti inattivi >24h",
|
||||
"zombie.removeAgent": "Rimuovi agente",
|
||||
"zombie.startC2": "Avvia C2",
|
||||
"zombie.stopC2": "Ferma C2",
|
||||
"zombie.systemLogs": "Log sistema",
|
||||
"zombieland.alive": "Vivo",
|
||||
"zombieland.c2Status": "Stato C2",
|
||||
"zombieland.dead": "Morto",
|
||||
"zombieland.totalAgents": "Totale agenti",
|
||||
"greeting": "Buongiorno",
|
||||
"start": "Avvia",
|
||||
"tick": "Tick",
|
||||
"common.ip": "IP",
|
||||
"common.mac": "MAC",
|
||||
"common.os": "OS",
|
||||
"zombie.never": "Mai",
|
||||
"zombie.openInConsole": "Apri in console",
|
||||
"common.saved": "Salvato",
|
||||
"attacks.tabs.attacks": "Attacchi",
|
||||
"attacks.tabs.comments": "Commenti",
|
||||
"attacks.tabs.images": "Immagini",
|
||||
"attacks.btn.addAttack": "Aggiungi attacco",
|
||||
"attacks.btn.removeAttack": "Elimina attacco",
|
||||
"attacks.btn.deleteAction": "Elimina azione",
|
||||
"attacks.btn.restoreDefaultsBundle": "Ripristina predefiniti",
|
||||
"attacks.btn.addSection": "Aggiungi sezione",
|
||||
"attacks.btn.deleteSection": "Elimina sezione",
|
||||
"attacks.btn.restoreDefault": "Ripristina predefinito",
|
||||
"attacks.btn.createCharacter": "Crea personaggio",
|
||||
"attacks.btn.deleteCharacter": "Elimina personaggio",
|
||||
"attacks.section.characters": "Personaggio",
|
||||
"attacks.section.statusImages": "Immagini stato",
|
||||
"attacks.section.staticImages": "Immagini statiche",
|
||||
"attacks.section.webImages": "Immagini web",
|
||||
"attacks.section.actionIcons": "Icone azione",
|
||||
"attacks.editor.selectAttack": "Seleziona attacco",
|
||||
"attacks.empty.noAttacks": "Nessun attacco trovato.",
|
||||
"attacks.empty.noComments": "Nessun commento trovato.",
|
||||
"attacks.comments.placeholder": "I commenti verranno mostrati qui...",
|
||||
"attacks.images.enterEditMode": "Attiva modalità modifica",
|
||||
"attacks.images.exitEditMode": "Esci modalità modifica",
|
||||
"attacks.images.sortName": "Ordine : nome",
|
||||
"attacks.images.sortDimensions": "Ordine : dimensioni",
|
||||
"attacks.images.search": "Cerca immagini...",
|
||||
"attacks.images.rename": "Rinomina immagine",
|
||||
"attacks.images.replace": "Sostituisci immagine",
|
||||
"attacks.images.resizeSelected": "Ridimensiona selezione",
|
||||
"attacks.images.addCharacters": "Aggiungi immagini personaggio",
|
||||
"attacks.images.deleteSelected": "Elimina selezione",
|
||||
"attacks.images.addStatus": "Aggiungi immagine stato",
|
||||
"attacks.images.addStatic": "Aggiungi immagine statica",
|
||||
"attacks.images.addWeb": "Aggiungi immagine web",
|
||||
"attacks.images.addIcon": "Aggiungi icona azione",
|
||||
"attacks.errors.loadAttacks": "Impossibile caricare gli attacchi.",
|
||||
"attacks.errors.loadImages": "Impossibile caricare le immagini.",
|
||||
"attacks.confirm.switchCharacter": "Passare al personaggio \"{{name}}\" ?",
|
||||
"attacks.confirm.removeAttack": "Eliminare l'attacco \"{{name}}\" ?",
|
||||
"attacks.confirm.deleteAction": "Eliminare l'azione \"{{name}}\" ?",
|
||||
"attacks.confirm.restoreAttack": "Ripristinare \"{{name}}\" alle impostazioni predefinite ?",
|
||||
"attacks.confirm.restoreDefaultsBundle": "Ripristinare TUTTI i valori predefiniti (azioni, immagini, commenti) ?",
|
||||
"attacks.confirm.deleteCharacter": "Eliminare il personaggio \"{{name}}\" ?",
|
||||
"attacks.confirm.deleteSection": "Eliminare la sezione \"{{name}}\" ?",
|
||||
"attacks.confirm.restoreDefaultComments": "Ripristinare i commenti predefiniti ?",
|
||||
"attacks.confirm.deleteSelectedImages": "Eliminare le immagini selezionate ?",
|
||||
"attacks.prompt.newCharacterName": "Nome del nuovo personaggio :",
|
||||
"attacks.prompt.characterToDelete": "Personaggio da eliminare :",
|
||||
"attacks.prompt.newSectionName": "Nome della nuova sezione :",
|
||||
"attacks.prompt.newImageName": "Nuovo nome :",
|
||||
"attacks.prompt.resizeWidth": "Larghezza ridimensionamento :",
|
||||
"attacks.prompt.resizeHeight": "Altezza ridimensionamento :",
|
||||
"attacks.toast.characterSwitched": "Personaggio cambiato",
|
||||
"attacks.toast.attackImported": "Attacco importato",
|
||||
"attacks.toast.selectAttackFirst": "Seleziona prima un attacco",
|
||||
"attacks.toast.actionDeleted": "Azione eliminata",
|
||||
"attacks.toast.defaultsRestored": "Valori predefiniti ripristinati",
|
||||
"attacks.toast.characterCreated": "Personaggio creato",
|
||||
"attacks.toast.noDeletableCharacters": "Nessun personaggio eliminabile",
|
||||
"attacks.toast.characterDeleted": "Personaggio eliminato",
|
||||
"attacks.toast.commentsRestored": "Commenti ripristinati",
|
||||
"attacks.toast.selectSectionFirst": "Seleziona prima una sezione",
|
||||
"attacks.toast.commentsSaved": "Commenti salvati",
|
||||
"attacks.toast.selectExactlyOneImage": "Seleziona esattamente un'immagine",
|
||||
"attacks.toast.selectAtLeastOneImage": "Seleziona almeno un'immagine",
|
||||
"attacks.toast.imagesResized": "Immagini ridimensionate",
|
||||
"attacks.toast.characterImagesUploaded": "Immagini personaggio caricate",
|
||||
"attacks.toast.selectStatusActionFirst": "Seleziona prima un'azione di stato",
|
||||
"actions.toast.presetApplied": "Preset applicato",
|
||||
"actions.toast.startingAction": "Avvio di {{name}}...",
|
||||
"actions.toast.actionStarted": "Azione avviata",
|
||||
"actions.toast.stoppedByUser": "Fermato dall'utente",
|
||||
"actions.toast.actionStopped": "Azione fermata",
|
||||
"actions.toast.stopFailed": "Arresto fallito",
|
||||
"actions.toast.failedToStop": "Impossibile fermare",
|
||||
"actions.toast.consoleCleared": "Console pulita",
|
||||
"actions.toast.noLogsToExport": "Nessun log da esportare",
|
||||
"actions.toast.logsExported": "Log esportati",
|
||||
"netkb.confirmRemoveAction": "Eliminare l'azione \"{{action}}\" per l'IP \"{{ip}}\" ?",
|
||||
"netkb.actionRemoved": "Azione rimossa",
|
||||
"actions.running": "In corso",
|
||||
"attacks.btn.syncMissing": "Sincronizza elementi mancanti",
|
||||
"attacks.images.gridDensity": "Densità griglia",
|
||||
"attacks.images.density": "Densità",
|
||||
"attacks.sync.defaultComment": "Aggiungi un commento per questa azione",
|
||||
"attacks.sync.none": "Nessun attacco da sincronizzare.",
|
||||
"attacks.sync.done": "Sincronizzazione completata. Nuovi commenti : {{comments}}, immagini stato : {{status}}, immagini personaggio : {{characters}}.",
|
||||
"attacks.sync.failed": "Sincronizzazione elementi mancanti fallita",
|
||||
"actions.args.free": "Argomenti liberi",
|
||||
"actions.args.none": "Nessun argomento configurabile",
|
||||
"actions.args.subtitle": "Generati auto dalle definizioni",
|
||||
"actions.args.title": "Argomenti",
|
||||
"actions.assign": "Assegna",
|
||||
"actions.emptyPane": "Nessuna azione selezionata",
|
||||
"actions.logs.completed": "Fatto",
|
||||
"actions.logs.empty": "Nessun log per ora",
|
||||
"actions.logs.waiting": "In attesa...",
|
||||
"actions.searchPlaceholder": "Cerca azioni...",
|
||||
"actions.tabs.actions": "Azioni",
|
||||
"actions.tabs.arguments": "Argomenti",
|
||||
"actions.toast.selectActionFirst": "Seleziona prima un'azione",
|
||||
"common.move": "Sposta",
|
||||
"common.ready": "Pronto",
|
||||
"common.menu": "Menu",
|
||||
"common.browse": "Sfoglia...",
|
||||
"common.platform": "Piattaforma",
|
||||
"common.generate": "Genera",
|
||||
"common.vendor": "Produttore",
|
||||
"common.hostname": "Hostname",
|
||||
"common.ports": "Porte",
|
||||
"zombie.generateClient": "Genera client",
|
||||
"zombie.checkStale": "Controlla inattivi",
|
||||
"zombie.selectedAgents": "agenti selezionati",
|
||||
"zombie.clientId": "ID Client",
|
||||
"zombie.labCreds": "Credenziali Lab",
|
||||
"zombie.deployOptions": "Opzioni deploy",
|
||||
"zombie.deployViaSSH": "Deploy via SSH",
|
||||
"zombie.fileBrowser": "File Browser",
|
||||
"dash.lastUpdate": "Ultimo aggiornamento",
|
||||
"netkb.searchPlaceholder": "Cerca host, IP, produttore, porta...",
|
||||
"netkb.searchHint": "Tip : scrivi 'port:80' o 'vendor:intel'",
|
||||
"files.dropzoneHint": "Trascina file qui o clicca per caricare",
|
||||
"files.moveToTitle": "Sposta in...",
|
||||
"files.selectDestinationFolder": "Seleziona cartella destinazione",
|
||||
"attacks.sidebar.management": "Gestione",
|
||||
"sched.upcoming": "In arrivo",
|
||||
"sched.success": "Successo",
|
||||
"sched.cancelled": "Annullato",
|
||||
"sched.history": "Cronologia",
|
||||
"sched.historyMsg": "Log cronologia",
|
||||
"creds.searchPlaceholder": "Cerca servizi, utenti...",
|
||||
"creds.uniqueHosts": "Host unici",
|
||||
"creds.totalCredentials": "Totale credenziali",
|
||||
"console.maxReconnect": "Console : raggiunto il massimo numero di tentativi di riconnessione",
|
||||
"console.scrollToBottom": "Scorri fino in fondo",
|
||||
"console.manual": "Manuale",
|
||||
"console.auto": "Auto",
|
||||
"console.turnOnAuto": "Attiva modalità Auto",
|
||||
"console.turnOnManual": "Attiva modalità Manuale",
|
||||
"console.noTarget": "Nessun target",
|
||||
"console.noAction": "Nessuna azione",
|
||||
"console.scanStarted": "Scan manuale avviato",
|
||||
"console.scanFailed": "Scan manuale fallito",
|
||||
"console.attackStarted": "Attacco manuale avviato",
|
||||
"console.attackFailed": "Attacco manuale fallito",
|
||||
"console.failedToggleMode": "Cambio modalità fallito",
|
||||
"console.reconnectAttempt": "Riconnessione (tentativo {{count}})...",
|
||||
"quick.close": "Chiudi pannello",
|
||||
"quick.connectingTo": "Connessione a {{ssid}}...",
|
||||
"quick.connectedTo": "Connesso a {{ssid}}",
|
||||
"quick.connectionFailed": "Connessione fallita",
|
||||
"quick.loadKnownFailed": "Impossibile caricare le reti note",
|
||||
"quick.priorityUpdated": "Priorità aggiornata",
|
||||
"quick.priorityUpdateFailed": "Aggiornamento priorità fallito",
|
||||
"quick.networkRemoved": "Rete rimossa",
|
||||
"quick.importingPotfiles": "Importazione potfile...",
|
||||
"quick.importedCount": "{{count}} credenziali importate",
|
||||
"quick.btScanFailed": "Scan Bluetooth fallito",
|
||||
"quick.btActioning": "{{action}} di {{name}}...",
|
||||
"quick.btActionDone": "{{name}} {{action}} completato",
|
||||
"quick.btActionFailed": "{{action}} fallito",
|
||||
"quick.btForgotten": "{{name}} dimenticato",
|
||||
"sidebar.close": "Chiudi barra laterale",
|
||||
"api.aborted": "Abortito",
|
||||
"api.timeout": "La richiesta è scaduta",
|
||||
"api.failed": "La richiesta è fallita",
|
||||
"router.notFound": "Pagina non trovata : {{path}}",
|
||||
"router.errorLoading": "Errore nel caricamento della pagina : {{message}}"
|
||||
}
|
||||
781
web/i18n/ru.json
Normal file
@@ -0,0 +1,781 @@
|
||||
{
|
||||
"nav.dashboard": "Панель управления",
|
||||
"nav.bjorn": "Bjorn",
|
||||
"nav.netkb": "База сети",
|
||||
"nav.network": "Сеть",
|
||||
"nav.credentials": "Пароли",
|
||||
"nav.vulnerabilities": "Уязвимости",
|
||||
"nav.attacks": "Атака",
|
||||
"nav.scheduler": "Планировщик",
|
||||
"nav.database": "База данных",
|
||||
"nav.files": "Файлы",
|
||||
"nav.loot": "Добыча",
|
||||
"nav.actions": "Действия",
|
||||
"nav.actionsStudio": "Студия действий",
|
||||
"nav.backup": "Бэкап и Обн.",
|
||||
"nav.webEnum": "Веб-энум",
|
||||
"nav.zombieland": "Зомбиленд",
|
||||
"nav.settings": "Настройки",
|
||||
"nav.shortcuts": "Горячие клавиши",
|
||||
"nav.pages": "Страницы",
|
||||
"status.initializing": "Инициализация...",
|
||||
"status.online": "В сети",
|
||||
"status.offline": "Не в сети",
|
||||
"console.title": "Консоль",
|
||||
"console.clear": "Очистить",
|
||||
"console.sseOn": "SSE Вкл",
|
||||
"console.sseOff": "SSE Выкл",
|
||||
"console.newLogs": "{{count}} новых логов",
|
||||
"settings.theme": "Тема",
|
||||
"settings.language": "Язык",
|
||||
"settings.general": "Общие",
|
||||
"settings.toggles": "Опции",
|
||||
"settings.editValue": "Изменить значение",
|
||||
"settings.addValues": "Добавить значения (через запятую)...",
|
||||
"settings.setValue": "Установить значение...",
|
||||
"settings.errorLoading": "Ошибка загрузки конфигурации",
|
||||
"settings.configSaved": "Конфигурация сохранена",
|
||||
"settings.errorSaving": "Ошибка сохранения конфигурации",
|
||||
"settings.defaultsRestored": "Настройки по умолчанию восстановлены",
|
||||
"settings.errorRestoring": "Ошибка восстановления настроек по умолчанию",
|
||||
"theme.group.colors": "Цвета",
|
||||
"theme.group.surfaces": "Поверхности",
|
||||
"theme.group.layout": "Макет",
|
||||
"theme.token.bg": "Фон",
|
||||
"theme.token.ink": "Цвет текста",
|
||||
"theme.token.accent1": "Акцент 1 (Кислотный)",
|
||||
"theme.token.accent2": "Акцент 2 (Циан)",
|
||||
"theme.token.danger": "Опасно",
|
||||
"theme.token.warning": "Предупреждение",
|
||||
"theme.token.ok": "Успех",
|
||||
"theme.token.panel": "Панель",
|
||||
"theme.token.panel2": "Панель Альт",
|
||||
"theme.token.ctrlPanel": "Панель управления",
|
||||
"theme.token.border": "Рамка",
|
||||
"theme.token.radius": "Радиус рамки",
|
||||
"theme.advanced": "Продвинутый CSS",
|
||||
"theme.applyRaw": "Применить",
|
||||
"theme.reset": "Сбросить",
|
||||
"dash.title": "Панель управления",
|
||||
"dash.battery": "Батарея",
|
||||
"dash.internet": "Интернет",
|
||||
"dash.cpu": "ЦП",
|
||||
"dash.ram": "ОЗУ",
|
||||
"dash.disk": "Диск",
|
||||
"dash.temp": "Темп",
|
||||
"dash.uptime": "Аптайм",
|
||||
"dash.hostsAlive": "Активные хосты",
|
||||
"dash.totalHosts": "Всего хостов",
|
||||
"dash.openPorts": "Открытые порты",
|
||||
"dash.credentials": "Пароли",
|
||||
"dash.vulnerabilities": "Уязвимости",
|
||||
"dash.actions": "Действия",
|
||||
"dash.connected": "Подключено",
|
||||
"dash.disconnected": "Отключено",
|
||||
"dash.charging": "Зарядка",
|
||||
"dash.discharging": "Разрядка",
|
||||
"dash.full": "Заряжен",
|
||||
"dash.connectivity": "Связь",
|
||||
"dash.liveOps": "Операции в реальном времени",
|
||||
"dash.tapRefresh": "Нажмите для обновления",
|
||||
"dash.wifi": "Wi-Fi",
|
||||
"dash.ethernet": "Ethernet",
|
||||
"dash.usb": "USB",
|
||||
"dash.bluetooth": "Bluetooth",
|
||||
"dash.mode": "Режим",
|
||||
"dash.gps": "GPS",
|
||||
"dash.age": "Возраст Bjorn",
|
||||
"dash.plugged": "Подключен",
|
||||
"dash.noBattery": "Нет батареи",
|
||||
"dash.sinceScan": "с последнего сканирования",
|
||||
"dash.wifiKnown": "Известные Wi-Fi",
|
||||
"dash.dataFiles": "Собранные данные / файлы",
|
||||
"dash.fileDescriptors": "Дескрипторы файлов",
|
||||
"dash.attackScripts": "Скрипты атаки",
|
||||
"dash.system": "Система",
|
||||
"dash.zombies": "Зомби",
|
||||
"netkb.title": "База знаний о сети",
|
||||
"netkb.showOffline": "Показать офлайн",
|
||||
"netkb.gridView": "Сетка",
|
||||
"netkb.listView": "Список",
|
||||
"netkb.hostname": "Имя хоста",
|
||||
"netkb.ip": "IP-адрес",
|
||||
"netkb.mac": "MAC-адрес",
|
||||
"netkb.vendor": "Производитель",
|
||||
"netkb.ports": "Порты",
|
||||
"netkb.essid": "ESSID",
|
||||
"netkb.lastSeen": "Был в сети",
|
||||
"netkb.firstSeen": "Первый раз",
|
||||
"netkb.online": "В сети",
|
||||
"netkb.offline": "Не в сети",
|
||||
"netkb.openPorts": "Открытые порты",
|
||||
"netkb.noHosts": "Хосты не найдены",
|
||||
"network.title": "Визуализация сети",
|
||||
"network.tableView": "Таблица",
|
||||
"network.mapView": "Карта",
|
||||
"network.hostname": "Имя хоста",
|
||||
"network.ip": "IP-адрес",
|
||||
"network.mac": "MAC",
|
||||
"network.ports": "Порты",
|
||||
"network.status": "Статус",
|
||||
"network.searchPlaceholder": "Поиск хостов...",
|
||||
"network.noData": "Нет данных о сети",
|
||||
"creds.title": "Пароли",
|
||||
"creds.total": "Всего",
|
||||
"creds.unique": "Уникальные",
|
||||
"creds.types": "Типы",
|
||||
"creds.username": "Имя пользователя",
|
||||
"creds.password": "Пароль",
|
||||
"creds.service": "Сервис",
|
||||
"creds.host": "Хост",
|
||||
"creds.port": "Порт",
|
||||
"creds.type": "Тип",
|
||||
"creds.timestamp": "Метка времени",
|
||||
"creds.showPassword": "Показать пароль",
|
||||
"creds.hidePassword": "Скрыть пароль",
|
||||
"creds.copyPassword": "Копировать",
|
||||
"creds.exportAll": "Экспортировать все",
|
||||
"creds.noCredentials": "Пароли не найдены",
|
||||
"vulns.title": "Таблица уязвимостей",
|
||||
"vulns.total": "Всего",
|
||||
"vulns.critical": "Критическая",
|
||||
"vulns.high": "Высокая",
|
||||
"vulns.medium": "Средняя",
|
||||
"vulns.low": "Низкая",
|
||||
"vulns.infoLevel": "Инфо",
|
||||
"vulns.host": "Хост",
|
||||
"vulns.port": "Порт",
|
||||
"vulns.service": "Сервис",
|
||||
"vulns.severity": "Критичность",
|
||||
"vulns.description": "Описание",
|
||||
"vulns.cve": "CVE",
|
||||
"vulns.scanDate": "Дата сканирования",
|
||||
"vulns.details": "Детали",
|
||||
"vulns.noVulns": "Уязвимости не найдены",
|
||||
"vulns.byHost": "По хосту",
|
||||
"vulns.bySeverity": "По критичности",
|
||||
"vulns.byService": "По сервису",
|
||||
"attacks.title": "Менеджер атак",
|
||||
"attacks.running": "Запущено",
|
||||
"attacks.completed": "Завершено",
|
||||
"attacks.failed": "Ошибка",
|
||||
"attacks.queued": "В очереди",
|
||||
"attacks.start": "Старт",
|
||||
"attacks.stop": "Стоп",
|
||||
"attacks.restart": "Рестарт",
|
||||
"attacks.status": "Статус",
|
||||
"attacks.target": "Цель",
|
||||
"attacks.action": "Действие",
|
||||
"attacks.duration": "Длительность",
|
||||
"attacks.progress": "Прогресс",
|
||||
"attacks.noAttacks": "Нет запущенных атак",
|
||||
"sched.title": "Планировщик действий",
|
||||
"sched.pending": "Ожидание",
|
||||
"sched.running": "Запущено",
|
||||
"sched.done": "Готово",
|
||||
"sched.failed": "Ошибка",
|
||||
"sched.all": "Все",
|
||||
"sched.searchPlaceholder": "Поиск задач...",
|
||||
"sched.noTasks": "Задачи не найдены",
|
||||
"sched.stats": "{{running}} запущено / {{pending}} ожидает / {{done}} готово",
|
||||
"db.title": "Менеджер БД",
|
||||
"db.tables": "Таблицы",
|
||||
"db.rows": "Строки",
|
||||
"db.columns": "Колонки",
|
||||
"db.search": "Поиск таблиц...",
|
||||
"db.searchRows": "Поиск строк...",
|
||||
"db.export": "Экспорт",
|
||||
"db.import": "Импорт",
|
||||
"db.addRow": "Добавить строку",
|
||||
"db.deleteRow": "Удалить строку",
|
||||
"db.deleteSelected": "Удалить выбранное",
|
||||
"db.saveChanges": "Сохранить",
|
||||
"db.discardChanges": "Отмена",
|
||||
"db.confirmDelete": "Подтвердить удаление?",
|
||||
"db.noTables": "Таблицы не найдены",
|
||||
"db.noData": "Нет данных в этой таблице",
|
||||
"db.hide": "Скрыть",
|
||||
"db.showSidebar": "Показать панель",
|
||||
"files.title": "Проводник",
|
||||
"files.gridView": "Сетка",
|
||||
"files.listView": "Список",
|
||||
"files.size": "Размер",
|
||||
"files.modified": "Изменен",
|
||||
"files.name": "Имя",
|
||||
"files.type": "Тип",
|
||||
"files.download": "Скачать",
|
||||
"files.preview": "Предпросмотр",
|
||||
"files.noFiles": "Файлы не найдены",
|
||||
"files.parentDir": "Родительская директория",
|
||||
"files.searchPlaceholder": "Поиск файлов...",
|
||||
"loot.title": "Добыча",
|
||||
"loot.directories": "Директории",
|
||||
"loot.totalFiles": "Всего файлов",
|
||||
"loot.totalSize": "Общий размер",
|
||||
"loot.download": "Скачать",
|
||||
"loot.downloadAll": "Скачать все",
|
||||
"loot.noLoot": "Добыча не найдена",
|
||||
"loot.explore": "Исследовать",
|
||||
"actions.title": "Менеджер действий",
|
||||
"actions.available": "Доступно",
|
||||
"actions.enabled": "Включено",
|
||||
"actions.disabled": "Выключено",
|
||||
"actions.category": "Категория",
|
||||
"actions.enableAll": "Включить все",
|
||||
"actions.disableAll": "Выключить все",
|
||||
"actions.import": "Импорт",
|
||||
"actions.export": "Экспорт",
|
||||
"actions.noActions": "Действия не найдены",
|
||||
"actions.description": "Описание",
|
||||
"actions.menu.restartService": "Перезапустить службу Bjorn",
|
||||
"actions.menu.deleteActionStatus": "Удалить все статусы действий",
|
||||
"actions.menu.clearOutput": "Очистить папку Output",
|
||||
"actions.menu.clearLogs": "Очистить логи",
|
||||
"actions.menu.reloadImages": "Перезагрузить изображения (экспериментально)",
|
||||
"actions.menu.reloadFonts": "Перезагрузить шрифты",
|
||||
"actions.menu.reloadActionsJson": "Перезагрузить JSON действий",
|
||||
"actions.menu.initializeCsv": "Инициализировать CSV файлы",
|
||||
"actions.menu.clearLivestatus": "Удалить файл Livestatus",
|
||||
"actions.menu.refreshActionsFile": "Обновить файл действий",
|
||||
"actions.menu.clearNetkb": "Очистить базу знаний сети",
|
||||
"actions.menu.clearSharedConfig": "Удалить JSON общей конфигурации",
|
||||
"actions.menu.eraseMemories": "Стереть память Bjorn",
|
||||
"actions.menu.reboot": "Перезагрузить систему",
|
||||
"actions.menu.shutdown": "Выключить систему",
|
||||
"actions.tip.restartService": "Перезапускает службу Bjorn для обновления ее состояния.",
|
||||
"actions.tip.deleteActionStatus": "Удаляет все статусы успеха и ошибок действий/атак в netkb.csv.",
|
||||
"actions.tip.clearOutput": "Удаляет все файлы в папках вывода и подпапках.",
|
||||
"actions.tip.clearLogs": "Удаляет все системные файлы логов.",
|
||||
"actions.tip.reloadImages": "Перезагружает изображения, используемые системой.",
|
||||
"actions.tip.reloadFonts": "Перезагружает шрифты приложения.",
|
||||
"actions.tip.reloadActionsJson": "Перезагружает сгенерированный JSON файл действий.",
|
||||
"actions.tip.initializeCsv": "Заново создает файлы CSV и JSON.",
|
||||
"actions.tip.clearLivestatus": "Удаляет файл статуса в реальном времени.",
|
||||
"actions.tip.refreshActionsFile": "Обновляет файл действий для включения новых действий.",
|
||||
"actions.tip.clearNetkb": "Удаляет всю информацию, сохраненную в базе знаний сети.",
|
||||
"actions.tip.clearSharedConfig": "Удаляет JSON файл общей конфигурации.",
|
||||
"actions.tip.eraseMemories": "Полностью стирает память и настройки Bjorn.",
|
||||
"actions.tip.reboot": "Перезагружает всю систему.",
|
||||
"actions.tip.shutdown": "Полностью выключает систему.",
|
||||
"actions.confirm.restartRecommended": "Рекомендуется перезапуск службы. Перезагрузить сейчас?",
|
||||
"actions.confirm.restartService": "Перезапустить службу Bjorn?",
|
||||
"actions.confirm.deleteActionStatus": "Удалить все сохраненные статусы действий?",
|
||||
"actions.confirm.clearOutput": "Полностью очистить папку output?",
|
||||
"actions.confirm.clearLogs": "Удалить все файлы логов?",
|
||||
"actions.confirm.clearNetkb": "Очистить базу знаний сети? Это действие необратимо.",
|
||||
"actions.confirm.clearLivestatus": "Удалить файл livestatus?",
|
||||
"actions.confirm.refreshActionsFile": "Обновить файл действий?",
|
||||
"actions.confirm.clearSharedConfig": "Удалить JSON общей конфигурации? Это действие необратимо.",
|
||||
"actions.confirm.eraseMemories": "Стереть всю память и настройки Bjorn? Это действие необратимо.",
|
||||
"actions.confirm.reboot": "Перезагрузить всю систему?",
|
||||
"actions.confirm.shutdown": "Выключить систему?",
|
||||
"actions.msg.restartingService": "Служба Bjorn перезапускается...",
|
||||
"actions.msg.restartFailed": "Ошибка перезапуска службы",
|
||||
"actions.msg.actionStatusDeleted": "Все статусы действий удалены.",
|
||||
"actions.msg.outputCleared": "Папка output очищена.",
|
||||
"actions.msg.logsCleared": "Логи очищены.",
|
||||
"actions.msg.netkbCleared": "База знаний сети очищена.",
|
||||
"actions.msg.livestatusDeleted": "Файл livestatus удален.",
|
||||
"actions.msg.actionsFileRefreshed": "Файл действий обновлен.",
|
||||
"actions.msg.sharedConfigDeleted": "JSON общей конфигурации удален.",
|
||||
"actions.msg.memoriesErased": "Память Bjorn стерта.",
|
||||
"actions.msg.rebooting": "Система перезагружается...",
|
||||
"actions.msg.shuttingDown": "Система выключается...",
|
||||
"actions.msg.csvInitialized": "CSV файлы инициализированы.",
|
||||
"actions.msg.actionsJsonReloaded": "JSON действий перезагружен.",
|
||||
"actions.msg.imagesReloaded": "Изображения перезагружены.",
|
||||
"actions.msg.fontsReloaded": "Шрифты перезагружены.",
|
||||
"actions.msg.unknownAction": "Неизвестное действие",
|
||||
"actions.msg.actionFailed": "Действие не удалось",
|
||||
"studio.title": "Студия действий",
|
||||
"studio.palette": "Палитра",
|
||||
"studio.canvas": "Холст",
|
||||
"studio.inspector": "Инспектор",
|
||||
"studio.actionsTab": "Действия",
|
||||
"studio.hostsTab": "Хосты",
|
||||
"studio.globalTab": "Глобально",
|
||||
"studio.save": "Сохранить",
|
||||
"studio.load": "Загрузить",
|
||||
"studio.run": "Запустить",
|
||||
"studio.clear": "Очистить",
|
||||
"studio.addNode": "Добавить узел",
|
||||
"studio.removeNode": "Удалить узел",
|
||||
"studio.search": "Поиск действий...",
|
||||
"backup.title": "Бэкап и Обн.",
|
||||
"backup.backupRestore": "Бэкап / Восстановление",
|
||||
"backup.update": "Обновление",
|
||||
"backup.createBackup": "Создать бэкап",
|
||||
"backup.restoreBackup": "Восстановить",
|
||||
"backup.downloadBackup": "Скачать",
|
||||
"backup.deleteBackup": "Удалить бэкап",
|
||||
"backup.lastBackup": "Последний бэкап",
|
||||
"backup.checkUpdates": "Проверить обн.",
|
||||
"backup.installUpdate": "Установить обн.",
|
||||
"backup.currentVersion": "Текущая версия",
|
||||
"backup.latestVersion": "Последняя версия",
|
||||
"backup.upToDate": "Обновлено",
|
||||
"backup.updateAvailable": "Доступно обн.",
|
||||
"backup.clearLogs": "Очистить логи",
|
||||
"backup.noBackups": "Бэкапы не найдены",
|
||||
"backup.restoring": "Восстановление...",
|
||||
"backup.creating": "Создание бэкапа...",
|
||||
"webenum.title": "Веб-энумерация",
|
||||
"webenum.totalResults": "Всего результатов",
|
||||
"webenum.uniqueHosts": "Уникальные хосты",
|
||||
"webenum.successCount": "Успех (2xx)",
|
||||
"webenum.errorCount": "Ошибки (4xx/5xx)",
|
||||
"webenum.host": "Хост",
|
||||
"webenum.ip": "IP",
|
||||
"webenum.port": "Порт",
|
||||
"webenum.directory": "Директория",
|
||||
"webenum.status": "Статус",
|
||||
"webenum.size": "Размер",
|
||||
"webenum.scanDate": "Дата сканирования",
|
||||
"webenum.link": "Ссылка",
|
||||
"webenum.exportJson": "Экспорт JSON",
|
||||
"webenum.exportCsv": "Экспорт CSV",
|
||||
"webenum.noResults": "Результаты не найдены",
|
||||
"webenum.details": "Детали результата",
|
||||
"webenum.openUrl": "Открыть URL",
|
||||
"webenum.copyUrl": "Копировать URL",
|
||||
"webenum.showing": "Показ {{start}}-{{end}} из {{total}} результатов",
|
||||
"webenum.itemsPerPage": "Элементов на странице",
|
||||
"webenum.refreshData": "Обновить данные",
|
||||
"webenum.responseTime": "Время ответа",
|
||||
"webenum.contentType": "Тип контента",
|
||||
"webenum.fullUrl": "Полный URL",
|
||||
"zombie.title": "Зомбиленд C2C",
|
||||
"zombie.agents": "Агенты",
|
||||
"zombie.terminal": "Терминал",
|
||||
"zombie.commands": "Команды",
|
||||
"zombie.totalAgents": "Всего агентов",
|
||||
"zombie.onlineAgents": "В сети",
|
||||
"zombie.offlineAgents": "Офлайн",
|
||||
"zombie.idleAgents": "Ожидают",
|
||||
"zombie.sendCommand": "Отправить команду",
|
||||
"zombie.broadcast": "Рассылка всем",
|
||||
"zombie.selectAgent": "Выбрать агента",
|
||||
"zombie.os": "ОС",
|
||||
"zombie.lastSeen": "Был в сети",
|
||||
"zombie.status": "Статус",
|
||||
"zombie.noAgents": "Нет подключенных агентов",
|
||||
"zombie.quickCommands": "Быстрые команды",
|
||||
"zombie.files": "Файлы",
|
||||
"quick.autoScan": "Авто-скан",
|
||||
"quick.connectWifi": "Подключить WiFi",
|
||||
"quick.knownNetworks": "Известные сети",
|
||||
"quick.importPotfiles": "Импорт Potfiles",
|
||||
"quick.subtitle": "WiFi и Bluetooth",
|
||||
"quick.pair": "Сопряжение",
|
||||
"quick.trust": "Доверять",
|
||||
"quick.forgetDevice": "Забыть устройство",
|
||||
"quick.forgetDevicePrompt": "Забыть {{name}}?",
|
||||
"quick.forgetNetworkPrompt": "Вы уверены, что хотите забыть эту сеть?",
|
||||
"bjorn.title": "Экран EPD Bjorn",
|
||||
"bjorn.epdScreen": "Экран e-Paper",
|
||||
"bjorn.refreshInterval": "Интервал обновления",
|
||||
"bjorn.autoRefresh": "Авто-обновление",
|
||||
"bjorn.manualRefresh": "Обновить сейчас",
|
||||
"bjorn.seconds": "секунд",
|
||||
"common.search": "Поиск",
|
||||
"common.filter": "Фильтр",
|
||||
"common.refresh": "Обновить",
|
||||
"common.save": "Сохранить",
|
||||
"common.cancel": "Отмена",
|
||||
"common.delete": "Удалить",
|
||||
"common.edit": "Изменить",
|
||||
"common.close": "Закрыть",
|
||||
"common.loading": "Загрузка...",
|
||||
"common.noData": "Данные недоступны",
|
||||
"common.error": "Ошибка",
|
||||
"common.success": "Успех",
|
||||
"common.confirm": "Подтвердить",
|
||||
"common.yes": "Да",
|
||||
"common.no": "Нет",
|
||||
"common.export": "Экспорт",
|
||||
"common.import": "Импорт",
|
||||
"common.download": "Скачать",
|
||||
"common.upload": "Загрузить",
|
||||
"common.copy": "Копировать",
|
||||
"common.start": "Старт",
|
||||
"common.stop": "Стоп",
|
||||
"common.restart": "Рестарт",
|
||||
"common.status": "Статус",
|
||||
"common.name": "Имя",
|
||||
"common.value": "Значение",
|
||||
"common.type": "Тип",
|
||||
"common.host": "Хост",
|
||||
"common.port": "Порт",
|
||||
"common.target": "Цель",
|
||||
"common.date": "Дата",
|
||||
"common.time": "Время",
|
||||
"common.size": "Размер",
|
||||
"common.actions": "Действия",
|
||||
"common.details": "Детали",
|
||||
"common.back": "Назад",
|
||||
"common.next": "Далее",
|
||||
"common.previous": "Назад",
|
||||
"common.first": "Первый",
|
||||
"common.last": "Последний",
|
||||
"common.all": "Все",
|
||||
"common.none": "Ничего",
|
||||
"common.showing": "Показ",
|
||||
"common.of": "из",
|
||||
"common.results": "результатов",
|
||||
"common.items": "элементов",
|
||||
"common.page": "Страница",
|
||||
"common.perPage": "на страницу",
|
||||
"common.sortBy": "Сортировать по",
|
||||
"common.ascending": "По возрастанию",
|
||||
"common.descending": "По убыванию",
|
||||
"common.view": "Вид",
|
||||
"common.table": "Таблица",
|
||||
"common.grid": "Сетка",
|
||||
"common.list": "Список",
|
||||
"common.map": "Карта",
|
||||
"common.enabled": "Включено",
|
||||
"common.disabled": "Выключено",
|
||||
"common.on": "Вкл",
|
||||
"common.off": "Выкл",
|
||||
"common.version": "Версия",
|
||||
"common.hide": "Скрыть",
|
||||
"common.show": "Показать",
|
||||
"common.add": "Добавить",
|
||||
"common.remove": "Удалить",
|
||||
"common.clear": "Очистить",
|
||||
"common.reset": "Сбросить",
|
||||
"common.apply": "Применить",
|
||||
"common.run": "Запуск",
|
||||
"common.send": "Отправить",
|
||||
"common.connect": "Подключить",
|
||||
"common.disconnect": "Отключить",
|
||||
"common.selectAll": "Выбрать все",
|
||||
"common.deselectAll": "Снять всё",
|
||||
"common.copied": "Скопировано!",
|
||||
"common.notFound": "Не найдено",
|
||||
"backup.checkUpdatesHint": "Нажмите «Проверить обн.», чтобы увидеть версии.",
|
||||
"backup.checkingUpdates": "Проверка обновлений...",
|
||||
"backup.confirmFreshStart": "Подтвердить чистый запуск?",
|
||||
"backup.createdSuccessfully": "Бэкап успешно создан.",
|
||||
"backup.defaultUpdated": "Бэкап по умолчанию обновлен.",
|
||||
"backup.deleted": "Бэкап удален.",
|
||||
"backup.descriptionPlaceholder": "Описание бэкапа...",
|
||||
"backup.enterDescription": "Пожалуйста, введите описание бэкапа.",
|
||||
"backup.failedCheckUpdates": "Ошибка при проверке обновлений",
|
||||
"backup.failedCreate": "Ошибка при создании бэкапа",
|
||||
"backup.failedDelete": "Ошибка при удалении бэкапа",
|
||||
"backup.failedLoadBackups": "Ошибка при загрузке бэкапов",
|
||||
"backup.failedSetDefault": "Ошибка при установке по умолчанию",
|
||||
"backup.freshStart": "Чистый запуск",
|
||||
"backup.freshStartFailed": "Ошибка при чистом запуске",
|
||||
"backup.freshStartInitiated": "Чистый запуск инициирован.",
|
||||
"backup.github": "github",
|
||||
"backup.keepActions": "Сохранить папку actions",
|
||||
"backup.keepConfig": "Сохранить папку config",
|
||||
"backup.keepData": "Сохранить папку data",
|
||||
"backup.keepResources": "Сохранить папку resources",
|
||||
"backup.noBackupsCreateAbove": "Бэкапы не найдены. Создайте один выше.",
|
||||
"backup.restoreCompleted": "Восстановление завершено.",
|
||||
"backup.restoreOptions": "Опции восстановления",
|
||||
"backup.restorePoint": "точка-восстановления",
|
||||
"backup.selectKeepFolders": "Выберите папки, которые нужно оставить во время операции:",
|
||||
"backup.setDefault": "Сделать по умолчанию",
|
||||
"backup.unnamedBackup": "Бэкап без имени",
|
||||
"backup.updateInitiated": "Обновление инициировано.",
|
||||
"backup.updateOptions": "Опции обновления",
|
||||
"common.confirmDiscardUnsaved": "Отменить несохраненные изменения?",
|
||||
"common.confirmQuestion": "Подтвердить?",
|
||||
"common.default": "по умолчанию",
|
||||
"common.deleteFailed": "Ошибка удаления",
|
||||
"common.deleted": "Удалено",
|
||||
"common.description": "Описание",
|
||||
"common.directory": "директория",
|
||||
"common.duplicate": "Дублировать",
|
||||
"common.exportJson": "Экспорт JSON",
|
||||
"common.failed": "ошибка",
|
||||
"common.file": "файл",
|
||||
"common.importJson": "Импорт JSON",
|
||||
"common.new": "Новый",
|
||||
"common.noMatches": "Нет совпадений",
|
||||
"common.options": "Опции",
|
||||
"common.processingPleaseWait": "Обработка, пожалуйста, подождите...",
|
||||
"common.refreshed": "Обновлено",
|
||||
"common.rename": "Переименовать",
|
||||
"common.saving": "Сохранение...",
|
||||
"common.unknown": "неизвестно",
|
||||
"common.unsavedChanges": "Несохраненные изменения",
|
||||
"db.autoRefresh": "Авто-обновление",
|
||||
"db.changesDiscarded": "Изменения отменены",
|
||||
"db.changesSaved": "Изменения сохранены",
|
||||
"db.confirmDrop": "УДАЛИТЬ таблицу «{{table}}»? Это действие необратимо!",
|
||||
"db.confirmTruncate": "Очистить все строки в «{{table}}»?",
|
||||
"db.dangerZone": "Опасная зона",
|
||||
"db.deletingRowsCount": "Удаление {{count}} строк(и)...",
|
||||
"db.dropFailed": "Ошибка удаления таблицы",
|
||||
"db.droppedTable": "Таблица {{table}} удалена",
|
||||
"db.dropping": "Удаление...",
|
||||
"db.emptyTable": "Пустая таблица",
|
||||
"db.errorLoadingData": "Ошибка при загрузке данных",
|
||||
"db.failedLoadCatalog": "Ошибка при загрузке каталога",
|
||||
"db.failedLoadTable": "Ошибка при загрузке таблицы",
|
||||
"db.filterTables": "Фильтр таблиц...",
|
||||
"db.insertFailed": "Ошибка вставки",
|
||||
"db.insertingRow": "Вставка строки...",
|
||||
"db.noRowsSelected": "Строки не выбраны",
|
||||
"db.rowInserted": "Строка вставлена",
|
||||
"db.rowsDeleted": "Строки удалены",
|
||||
"db.runningVacuum": "Выполнение VACUUM...",
|
||||
"db.saveFailed": "Ошибка сохранения",
|
||||
"db.selectTableFromSidebar": "Выберите таблицу на боковой панели",
|
||||
"db.tableDropped": "Таблица удалена",
|
||||
"db.tableTruncated": "Таблица очищена",
|
||||
"db.truncateFailed": "Ошибка очистки",
|
||||
"db.truncating": "Очистка...",
|
||||
"db.vacuumComplete": "VACUUM завершен",
|
||||
"db.vacuumDone": "VACUUM выполнен",
|
||||
"db.vacuumFailed": "Ошибка VACUUM",
|
||||
"files.confirmDelete": "Удалить {{label}} «{{name}}»?",
|
||||
"files.downloadFile": "Скачать файл",
|
||||
"files.duplicateFailed": "Ошибка дублирования",
|
||||
"files.duplicated": "Дублировано",
|
||||
"files.emptyDirectory": "Пустая директория",
|
||||
"files.errorLoading": "Ошибка при загрузке файлов",
|
||||
"files.failedLoadDir": "Ошибка при загрузке директории",
|
||||
"files.filterPlaceholder": "Фильтр файлов...",
|
||||
"files.itemsCount": "{{count}} элемент(ов)",
|
||||
"files.newNamePrompt": "Новое имя:",
|
||||
"files.noMatch": "Нет совпадающих файлов",
|
||||
"files.openDirectory": "Открыть директорию",
|
||||
"files.parent": ".. (родительская)",
|
||||
"files.renameFailed": "Ошибка переименования",
|
||||
"files.renamed": "Переименовано",
|
||||
"files.root": "Корень",
|
||||
"files.uploadComplete": "Загрузка завершена",
|
||||
"files.uploadFailed": "Загрузка не удалась",
|
||||
"files.uploadingCount": "Загрузка {{count}} файл(ов)...",
|
||||
"studio.actionNotFound": "Действие не найдено",
|
||||
"studio.classNameRequired": "Имя класса обязательно",
|
||||
"studio.confirmDeleteAction": "Удалить действие «{{name}}»? Это действие необратимо.",
|
||||
"studio.deletedName": "Удалено: {{name}}",
|
||||
"studio.exportedFile": "Экспортировано: {{name}}",
|
||||
"studio.filterActions": "Фильтр действий...",
|
||||
"studio.importFailed": "Импорт не удался",
|
||||
"studio.importedFile": "Импортировано: {{name}}",
|
||||
"studio.loadFailed": "Загрузка не удалась",
|
||||
"studio.loadedFromCacheName": "Загружено из кэша: {{name}}",
|
||||
"studio.loadedName": "Загружено: {{name}}",
|
||||
"studio.newActionCreated": "Новое действие создано",
|
||||
"studio.noActionLoaded": "Действие не загружено",
|
||||
"studio.saveFailedBackedUp": "Ошибка сохранения (создан локальный бэкап)",
|
||||
"studio.savedName": "Сохранено: {{name}}",
|
||||
"studio.setClassBeforeExport": "Установите класс перед экспортом",
|
||||
"zombie.agentRemoved": "Агент {{name}} удален",
|
||||
"zombie.agentsPurged": "{{count}} агент(ов) очищено",
|
||||
"zombie.allAgents": "Все агенты",
|
||||
"zombie.c2StartedOnPort": "C2-сервер запущен на порту {{port}}",
|
||||
"zombie.c2Stopped": "C2-сервер остановлен",
|
||||
"zombie.clearConsole": "Очистить консоль",
|
||||
"zombie.clearLogs": "Очистить логи",
|
||||
"zombie.commandBroadcasted": "Команда разослана всем",
|
||||
"zombie.commandSentToAgents": "Команда отправлена {{count}} агент(ам)",
|
||||
"zombie.confirmPurgeStale": "Очистить всех агентов, неактивных более 24 часов?",
|
||||
"zombie.confirmRemoveAgent": "Удалить агента {{name}}?",
|
||||
"zombie.confirmStopC2": "Остановить C2-сервер?",
|
||||
"zombie.consoleCleared": "Консоль очищена",
|
||||
"zombie.enterC2Port": "Введите порт C2:",
|
||||
"zombie.enterCommand": "Введите команду...",
|
||||
"zombie.failedPurgeStale": "Ошибка очистки неактивных агентов",
|
||||
"zombie.failedRemoveAgent": "Ошибка удаления агента {{name}}",
|
||||
"zombie.failedSendCommand": "Ошибка отправки команды",
|
||||
"zombie.failedStartC2": "Ошибка запуска C2",
|
||||
"zombie.failedStopC2": "Ошибка остановки C2",
|
||||
"zombie.noAgentsConnected": "Нет подключенных агентов",
|
||||
"zombie.noAgentsMatchSearch": "Нет агентов, соответствующих поиску",
|
||||
"zombie.purgeStale": "Очистить неактивных",
|
||||
"zombie.purgeStaleHint": "Очистить агентов, неактивных >24ч",
|
||||
"zombie.removeAgent": "Удалить агента",
|
||||
"zombie.startC2": "Запустить C2",
|
||||
"zombie.stopC2": "Остановить C2",
|
||||
"zombie.systemLogs": "Системные логи",
|
||||
"zombieland.alive": "Жив",
|
||||
"zombieland.c2Status": "Статус C2",
|
||||
"zombieland.dead": "Мертв",
|
||||
"zombieland.totalAgents": "Всего агентов",
|
||||
"greeting": "Привет",
|
||||
"start": "Старт",
|
||||
"tick": "Тик",
|
||||
"common.ip": "IP",
|
||||
"common.mac": "MAC",
|
||||
"common.os": "ОС",
|
||||
"zombie.never": "Никогда",
|
||||
"zombie.openInConsole": "Открыть в консоли",
|
||||
"common.saved": "Сохранено",
|
||||
"attacks.tabs.attacks": "Атаки",
|
||||
"attacks.tabs.comments": "Комментарии",
|
||||
"attacks.tabs.images": "Изображения",
|
||||
"attacks.btn.addAttack": "Добавить атаку",
|
||||
"attacks.btn.removeAttack": "Удалить атаку",
|
||||
"attacks.btn.deleteAction": "Удалить действие",
|
||||
"attacks.btn.restoreDefaultsBundle": "Восстановить настройки по умолчанию",
|
||||
"attacks.btn.addSection": "Добавить раздел",
|
||||
"attacks.btn.deleteSection": "Удалить раздел",
|
||||
"attacks.btn.restoreDefault": "Восстановить по умолчанию",
|
||||
"attacks.btn.createCharacter": "Создать персонажа",
|
||||
"attacks.btn.deleteCharacter": "Удалить персонажа",
|
||||
"attacks.section.characters": "Персонаж",
|
||||
"attacks.section.statusImages": "Изображения статуса",
|
||||
"attacks.section.staticImages": "Статичные изображения",
|
||||
"attacks.section.webImages": "Веб-изображения",
|
||||
"attacks.section.actionIcons": "Иконки действий",
|
||||
"attacks.editor.selectAttack": "Выбрать атаку",
|
||||
"attacks.empty.noAttacks": "Атаки не найдены.",
|
||||
"attacks.empty.noComments": "Комментарии не найдены.",
|
||||
"attacks.comments.placeholder": "Комментарии будут отображаться здесь...",
|
||||
"attacks.images.enterEditMode": "Включить режим редактирования",
|
||||
"attacks.images.exitEditMode": "Выйти из режима редактирования",
|
||||
"attacks.images.sortName": "Сортировка: имя",
|
||||
"attacks.images.sortDimensions": "Сортировка: размеры",
|
||||
"attacks.images.search": "Поиск изображений...",
|
||||
"attacks.images.rename": "Переименовать изображение",
|
||||
"attacks.images.replace": "Заменить изображение",
|
||||
"attacks.images.resizeSelected": "Изменить размер выбранного",
|
||||
"attacks.images.addCharacters": "Добавить изображения персонажей",
|
||||
"attacks.images.deleteSelected": "Удалить выбранное",
|
||||
"attacks.images.addStatus": "Добавить изобр. статуса",
|
||||
"attacks.images.addStatic": "Добавить статичное изобр.",
|
||||
"attacks.images.addWeb": "Добавить веб-изобр.",
|
||||
"attacks.images.addIcon": "Добавить иконку действия",
|
||||
"attacks.errors.loadAttacks": "Ошибка загрузки атак.",
|
||||
"attacks.errors.loadImages": "Ошибка загрузки изображений.",
|
||||
"attacks.confirm.switchCharacter": "Переключиться на персонажа «{{name}}»?",
|
||||
"attacks.confirm.removeAttack": "Удалить атаку «{{name}}»?",
|
||||
"attacks.confirm.deleteAction": "Удалить действие «{{name}}»?",
|
||||
"attacks.confirm.restoreAttack": "Восстановить «{{name}}» по умолчанию?",
|
||||
"attacks.confirm.restoreDefaultsBundle": "Восстановить ВСЕ настройки по умолчанию (действия, изображения, комментарии)?",
|
||||
"attacks.confirm.deleteCharacter": "Удалить персонажа «{{name}}»?",
|
||||
"attacks.confirm.deleteSection": "Удалить раздел «{{name}}»?",
|
||||
"attacks.confirm.restoreDefaultComments": "Восстановить комментарии по умолчанию?",
|
||||
"attacks.confirm.deleteSelectedImages": "Удалить выбранные изображения?",
|
||||
"attacks.prompt.newCharacterName": "Имя нового персонажа:",
|
||||
"attacks.prompt.characterToDelete": "Персонаж для удаления:",
|
||||
"attacks.prompt.newSectionName": "Имя нового раздела:",
|
||||
"attacks.prompt.newImageName": "Новое имя:",
|
||||
"attacks.prompt.resizeWidth": "Ширина изменения размера:",
|
||||
"attacks.prompt.resizeHeight": "Высота изменения размера:",
|
||||
"attacks.toast.characterSwitched": "Персонаж изменен",
|
||||
"attacks.toast.attackImported": "Атака импортирована",
|
||||
"attacks.toast.selectAttackFirst": "Сначала выберите атаку",
|
||||
"attacks.toast.actionDeleted": "Действие удалено",
|
||||
"attacks.toast.defaultsRestored": "Настройки по умолчанию восстановлены",
|
||||
"attacks.toast.characterCreated": "Персонаж создан",
|
||||
"attacks.toast.noDeletableCharacters": "Нет удаляемых персонажей",
|
||||
"attacks.toast.characterDeleted": "Персонаж удален",
|
||||
"attacks.toast.commentsRestored": "Комментарии восстановлены",
|
||||
"attacks.toast.selectSectionFirst": "Сначала выберите раздел",
|
||||
"attacks.toast.commentsSaved": "Комментарии сохранены",
|
||||
"attacks.toast.selectExactlyOneImage": "Выберите ровно одно изображение",
|
||||
"attacks.toast.selectAtLeastOneImage": "Выберите хотя бы одно изображение",
|
||||
"attacks.toast.imagesResized": "Размеры изображений изменены",
|
||||
"attacks.toast.characterImagesUploaded": "Изображения персонажей загружены",
|
||||
"attacks.toast.selectStatusActionFirst": "Сначала выберите действие статуса",
|
||||
"actions.toast.presetApplied": "Пресет применен",
|
||||
"actions.toast.startingAction": "Запуск {{name}}...",
|
||||
"actions.toast.actionStarted": "Действие запущено",
|
||||
"actions.toast.stoppedByUser": "Остановлено пользователем",
|
||||
"actions.toast.actionStopped": "Действие остановлено",
|
||||
"actions.toast.stopFailed": "Остановка не удалась",
|
||||
"actions.toast.failedToStop": "Не удалось остановить",
|
||||
"actions.toast.consoleCleared": "Консоль очищена",
|
||||
"actions.toast.noLogsToExport": "Нет логов для экспорта",
|
||||
"actions.toast.logsExported": "Логи экспортированы",
|
||||
"netkb.confirmRemoveAction": "Удалить действие «{{action}}» для IP «{{ip}}»?",
|
||||
"netkb.actionRemoved": "Действие удалено",
|
||||
"actions.running": "Запущено",
|
||||
"attacks.btn.syncMissing": "Синхронизировать отсутствующие",
|
||||
"attacks.images.gridDensity": "Плотность сетки",
|
||||
"attacks.images.density": "Плотность",
|
||||
"attacks.sync.defaultComment": "Добавить комментарий к этому действию",
|
||||
"attacks.sync.none": "Нет атак для синхронизации.",
|
||||
"attacks.sync.done": "Синхронизация завершена. Новые комментарии: {{comments}}, изобр. статуса: {{status}}, изобр. персонажей: {{characters}}.",
|
||||
"attacks.sync.failed": "Ошибка синхронизации отсутствующих элементов",
|
||||
"actions.args.free": "Свободные аргументы",
|
||||
"actions.args.none": "Нет настраиваемых аргументов",
|
||||
"actions.args.subtitle": "Сгенерировано автоматически из определений действий",
|
||||
"actions.args.title": "Аргументы",
|
||||
"actions.assign": "Назначить",
|
||||
"actions.emptyPane": "Действие не выбрано",
|
||||
"actions.logs.completed": "Готово",
|
||||
"actions.logs.empty": "Логов пока нет",
|
||||
"actions.logs.waiting": "Ожидание...",
|
||||
"actions.searchPlaceholder": "Поиск действий...",
|
||||
"actions.tabs.actions": "Действия",
|
||||
"actions.tabs.arguments": "Аргументы",
|
||||
"actions.toast.selectActionFirst": "Сначала выберите действие",
|
||||
"common.move": "Переместить",
|
||||
"common.ready": "Готов",
|
||||
"common.menu": "Меню",
|
||||
"common.browse": "Обзор...",
|
||||
"common.platform": "Платформа",
|
||||
"common.generate": "Генерировать",
|
||||
"common.vendor": "Производитель",
|
||||
"common.hostname": "Имя хоста",
|
||||
"common.ports": "Порты",
|
||||
"zombie.generateClient": "Генерировать клиент",
|
||||
"zombie.checkStale": "Проверить неактивных",
|
||||
"zombie.selectedAgents": "выбранных агентов",
|
||||
"zombie.clientId": "ID клиента",
|
||||
"zombie.labCreds": "Учетные данные лабы",
|
||||
"zombie.deployOptions": "Опции развертывания",
|
||||
"zombie.deployViaSSH": "Развернуть по SSH",
|
||||
"zombie.fileBrowser": "Файловый менеджер",
|
||||
"dash.lastUpdate": "Последнее обновление",
|
||||
"netkb.searchPlaceholder": "Поиск по хосту, IP, производителю, порту...",
|
||||
"netkb.searchHint": "Совет: введите «port:80» или «vendor:intel»",
|
||||
"files.dropzoneHint": "Перетащите файлы сюда или нажмите для загрузки",
|
||||
"files.moveToTitle": "Переместить в...",
|
||||
"files.selectDestinationFolder": "Выберите папку назначения",
|
||||
"attacks.sidebar.management": "Управление",
|
||||
"sched.upcoming": "Предстоящие",
|
||||
"sched.success": "Успех",
|
||||
"sched.cancelled": "Отменено",
|
||||
"sched.history": "История",
|
||||
"sched.historyMsg": "Логи истории",
|
||||
"creds.searchPlaceholder": "Поиск сервисов, пользователей...",
|
||||
"creds.uniqueHosts": "Уникальные хосты",
|
||||
"creds.totalCredentials": "Всего паролей",
|
||||
"console.maxReconnect": "Консоль: достигнуто максимальное количество попыток переподключения",
|
||||
"console.scrollToBottom": "Прокрутить вниз",
|
||||
"console.manual": "Ручной",
|
||||
"console.auto": "Авто",
|
||||
"console.turnOnAuto": "Включить авто-режим",
|
||||
"console.turnOnManual": "Включить ручной режим",
|
||||
"console.noTarget": "Нет цели",
|
||||
"console.noAction": "Нет действия",
|
||||
"console.scanStarted": "Ручной скан запущен",
|
||||
"console.scanFailed": "Ручной скан не удался",
|
||||
"console.attackStarted": "Ручная атака запущена",
|
||||
"console.attackFailed": "Ручная атака не удалась",
|
||||
"console.failedToggleMode": "Не удалось переключить режим",
|
||||
"console.reconnectAttempt": "Переподключение (попытка {{count}})...",
|
||||
"quick.close": "Закрыть панель",
|
||||
"quick.connectingTo": "Подключение к {{ssid}}...",
|
||||
"quick.connectedTo": "Подключено к {{ssid}}",
|
||||
"quick.connectionFailed": "Подключение не удалось",
|
||||
"quick.loadKnownFailed": "Ошибка загрузки известных сетей",
|
||||
"quick.priorityUpdated": "Приоритет обновлен",
|
||||
"quick.priorityUpdateFailed": "Ошибка обновления приоритета",
|
||||
"quick.networkRemoved": "Сеть удалена",
|
||||
"quick.importingPotfiles": "Импорт pot-файлов...",
|
||||
"quick.importedCount": "Импортировано {{count}} паролей",
|
||||
"quick.btScanFailed": "Bluetooth-скан не удался",
|
||||
"quick.btActioning": "{{action}} для {{name}}...",
|
||||
"quick.btActionDone": "{{name}} : {{action}} выполнено",
|
||||
"quick.btActionFailed": "Ошибка при {{action}}",
|
||||
"quick.btForgotten": "{{name}} забыт",
|
||||
"sidebar.close": "Закрыть боковую панель",
|
||||
"api.aborted": "Прервано",
|
||||
"api.timeout": "Время запроса истекло",
|
||||
"api.failed": "Запрос не удался",
|
||||
"router.notFound": "Страница не найдена: {{path}}",
|
||||
"router.errorLoading": "Ошибка при загрузке страницы: {{message}}"
|
||||
}
|
||||
781
web/i18n/zh.json
Normal file
@@ -0,0 +1,781 @@
|
||||
{
|
||||
"nav.dashboard": "仪表盘",
|
||||
"nav.bjorn": "Bjorn",
|
||||
"nav.netkb": "网络基础",
|
||||
"nav.network": "网络",
|
||||
"nav.credentials": "凭据",
|
||||
"nav.vulnerabilities": "漏洞",
|
||||
"nav.attacks": "攻击",
|
||||
"nav.scheduler": "调度器",
|
||||
"nav.database": "数据库",
|
||||
"nav.files": "文件",
|
||||
"nav.loot": "战利品",
|
||||
"nav.actions": "操作",
|
||||
"nav.actionsStudio": "操作工作室",
|
||||
"nav.backup": "备份与更新",
|
||||
"nav.webEnum": "Web 枚举",
|
||||
"nav.zombieland": "僵尸乐园",
|
||||
"nav.settings": "设置",
|
||||
"nav.shortcuts": "快捷键",
|
||||
"nav.pages": "页面",
|
||||
"status.initializing": "正在初始化...",
|
||||
"status.online": "在线",
|
||||
"status.offline": "离线",
|
||||
"console.title": "控制台",
|
||||
"console.clear": "清除",
|
||||
"console.sseOn": "SSE 已开启",
|
||||
"console.sseOff": "SSE 已关闭",
|
||||
"console.newLogs": "{{count}} 条新日志",
|
||||
"settings.theme": "主题",
|
||||
"settings.language": "语言",
|
||||
"settings.general": "常规",
|
||||
"settings.toggles": "选项",
|
||||
"settings.editValue": "编辑值",
|
||||
"settings.addValues": "添加值(逗号分隔)...",
|
||||
"settings.setValue": "设置值...",
|
||||
"settings.errorLoading": "加载配置时出错",
|
||||
"settings.configSaved": "设置已保存",
|
||||
"settings.errorSaving": "保存配置时出错",
|
||||
"settings.defaultsRestored": "已恢复默认值",
|
||||
"settings.errorRestoring": "恢复默认值时出错",
|
||||
"theme.group.colors": "颜色",
|
||||
"theme.group.surfaces": "界面",
|
||||
"theme.group.layout": "布局",
|
||||
"theme.token.bg": "背景",
|
||||
"theme.token.ink": "文字颜色",
|
||||
"theme.token.accent1": "强调色 1 (酸性)",
|
||||
"theme.token.accent2": "强调色 2 (青色)",
|
||||
"theme.token.danger": "危险",
|
||||
"theme.token.warning": "警告",
|
||||
"theme.token.ok": "成功",
|
||||
"theme.token.panel": "面板",
|
||||
"theme.token.panel2": "备选面板",
|
||||
"theme.token.ctrlPanel": "控制面板",
|
||||
"theme.token.border": "边框",
|
||||
"theme.token.radius": "圆角",
|
||||
"theme.advanced": "高级 CSS",
|
||||
"theme.applyRaw": "应用",
|
||||
"theme.reset": "重置",
|
||||
"dash.title": "仪表盘",
|
||||
"dash.battery": "电池",
|
||||
"dash.internet": "互联网",
|
||||
"dash.cpu": "CPU",
|
||||
"dash.ram": "内存",
|
||||
"dash.disk": "磁盘",
|
||||
"dash.temp": "温度",
|
||||
"dash.uptime": "运行时间",
|
||||
"dash.hostsAlive": "在线主机",
|
||||
"dash.totalHosts": "主机总数",
|
||||
"dash.openPorts": "开放端口",
|
||||
"dash.credentials": "凭据",
|
||||
"dash.vulnerabilities": "漏洞",
|
||||
"dash.actions": "操作",
|
||||
"dash.connected": "已连接",
|
||||
"dash.disconnected": "已断开",
|
||||
"dash.charging": "充电中",
|
||||
"dash.discharging": "放电中",
|
||||
"dash.full": "已充满",
|
||||
"dash.connectivity": "连通性",
|
||||
"dash.liveOps": "实时操作",
|
||||
"dash.tapRefresh": "点击刷新",
|
||||
"dash.wifi": "Wi-Fi",
|
||||
"dash.ethernet": "以太网",
|
||||
"dash.usb": "USB",
|
||||
"dash.bluetooth": "蓝牙",
|
||||
"dash.mode": "模式",
|
||||
"dash.gps": "GPS",
|
||||
"dash.age": "Bjorn 运行时间",
|
||||
"dash.plugged": "已接通电源",
|
||||
"dash.noBattery": "无电池",
|
||||
"dash.sinceScan": "自上次扫描以来",
|
||||
"dash.wifiKnown": "已知 Wi-Fi",
|
||||
"dash.dataFiles": "收集的数据/文件",
|
||||
"dash.fileDescriptors": "文件句柄",
|
||||
"dash.attackScripts": "攻击脚本",
|
||||
"dash.system": "系统",
|
||||
"dash.zombies": "僵尸",
|
||||
"netkb.title": "网络知识库",
|
||||
"netkb.showOffline": "显示离线",
|
||||
"netkb.gridView": "网格",
|
||||
"netkb.listView": "列表",
|
||||
"netkb.hostname": "主机名",
|
||||
"netkb.ip": "IP 地址",
|
||||
"netkb.mac": "MAC 地址",
|
||||
"netkb.vendor": "厂商",
|
||||
"netkb.ports": "端口",
|
||||
"netkb.essid": "ESSID",
|
||||
"netkb.lastSeen": "最后上线",
|
||||
"netkb.firstSeen": "首次发现",
|
||||
"netkb.online": "在线",
|
||||
"netkb.offline": "离线",
|
||||
"netkb.openPorts": "开放端口",
|
||||
"netkb.noHosts": "未发现主机",
|
||||
"network.title": "网络可视化",
|
||||
"network.tableView": "表格",
|
||||
"network.mapView": "地图",
|
||||
"network.hostname": "主机名",
|
||||
"network.ip": "IP 地址",
|
||||
"network.mac": "MAC",
|
||||
"network.ports": "端口",
|
||||
"network.status": "状态",
|
||||
"network.searchPlaceholder": "搜索主机...",
|
||||
"network.noData": "无网络数据",
|
||||
"creds.title": "凭据",
|
||||
"creds.total": "总计",
|
||||
"creds.unique": "唯一",
|
||||
"creds.types": "类型",
|
||||
"creds.username": "用户名",
|
||||
"creds.password": "密码",
|
||||
"creds.service": "服务",
|
||||
"creds.host": "主机",
|
||||
"creds.port": "端口",
|
||||
"creds.type": "类型",
|
||||
"creds.timestamp": "时间戳",
|
||||
"creds.showPassword": "显示密码",
|
||||
"creds.hidePassword": "隐藏密码",
|
||||
"creds.copyPassword": "复制",
|
||||
"creds.exportAll": "导出全部",
|
||||
"creds.noCredentials": "未发现凭据",
|
||||
"vulns.title": "漏洞看板",
|
||||
"vulns.total": "总计",
|
||||
"vulns.critical": "紧急",
|
||||
"vulns.high": "高危",
|
||||
"vulns.medium": "中危",
|
||||
"vulns.low": "低危",
|
||||
"vulns.infoLevel": "信息",
|
||||
"vulns.host": "主机",
|
||||
"vulns.port": "端口",
|
||||
"vulns.service": "服务",
|
||||
"vulns.severity": "级别",
|
||||
"vulns.description": "描述",
|
||||
"vulns.cve": "CVE",
|
||||
"vulns.scanDate": "扫描日期",
|
||||
"vulns.details": "详情",
|
||||
"vulns.noVulns": "未发现漏洞",
|
||||
"vulns.byHost": "按主机",
|
||||
"vulns.bySeverity": "按级别",
|
||||
"vulns.byService": "按服务",
|
||||
"attacks.title": "攻击管理器",
|
||||
"attacks.running": "进行中",
|
||||
"attacks.completed": "已完成",
|
||||
"attacks.failed": "失败",
|
||||
"attacks.queued": "队列中",
|
||||
"attacks.start": "开始",
|
||||
"attacks.stop": "停止",
|
||||
"attacks.restart": "重启",
|
||||
"attacks.status": "状态",
|
||||
"attacks.target": "目标",
|
||||
"attacks.action": "操作",
|
||||
"attacks.duration": "时长",
|
||||
"attacks.progress": "进度",
|
||||
"attacks.noAttacks": "当前无进行中的攻击",
|
||||
"sched.title": "操作调度器",
|
||||
"sched.pending": "等待中",
|
||||
"sched.running": "运行中",
|
||||
"sched.done": "已完成",
|
||||
"sched.failed": "失败",
|
||||
"sched.all": "全部",
|
||||
"sched.searchPlaceholder": "搜索任务...",
|
||||
"sched.noTasks": "未发现任务",
|
||||
"sched.stats": "{{running}} 个运行中 / {{pending}} 个等待中 / {{done}} 个已完成",
|
||||
"db.title": "数据库管理器",
|
||||
"db.tables": "表",
|
||||
"db.rows": "行",
|
||||
"db.columns": "列",
|
||||
"db.search": "搜索表...",
|
||||
"db.searchRows": "搜索行...",
|
||||
"db.export": "导出",
|
||||
"db.import": "导入",
|
||||
"db.addRow": "添加行",
|
||||
"db.deleteRow": "删除行",
|
||||
"db.deleteSelected": "删除选中项",
|
||||
"db.saveChanges": "保存",
|
||||
"db.discardChanges": "放弃",
|
||||
"db.confirmDelete": "确认删除?",
|
||||
"db.noTables": "未发现表",
|
||||
"db.noData": "该表中无数据",
|
||||
"db.hide": "隐藏",
|
||||
"db.showSidebar": "显示侧边栏",
|
||||
"files.title": "文件浏览器",
|
||||
"files.gridView": "网格",
|
||||
"files.listView": "列表",
|
||||
"files.size": "大小",
|
||||
"files.modified": "修改日期",
|
||||
"files.name": "名称",
|
||||
"files.type": "类型",
|
||||
"files.download": "下载",
|
||||
"files.preview": "预览",
|
||||
"files.noFiles": "未发现文件",
|
||||
"files.parentDir": "上级目录",
|
||||
"files.searchPlaceholder": "搜索文件...",
|
||||
"loot.title": "战利品",
|
||||
"loot.directories": "目录",
|
||||
"loot.totalFiles": "文件总数",
|
||||
"loot.totalSize": "总大小",
|
||||
"loot.download": "下载",
|
||||
"loot.downloadAll": "下载全部",
|
||||
"loot.noLoot": "未发现战利品",
|
||||
"loot.explore": "探索",
|
||||
"actions.title": "操作管理器",
|
||||
"actions.available": "可用",
|
||||
"actions.enabled": "已启用",
|
||||
"actions.disabled": "已禁用",
|
||||
"actions.category": "类别",
|
||||
"actions.enableAll": "全部启用",
|
||||
"actions.disableAll": "全部禁用",
|
||||
"actions.import": "导入",
|
||||
"actions.export": "导出",
|
||||
"actions.noActions": "未发现操作",
|
||||
"actions.description": "描述",
|
||||
"actions.menu.restartService": "重启 Bjorn 服务",
|
||||
"actions.menu.deleteActionStatus": "删除所有操作状态",
|
||||
"actions.menu.clearOutput": "清空输出文件夹",
|
||||
"actions.menu.clearLogs": "清除日志",
|
||||
"actions.menu.reloadImages": "重新加载图片(实验性)",
|
||||
"actions.menu.reloadFonts": "重新加载字体",
|
||||
"actions.menu.reloadActionsJson": "重新加载操作 JSON",
|
||||
"actions.menu.initializeCsv": "初始化 CSV 文件",
|
||||
"actions.menu.clearLivestatus": "删除实时状态文件",
|
||||
"actions.menu.refreshActionsFile": "刷新操作文件",
|
||||
"actions.menu.clearNetkb": "清空网络知识库",
|
||||
"actions.menu.clearSharedConfig": "删除共享配置 JSON",
|
||||
"actions.menu.eraseMemories": "擦除 Bjorn 记忆",
|
||||
"actions.menu.reboot": "重启系统",
|
||||
"actions.menu.shutdown": "关机",
|
||||
"actions.tip.restartService": "重启 Bjorn 服务以刷新其状态。",
|
||||
"actions.tip.deleteActionStatus": "删除 netkb.csv 中操作/攻击的所有成功和失败状态。",
|
||||
"actions.tip.clearOutput": "删除输出文件夹及子文件夹中的所有文件。",
|
||||
"actions.tip.clearLogs": "删除所有系统日志文件。",
|
||||
"actions.tip.reloadImages": "重新加载系统使用的图片。",
|
||||
"actions.tip.reloadFonts": "重新加载应用程序字体。",
|
||||
"actions.tip.reloadActionsJson": "重新加载生成的 Actions.json 文件。",
|
||||
"actions.tip.initializeCsv": "重新创建 CSV 和 JSON 文件。",
|
||||
"actions.tip.clearLivestatus": "删除实时状态文件。",
|
||||
"actions.tip.refreshActionsFile": "刷新操作文件以包含新操作。",
|
||||
"actions.tip.clearNetkb": "删除网络知识库中保存的所有信息。",
|
||||
"actions.tip.clearSharedConfig": "删除共享配置 JSON 文件。",
|
||||
"actions.tip.eraseMemories": "完全擦除 Bjorn 的记忆和设置。",
|
||||
"actions.tip.reboot": "重启整个系统。",
|
||||
"actions.tip.shutdown": "完全关闭系统。",
|
||||
"actions.confirm.restartRecommended": "建议重启服务。现在重启?",
|
||||
"actions.confirm.restartService": "确认重启 Bjorn 服务?",
|
||||
"actions.confirm.deleteActionStatus": "确认删除所有保存的操作状态?",
|
||||
"actions.confirm.clearOutput": "确认清空整个输出文件夹?",
|
||||
"actions.confirm.clearLogs": "确认删除所有日志文件?",
|
||||
"actions.confirm.clearNetkb": "确认清空网络知识库?此操作不可逆。",
|
||||
"actions.confirm.clearLivestatus": "确认删除实时状态文件?",
|
||||
"actions.confirm.refreshActionsFile": "确认刷新操作文件?",
|
||||
"actions.confirm.clearSharedConfig": "确认删除共享配置 JSON?此操作不可逆。",
|
||||
"actions.confirm.eraseMemories": "确认擦除 Bjorn 的所有记忆和设置?此操作不可逆。",
|
||||
"actions.confirm.reboot": "确认重启整个系统?",
|
||||
"actions.confirm.shutdown": "确认关机?",
|
||||
"actions.msg.restartingService": "正在重启 Bjorn 服务...",
|
||||
"actions.msg.restartFailed": "重启服务失败",
|
||||
"actions.msg.actionStatusDeleted": "所有操作状态已删除。",
|
||||
"actions.msg.outputCleared": "输出文件夹已清空。",
|
||||
"actions.msg.logsCleared": "日志已清除。",
|
||||
"actions.msg.netkbCleared": "网络知识库已清空。",
|
||||
"actions.msg.livestatusDeleted": "实时状态文件已删除。",
|
||||
"actions.msg.actionsFileRefreshed": "操作文件已刷新。",
|
||||
"actions.msg.sharedConfigDeleted": "共享配置 JSON 已删除。",
|
||||
"actions.msg.memoriesErased": "Bjorn 记忆已擦除。",
|
||||
"actions.msg.rebooting": "正在重启系统...",
|
||||
"actions.msg.shuttingDown": "正在关机...",
|
||||
"actions.msg.csvInitialized": "CSV 文件已初始化。",
|
||||
"actions.msg.actionsJsonReloaded": "操作 JSON 已重新加载。",
|
||||
"actions.msg.imagesReloaded": "图片已重新加载。",
|
||||
"actions.msg.fontsReloaded": "字体已重新加载。",
|
||||
"actions.msg.unknownAction": "未知操作",
|
||||
"actions.msg.actionFailed": "操作失败",
|
||||
"studio.title": "操作工作室",
|
||||
"studio.palette": "调色板",
|
||||
"studio.canvas": "画布",
|
||||
"studio.inspector": "检查器",
|
||||
"studio.actionsTab": "操作",
|
||||
"studio.hostsTab": "主机",
|
||||
"studio.globalTab": "全局",
|
||||
"studio.save": "保存",
|
||||
"studio.load": "加载",
|
||||
"studio.run": "运行",
|
||||
"studio.clear": "清空",
|
||||
"studio.addNode": "添加节点",
|
||||
"studio.removeNode": "删除节点",
|
||||
"studio.search": "搜索操作...",
|
||||
"backup.title": "备份与更新",
|
||||
"backup.backupRestore": "备份 / 恢复",
|
||||
"backup.update": "更新",
|
||||
"backup.createBackup": "创建备份",
|
||||
"backup.restoreBackup": "恢复",
|
||||
"backup.downloadBackup": "下载",
|
||||
"backup.deleteBackup": "删除备份",
|
||||
"backup.lastBackup": "上次备份",
|
||||
"backup.checkUpdates": "检查更新",
|
||||
"backup.installUpdate": "安装更新",
|
||||
"backup.currentVersion": "当前版本",
|
||||
"backup.latestVersion": "最新版本",
|
||||
"backup.upToDate": "已是最新",
|
||||
"backup.updateAvailable": "发现更新",
|
||||
"backup.clearLogs": "清除日志",
|
||||
"backup.noBackups": "未发现备份",
|
||||
"backup.restoring": "正在恢复...",
|
||||
"backup.creating": "正在创建备份...",
|
||||
"webenum.title": "Web 枚举",
|
||||
"webenum.totalResults": "结果总数",
|
||||
"webenum.uniqueHosts": "唯一主机",
|
||||
"webenum.successCount": "成功 (2xx)",
|
||||
"webenum.errorCount": "错误 (4xx/5xx)",
|
||||
"webenum.host": "主机",
|
||||
"webenum.ip": "IP",
|
||||
"webenum.port": "端口",
|
||||
"webenum.directory": "目录",
|
||||
"webenum.status": "状态",
|
||||
"webenum.size": "大小",
|
||||
"webenum.scanDate": "扫描日期",
|
||||
"webenum.link": "链接",
|
||||
"webenum.exportJson": "导出 JSON",
|
||||
"webenum.exportCsv": "导出 CSV",
|
||||
"webenum.noResults": "未发现结果",
|
||||
"webenum.details": "结果详情",
|
||||
"webenum.openUrl": "打开 URL",
|
||||
"webenum.copyUrl": "复制 URL",
|
||||
"webenum.showing": "显示第 {{start}}-{{end}} 条,共 {{total}} 条结果",
|
||||
"webenum.itemsPerPage": "每页项数",
|
||||
"webenum.refreshData": "刷新数据",
|
||||
"webenum.responseTime": "响应时间",
|
||||
"webenum.contentType": "内容类型",
|
||||
"webenum.fullUrl": "完整 URL",
|
||||
"zombie.title": "僵尸乐园 C2C",
|
||||
"zombie.agents": "代理",
|
||||
"zombie.terminal": "终端",
|
||||
"zombie.commands": "命令",
|
||||
"zombie.totalAgents": "代理总数",
|
||||
"zombie.onlineAgents": "在线",
|
||||
"zombie.offlineAgents": "离线",
|
||||
"zombie.idleAgents": "空闲",
|
||||
"zombie.sendCommand": "发送命令",
|
||||
"zombie.broadcast": "广播",
|
||||
"zombie.selectAgent": "选择代理",
|
||||
"zombie.os": "操作系统",
|
||||
"zombie.lastSeen": "最后上线",
|
||||
"zombie.status": "状态",
|
||||
"zombie.noAgents": "无已连接的代理",
|
||||
"zombie.quickCommands": "快速命令",
|
||||
"zombie.files": "文件",
|
||||
"quick.autoScan": "自动扫描",
|
||||
"quick.connectWifi": "连接 Wi-Fi",
|
||||
"quick.knownNetworks": "已知网络",
|
||||
"quick.importPotfiles": "导入 Potfile",
|
||||
"quick.subtitle": "Wi-Fi 和蓝牙",
|
||||
"quick.pair": "配对",
|
||||
"quick.trust": "信任",
|
||||
"quick.forgetDevice": "忘记设备",
|
||||
"quick.forgetDevicePrompt": "确认忘记 {{name}}?",
|
||||
"quick.forgetNetworkPrompt": "确认忘记此网络?",
|
||||
"bjorn.title": "Bjorn EPD 屏幕",
|
||||
"bjorn.epdScreen": "电子墨水屏",
|
||||
"bjorn.refreshInterval": "刷新间隔",
|
||||
"bjorn.autoRefresh": "自动刷新",
|
||||
"bjorn.manualRefresh": "手动刷新",
|
||||
"bjorn.seconds": "秒",
|
||||
"common.search": "搜索",
|
||||
"common.filter": "过滤",
|
||||
"common.refresh": "刷新",
|
||||
"common.save": "保存",
|
||||
"common.cancel": "取消",
|
||||
"common.delete": "删除",
|
||||
"common.edit": "编辑",
|
||||
"common.close": "关闭",
|
||||
"common.loading": "正在加载...",
|
||||
"common.noData": "无可用数据",
|
||||
"common.error": "错误",
|
||||
"common.success": "成功",
|
||||
"common.confirm": "确认",
|
||||
"common.yes": "是",
|
||||
"common.no": "否",
|
||||
"common.export": "导出",
|
||||
"common.import": "导入",
|
||||
"common.download": "下载",
|
||||
"common.upload": "上传",
|
||||
"common.copy": "复制",
|
||||
"common.start": "开始",
|
||||
"common.stop": "停止",
|
||||
"common.restart": "重启",
|
||||
"common.status": "状态",
|
||||
"common.name": "名称",
|
||||
"common.value": "值",
|
||||
"common.type": "类型",
|
||||
"common.host": "主机",
|
||||
"common.port": "端口",
|
||||
"common.target": "目标",
|
||||
"common.date": "日期",
|
||||
"common.time": "时间",
|
||||
"common.size": "大小",
|
||||
"common.actions": "操作",
|
||||
"common.details": "详情",
|
||||
"common.back": "返回",
|
||||
"common.next": "下一步",
|
||||
"common.previous": "上一步",
|
||||
"common.first": "第一页",
|
||||
"common.last": "最后一页",
|
||||
"common.all": "全部",
|
||||
"common.none": "无",
|
||||
"common.showing": "显示",
|
||||
"common.of": "/",
|
||||
"common.results": "个结果",
|
||||
"common.items": "项",
|
||||
"common.page": "页码",
|
||||
"common.perPage": "每页",
|
||||
"common.sortBy": "排序方式",
|
||||
"common.ascending": "升序",
|
||||
"common.descending": "降序",
|
||||
"common.view": "视图",
|
||||
"common.table": "表格",
|
||||
"common.grid": "网格",
|
||||
"common.list": "列表",
|
||||
"common.map": "地图",
|
||||
"common.enabled": "已启用",
|
||||
"common.disabled": "已禁用",
|
||||
"common.on": "开",
|
||||
"common.off": "关",
|
||||
"common.version": "版本",
|
||||
"common.hide": "隐藏",
|
||||
"common.show": "显示",
|
||||
"common.add": "添加",
|
||||
"common.remove": "移除",
|
||||
"common.clear": "清空",
|
||||
"common.reset": "重置",
|
||||
"common.apply": "应用",
|
||||
"common.run": "运行",
|
||||
"common.send": "发送",
|
||||
"common.connect": "连接",
|
||||
"common.disconnect": "断开连接",
|
||||
"common.selectAll": "全选",
|
||||
"common.deselectAll": "全不选",
|
||||
"common.copied": "已复制!",
|
||||
"common.notFound": "未找到",
|
||||
"backup.checkUpdatesHint": "点击“检查更新”以查看版本。",
|
||||
"backup.checkingUpdates": "正在检查更新...",
|
||||
"backup.confirmFreshStart": "确认全新启动?",
|
||||
"backup.createdSuccessfully": "备份创建成功。",
|
||||
"backup.defaultUpdated": "默认备份已更新。",
|
||||
"backup.deleted": "备份已删除。",
|
||||
"backup.descriptionPlaceholder": "备份描述...",
|
||||
"backup.enterDescription": "请输入备份描述。",
|
||||
"backup.failedCheckUpdates": "检查更新失败",
|
||||
"backup.failedCreate": "创建备份失败",
|
||||
"backup.failedDelete": "删除备份失败",
|
||||
"backup.failedLoadBackups": "加载备份失败",
|
||||
"backup.failedSetDefault": "设置默认失败",
|
||||
"backup.freshStart": "全新启动",
|
||||
"backup.freshStartFailed": "全新启动失败",
|
||||
"backup.freshStartInitiated": "全新启动已启动。",
|
||||
"backup.github": "github",
|
||||
"backup.keepActions": "保留 actions 文件夹",
|
||||
"backup.keepConfig": "保留 config 文件夹",
|
||||
"backup.keepData": "保留 data 文件夹",
|
||||
"backup.keepResources": "保留 resources 文件夹",
|
||||
"backup.noBackupsCreateAbove": "未发现备份。请在上方创建。",
|
||||
"backup.restoreCompleted": "恢复完成。",
|
||||
"backup.restoreOptions": "恢复选项",
|
||||
"backup.restorePoint": "恢复点",
|
||||
"backup.selectKeepFolders": "选择操作期间要保留的文件夹:",
|
||||
"backup.setDefault": "设为默认",
|
||||
"backup.unnamedBackup": "未命名备份",
|
||||
"backup.updateInitiated": "更新已启动。",
|
||||
"backup.updateOptions": "更新选项",
|
||||
"common.confirmDiscardUnsaved": "放弃未保存的更改?",
|
||||
"common.confirmQuestion": "确认?",
|
||||
"common.default": "默认",
|
||||
"common.deleteFailed": "删除失败",
|
||||
"common.deleted": "已删除",
|
||||
"common.description": "描述",
|
||||
"common.directory": "目录",
|
||||
"common.duplicate": "复制副本",
|
||||
"common.exportJson": "导出 JSON",
|
||||
"common.failed": "失败",
|
||||
"common.file": "文件",
|
||||
"common.importJson": "导入 JSON",
|
||||
"common.new": "新建",
|
||||
"common.noMatches": "无匹配项",
|
||||
"common.options": "选项",
|
||||
"common.processingPleaseWait": "正在处理,请稍候...",
|
||||
"common.refreshed": "已刷新",
|
||||
"common.rename": "重命名",
|
||||
"common.saving": "正在保存...",
|
||||
"common.unknown": "未知",
|
||||
"common.unsavedChanges": "未保存的更改",
|
||||
"db.autoRefresh": "自动刷新",
|
||||
"db.changesDiscarded": "更改已放弃",
|
||||
"db.changesSaved": "更改已保存",
|
||||
"db.confirmDrop": "确定删除表“{{table}}”?此操作不可逆!",
|
||||
"db.confirmTruncate": "确认清空表“{{table}}”中的所有行?",
|
||||
"db.dangerZone": "危险区域",
|
||||
"db.deletingRowsCount": "正在删除 {{count}} 行...",
|
||||
"db.dropFailed": "删除表失败",
|
||||
"db.droppedTable": "表“{{table}}”已删除",
|
||||
"db.dropping": "正在删除...",
|
||||
"db.emptyTable": "空表",
|
||||
"db.errorLoadingData": "加载数据时出错",
|
||||
"db.failedLoadCatalog": "加载目录失败",
|
||||
"db.failedLoadTable": "加载表失败",
|
||||
"db.filterTables": "过滤表...",
|
||||
"db.insertFailed": "插入失败",
|
||||
"db.insertingRow": "正在插入行...",
|
||||
"db.noRowsSelected": "未选中任何行",
|
||||
"db.rowInserted": "行已插入",
|
||||
"db.rowsDeleted": "行已删除",
|
||||
"db.runningVacuum": "正在执行 VACUUM...",
|
||||
"db.saveFailed": "保存失败",
|
||||
"db.selectTableFromSidebar": "请从侧边栏选择一个表",
|
||||
"db.tableDropped": "表已删除",
|
||||
"db.tableTruncated": "表已清空",
|
||||
"db.truncateFailed": "清空失败",
|
||||
"db.truncating": "正在清空...",
|
||||
"db.vacuumComplete": "VACUUM 已完成",
|
||||
"db.vacuumDone": "VACUUM 已执行",
|
||||
"db.vacuumFailed": "VACUUM 失败",
|
||||
"files.confirmDelete": "确认删除 {{label}}“{{name}}”?",
|
||||
"files.downloadFile": "下载文件",
|
||||
"files.duplicateFailed": "创建副本失败",
|
||||
"files.duplicated": "已创建副本",
|
||||
"files.emptyDirectory": "空目录",
|
||||
"files.errorLoading": "加载文件时出错",
|
||||
"files.failedLoadDir": "加载目录失败",
|
||||
"files.filterPlaceholder": "过滤文件...",
|
||||
"files.itemsCount": "{{count}} 个项",
|
||||
"files.newNamePrompt": "新名称:",
|
||||
"files.noMatch": "无匹配文件",
|
||||
"files.openDirectory": "打开目录",
|
||||
"files.parent": ".. (上级)",
|
||||
"files.renameFailed": "重命名失败",
|
||||
"files.renamed": "已重命名",
|
||||
"files.root": "根目录",
|
||||
"files.uploadComplete": "上传完成",
|
||||
"files.uploadFailed": "上传失败",
|
||||
"files.uploadingCount": "正在上传 {{count}} 个文件...",
|
||||
"studio.actionNotFound": "未发现操作",
|
||||
"studio.classNameRequired": "类名是必填项",
|
||||
"studio.confirmDeleteAction": "确认删除操作“{{name}}”?此操作不可逆。",
|
||||
"studio.deletedName": "已删除:{{name}}",
|
||||
"studio.exportedFile": "已导出:{{name}}",
|
||||
"studio.filterActions": "过滤操作...",
|
||||
"studio.importFailed": "导入失败",
|
||||
"studio.importedFile": "已导入:{{name}}",
|
||||
"studio.loadFailed": "加载失败",
|
||||
"studio.loadedFromCacheName": "从缓存加载:{{name}}",
|
||||
"studio.loadedName": "已加载:{{name}}",
|
||||
"studio.newActionCreated": "新操作已创建",
|
||||
"studio.noActionLoaded": "未加载操作",
|
||||
"studio.saveFailedBackedUp": "保存失败(已创建本地备份)",
|
||||
"studio.savedName": "已保存:{{name}}",
|
||||
"studio.setClassBeforeExport": "导出前请先设置类名",
|
||||
"zombie.agentRemoved": "代理 {{name}} 已移除",
|
||||
"zombie.agentsPurged": "已清除 {{count}} 个代理",
|
||||
"zombie.allAgents": "所有代理",
|
||||
"zombie.c2StartedOnPort": "C2 服务器已在端口 {{port}} 启动",
|
||||
"zombie.c2Stopped": "C2 服务器已停止",
|
||||
"zombie.clearConsole": "清空控制台",
|
||||
"zombie.clearLogs": "清除日志",
|
||||
"zombie.commandBroadcasted": "命令已广播",
|
||||
"zombie.commandSentToAgents": "命令已发送至 {{count}} 个代理",
|
||||
"zombie.confirmPurgeStale": "确认清除所有超过 24 小时未上线的代理?",
|
||||
"zombie.confirmRemoveAgent": "确认移除代理 {{name}}?",
|
||||
"zombie.confirmStopC2": "确认停止 C2 服务器?",
|
||||
"zombie.consoleCleared": "控制台已清空",
|
||||
"zombie.enterC2Port": "输入 C2 端口:",
|
||||
"zombie.enterCommand": "输入命令...",
|
||||
"zombie.failedPurgeStale": "清除不活跃代理失败",
|
||||
"zombie.failedRemoveAgent": "移除代理 {{name}} 失败",
|
||||
"zombie.failedSendCommand": "发送命令失败",
|
||||
"zombie.failedStartC2": "启动 C2 失败",
|
||||
"zombie.failedStopC2": "停止 C2 失败",
|
||||
"zombie.noAgentsConnected": "无已连接的代理",
|
||||
"zombie.noAgentsMatchSearch": "无匹配的代理",
|
||||
"zombie.purgeStale": "清除不活跃代理",
|
||||
"zombie.purgeStaleHint": "清除 >24h 不活跃的代理",
|
||||
"zombie.removeAgent": "移除代理",
|
||||
"zombie.startC2": "启动 C2",
|
||||
"zombie.stopC2": "停止 C2",
|
||||
"zombie.systemLogs": "系统日志",
|
||||
"zombieland.alive": "存活",
|
||||
"zombieland.c2Status": "C2 状态",
|
||||
"zombieland.dead": "离线",
|
||||
"zombieland.totalAgents": "代理总数",
|
||||
"greeting": "你好",
|
||||
"start": "开始",
|
||||
"tick": "Tick",
|
||||
"common.ip": "IP",
|
||||
"common.mac": "MAC",
|
||||
"common.os": "系统",
|
||||
"zombie.never": "从未",
|
||||
"zombie.openInConsole": "在控制台中打开",
|
||||
"common.saved": "已保存",
|
||||
"attacks.tabs.attacks": "攻击",
|
||||
"attacks.tabs.comments": "评论",
|
||||
"attacks.tabs.images": "图片",
|
||||
"attacks.btn.addAttack": "添加攻击",
|
||||
"attacks.btn.removeAttack": "删除攻击",
|
||||
"attacks.btn.deleteAction": "删除操作",
|
||||
"attacks.btn.restoreDefaultsBundle": "恢复默认值",
|
||||
"attacks.btn.addSection": "添加章节",
|
||||
"attacks.btn.deleteSection": "删除章节",
|
||||
"attacks.btn.restoreDefault": "恢复默认",
|
||||
"attacks.btn.createCharacter": "创建角色",
|
||||
"attacks.btn.deleteCharacter": "删除角色",
|
||||
"attacks.section.characters": "角色",
|
||||
"attacks.section.statusImages": "状态图片",
|
||||
"attacks.section.staticImages": "静态图片",
|
||||
"attacks.section.webImages": "Web 图片",
|
||||
"attacks.section.actionIcons": "操作图标",
|
||||
"attacks.editor.selectAttack": "选择攻击",
|
||||
"attacks.empty.noAttacks": "未发现攻击。",
|
||||
"attacks.empty.noComments": "未发现评论。",
|
||||
"attacks.comments.placeholder": "评论将在此处显示...",
|
||||
"attacks.images.enterEditMode": "进入编辑模式",
|
||||
"attacks.images.exitEditMode": "退出编辑模式",
|
||||
"attacks.images.sortName": "排序:名称",
|
||||
"attacks.images.sortDimensions": "排序:尺寸",
|
||||
"attacks.images.search": "搜索图片...",
|
||||
"attacks.images.rename": "重命名图片",
|
||||
"attacks.images.replace": "替换图片",
|
||||
"attacks.images.resizeSelected": "调整选中项尺寸",
|
||||
"attacks.images.addCharacters": "添加角色图片",
|
||||
"attacks.images.deleteSelected": "删除选中项",
|
||||
"attacks.images.addStatus": "添加状态图片",
|
||||
"attacks.images.addStatic": "添加静态图片",
|
||||
"attacks.images.addWeb": "添加 Web 图片",
|
||||
"attacks.images.addIcon": "添加操作图标",
|
||||
"attacks.errors.loadAttacks": "加载攻击失败。",
|
||||
"attacks.errors.loadImages": "加载图片失败。",
|
||||
"attacks.confirm.switchCharacter": "切换到角色“{{name}}”?",
|
||||
"attacks.confirm.removeAttack": "删除攻击“{{name}}”?",
|
||||
"attacks.confirm.deleteAction": "删除操作“{{name}}”?",
|
||||
"attacks.confirm.restoreAttack": "将“{{name}}”恢复为默认值?",
|
||||
"attacks.confirm.restoreDefaultsBundle": "确认恢复所有默认值(操作、图片、评论)?",
|
||||
"attacks.confirm.deleteCharacter": "删除角色“{{name}}”?",
|
||||
"attacks.confirm.deleteSection": "删除章节“{{name}}”?",
|
||||
"attacks.confirm.restoreDefaultComments": "恢复默认评论?",
|
||||
"attacks.confirm.deleteSelectedImages": "删除选中的图片?",
|
||||
"attacks.prompt.newCharacterName": "新角色名称:",
|
||||
"attacks.prompt.characterToDelete": "要删除的角色:",
|
||||
"attacks.prompt.newSectionName": "新章节名称:",
|
||||
"attacks.prompt.newImageName": "新名称:",
|
||||
"attacks.prompt.resizeWidth": "缩放宽度:",
|
||||
"attacks.prompt.resizeHeight": "缩放高度:",
|
||||
"attacks.toast.characterSwitched": "已切换角色",
|
||||
"attacks.toast.attackImported": "攻击已导入",
|
||||
"attacks.toast.selectAttackFirst": "请先选择攻击",
|
||||
"attacks.toast.actionDeleted": "操作已删除",
|
||||
"attacks.toast.defaultsRestored": "已恢复默认值",
|
||||
"attacks.toast.characterCreated": "角色已创建",
|
||||
"attacks.toast.noDeletableCharacters": "无可删除的角色",
|
||||
"attacks.toast.characterDeleted": "角色已删除",
|
||||
"attacks.toast.commentsRestored": "评论已恢复",
|
||||
"attacks.toast.selectSectionFirst": "请先选择章节",
|
||||
"attacks.toast.commentsSaved": "评论已保存",
|
||||
"attacks.toast.selectExactlyOneImage": "请精确选择一张图片",
|
||||
"attacks.toast.selectAtLeastOneImage": "请至少选择一张图片",
|
||||
"attacks.toast.imagesResized": "图片尺寸已调整",
|
||||
"attacks.toast.characterImagesUploaded": "角色图片已上传",
|
||||
"attacks.toast.selectStatusActionFirst": "请先选择状态操作",
|
||||
"actions.toast.presetApplied": "预设已应用",
|
||||
"actions.toast.startingAction": "正在启动 {{name}}...",
|
||||
"actions.toast.actionStarted": "操作已启动",
|
||||
"actions.toast.stoppedByUser": "已被用户停止",
|
||||
"actions.toast.actionStopped": "操作已停止",
|
||||
"actions.toast.stopFailed": "停止失败",
|
||||
"actions.toast.failedToStop": "无法停止",
|
||||
"actions.toast.consoleCleared": "控制台已清空",
|
||||
"actions.toast.noLogsToExport": "无日志可导出",
|
||||
"actions.toast.logsExported": "日志已导出",
|
||||
"netkb.confirmRemoveAction": "确认移除 IP 为“{{ip}}”的操作“{{action}}”?",
|
||||
"netkb.actionRemoved": "操作已移除",
|
||||
"actions.running": "进行中",
|
||||
"attacks.btn.syncMissing": "同步缺失项",
|
||||
"attacks.images.gridDensity": "网格密度",
|
||||
"attacks.images.density": "密度",
|
||||
"attacks.sync.defaultComment": "为此操作添加评论",
|
||||
"attacks.sync.none": "无待同步的攻击。",
|
||||
"attacks.sync.done": "同步完成。新评论:{{comments}},状态图片:{{status}},角色图片:{{characters}}。",
|
||||
"attacks.sync.failed": "同步缺失项失败",
|
||||
"actions.args.free": "自由参数",
|
||||
"actions.args.none": "无可配置参数",
|
||||
"actions.args.subtitle": "自动根据操作定义生成",
|
||||
"actions.args.title": "参数",
|
||||
"actions.assign": "分配",
|
||||
"actions.emptyPane": "未选择操作",
|
||||
"actions.logs.completed": "完成",
|
||||
"actions.logs.empty": "目前尚无日志",
|
||||
"actions.logs.waiting": "正在等待...",
|
||||
"actions.searchPlaceholder": "搜索操作...",
|
||||
"actions.tabs.actions": "操作",
|
||||
"actions.tabs.arguments": "参数",
|
||||
"actions.toast.selectActionFirst": "请先选择操作",
|
||||
"common.move": "移动",
|
||||
"common.ready": "就绪",
|
||||
"common.menu": "菜单",
|
||||
"common.browse": "浏览...",
|
||||
"common.platform": "平台",
|
||||
"common.generate": "生成",
|
||||
"common.vendor": "厂商",
|
||||
"common.hostname": "主机名",
|
||||
"common.ports": "端口",
|
||||
"zombie.generateClient": "生成客户端",
|
||||
"zombie.checkStale": "检查不活跃代理",
|
||||
"zombie.selectedAgents": "个选中的代理",
|
||||
"zombie.clientId": "客户端 ID",
|
||||
"zombie.labCreds": "实验凭据",
|
||||
"zombie.deployOptions": "部署选项",
|
||||
"zombie.deployViaSSH": "通过 SSH 部署",
|
||||
"zombie.fileBrowser": "文件浏览器",
|
||||
"dash.lastUpdate": "最后更新",
|
||||
"netkb.searchPlaceholder": "搜索主机、IP、厂商、端口...",
|
||||
"netkb.searchHint": "提示:输入 'port:80' 或 'vendor:intel'",
|
||||
"files.dropzoneHint": "拖放文件至此处或点击上传",
|
||||
"files.moveToTitle": "移动至...",
|
||||
"files.selectDestinationFolder": "选择目标文件夹",
|
||||
"attacks.sidebar.management": "管理",
|
||||
"sched.upcoming": "即将到来",
|
||||
"sched.success": "成功",
|
||||
"sched.cancelled": "已取消",
|
||||
"sched.history": "历史",
|
||||
"sched.historyMsg": "历史日志",
|
||||
"creds.searchPlaceholder": "搜索服务、用户...",
|
||||
"creds.uniqueHosts": "唯一主机",
|
||||
"creds.totalCredentials": "凭据总数",
|
||||
"console.maxReconnect": "控制台:已达到最大重连次数",
|
||||
"console.scrollToBottom": "滚动到底部",
|
||||
"console.manual": "手动",
|
||||
"console.auto": "自动",
|
||||
"console.turnOnAuto": "开启自动模式",
|
||||
"console.turnOnManual": "开启手动模式",
|
||||
"console.noTarget": "无目标",
|
||||
"console.noAction": "无操作",
|
||||
"console.scanStarted": "手动扫描已启动",
|
||||
"console.scanFailed": "手动扫描失败",
|
||||
"console.attackStarted": "手动攻击已启动",
|
||||
"console.attackFailed": "手动攻击失败",
|
||||
"console.failedToggleMode": "切换模式失败",
|
||||
"console.reconnectAttempt": "正在重连(第 {{count}} 次尝试)...",
|
||||
"quick.close": "关闭面板",
|
||||
"quick.connectingTo": "正在连接 {{ssid}}...",
|
||||
"quick.connectedTo": "已连接到 {{ssid}}",
|
||||
"quick.connectionFailed": "连接失败",
|
||||
"quick.loadKnownFailed": "加载已知网络失败",
|
||||
"quick.priorityUpdated": "优先级已更新",
|
||||
"quick.priorityUpdateFailed": "更新优先级失败",
|
||||
"quick.networkRemoved": "网络已移除",
|
||||
"quick.importingPotfiles": "正在导入 Potfile...",
|
||||
"quick.importedCount": "已导入 {{count}} 条凭据",
|
||||
"quick.btScanFailed": "蓝牙扫描失败",
|
||||
"quick.btActioning": "正在为 {{name}} 执行 {{action}}...",
|
||||
"quick.btActionDone": "{{name}} 的 {{action}} 已完成",
|
||||
"quick.btActionFailed": "{{action}} 失败",
|
||||
"quick.btForgotten": "已忘记 {{name}}",
|
||||
"sidebar.close": "关闭侧边栏",
|
||||
"api.aborted": "已中止",
|
||||
"api.timeout": "请求超时",
|
||||
"api.failed": "请求失败",
|
||||
"router.notFound": "页面未找到:{{path}}",
|
||||
"router.errorLoading": "加载页面时出错:{{message}}"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 17 KiB |
BIN
web/images/ai_dashboard.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 22 KiB |
BIN
web/images/auto.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 24 KiB |
1024
web/index.html
721
web/js/app.js
Normal file
@@ -0,0 +1,721 @@
|
||||
/**
|
||||
* app.js — SPA bootstrap.
|
||||
* Initializes core modules, registers routes, starts the router.
|
||||
* Wires shell UI: console, quickpanel, actions, settings, launcher, pollers.
|
||||
*/
|
||||
|
||||
import * as router from './core/router.js';
|
||||
import * as i18n from './core/i18n.js';
|
||||
import * as theme from './core/theme.js';
|
||||
import { api, Poller } from './core/api.js';
|
||||
import { $, el, setText, toast } from './core/dom.js';
|
||||
import * as consoleSSE from './core/console-sse.js';
|
||||
import * as quickpanel from './core/quickpanel.js';
|
||||
import * as actions from './core/actions.js';
|
||||
import * as settingsConfig from './core/settings-config.js';
|
||||
|
||||
/* =========================================
|
||||
* 1) Initialize core modules
|
||||
* ========================================= */
|
||||
|
||||
// Theme: apply saved CSS vars immediately (no flash)
|
||||
theme.init();
|
||||
|
||||
// i18n: load translations, then boot UI
|
||||
i18n.init().then(() => {
|
||||
bootUI();
|
||||
}).catch(err => {
|
||||
console.error('[App] i18n init failed:', err);
|
||||
bootUI(); // Boot anyway with fallback keys
|
||||
});
|
||||
|
||||
function bootUI() {
|
||||
// Runtime i18n wrappers for legacy hardcoded dialogs.
|
||||
if (!window.__bjornDialogsPatched) {
|
||||
const nativeConfirm = window.confirm.bind(window);
|
||||
const nativePrompt = window.prompt.bind(window);
|
||||
window.confirm = (msg) => nativeConfirm(i18n.trLoose(String(msg ?? '')));
|
||||
window.prompt = (msg, def = '') => nativePrompt(i18n.trLoose(String(msg ?? '')), def);
|
||||
window.__bjornDialogsPatched = true;
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
* 2) Register all routes (lazy-loaded)
|
||||
* ========================================= */
|
||||
router.route('/dashboard', () => import('./pages/dashboard.js'));
|
||||
router.route('/netkb', () => import('./pages/netkb.js'));
|
||||
router.route('/network', () => import('./pages/network.js'));
|
||||
router.route('/credentials', () => import('./pages/credentials.js'));
|
||||
router.route('/vulnerabilities', () => import('./pages/vulnerabilities.js'));
|
||||
router.route('/attacks', () => import('./pages/attacks.js'));
|
||||
router.route('/scheduler', () => import('./pages/scheduler.js'));
|
||||
router.route('/database', () => import('./pages/database.js'));
|
||||
router.route('/files', () => import('./pages/files.js'));
|
||||
router.route('/loot', () => import('./pages/loot.js'));
|
||||
router.route('/actions', () => import('./pages/actions.js'));
|
||||
router.route('/actions-studio', () => import('./pages/actions-studio.js'));
|
||||
router.route('/backup', () => import('./pages/backup.js'));
|
||||
router.route('/web-enum', () => import('./pages/web-enum.js'));
|
||||
router.route('/zombieland', () => import('./pages/zombieland.js'));
|
||||
router.route('/ai-dashboard', () => import('./pages/rl-dashboard.js?t=' + Date.now()));
|
||||
router.route('/bjorn-debug', () => import('./pages/bjorn-debug.js'));
|
||||
router.route('/bjorn', () => import('./pages/bjorn.js'));
|
||||
|
||||
// 404 fallback
|
||||
router.setNotFound((container, path) => {
|
||||
container.appendChild(
|
||||
el('div', { class: 'not-found' }, [
|
||||
el('h2', {}, [i18n.t('common.notFound')]),
|
||||
el('p', {}, [`${i18n.t('common.notFound')}: ${path}`]),
|
||||
el('a', { href: '#/dashboard' }, [i18n.t('nav.dashboard')])
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
/* =========================================
|
||||
* 3) Mount language selector in topbar
|
||||
* ========================================= */
|
||||
const langContainer = $('#langSelect');
|
||||
if (langContainer) {
|
||||
i18n.mountLangSelector(langContainer);
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
* 4) Initialize router (reads hash, loads first page)
|
||||
* ========================================= */
|
||||
const appContainer = $('#app');
|
||||
router.init(appContainer);
|
||||
window.addEventListener('i18n:changed', () => {
|
||||
i18n.updateDOM(document);
|
||||
router.reloadCurrent?.();
|
||||
});
|
||||
|
||||
/* =========================================
|
||||
* 5) Wire up topbar buttons
|
||||
* ========================================= */
|
||||
wireTopbar();
|
||||
|
||||
/* =========================================
|
||||
* 6) Start global pollers (status, character, say)
|
||||
* ========================================= */
|
||||
ensureBjornProgress();
|
||||
startGlobalPollers();
|
||||
|
||||
/* =========================================
|
||||
* 7) Wire page launcher overlay
|
||||
* ========================================= */
|
||||
wireLauncher();
|
||||
|
||||
/* =========================================
|
||||
* 8) Initialize shell modules
|
||||
* ========================================= */
|
||||
consoleSSE.init();
|
||||
quickpanel.init();
|
||||
actions.init();
|
||||
|
||||
/* =========================================
|
||||
* 9) Wire bottombar extras (liveview, footer fit)
|
||||
* ========================================= */
|
||||
wireLiveview();
|
||||
setupFooterFit();
|
||||
|
||||
/* =========================================
|
||||
* 10) Wire settings modal
|
||||
* ========================================= */
|
||||
wireSettingsModal();
|
||||
|
||||
/* =========================================
|
||||
* 11) Wire chip editor
|
||||
* ========================================= */
|
||||
wireChipEditor();
|
||||
|
||||
/* =========================================
|
||||
* 12) Global toast bridge
|
||||
* ========================================= */
|
||||
window.toast = (msg, ms = 2600) => toast(msg, ms);
|
||||
|
||||
console.info('[App] Bjorn SPA initialized');
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
* Global pollers — status bar updates
|
||||
* OPTIMIZED: Staggered timings to reduce CPU load
|
||||
* ========================================= */
|
||||
function ensureBjornProgress() {
|
||||
const host = document.querySelector('.status-left .status-text');
|
||||
if (!host) return;
|
||||
|
||||
if (document.getElementById('bjornProgress')) return; // déjà là
|
||||
|
||||
const progress = el('div', {
|
||||
id: 'bjornProgress',
|
||||
class: 'bjorn-progress',
|
||||
style: 'display:none;'
|
||||
}, [
|
||||
el('div', { class: 'bjorn-progress-bar' }),
|
||||
el('span', { class: 'bjorn-progress-text' })
|
||||
]);
|
||||
|
||||
host.appendChild(progress);
|
||||
}
|
||||
|
||||
function startGlobalPollers() {
|
||||
// Status (Toutes les 6s)
|
||||
const statusPoller = new Poller(async () => {
|
||||
try {
|
||||
const data = await api.get('/bjorn_status', { timeout: 5000, retries: 0 });
|
||||
|
||||
const statusEl = $('#bjornStatus');
|
||||
const status2El = $('#bjornStatus2');
|
||||
|
||||
const progressEl = $('#bjornProgress');
|
||||
const progressBar = progressEl?.querySelector('.bjorn-progress-bar');
|
||||
const progressText = progressEl?.querySelector('.bjorn-progress-text');
|
||||
|
||||
const imgEl = $('#bjornStatusImage');
|
||||
|
||||
if (statusEl && data.status) setText(statusEl, data.status);
|
||||
|
||||
if (status2El) {
|
||||
if (data.status2) {
|
||||
setText(status2El, data.status2);
|
||||
status2El.style.display = '';
|
||||
} else {
|
||||
status2El.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 🟢 PROGRESS — show only when actively running (1-100)
|
||||
if (progressEl) {
|
||||
const pct = Number(data.progress) || 0;
|
||||
if (pct > 0) {
|
||||
progressEl.style.display = '';
|
||||
progressBar.style.setProperty('--progress', `${pct}%`);
|
||||
progressText.textContent = `${pct}%`;
|
||||
} else {
|
||||
progressEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (imgEl && data.image_path) {
|
||||
imgEl.src = data.image_path + '?t=' + Date.now();
|
||||
}
|
||||
} catch (e) { }
|
||||
}, 6000);
|
||||
|
||||
// Character (Toutes les 10s - C'est suffisant pour une icône)
|
||||
const charPoller = new Poller(async () => {
|
||||
try {
|
||||
const imgEl = $('#bjorncharacter');
|
||||
if (!imgEl) return;
|
||||
const res = await fetch('/bjorn_character');
|
||||
if (!res.ok) return;
|
||||
const blob = await res.blob();
|
||||
if (imgEl.src && imgEl.src.startsWith('blob:')) URL.revokeObjectURL(imgEl.src);
|
||||
imgEl.src = URL.createObjectURL(blob);
|
||||
} catch (e) { }
|
||||
}, 10000);
|
||||
|
||||
// Say (Toutes les 8s)
|
||||
const sayPoller = new Poller(async () => {
|
||||
try {
|
||||
const data = await api.get('/bjorn_say', { timeout: 5000, retries: 0 });
|
||||
const sayEl = $('#bjornSay');
|
||||
if (sayEl && data?.text) setText(sayEl, data.text);
|
||||
} catch (e) { }
|
||||
}, 8000);
|
||||
|
||||
statusPoller.start();
|
||||
charPoller.start();
|
||||
sayPoller.start();
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
* Topbar wiring
|
||||
* ========================================= */
|
||||
|
||||
function wireTopbar() {
|
||||
// Logo -> dashboard
|
||||
const logo = $('#logoBtn');
|
||||
if (logo) {
|
||||
logo.addEventListener('click', () => router.navigate('/dashboard'));
|
||||
logo.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); router.navigate('/dashboard'); }
|
||||
});
|
||||
}
|
||||
|
||||
// Settings button
|
||||
const settingsBtn = $('#openSettings');
|
||||
if (settingsBtn) {
|
||||
settingsBtn.addEventListener('click', () => toggleSettings());
|
||||
}
|
||||
|
||||
// Launcher button
|
||||
const launcherBtn = $('#openLauncher');
|
||||
if (launcherBtn) {
|
||||
launcherBtn.addEventListener('click', () => toggleLauncher());
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
* Liveview dropdown (BÉTON EDITION)
|
||||
* Uses recursive setTimeout to prevent thread stacking
|
||||
* ========================================= */
|
||||
|
||||
function wireLiveview() {
|
||||
const character = $('#bjorncharacter');
|
||||
const center = $('.status-center');
|
||||
if (!character || !center) return;
|
||||
|
||||
const dropdown = el('div', { class: 'bjorn-dropdown' }, [
|
||||
el('img', { id: 'screenImage_Home', src: '/web/screen.png', alt: 'Bjorn', style: 'cursor:pointer;max-width:200px;border-radius:6px' })
|
||||
]);
|
||||
center.appendChild(dropdown);
|
||||
|
||||
const liveImg = $('#screenImage_Home', dropdown);
|
||||
let timer = null;
|
||||
const LIVE_DELAY = 4000; // On passe à 4s pour matcher display.py
|
||||
|
||||
function updateLive() {
|
||||
if (dropdown.style.display !== 'block') return; // Stop si caché
|
||||
|
||||
const n = new Image();
|
||||
n.onload = () => {
|
||||
liveImg.src = n.src;
|
||||
// On ne planifie la suivante QUE quand celle-ci est affichée
|
||||
timer = setTimeout(updateLive, LIVE_DELAY);
|
||||
};
|
||||
n.onerror = () => {
|
||||
// En cas d'erreur, on attend un peu avant de réessayer
|
||||
timer = setTimeout(updateLive, LIVE_DELAY * 2);
|
||||
};
|
||||
n.src = '/web/screen.png?t=' + Date.now();
|
||||
}
|
||||
|
||||
const show = () => {
|
||||
dropdown.style.display = 'block';
|
||||
if (!timer) updateLive();
|
||||
};
|
||||
const hide = () => {
|
||||
dropdown.style.display = 'none';
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
};
|
||||
|
||||
// Events
|
||||
character.addEventListener('mouseenter', show);
|
||||
character.addEventListener('mouseleave', () => setTimeout(() => {
|
||||
if (!dropdown.matches(':hover') && !character.matches(':hover')) hide();
|
||||
}, 300));
|
||||
|
||||
character.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
dropdown.style.display === 'block' ? hide() : show();
|
||||
});
|
||||
|
||||
document.addEventListener('click', (ev) => {
|
||||
if (!dropdown.contains(ev.target) && !character.contains(ev.target)) hide();
|
||||
});
|
||||
|
||||
if (liveImg) {
|
||||
liveImg.addEventListener('click', () => router.navigate('/bjorn'));
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
* Footer text fitting (adaptive font size)
|
||||
* ========================================= */
|
||||
|
||||
function setupFooterFit() {
|
||||
function fitTextById(id, opts = {}) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
const box = el.parentElement || el;
|
||||
const max = opts.max || 12;
|
||||
const min = opts.min || 7;
|
||||
let size = max;
|
||||
el.style.fontSize = size + 'px';
|
||||
const maxH = parseFloat(getComputedStyle(el).maxHeight) || Infinity;
|
||||
|
||||
while ((el.scrollWidth > box.clientWidth || el.scrollHeight > maxH) && size > min) {
|
||||
size--;
|
||||
el.style.fontSize = size + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function runFooterFit() {
|
||||
fitTextById('bjornStatus', { max: 12, min: 7 });
|
||||
fitTextById('bjornSay', { max: 12, min: 7 });
|
||||
fitTextById('bjornStatus2', { max: 12, min: 7 });
|
||||
fitTextById('bjornProgress', { max: 11, min: 7 }); // 🟢
|
||||
}
|
||||
|
||||
// Run on load & resize
|
||||
window.addEventListener('load', runFooterFit);
|
||||
window.addEventListener('resize', runFooterFit);
|
||||
|
||||
// Observe size/content changes
|
||||
const left = document.querySelector('.status-left');
|
||||
const right = document.querySelector('.status-right');
|
||||
const ro = new ResizeObserver(runFooterFit);
|
||||
if (left) ro.observe(left);
|
||||
if (right) ro.observe(right);
|
||||
|
||||
['bjornStatus', 'bjornSay', 'bjornStatus2', 'bjornProgress'].forEach(id => {
|
||||
const elem = document.getElementById(id);
|
||||
if (!elem) return;
|
||||
ro.observe(elem);
|
||||
new MutationObserver(runFooterFit).observe(elem, {
|
||||
childList: true,
|
||||
characterData: true,
|
||||
subtree: true
|
||||
});
|
||||
});
|
||||
|
||||
const imgs = [document.getElementById('bjornStatusImage'), document.getElementById('bjorncharacter')];
|
||||
imgs.forEach(img => {
|
||||
if (!img) return;
|
||||
if (img.complete) runFooterFit();
|
||||
else img.addEventListener('load', runFooterFit, { once: true });
|
||||
});
|
||||
|
||||
// Initial run
|
||||
runFooterFit();
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
* Page launcher
|
||||
* ========================================= */
|
||||
|
||||
const NAV_MODE_KEY = 'bjorn.navMode'; // 'rail' or 'grid'
|
||||
function getNavMode() { return localStorage.getItem(NAV_MODE_KEY) || 'rail'; }
|
||||
function setNavMode(mode) { localStorage.setItem(NAV_MODE_KEY, mode); }
|
||||
|
||||
const PAGES = [
|
||||
{ path: '/dashboard', icon: 'home.png', label: 'nav.dashboard' },
|
||||
{ path: '/bjorn', icon: 'bjorn_icon.png', label: 'nav.bjorn' },
|
||||
{ path: '/netkb', icon: 'netkb.png', label: 'nav.netkb' },
|
||||
{ path: '/network', icon: 'network.png', label: 'nav.network' },
|
||||
{ path: '/credentials', icon: 'credentials.png', label: 'nav.credentials' },
|
||||
{ path: '/vulnerabilities', icon: 'vulnerabilities.png', label: 'nav.vulnerabilities' },
|
||||
{ path: '/attacks', icon: 'attacks.png', label: 'nav.attacks' },
|
||||
{ path: '/scheduler', icon: 'scheduler.png', label: 'nav.scheduler' },
|
||||
{ path: '/database', icon: 'database.png', label: 'nav.database' },
|
||||
{ path: '/files', icon: 'files_explorer.png', label: 'nav.files' },
|
||||
{ path: '/loot', icon: 'loot.png', label: 'nav.loot' },
|
||||
{ path: '/actions', icon: 'actions_launcher.png', label: 'nav.actions' },
|
||||
{ path: '/actions-studio', icon: 'actions_studio.png', label: 'nav.actionsStudio' },
|
||||
{ path: '/backup', icon: 'backup_update.png', label: 'nav.backup' },
|
||||
{ path: '/web-enum', icon: 'web_enum.png', label: 'nav.webEnum' },
|
||||
{ path: '/zombieland', icon: 'zombieland.png', label: 'nav.zombieland' },
|
||||
{ path: '/ai-dashboard', icon: 'ai_dashboard.png', label: 'nav.ai_dashboard' },
|
||||
{ path: '/bjorn-debug', icon: 'database.png', label: 'Bjorn Debug' },
|
||||
];
|
||||
|
||||
function wireLauncher() {
|
||||
const railOverlay = $('#launcher');
|
||||
const gridOverlay = $('#navOverlay');
|
||||
const navGrid = $('#navGrid');
|
||||
if (!railOverlay) return;
|
||||
|
||||
// Build rail launcher
|
||||
railOverlay.innerHTML = '';
|
||||
const scroll = el('div', { class: 'launcher-scroll' });
|
||||
for (const page of PAGES) {
|
||||
const card = el('button', {
|
||||
class: 'lbtn',
|
||||
role: 'button',
|
||||
tabindex: '0',
|
||||
title: i18n.t(page.label),
|
||||
onclick: () => {
|
||||
router.navigate(page.path);
|
||||
closeLauncher();
|
||||
},
|
||||
}, [
|
||||
el('img', { src: `/web/images/${page.icon}`, alt: '', width: '48', height: '48' }),
|
||||
el('span', { class: 'lbtn-label', 'data-i18n': page.label }, [i18n.t(page.label)]),
|
||||
]);
|
||||
scroll.appendChild(card);
|
||||
}
|
||||
railOverlay.appendChild(scroll);
|
||||
|
||||
// Build grid launcher
|
||||
if (navGrid) {
|
||||
navGrid.innerHTML = '';
|
||||
for (const page of PAGES) {
|
||||
const card = el('button', {
|
||||
class: 'lbtn',
|
||||
role: 'button',
|
||||
tabindex: '0',
|
||||
title: i18n.t(page.label),
|
||||
onclick: () => {
|
||||
router.navigate(page.path);
|
||||
closeNavOverlay();
|
||||
},
|
||||
}, [
|
||||
el('img', { src: `/web/images/${page.icon}`, alt: '', width: '48', height: '48' }),
|
||||
el('span', { class: 'lbtn-label', 'data-i18n': page.label }, [i18n.t(page.label)]),
|
||||
]);
|
||||
navGrid.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
// Close rail on outside click
|
||||
document.addEventListener('pointerdown', (e) => {
|
||||
const btn = $('#openLauncher');
|
||||
if (!railOverlay.classList.contains('show')) return;
|
||||
if (railOverlay.contains(e.target)) return;
|
||||
if (btn && btn.contains(e.target)) return;
|
||||
closeLauncher();
|
||||
});
|
||||
|
||||
// Close grid overlay on backdrop click
|
||||
if (gridOverlay) {
|
||||
gridOverlay.addEventListener('click', (e) => {
|
||||
if (e.target === gridOverlay) closeNavOverlay();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLauncher() {
|
||||
if (getNavMode() === 'grid') {
|
||||
toggleNavOverlay();
|
||||
} else {
|
||||
const overlay = $('#launcher');
|
||||
if (!overlay) return;
|
||||
const isOpen = overlay.getAttribute('aria-hidden') !== 'false';
|
||||
overlay.setAttribute('aria-hidden', String(!isOpen));
|
||||
overlay.classList.toggle('show', isOpen);
|
||||
}
|
||||
}
|
||||
|
||||
function closeLauncher() {
|
||||
const overlay = $('#launcher');
|
||||
if (!overlay) return;
|
||||
overlay.setAttribute('aria-hidden', 'true');
|
||||
overlay.classList.remove('show');
|
||||
}
|
||||
|
||||
function toggleNavOverlay() {
|
||||
const overlay = $('#navOverlay');
|
||||
if (!overlay) return;
|
||||
const isOpen = overlay.classList.contains('show');
|
||||
if (isOpen) {
|
||||
overlay.classList.remove('show');
|
||||
overlay.setAttribute('aria-hidden', 'true');
|
||||
} else {
|
||||
overlay.classList.add('show');
|
||||
overlay.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
function closeNavOverlay() {
|
||||
const overlay = $('#navOverlay');
|
||||
if (!overlay) return;
|
||||
overlay.classList.remove('show');
|
||||
overlay.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
* Settings modal (tabbed: General, Theme, Config)
|
||||
* Uses the old-style modal-backdrop + modal with tabs
|
||||
* ========================================= */
|
||||
|
||||
function wireSettingsModal() {
|
||||
// Build modal content inside #settingsBackdrop
|
||||
const backdrop = $('#settingsBackdrop');
|
||||
if (!backdrop) return;
|
||||
|
||||
function buildSettings() {
|
||||
backdrop.innerHTML = '';
|
||||
const modal = el('div', { class: 'modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Settings' });
|
||||
|
||||
// Tabs navigation
|
||||
const tabs = el('nav', { class: 'tabs', id: 'settingsTabs' });
|
||||
const btnGeneral = el('button', { class: 'tabbtn active', 'data-tab': 'general' }, ['General']);
|
||||
const btnTheme = el('button', { class: 'tabbtn', 'data-tab': 'theme' }, ['Theme']);
|
||||
const btnConfig = el('button', { class: 'tabbtn', 'data-tab': 'config' }, ['Config']);
|
||||
tabs.append(btnGeneral, btnTheme, btnConfig);
|
||||
|
||||
// General tab
|
||||
const tabGeneral = el('section', { class: 'tabpanel', id: 'tab-general' });
|
||||
tabGeneral.append(
|
||||
el('h3', {}, [i18n.t('settings.general')]),
|
||||
el('div', { class: 'row' }, [
|
||||
el('label', {}, ['Notifications']),
|
||||
el('div', { class: 'switch', id: 'switchNotifs' })
|
||||
]),
|
||||
el('div', { class: 'row' }, [
|
||||
el('label', {}, ['Navigation Mode']),
|
||||
el('select', { id: 'selectNavMode', class: 'select' }, [
|
||||
el('option', { value: 'rail' }, ['Floating Bar']),
|
||||
el('option', { value: 'grid' }, ['Grid Overlay']),
|
||||
])
|
||||
]),
|
||||
el('div', { class: 'row' }, [
|
||||
el('label', {}, [i18n.t('settings.language')]),
|
||||
])
|
||||
);
|
||||
// Set current nav mode selection
|
||||
const navModeSelect = tabGeneral.querySelector('#selectNavMode');
|
||||
if (navModeSelect) {
|
||||
navModeSelect.value = getNavMode();
|
||||
navModeSelect.addEventListener('change', () => setNavMode(navModeSelect.value));
|
||||
}
|
||||
// Mount language selector inside general tab
|
||||
const langRow = tabGeneral.querySelector('.row:last-child');
|
||||
if (langRow) i18n.mountLangSelector(langRow);
|
||||
|
||||
// Theme tab
|
||||
const tabTheme = el('section', { class: 'tabpanel', id: 'tab-theme', hidden: '' });
|
||||
tabTheme.append(el('h3', {}, [i18n.t('settings.theme')]));
|
||||
theme.mountEditor(tabTheme);
|
||||
|
||||
// Config tab
|
||||
const tabConfig = el('section', { class: 'tabpanel', id: 'tab-config', hidden: '' }, [
|
||||
el('div', { class: 'cfg-toolbar' }, [
|
||||
el('button', { class: 'btn', id: 'cfgReload' }, [i18n.t('common.refresh')]),
|
||||
el('button', { class: 'btn', id: 'cfgRestore' }, [i18n.t('common.reset')]),
|
||||
el('button', { class: 'btn btn-primary', id: 'cfgSave' }, [i18n.t('common.save')]),
|
||||
]),
|
||||
el('div', { id: 'configFormHost', class: 'cfg-host' }),
|
||||
]);
|
||||
|
||||
modal.append(tabs, tabGeneral, tabTheme, tabConfig);
|
||||
backdrop.appendChild(modal);
|
||||
|
||||
const cfgHost = modal.querySelector('#configFormHost');
|
||||
settingsConfig.mountConfig(cfgHost);
|
||||
|
||||
// Tab switching
|
||||
tabs.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.tabbtn');
|
||||
if (!btn) return;
|
||||
const tabId = btn.dataset.tab;
|
||||
tabs.querySelectorAll('.tabbtn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
modal.querySelectorAll('.tabpanel').forEach(p => p.hidden = true);
|
||||
const panel = modal.querySelector(`#tab-${tabId}`);
|
||||
if (panel) panel.hidden = false;
|
||||
if (tabId === 'config') settingsConfig.loadConfig(cfgHost);
|
||||
});
|
||||
|
||||
// Notifications switch
|
||||
const notifSwitch = modal.querySelector('#switchNotifs');
|
||||
if (notifSwitch) {
|
||||
const notifOn = localStorage.getItem('bjorn.notifs') !== 'off';
|
||||
if (notifOn) notifSwitch.classList.add('on');
|
||||
notifSwitch.addEventListener('click', () => {
|
||||
notifSwitch.classList.toggle('on');
|
||||
localStorage.setItem('bjorn.notifs', notifSwitch.classList.contains('on') ? 'on' : 'off');
|
||||
});
|
||||
}
|
||||
|
||||
// Config actions
|
||||
modal.querySelector('#cfgReload')?.addEventListener('click', () => settingsConfig.loadConfig(cfgHost));
|
||||
modal.querySelector('#cfgSave')?.addEventListener('click', () => settingsConfig.saveConfig());
|
||||
modal.querySelector('#cfgRestore')?.addEventListener('click', () => settingsConfig.restoreDefaults(cfgHost));
|
||||
}
|
||||
|
||||
// Store build function for reuse
|
||||
backdrop._buildSettings = buildSettings;
|
||||
}
|
||||
|
||||
function toggleSettings() {
|
||||
const backdrop = $('#settingsBackdrop');
|
||||
if (!backdrop) return;
|
||||
|
||||
const isOpen = backdrop.style.display === 'flex';
|
||||
if (isOpen) {
|
||||
backdrop.style.display = 'none';
|
||||
backdrop.setAttribute('aria-hidden', 'true');
|
||||
} else {
|
||||
if (backdrop._buildSettings) backdrop._buildSettings();
|
||||
backdrop.style.display = 'flex';
|
||||
backdrop.setAttribute('aria-hidden', 'false');
|
||||
setTimeout(() => {
|
||||
const m = backdrop.querySelector('.modal');
|
||||
if (m) m.classList.add('show');
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Close settings on backdrop click (persistent listener) */
|
||||
document.addEventListener('click', (e) => {
|
||||
const backdrop = $('#settingsBackdrop');
|
||||
if (!backdrop || backdrop.style.display !== 'flex') return;
|
||||
if (e.target === backdrop) toggleSettings();
|
||||
});
|
||||
|
||||
/* Close settings on Escape (persistent listener) */
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Escape') return;
|
||||
const backdrop = $('#settingsBackdrop');
|
||||
if (!backdrop || backdrop.style.display !== 'flex') return;
|
||||
toggleSettings();
|
||||
});
|
||||
|
||||
/* =========================================
|
||||
* Chip Editor (global singleton)
|
||||
* Wires the existing #chipEditBackdrop from HTML
|
||||
* ========================================= */
|
||||
|
||||
function wireChipEditor() {
|
||||
const backdrop = $('#chipEditBackdrop');
|
||||
if (!backdrop || window.ChipsEditor) return;
|
||||
|
||||
const title = $('#chipEditTitle');
|
||||
const label = $('#chipEditLabel');
|
||||
const input = $('#chipEditInput');
|
||||
const ta = $('#chipEditTextarea');
|
||||
const btnSave = $('#chipEditSave');
|
||||
const btnCancel = $('#chipEditCancel');
|
||||
const btnClose = $('#chipEditClose');
|
||||
if (!input || !ta || !btnSave) return;
|
||||
|
||||
let resolver = null;
|
||||
function show() { backdrop.classList.add('show'); requestAnimationFrame(() => (input.offsetParent ? input : ta).focus()); }
|
||||
function hide() { backdrop.classList.remove('show'); resolver = null; }
|
||||
function currentValue() { return (input.offsetParent ? input.value : ta.value).trim(); }
|
||||
function resolve(val) { if (resolver) { resolver(val); hide(); } }
|
||||
function save() { resolve(currentValue()); }
|
||||
function cancel() { resolve(null); }
|
||||
|
||||
btnSave.addEventListener('click', save);
|
||||
btnCancel.addEventListener('click', cancel);
|
||||
btnClose.addEventListener('click', cancel);
|
||||
backdrop.addEventListener('click', (e) => { if (e.target === backdrop) cancel(); });
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!backdrop.classList.contains('show')) return;
|
||||
if (e.key === 'Escape') { e.preventDefault(); cancel(); }
|
||||
if (e.key === 'Enter' && e.target.closest('#chipEditBackdrop') && e.target.id !== 'chipEditTextarea') {
|
||||
e.preventDefault(); save();
|
||||
}
|
||||
});
|
||||
|
||||
window.ChipsEditor = {
|
||||
open(opts = {}) {
|
||||
const { value = '', title: ttl = 'Edit value', label: lab = 'Value', placeholder = '', multiline = false, maxLength, confirmLabel = 'Save' } = opts;
|
||||
if (title) title.textContent = ttl;
|
||||
if (label) label.textContent = lab;
|
||||
if (btnSave) btnSave.textContent = confirmLabel;
|
||||
if (multiline) {
|
||||
ta.style.display = '';
|
||||
input.style.display = 'none';
|
||||
ta.value = value;
|
||||
ta.placeholder = placeholder;
|
||||
ta.removeAttribute('maxlength');
|
||||
if (maxLength) ta.setAttribute('maxlength', String(maxLength));
|
||||
} else {
|
||||
input.style.display = '';
|
||||
ta.style.display = 'none';
|
||||
input.value = value;
|
||||
input.placeholder = placeholder;
|
||||
input.removeAttribute('maxlength');
|
||||
if (maxLength) input.setAttribute('maxlength', String(maxLength));
|
||||
}
|
||||
show();
|
||||
return new Promise(res => { resolver = res; });
|
||||
}
|
||||
};
|
||||
}
|
||||
437
web/js/core/actions.js
Normal file
@@ -0,0 +1,437 @@
|
||||
/**
|
||||
* Actions Dropdown — ES module replacement for the monolithic global.js
|
||||
* actions/dropdown logic. Builds the dropdown menu, wires hover/touch/keyboard
|
||||
* behaviour, and dispatches action API calls.
|
||||
*/
|
||||
|
||||
import { $, el, toast } from './dom.js';
|
||||
import { api } from './api.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Dropdown item definitions */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
const dropdownItems = [
|
||||
{ action: 'restart_bjorn_service', textKey: 'actions.menu.restartService', tipKey: 'actions.tip.restartService' },
|
||||
{ action: 'remove_All_Actions', textKey: 'actions.menu.deleteActionStatus', tipKey: 'actions.tip.deleteActionStatus' },
|
||||
{ action: 'clear_output_folder', textKey: 'actions.menu.clearOutput', tipKey: 'actions.tip.clearOutput' },
|
||||
{ action: 'clear_logs', textKey: 'actions.menu.clearLogs', tipKey: 'actions.tip.clearLogs' },
|
||||
{ action: 'reload_images', textKey: 'actions.menu.reloadImages', tipKey: 'actions.tip.reloadImages' },
|
||||
{ action: 'reload_fonts', textKey: 'actions.menu.reloadFonts', tipKey: 'actions.tip.reloadFonts' },
|
||||
{ action: 'reload_generate_actions_json', textKey: 'actions.menu.reloadActionsJson', tipKey: 'actions.tip.reloadActionsJson' },
|
||||
{ action: 'initialize_csv', textKey: 'actions.menu.initializeCsv', tipKey: 'actions.tip.initializeCsv' },
|
||||
{ action: 'clear_livestatus', textKey: 'actions.menu.clearLivestatus', tipKey: 'actions.tip.clearLivestatus' },
|
||||
{ action: 'clear_actions_file', textKey: 'actions.menu.refreshActionsFile', tipKey: 'actions.tip.refreshActionsFile' },
|
||||
{ action: 'clear_netkb', textKey: 'actions.menu.clearNetkb', tipKey: 'actions.tip.clearNetkb' },
|
||||
{ action: 'clear_shared_config_json', textKey: 'actions.menu.clearSharedConfig', tipKey: 'actions.tip.clearSharedConfig' },
|
||||
{ action: 'erase_bjorn_memories', textKey: 'actions.menu.eraseMemories', tipKey: 'actions.tip.eraseMemories' },
|
||||
{ action: 'reboot_system', textKey: 'actions.menu.reboot', tipKey: 'actions.tip.reboot' },
|
||||
{ action: 'shutdown_system', textKey: 'actions.menu.shutdown', tipKey: 'actions.tip.shutdown' },
|
||||
];
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Action handlers — each returns a Promise */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Helper: after a successful action that recommends a service restart,
|
||||
* prompt the user and fire the restart if they agree.
|
||||
*/
|
||||
async function offerRestart() {
|
||||
if (confirm(t('actions.confirm.restartRecommended'))) {
|
||||
try {
|
||||
await api.post('/restart_bjorn_service');
|
||||
toast(t('actions.msg.restartingService'), 3000, 'success');
|
||||
} catch (err) {
|
||||
toast(`${t('actions.msg.restartFailed')}: ${err.message}`, 4000, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Map of action name -> handler function */
|
||||
const actionHandlers = {
|
||||
async restart_bjorn_service() {
|
||||
if (!confirm(t('actions.confirm.restartService'))) return;
|
||||
await api.post('/restart_bjorn_service');
|
||||
toast(t('actions.msg.restartingService'), 3000, 'success');
|
||||
},
|
||||
|
||||
async remove_All_Actions() {
|
||||
if (!confirm(t('actions.confirm.deleteActionStatus'))) return;
|
||||
await api.post('/delete_all_actions', { ip: '' });
|
||||
toast(t('actions.msg.actionStatusDeleted'), 3000, 'success');
|
||||
},
|
||||
|
||||
async clear_output_folder() {
|
||||
if (!confirm(t('actions.confirm.clearOutput'))) return;
|
||||
await api.post('/clear_output_folder');
|
||||
toast(t('actions.msg.outputCleared'), 3000, 'success');
|
||||
},
|
||||
|
||||
async clear_logs() {
|
||||
if (!confirm(t('actions.confirm.clearLogs'))) return;
|
||||
await api.post('/clear_logs');
|
||||
toast(t('actions.msg.logsCleared'), 3000, 'success');
|
||||
},
|
||||
|
||||
async clear_netkb() {
|
||||
if (!confirm(t('actions.confirm.clearNetkb'))) return;
|
||||
await api.post('/clear_netkb');
|
||||
toast(t('actions.msg.netkbCleared'), 3000, 'success');
|
||||
await offerRestart();
|
||||
},
|
||||
|
||||
async clear_livestatus() {
|
||||
if (!confirm(t('actions.confirm.clearLivestatus'))) return;
|
||||
await api.post('/clear_livestatus');
|
||||
toast(t('actions.msg.livestatusDeleted'), 3000, 'success');
|
||||
await offerRestart();
|
||||
},
|
||||
|
||||
async clear_actions_file() {
|
||||
if (!confirm(t('actions.confirm.refreshActionsFile'))) return;
|
||||
await api.post('/clear_actions_file');
|
||||
toast(t('actions.msg.actionsFileRefreshed'), 3000, 'success');
|
||||
await offerRestart();
|
||||
},
|
||||
|
||||
async clear_shared_config_json() {
|
||||
if (!confirm(t('actions.confirm.clearSharedConfig'))) return;
|
||||
await api.post('/clear_shared_config_json');
|
||||
toast(t('actions.msg.sharedConfigDeleted'), 3000, 'success');
|
||||
await offerRestart();
|
||||
},
|
||||
|
||||
async erase_bjorn_memories() {
|
||||
if (!confirm(t('actions.confirm.eraseMemories'))) return;
|
||||
await api.post('/erase_bjorn_memories');
|
||||
toast(t('actions.msg.memoriesErased'), 3000, 'success');
|
||||
await offerRestart();
|
||||
},
|
||||
|
||||
async reboot_system() {
|
||||
if (!confirm(t('actions.confirm.reboot'))) return;
|
||||
await api.post('/reboot_system');
|
||||
toast(t('actions.msg.rebooting'), 3000, 'success');
|
||||
},
|
||||
|
||||
async shutdown_system() {
|
||||
if (!confirm(t('actions.confirm.shutdown'))) return;
|
||||
await api.post('/shutdown_system');
|
||||
toast(t('actions.msg.shuttingDown'), 3000, 'success');
|
||||
},
|
||||
|
||||
async initialize_csv() {
|
||||
await api.post('/initialize_csv');
|
||||
toast(t('actions.msg.csvInitialized'), 3000, 'success');
|
||||
},
|
||||
|
||||
async reload_generate_actions_json() {
|
||||
await api.post('/reload_generate_actions_json');
|
||||
toast(t('actions.msg.actionsJsonReloaded'), 3000, 'success');
|
||||
},
|
||||
|
||||
async reload_images() {
|
||||
await api.post('/reload_images');
|
||||
toast(t('actions.msg.imagesReloaded'), 3000, 'success');
|
||||
},
|
||||
|
||||
async reload_fonts() {
|
||||
await api.post('/reload_fonts');
|
||||
toast(t('actions.msg.fontsReloaded'), 3000, 'success');
|
||||
},
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Dropdown open / close helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
let actionsBtn = null;
|
||||
let actionsMenu = null;
|
||||
let actionsWrap = null;
|
||||
|
||||
/** Whether the menu was explicitly toggled open via pointer/keyboard */
|
||||
let sticky = false;
|
||||
let hoverTimer = null;
|
||||
const hoverMQ = window.matchMedia('(hover: hover) and (pointer: fine)');
|
||||
|
||||
function openMenu() {
|
||||
if (!actionsMenu || !actionsBtn) return;
|
||||
actionsMenu.style.display = 'block';
|
||||
actionsMenu.hidden = false;
|
||||
actionsMenu.classList.add('open');
|
||||
actionsMenu.setAttribute('aria-hidden', 'false');
|
||||
actionsBtn.setAttribute('aria-expanded', 'true');
|
||||
placeActionsMenu();
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
if (!actionsMenu || !actionsBtn) return;
|
||||
actionsMenu.classList.remove('open');
|
||||
actionsMenu.setAttribute('aria-hidden', 'true');
|
||||
actionsBtn.setAttribute('aria-expanded', 'false');
|
||||
actionsMenu.hidden = true;
|
||||
actionsMenu.style.display = '';
|
||||
sticky = false;
|
||||
}
|
||||
|
||||
function isOpen() {
|
||||
return actionsMenu && actionsMenu.classList.contains('open');
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the dropdown menu beneath the topbar, horizontally centered.
|
||||
*/
|
||||
function placeActionsMenu() {
|
||||
if (!actionsMenu || !actionsBtn) return;
|
||||
|
||||
const btnRect = actionsBtn.getBoundingClientRect();
|
||||
const top = Math.round(btnRect.bottom + 6);
|
||||
const margin = 8;
|
||||
|
||||
actionsMenu.style.position = 'fixed';
|
||||
actionsMenu.style.top = `${top}px`;
|
||||
actionsMenu.style.left = '0px';
|
||||
actionsMenu.style.transform = 'none';
|
||||
|
||||
const menuWidth = actionsMenu.offsetWidth || 320;
|
||||
const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 1024;
|
||||
const maxLeft = Math.max(margin, viewportWidth - menuWidth - margin);
|
||||
let left = Math.round(btnRect.left + (btnRect.width - menuWidth) / 2);
|
||||
left = Math.max(margin, Math.min(maxLeft, left));
|
||||
|
||||
actionsMenu.style.left = `${left}px`;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Build the menu items into the DOM */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function buildMenu() {
|
||||
if (!actionsMenu) return;
|
||||
|
||||
// Clear any existing children (idempotent rebuild)
|
||||
while (actionsMenu.firstChild) actionsMenu.removeChild(actionsMenu.firstChild);
|
||||
|
||||
for (const item of dropdownItems) {
|
||||
const btn = el('button', {
|
||||
class: 'dropdown-item',
|
||||
role: 'menuitem',
|
||||
tabindex: '-1',
|
||||
title: t(item.tipKey),
|
||||
'data-action': item.action,
|
||||
}, [t(item.textKey)]);
|
||||
|
||||
actionsMenu.appendChild(btn);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Execute an action by name */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
async function executeAction(actionName) {
|
||||
const handler = actionHandlers[actionName];
|
||||
if (!handler) {
|
||||
toast(`${t('actions.msg.unknownAction')}: ${actionName}`, 3000, 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await handler();
|
||||
} catch (err) {
|
||||
toast(`${t('actions.msg.actionFailed')}: ${err.message}`, 4000, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Keyboard navigation helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function getMenuItems() {
|
||||
if (!actionsMenu) return [];
|
||||
return Array.from(actionsMenu.querySelectorAll('[role="menuitem"]'));
|
||||
}
|
||||
|
||||
function focusItem(items, index) {
|
||||
if (index < 0 || index >= items.length) return;
|
||||
items[index].focus();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Event wiring */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function wireEvents() {
|
||||
if (!actionsBtn || !actionsMenu || !actionsWrap) return;
|
||||
|
||||
/* -- Hover behavior (desktop only) -- */
|
||||
actionsWrap.addEventListener('mouseenter', () => {
|
||||
if (!hoverMQ.matches) return;
|
||||
if (hoverTimer) {
|
||||
clearTimeout(hoverTimer);
|
||||
hoverTimer = null;
|
||||
}
|
||||
if (!sticky) openMenu();
|
||||
});
|
||||
|
||||
actionsWrap.addEventListener('mouseleave', () => {
|
||||
if (!hoverMQ.matches) return;
|
||||
if (sticky) return;
|
||||
hoverTimer = setTimeout(() => {
|
||||
hoverTimer = null;
|
||||
if (!sticky) closeMenu();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
/* -- Button toggle (desktop + mobile) -- */
|
||||
let lastToggleTime = 0;
|
||||
function toggleFromButton(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Guard against double-firing (pointerup + click both fire on mobile tap)
|
||||
const now = Date.now();
|
||||
if (now - lastToggleTime < 300) return;
|
||||
lastToggleTime = now;
|
||||
|
||||
if (isOpen()) {
|
||||
closeMenu();
|
||||
} else {
|
||||
sticky = true;
|
||||
openMenu();
|
||||
}
|
||||
}
|
||||
actionsBtn.addEventListener('click', toggleFromButton);
|
||||
actionsBtn.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') toggleFromButton(e);
|
||||
});
|
||||
|
||||
/* -- Close on pointerdown outside -- */
|
||||
document.addEventListener('pointerdown', (e) => {
|
||||
if (!isOpen()) return;
|
||||
if (!actionsWrap.contains(e.target)) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
/* -- Menu item clicks -- */
|
||||
actionsMenu.addEventListener('click', (e) => {
|
||||
const item = e.target.closest('[data-action]');
|
||||
if (!item) return;
|
||||
const actionName = item.getAttribute('data-action');
|
||||
closeMenu();
|
||||
executeAction(actionName);
|
||||
});
|
||||
|
||||
/* -- Keyboard navigation -- */
|
||||
actionsWrap.addEventListener('keydown', (e) => {
|
||||
const items = getMenuItems();
|
||||
if (!items.length) return;
|
||||
|
||||
const currentIndex = items.indexOf(document.activeElement);
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
closeMenu();
|
||||
actionsBtn.focus();
|
||||
break;
|
||||
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
if (!isOpen()) {
|
||||
openMenu();
|
||||
focusItem(items, 0);
|
||||
} else {
|
||||
focusItem(items, currentIndex < items.length - 1 ? currentIndex + 1 : 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
if (!isOpen()) {
|
||||
openMenu();
|
||||
focusItem(items, items.length - 1);
|
||||
} else {
|
||||
focusItem(items, currentIndex > 0 ? currentIndex - 1 : items.length - 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Home':
|
||||
if (isOpen()) {
|
||||
e.preventDefault();
|
||||
focusItem(items, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'End':
|
||||
if (isOpen()) {
|
||||
e.preventDefault();
|
||||
focusItem(items, items.length - 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
if (document.activeElement && document.activeElement.hasAttribute('data-action')) {
|
||||
e.preventDefault();
|
||||
const actionName = document.activeElement.getAttribute('data-action');
|
||||
closeMenu();
|
||||
executeAction(actionName);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/* -- Reposition on resize / scroll -- */
|
||||
window.addEventListener('resize', () => {
|
||||
if (isOpen()) placeActionsMenu();
|
||||
});
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (isOpen()) placeActionsMenu();
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && isOpen()) closeMenu();
|
||||
});
|
||||
window.addEventListener('hashchange', closeMenu);
|
||||
}
|
||||
|
||||
function onLanguageChanged() {
|
||||
buildMenu();
|
||||
if (isOpen()) placeActionsMenu();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public init — idempotent */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
let _initialised = false;
|
||||
|
||||
/**
|
||||
* Initialise the Actions dropdown.
|
||||
* Safe to call once; subsequent calls are no-ops.
|
||||
*/
|
||||
export function init() {
|
||||
if (_initialised) return;
|
||||
|
||||
actionsBtn = $('#actionsBtn');
|
||||
actionsMenu = $('#actionsMenu');
|
||||
actionsWrap = $('#actionsWrap');
|
||||
|
||||
if (!actionsBtn || !actionsMenu || !actionsWrap) {
|
||||
console.warn('[actions] Required DOM elements not found; skipping init.');
|
||||
return;
|
||||
}
|
||||
|
||||
buildMenu();
|
||||
wireEvents();
|
||||
window.addEventListener('i18n:changed', onLanguageChanged);
|
||||
|
||||
_initialised = true;
|
||||
console.debug('[actions] initialised');
|
||||
}
|
||||
178
web/js/core/api.js
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* API client wrapper — fetch with timeout, abort, retry, backoff.
|
||||
* Provides Poller utility with adaptive intervals and visibility awareness.
|
||||
*/
|
||||
import { t } from './i18n.js';
|
||||
|
||||
const DEFAULT_TIMEOUT = 10000; // 10s
|
||||
const MAX_RETRIES = 2;
|
||||
const BACKOFF = [200, 800]; // ms per retry
|
||||
|
||||
/** Consistent error shape */
|
||||
class ApiError extends Error {
|
||||
constructor(message, status = 0, data = null) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
this.status = status;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Core fetch wrapper with timeout + abort + retry.
|
||||
* @param {string} url
|
||||
* @param {object} opts - fetch options + {timeout, retries, signal}
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
async function request(url, opts = {}) {
|
||||
const {
|
||||
timeout = DEFAULT_TIMEOUT,
|
||||
retries = MAX_RETRIES,
|
||||
signal: externalSignal,
|
||||
...fetchOpts
|
||||
} = opts;
|
||||
|
||||
let lastError;
|
||||
|
||||
for (let attempt = 0; attempt <= retries; attempt++) {
|
||||
const ac = new AbortController();
|
||||
const timer = setTimeout(() => ac.abort(), timeout);
|
||||
|
||||
// Link external signal if provided
|
||||
if (externalSignal) {
|
||||
if (externalSignal.aborted) { clearTimeout(timer); throw new ApiError('Aborted', 0); }
|
||||
externalSignal.addEventListener('abort', () => ac.abort(), { once: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { ...fetchOpts, signal: ac.signal });
|
||||
clearTimeout(timer);
|
||||
|
||||
if (!res.ok) {
|
||||
let body = null;
|
||||
try { body = await res.json(); } catch { /* not JSON */ }
|
||||
throw new ApiError(body?.message || res.statusText, res.status, body);
|
||||
}
|
||||
|
||||
// Parse response
|
||||
const ct = res.headers.get('content-type') || '';
|
||||
if (ct.includes('application/json')) return await res.json();
|
||||
if (ct.includes('text/')) return await res.text();
|
||||
return res;
|
||||
} catch (err) {
|
||||
clearTimeout(timer);
|
||||
lastError = err;
|
||||
|
||||
// Don't retry on abort or client errors (4xx)
|
||||
if (err.name === 'AbortError' || err.name === 'ApiError') {
|
||||
if (err.name === 'AbortError') throw new ApiError(t('api.timeout'), 0);
|
||||
if (err.status >= 400 && err.status < 500) throw err;
|
||||
}
|
||||
|
||||
// Retry with backoff for transient errors
|
||||
if (attempt < retries) {
|
||||
const delay = BACKOFF[attempt] || BACKOFF[BACKOFF.length - 1];
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new ApiError(t('api.failed'));
|
||||
}
|
||||
|
||||
/* -- Convenience methods -- */
|
||||
|
||||
export const api = {
|
||||
get(url, opts = {}) {
|
||||
return request(url, { method: 'GET', ...opts });
|
||||
},
|
||||
|
||||
post(url, data, opts = {}) {
|
||||
const isFormData = data instanceof FormData;
|
||||
return request(url, {
|
||||
method: 'POST',
|
||||
headers: isFormData ? {} : { 'Content-Type': 'application/json' },
|
||||
body: isFormData ? data : JSON.stringify(data),
|
||||
...opts
|
||||
});
|
||||
},
|
||||
|
||||
del(url, opts = {}) {
|
||||
return request(url, { method: 'DELETE', ...opts });
|
||||
},
|
||||
|
||||
ApiError
|
||||
};
|
||||
|
||||
/**
|
||||
* Poller — adaptive polling with visibility awareness.
|
||||
* Slows down when document is hidden, stops on unmount.
|
||||
*
|
||||
* Usage:
|
||||
* const p = new Poller(() => fetch('/status'), 5000);
|
||||
* p.start(); // begins polling
|
||||
* p.stop(); // stops (call in unmount)
|
||||
*/
|
||||
export class Poller {
|
||||
/**
|
||||
* @param {Function} fn - async function to call each tick
|
||||
* @param {number} interval - base interval in ms
|
||||
* @param {object} opts - { hiddenMultiplier, maxInterval, immediate }
|
||||
*/
|
||||
constructor(fn, interval, opts = {}) {
|
||||
this._fn = fn;
|
||||
this._baseInterval = interval;
|
||||
this._hiddenMultiplier = opts.hiddenMultiplier || 4;
|
||||
this._maxInterval = opts.maxInterval || 120000; // 2min cap
|
||||
this._immediate = opts.immediate !== false;
|
||||
this._timer = null;
|
||||
this._running = false;
|
||||
this._onVisibility = this._handleVisibility.bind(this);
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this._running) return;
|
||||
this._running = true;
|
||||
document.addEventListener('visibilitychange', this._onVisibility);
|
||||
if (this._immediate) this._tick();
|
||||
else this._schedule();
|
||||
console.debug(`[Poller] started (${this._baseInterval}ms)`);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this._running = false;
|
||||
clearTimeout(this._timer);
|
||||
this._timer = null;
|
||||
document.removeEventListener('visibilitychange', this._onVisibility);
|
||||
console.debug('[Poller] stopped');
|
||||
}
|
||||
|
||||
_currentInterval() {
|
||||
if (document.hidden) {
|
||||
return Math.min(this._baseInterval * this._hiddenMultiplier, this._maxInterval);
|
||||
}
|
||||
return this._baseInterval;
|
||||
}
|
||||
|
||||
async _tick() {
|
||||
if (!this._running) return;
|
||||
try {
|
||||
await this._fn();
|
||||
} catch (err) {
|
||||
console.warn('[Poller] tick error:', err.message);
|
||||
}
|
||||
this._schedule();
|
||||
}
|
||||
|
||||
_schedule() {
|
||||
if (!this._running) return;
|
||||
clearTimeout(this._timer);
|
||||
this._timer = setTimeout(() => this._tick(), this._currentInterval());
|
||||
}
|
||||
|
||||
_handleVisibility() {
|
||||
// Reschedule with adjusted interval when visibility changes
|
||||
if (this._running) this._schedule();
|
||||
}
|
||||
}
|
||||
998
web/js/core/console-sse.js
Normal file
@@ -0,0 +1,998 @@
|
||||
/**
|
||||
* Console SSE — streaming log viewer with SSE, scroll management,
|
||||
* font sizing, resize dragging, and floating UI indicators.
|
||||
*
|
||||
* Replaces the legacy BjornUI.ConsoleSSE IIFE from global.js.
|
||||
*
|
||||
* @module core/console-sse
|
||||
*/
|
||||
|
||||
import { $, el, toast } from './dom.js';
|
||||
import { api } from './api.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Constants */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
const MAX_VISIBLE_LINES = 200;
|
||||
const MAX_RECONNECT = 5;
|
||||
const RECONNECT_DELAY_MS = 2000;
|
||||
const LS_FONT_KEY = 'Console.fontPx';
|
||||
const LS_DOCK_KEY = 'Console.docked';
|
||||
const DEFAULT_FONT_PX = 12;
|
||||
const MOBILE_FONT_PX = 11;
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
/** Map canonical log-level tokens to CSS class names. */
|
||||
const LEVEL_CLASSES = {
|
||||
DEBUG: 'debug',
|
||||
INFO: 'info',
|
||||
WARNING: 'warning',
|
||||
ERROR: 'error',
|
||||
CRITICAL: 'critical',
|
||||
SUCCESS: 'success',
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Module state */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
let evtSource = null;
|
||||
let reconnectCount = 0;
|
||||
let reconnectTimer = null;
|
||||
|
||||
let isUserScrolling = false;
|
||||
let autoScroll = true;
|
||||
let lineBuffer = []; // lines held while user is scrolled up
|
||||
let isDocked = false;
|
||||
|
||||
/* Cached DOM refs (populated in init) */
|
||||
let elConsole = null;
|
||||
let elLogout = null;
|
||||
let elFontInput = null;
|
||||
let elModePill = null;
|
||||
let elModeToggle = null;
|
||||
let elAttackToggle = null;
|
||||
let elDockBtn = null;
|
||||
let elSelIp = null;
|
||||
let elSelPort = null;
|
||||
let elSelAction = null;
|
||||
let elBtnScan = null;
|
||||
let elBtnAttack = null;
|
||||
let elScrollBtn = null; // floating scroll-to-bottom button
|
||||
let elBufferBadge = null; // floating buffer count indicator
|
||||
|
||||
/* Resize drag state */
|
||||
let resizeDragging = false;
|
||||
let resizeStartY = 0;
|
||||
let resizeStartH = 0;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Deterministic hue from a string (0-359).
|
||||
* @param {string} str
|
||||
* @returns {number}
|
||||
*/
|
||||
function hueFromString(str) {
|
||||
let h = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
h = (h * 31 + str.charCodeAt(i)) >>> 0;
|
||||
}
|
||||
return h % 360;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default font size based on viewport width.
|
||||
* @returns {number}
|
||||
*/
|
||||
function defaultFontPx() {
|
||||
return window.innerWidth <= MOBILE_BREAKPOINT ? MOBILE_FONT_PX : DEFAULT_FONT_PX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the range input's background gradient so the filled portion
|
||||
* matches the current thumb position.
|
||||
* @param {HTMLInputElement} input
|
||||
*/
|
||||
function paintRangeTrack(input) {
|
||||
if (!input) return;
|
||||
const min = Number(input.min) || 0;
|
||||
const max = Number(input.max) || 100;
|
||||
const val = Number(input.value);
|
||||
const pct = ((val - min) / (max - min)) * 100;
|
||||
input.style.backgroundSize = `${pct}% 100%`;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Dock / Anchor */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function readDockPref() {
|
||||
try {
|
||||
return localStorage.getItem(LS_DOCK_KEY) === '1';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function writeDockPref(on) {
|
||||
try {
|
||||
localStorage.setItem(LS_DOCK_KEY, on ? '1' : '0');
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function syncDockSpace() {
|
||||
if (!elConsole) return;
|
||||
|
||||
const open = elConsole.classList.contains('open');
|
||||
const active = !!isDocked && !!open;
|
||||
|
||||
document.body.classList.toggle('console-docked', active);
|
||||
elConsole.classList.toggle('docked', active);
|
||||
|
||||
if (elDockBtn) {
|
||||
elDockBtn.classList.toggle('on', !!isDocked);
|
||||
elDockBtn.setAttribute('aria-pressed', String(!!isDocked));
|
||||
elDockBtn.title = isDocked ? 'Unanchor console' : 'Anchor console';
|
||||
}
|
||||
|
||||
const root = document.documentElement;
|
||||
if (!active) {
|
||||
root.style.setProperty('--console-dock-h', '0px');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reserve space equal to console height so the app container doesn't sit under it.
|
||||
const h = Math.max(0, Math.round(elConsole.getBoundingClientRect().height));
|
||||
root.style.setProperty('--console-dock-h', `${h}px`);
|
||||
}
|
||||
|
||||
function ensureDockButton() {
|
||||
if (!elConsole || elDockBtn) return;
|
||||
|
||||
const head = elConsole.querySelector('.console-head');
|
||||
const closeBtn = $('#closeConsole');
|
||||
if (!head || !closeBtn) return;
|
||||
|
||||
elDockBtn = el('button', {
|
||||
class: 'btn console-dock-btn',
|
||||
id: 'consoleDock',
|
||||
type: 'button',
|
||||
title: 'Anchor console',
|
||||
'aria-label': 'Anchor console',
|
||||
'aria-pressed': 'false',
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isDocked = !isDocked;
|
||||
writeDockPref(isDocked);
|
||||
syncDockSpace();
|
||||
},
|
||||
}, ['PIN']);
|
||||
|
||||
head.insertBefore(elDockBtn, closeBtn);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Log-line processing */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Transform a raw log line into an HTML string with highlighted
|
||||
* filenames, log levels, and numbers.
|
||||
*
|
||||
* NOTE: The log content originates from the server's own log stream;
|
||||
* it is NOT user-supplied input, so innerHTML is acceptable here.
|
||||
*
|
||||
* @param {string} line
|
||||
* @returns {string} HTML string
|
||||
*/
|
||||
function processLogLine(line) {
|
||||
// 1. Highlight *.py filenames
|
||||
line = line.replace(
|
||||
/\b([\w\-]+\.py)\b/g,
|
||||
(_match, name) => {
|
||||
const hue = hueFromString(name);
|
||||
return `<span class="logfile" style="--h:${hue}">${name}</span>`;
|
||||
}
|
||||
);
|
||||
|
||||
// 2. Highlight canonical log levels
|
||||
const levelPattern = /\b(DEBUG|INFO|WARNING|ERROR|CRITICAL|SUCCESS)\b/g;
|
||||
line = line.replace(levelPattern, (_match, lvl) => {
|
||||
const cls = LEVEL_CLASSES[lvl] || lvl.toLowerCase();
|
||||
return `<span class="loglvl ${cls}">${lvl}</span>`;
|
||||
});
|
||||
|
||||
// 3. Highlight special-case tokens
|
||||
line = line.replace(
|
||||
/\b(failed)\b/gi,
|
||||
(_m, tok) => `<span class="loglvl failed">${tok}</span>`
|
||||
);
|
||||
line = line.replace(
|
||||
/\b(Connected)\b/g,
|
||||
(_m, tok) => `<span class="loglvl connected">${tok}</span>`
|
||||
);
|
||||
line = line.replace(
|
||||
/(SSE stream closed)/g,
|
||||
(_m, tok) => `<span class="loglvl sseclosed">${tok}</span>`
|
||||
);
|
||||
|
||||
// 4. Highlight numbers that are NOT inside HTML tags
|
||||
// Strategy: split on HTML tags, process only the text segments.
|
||||
line = line.replace(
|
||||
/(<[^>]*>)|(\b\d+(?:\.\d+)?\b)/g,
|
||||
(match, tag, num) => {
|
||||
if (tag) return tag; // pass tags through
|
||||
return `<span class="number">${num}</span>`;
|
||||
}
|
||||
);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Scroll management */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function scrollToBottom() {
|
||||
if (!elLogout) return;
|
||||
elLogout.scrollTop = elLogout.scrollHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the console body is scrolled to (or near) the bottom.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isAtBottom() {
|
||||
if (!elLogout) return true;
|
||||
return elLogout.scrollTop + elLogout.clientHeight >= elLogout.scrollHeight - 8;
|
||||
}
|
||||
|
||||
/** Flush any buffered lines into the visible log. */
|
||||
function flushBuffer() {
|
||||
if (!elLogout || lineBuffer.length === 0) return;
|
||||
|
||||
for (const html of lineBuffer) {
|
||||
appendLogHtml(html, false);
|
||||
}
|
||||
lineBuffer = [];
|
||||
updateBufferBadge();
|
||||
trimLines();
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
/** Trim oldest lines if the visible count exceeds the maximum. */
|
||||
function trimLines() {
|
||||
if (!elLogout) return;
|
||||
while (elLogout.childElementCount > MAX_VISIBLE_LINES) {
|
||||
elLogout.removeChild(elLogout.firstElementChild);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append one processed HTML line into the console body.
|
||||
* @param {string} html
|
||||
* @param {boolean} shouldAutoScroll
|
||||
*/
|
||||
function appendLogHtml(html, shouldAutoScroll = true) {
|
||||
if (!elLogout) return;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'log-line';
|
||||
div.innerHTML = html;
|
||||
elLogout.appendChild(div);
|
||||
|
||||
if (shouldAutoScroll) {
|
||||
trimLines();
|
||||
if (autoScroll) scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle scroll events on the console body. */
|
||||
function onLogScroll() {
|
||||
const atBottom = isAtBottom();
|
||||
|
||||
if (!atBottom) {
|
||||
isUserScrolling = true;
|
||||
autoScroll = false;
|
||||
} else {
|
||||
isUserScrolling = false;
|
||||
autoScroll = true;
|
||||
flushBuffer();
|
||||
}
|
||||
|
||||
updateFloatingUI();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Floating UI (scroll-to-bottom button & buffer badge) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function ensureFloatingUI() {
|
||||
if (elScrollBtn) return;
|
||||
|
||||
// Scroll-to-bottom button
|
||||
elScrollBtn = el('button', {
|
||||
class: 'console-scroll-btn hidden',
|
||||
title: t('console.scrollToBottom'),
|
||||
onclick: () => forceBottom(),
|
||||
}, ['\u2193']);
|
||||
|
||||
// Buffer badge
|
||||
elBufferBadge = el('span', { class: 'console-buffer-badge hidden' }, ['0']);
|
||||
|
||||
if (elConsole) {
|
||||
elConsole.appendChild(elScrollBtn);
|
||||
elConsole.appendChild(elBufferBadge);
|
||||
}
|
||||
}
|
||||
|
||||
function updateFloatingUI() {
|
||||
if (!elScrollBtn || !elBufferBadge) return;
|
||||
|
||||
if (!autoScroll && !isAtBottom()) {
|
||||
elScrollBtn.classList.remove('hidden');
|
||||
} else {
|
||||
elScrollBtn.classList.add('hidden');
|
||||
}
|
||||
|
||||
updateBufferBadge();
|
||||
}
|
||||
|
||||
function updateBufferBadge() {
|
||||
if (!elBufferBadge) return;
|
||||
if (lineBuffer.length > 0) {
|
||||
elBufferBadge.textContent = String(lineBuffer.length);
|
||||
elBufferBadge.classList.remove('hidden');
|
||||
} else {
|
||||
elBufferBadge.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* SSE connection */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function connectSSE() {
|
||||
if (evtSource) return;
|
||||
|
||||
evtSource = new EventSource('/stream_logs');
|
||||
|
||||
evtSource.onmessage = (evt) => {
|
||||
reconnectCount = 0; // healthy connection resets counter
|
||||
|
||||
const raw = evt.data;
|
||||
if (!raw) return;
|
||||
|
||||
// Detect Mode Change Logs (Server -> Client Push)
|
||||
// Log format: "... - Operation mode switched to: AI"
|
||||
if (raw.includes('Operation mode switched to:')) {
|
||||
const parts = raw.split('Operation mode switched to:');
|
||||
if (parts.length > 1) {
|
||||
const newMode = parts[1].trim().split(' ')[0]; // Take first word just in case
|
||||
setModeUI(newMode);
|
||||
}
|
||||
}
|
||||
|
||||
// --- NEW: AI Dashboard Real-time Events ---
|
||||
if (raw.includes('[AI_EXEC]')) {
|
||||
try {
|
||||
const json = raw.split('[AI_EXEC]')[1].trim();
|
||||
const data = JSON.parse(json);
|
||||
window.dispatchEvent(new CustomEvent('bjorn:ai_exec', { detail: data }));
|
||||
} catch (e) { console.warn('[ConsoleSSE] Failed to parse AI_EXEC:', e); }
|
||||
}
|
||||
if (raw.includes('[AI_DONE]')) {
|
||||
try {
|
||||
const json = raw.split('[AI_DONE]')[1].trim();
|
||||
const data = JSON.parse(json);
|
||||
window.dispatchEvent(new CustomEvent('bjorn:ai_done', { detail: data }));
|
||||
} catch (e) { console.warn('[ConsoleSSE] Failed to parse AI_DONE:', e); }
|
||||
}
|
||||
|
||||
const html = processLogLine(raw);
|
||||
if (isUserScrolling && !autoScroll) {
|
||||
lineBuffer.push(html);
|
||||
updateBufferBadge();
|
||||
} else {
|
||||
appendLogHtml(html);
|
||||
}
|
||||
};
|
||||
|
||||
evtSource.onerror = () => {
|
||||
disconnectSSE();
|
||||
scheduleReconnect();
|
||||
};
|
||||
}
|
||||
|
||||
function disconnectSSE() {
|
||||
if (evtSource) {
|
||||
evtSource.close();
|
||||
evtSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleReconnect() {
|
||||
if (reconnectTimer) return;
|
||||
if (reconnectCount >= MAX_RECONNECT) {
|
||||
toast(t('console.maxReconnect'), 4000, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
reconnectCount++;
|
||||
reconnectTimer = setTimeout(() => {
|
||||
reconnectTimer = null;
|
||||
// Only reconnect if console is still open
|
||||
if (elConsole && elConsole.classList.contains('open')) {
|
||||
connectSSE();
|
||||
}
|
||||
}, RECONNECT_DELAY_MS);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Font size */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Set the console font size in pixels. Clamped to the range input's
|
||||
* min/max bounds. Persisted to localStorage.
|
||||
* @param {number|string} px
|
||||
*/
|
||||
export function setFont(px) {
|
||||
if (!elConsole || !elFontInput) return;
|
||||
|
||||
const min = Number(elFontInput.min) || 2;
|
||||
const max = Number(elFontInput.max) || 24;
|
||||
let val = Math.round(Number(px));
|
||||
if (Number.isNaN(val)) val = defaultFontPx();
|
||||
val = Math.max(min, Math.min(max, val));
|
||||
|
||||
elConsole.style.setProperty('--console-font', `${val}px`);
|
||||
elFontInput.value = val;
|
||||
paintRangeTrack(elFontInput);
|
||||
|
||||
try {
|
||||
localStorage.setItem(LS_FONT_KEY, String(val));
|
||||
} catch { /* storage full / blocked */ }
|
||||
}
|
||||
|
||||
/** Load saved font size or apply sensible default. */
|
||||
function loadFont() {
|
||||
let saved = null;
|
||||
try {
|
||||
saved = localStorage.getItem(LS_FONT_KEY);
|
||||
} catch { /* blocked */ }
|
||||
|
||||
const px = saved !== null ? Number(saved) : defaultFontPx();
|
||||
setFont(px);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Console resize (drag) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function onResizeStart(e) {
|
||||
e.preventDefault();
|
||||
resizeDragging = true;
|
||||
resizeStartY = e.type.startsWith('touch') ? e.touches[0].clientY : e.clientY;
|
||||
resizeStartH = elConsole ? elConsole.offsetHeight : 0;
|
||||
|
||||
document.addEventListener('mousemove', onResizeMove);
|
||||
document.addEventListener('mouseup', onResizeEnd);
|
||||
document.addEventListener('touchmove', onResizeMove, { passive: false });
|
||||
document.addEventListener('touchend', onResizeEnd);
|
||||
}
|
||||
|
||||
function onResizeMove(e) {
|
||||
if (!resizeDragging || !elConsole) return;
|
||||
e.preventDefault();
|
||||
|
||||
const clientY = e.type.startsWith('touch') ? e.touches[0].clientY : e.clientY;
|
||||
const delta = resizeStartY - clientY; // drag up = larger
|
||||
const newH = Math.max(80, resizeStartH + delta); // floor at 80px
|
||||
elConsole.style.height = `${newH}px`;
|
||||
if (isDocked) syncDockSpace();
|
||||
}
|
||||
|
||||
function onResizeEnd() {
|
||||
resizeDragging = false;
|
||||
document.removeEventListener('mousemove', onResizeMove);
|
||||
document.removeEventListener('mouseup', onResizeEnd);
|
||||
document.removeEventListener('touchmove', onResizeMove);
|
||||
document.removeEventListener('touchend', onResizeEnd);
|
||||
if (isDocked) syncDockSpace();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Mode / Attack toggles */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
|
||||
/**
|
||||
* Set the Mode UI based on the mode string: 'MANUAL', 'AUTO', or 'AI'.
|
||||
* @param {string} mode
|
||||
*/
|
||||
function setModeUI(mode) {
|
||||
if (!elModePill || !elModeToggle) return;
|
||||
|
||||
// Normalize
|
||||
mode = String(mode || 'AUTO').toUpperCase().trim();
|
||||
if (mode === 'TRUE') mode = 'MANUAL'; // Legacy fallback
|
||||
if (mode === 'FALSE') mode = 'AUTO'; // Legacy fallback
|
||||
|
||||
// Default to AUTO if unrecognized
|
||||
if (!['MANUAL', 'AUTO', 'AI'].includes(mode)) {
|
||||
mode = 'AUTO';
|
||||
}
|
||||
|
||||
const isManual = mode === 'MANUAL';
|
||||
const isAi = mode === 'AI';
|
||||
|
||||
// Pill classes
|
||||
elModePill.classList.remove('manual', 'auto', 'ai');
|
||||
if (isManual) {
|
||||
elModePill.classList.add('manual');
|
||||
} else if (isAi) {
|
||||
elModePill.classList.add('ai');
|
||||
} else {
|
||||
elModePill.classList.add('auto');
|
||||
}
|
||||
|
||||
// Pill Text
|
||||
let pillText = t('console.auto');
|
||||
if (isManual) pillText = t('console.manual');
|
||||
if (isAi) pillText = 'AI Mode';
|
||||
|
||||
elModePill.innerHTML = `<span class="dot"></span> ${pillText}`;
|
||||
|
||||
// Toggle Button Text (Show what NEXT click does)
|
||||
// Cycle: MANUAL -> AUTO -> AI -> MANUAL
|
||||
if (isManual) {
|
||||
elModeToggle.textContent = 'Enable Auto';
|
||||
} else if (isAi) {
|
||||
elModeToggle.textContent = 'Stop (Manual)'; // AI -> Manual is safer "Stop"
|
||||
} else {
|
||||
// Auto
|
||||
elModeToggle.textContent = 'Enable AI';
|
||||
}
|
||||
|
||||
elModeToggle.setAttribute('aria-pressed', String(isManual));
|
||||
showAttackForMode(isManual);
|
||||
}
|
||||
|
||||
function showAttackForMode(isManual) {
|
||||
const attackBar = $('#attackBar');
|
||||
if (!elConsole || !attackBar) return;
|
||||
const visible = !!isManual && window.innerWidth > 700;
|
||||
elConsole.classList.toggle('with-attack', visible);
|
||||
attackBar.style.display = visible ? 'flex' : 'none';
|
||||
if (elAttackToggle) elAttackToggle.setAttribute('aria-expanded', String(visible));
|
||||
}
|
||||
|
||||
|
||||
async function refreshModeFromServer() {
|
||||
try {
|
||||
// Returns "MANUAL", "AUTO", or "AI" string (text/plain)
|
||||
// We must await .text() if the api wrapper returns the fetch response,
|
||||
// but the 'api' helper usually returns parsed JSON or text based on content-type.
|
||||
// Let's assume api.get returns the direct body.
|
||||
// We'll treat it as string and trim it.
|
||||
let mode = await api.get('/check_manual_mode', { timeout: 5000, retries: 0 });
|
||||
|
||||
if (typeof mode === 'string') {
|
||||
mode = mode.trim().replace(/^"|"$/g, ''); // Remove quotes if JSON encoded
|
||||
}
|
||||
|
||||
setModeUI(mode);
|
||||
} catch (e) {
|
||||
// Keep UI as-is
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function loadManualTargets() {
|
||||
if (!elSelIp || !elSelPort || !elSelAction) return;
|
||||
try {
|
||||
const data = await api.get('/netkb_data_json', { timeout: 10000, retries: 0 });
|
||||
const ips = Array.isArray(data?.ips) ? data.ips : [];
|
||||
const actions = Array.isArray(data?.actions) ? data.actions : [];
|
||||
const portsByIp = data?.ports && typeof data.ports === 'object' ? data.ports : {};
|
||||
|
||||
const currentIp = elSelIp.value;
|
||||
const currentAction = elSelAction.value;
|
||||
|
||||
elSelIp.innerHTML = '';
|
||||
if (!ips.length) {
|
||||
const op = document.createElement('option');
|
||||
op.value = '';
|
||||
op.textContent = t('console.noTarget');
|
||||
elSelIp.appendChild(op);
|
||||
} else {
|
||||
for (const ip of ips) {
|
||||
const op = document.createElement('option');
|
||||
op.value = String(ip);
|
||||
op.textContent = String(ip);
|
||||
elSelIp.appendChild(op);
|
||||
}
|
||||
if (currentIp && ips.includes(currentIp)) elSelIp.value = currentIp;
|
||||
}
|
||||
|
||||
elSelAction.innerHTML = '';
|
||||
if (!actions.length) {
|
||||
const op = document.createElement('option');
|
||||
op.value = '';
|
||||
op.textContent = t('console.noAction');
|
||||
elSelAction.appendChild(op);
|
||||
} else {
|
||||
for (const action of actions) {
|
||||
const op = document.createElement('option');
|
||||
op.value = String(action);
|
||||
op.textContent = String(action);
|
||||
elSelAction.appendChild(op);
|
||||
}
|
||||
if (currentAction && actions.includes(currentAction)) elSelAction.value = currentAction;
|
||||
}
|
||||
|
||||
updatePortsForSelectedIp(portsByIp);
|
||||
} catch {
|
||||
// Keep existing options if loading fails.
|
||||
}
|
||||
}
|
||||
|
||||
function updatePortsForSelectedIp(cachedPortsByIp = null) {
|
||||
if (!elSelIp || !elSelPort) return;
|
||||
const render = (ports) => {
|
||||
elSelPort.innerHTML = '';
|
||||
const list = Array.isArray(ports) ? ports : [];
|
||||
if (!list.length) {
|
||||
const op = document.createElement('option');
|
||||
op.value = '';
|
||||
op.textContent = t('console.auto');
|
||||
elSelPort.appendChild(op);
|
||||
return;
|
||||
}
|
||||
for (const p of list) {
|
||||
const op = document.createElement('option');
|
||||
op.value = String(p);
|
||||
op.textContent = String(p);
|
||||
elSelPort.appendChild(op);
|
||||
}
|
||||
};
|
||||
|
||||
if (cachedPortsByIp && typeof cachedPortsByIp === 'object') {
|
||||
render(cachedPortsByIp[elSelIp.value]);
|
||||
return;
|
||||
}
|
||||
|
||||
api.get('/netkb_data_json', { timeout: 10000, retries: 0 })
|
||||
.then((data) => render(data?.ports?.[elSelIp.value]))
|
||||
.catch(() => render([]));
|
||||
}
|
||||
|
||||
async function runManualScan() {
|
||||
if (!elBtnScan) return;
|
||||
elBtnScan.classList.add('scanning');
|
||||
try {
|
||||
await api.post('/manual_scan');
|
||||
toast(t('console.scanStarted'), 1600, 'success');
|
||||
} catch {
|
||||
toast(t('console.scanFailed'), 2500, 'error');
|
||||
} finally {
|
||||
setTimeout(() => elBtnScan?.classList.remove('scanning'), 800);
|
||||
}
|
||||
}
|
||||
|
||||
async function runManualAttack() {
|
||||
if (!elBtnAttack) return;
|
||||
elBtnAttack.classList.add('attacking');
|
||||
try {
|
||||
await api.post('/manual_attack', {
|
||||
ip: elSelIp?.value || '',
|
||||
port: elSelPort?.value || '',
|
||||
action: elSelAction?.value || '',
|
||||
});
|
||||
toast(t('console.attackStarted'), 1600, 'success');
|
||||
} catch {
|
||||
toast(t('console.attackFailed'), 2500, 'error');
|
||||
} finally {
|
||||
setTimeout(() => elBtnAttack?.classList.remove('attacking'), 900);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleMode() {
|
||||
if (!elModePill) return;
|
||||
|
||||
// Determine current mode from class
|
||||
let current = 'AUTO';
|
||||
if (elModePill.classList.contains('manual')) current = 'MANUAL';
|
||||
if (elModePill.classList.contains('ai')) current = 'AI';
|
||||
|
||||
// Cycle: MANUAL -> AUTO -> AI -> MANUAL
|
||||
let next = 'AUTO';
|
||||
if (current === 'MANUAL') next = 'AUTO';
|
||||
else if (current === 'AUTO') next = 'AI';
|
||||
else if (current === 'AI') next = 'MANUAL';
|
||||
|
||||
try {
|
||||
// Use the new centralized config endpoint
|
||||
const res = await api.post('/api/rl/config', { mode: next });
|
||||
if (res && res.status === 'ok') {
|
||||
setModeUI(res.mode);
|
||||
toast(`Mode: ${res.mode}`, 2000, 'success');
|
||||
} else {
|
||||
toast('Failed to change mode', 3000, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast(t('console.failedToggleMode'), 3000, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleModeQuick() {
|
||||
if (!elModePill) return;
|
||||
|
||||
// Quick toggle intended for the pill:
|
||||
// AI <-> AUTO (MANUAL -> AUTO).
|
||||
let current = 'AUTO';
|
||||
if (elModePill.classList.contains('manual')) current = 'MANUAL';
|
||||
if (elModePill.classList.contains('ai')) current = 'AI';
|
||||
|
||||
let next = 'AUTO';
|
||||
if (current === 'AI') next = 'AUTO';
|
||||
else if (current === 'AUTO') next = 'AI';
|
||||
else if (current === 'MANUAL') next = 'AUTO';
|
||||
|
||||
try {
|
||||
const res = await api.post('/api/rl/config', { mode: next });
|
||||
if (res && res.status === 'ok') {
|
||||
setModeUI(res.mode);
|
||||
toast(`Mode: ${res.mode}`, 2000, 'success');
|
||||
} else {
|
||||
toast('Failed to change mode', 3000, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast(t('console.failedToggleMode'), 3000, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAttackBar() {
|
||||
const attackBar = $('#attackBar');
|
||||
if (!elConsole || !attackBar) return;
|
||||
const on = !elConsole.classList.contains('with-attack');
|
||||
elConsole.classList.toggle('with-attack', on);
|
||||
attackBar.style.display = on ? 'flex' : 'none';
|
||||
if (elAttackToggle) elAttackToggle.setAttribute('aria-expanded', String(on));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Console open / close */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Open the console panel and start the SSE stream.
|
||||
*/
|
||||
export function openConsole() {
|
||||
if (!elConsole) return;
|
||||
elConsole.classList.add('open');
|
||||
reconnectCount = 0;
|
||||
start();
|
||||
syncDockSpace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the console panel and stop the SSE stream.
|
||||
*/
|
||||
export function closeConsole() {
|
||||
if (!elConsole) return;
|
||||
elConsole.classList.remove('open');
|
||||
stop();
|
||||
syncDockSpace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the console between open and closed states.
|
||||
*/
|
||||
export function toggleConsole() {
|
||||
if (!elConsole) return;
|
||||
if (elConsole.classList.contains('open')) {
|
||||
closeConsole();
|
||||
} else {
|
||||
openConsole();
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public API */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Start the SSE log stream (idempotent).
|
||||
*/
|
||||
export function start() {
|
||||
connectSSE();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the SSE log stream and clear reconnect state.
|
||||
*/
|
||||
export function stop() {
|
||||
disconnectSSE();
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer);
|
||||
reconnectTimer = null;
|
||||
}
|
||||
reconnectCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the SSE stream on/off.
|
||||
*/
|
||||
export function toggle() {
|
||||
if (evtSource) {
|
||||
stop();
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the console to scroll to the bottom, flushing any buffered
|
||||
* lines and re-enabling auto-scroll.
|
||||
*/
|
||||
export function forceBottom() {
|
||||
autoScroll = true;
|
||||
isUserScrolling = false;
|
||||
flushBuffer();
|
||||
scrollToBottom();
|
||||
updateFloatingUI();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Initialisation */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Initialise the console SSE module.
|
||||
* Wires up all event listeners, loads persisted state, and checks
|
||||
* whether the console should auto-start.
|
||||
*/
|
||||
export function init() {
|
||||
/* Cache DOM references */
|
||||
elConsole = $('#console');
|
||||
elLogout = $('#logout');
|
||||
elFontInput = $('#consoleFont');
|
||||
elModePill = $('#modePill');
|
||||
elModeToggle = $('#modeToggle');
|
||||
elAttackToggle = $('#attackToggle');
|
||||
elSelIp = $('#selIP');
|
||||
elSelPort = $('#selPort');
|
||||
elSelAction = $('#selAction');
|
||||
elBtnScan = $('#btnScan');
|
||||
elBtnAttack = $('#btnAttack');
|
||||
|
||||
if (!elConsole || !elLogout) {
|
||||
console.warn('[ConsoleSSE] Required DOM elements not found — aborting init.');
|
||||
return;
|
||||
}
|
||||
|
||||
/* Floating UI (scroll-to-bottom btn, buffer badge) */
|
||||
ensureFloatingUI();
|
||||
isDocked = readDockPref();
|
||||
ensureDockButton();
|
||||
syncDockSpace();
|
||||
|
||||
/* -- Font size --------------------------------------------------- */
|
||||
loadFont();
|
||||
if (elFontInput) {
|
||||
elFontInput.addEventListener('input', () => setFont(elFontInput.value));
|
||||
}
|
||||
|
||||
/* -- Close / Clear ----------------------------------------------- */
|
||||
const btnClose = $('#closeConsole');
|
||||
if (btnClose) btnClose.addEventListener('click', closeConsole);
|
||||
|
||||
const btnClear = $('#clearLogs');
|
||||
if (btnClear) {
|
||||
btnClear.addEventListener('click', () => {
|
||||
if (elLogout) {
|
||||
while (elLogout.firstChild) elLogout.removeChild(elLogout.firstChild);
|
||||
}
|
||||
lineBuffer = [];
|
||||
updateBufferBadge();
|
||||
});
|
||||
}
|
||||
|
||||
/* -- Old behavior: click bottombar to toggle console ------------- */
|
||||
const bottomBar = $('#bottombar');
|
||||
if (bottomBar) {
|
||||
bottomBar.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
if (!(target instanceof Element)) return;
|
||||
// Avoid hijacking liveview interactions.
|
||||
if (target.closest('#bjorncharacter') || target.closest('.bjorn-dropdown')) return;
|
||||
toggleConsole();
|
||||
});
|
||||
}
|
||||
|
||||
/* -- Mode toggle ------------------------------------------------- */
|
||||
if (elModeToggle) {
|
||||
elModeToggle.addEventListener('click', toggleMode);
|
||||
}
|
||||
if (elModePill) {
|
||||
elModePill.addEventListener('click', (e) => {
|
||||
// Prevent bubbling to bottom bar toggle (if nested)
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleModeQuick();
|
||||
});
|
||||
}
|
||||
|
||||
/* -- Attack bar toggle ------------------------------------------- */
|
||||
if (elAttackToggle) {
|
||||
elAttackToggle.addEventListener('click', toggleAttackBar);
|
||||
}
|
||||
|
||||
if (elSelIp) {
|
||||
elSelIp.addEventListener('change', () => updatePortsForSelectedIp());
|
||||
}
|
||||
if (elBtnScan) {
|
||||
elBtnScan.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
runManualScan();
|
||||
});
|
||||
}
|
||||
if (elBtnAttack) {
|
||||
elBtnAttack.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
runManualAttack();
|
||||
});
|
||||
}
|
||||
|
||||
/* -- Scroll tracking --------------------------------------------- */
|
||||
elLogout.addEventListener('scroll', onLogScroll);
|
||||
|
||||
/* -- Console resize ---------------------------------------------- */
|
||||
const elResize = $('#consoleResize');
|
||||
if (elResize) {
|
||||
elResize.addEventListener('mousedown', onResizeStart);
|
||||
elResize.addEventListener('touchstart', onResizeStart, { passive: false });
|
||||
}
|
||||
|
||||
/* -- Keyboard shortcut: Ctrl + ` to toggle console --------------- */
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.key === '`') {
|
||||
e.preventDefault();
|
||||
toggleConsole();
|
||||
}
|
||||
});
|
||||
|
||||
/* -- Autostart check --------------------------------------------- */
|
||||
loadManualTargets();
|
||||
refreshModeFromServer();
|
||||
window.addEventListener('resize', () => refreshModeFromServer());
|
||||
|
||||
// BroadcastChannel for instant Tab-to-Tab sync
|
||||
const bc = new BroadcastChannel('bjorn_mode_sync');
|
||||
bc.onmessage = (ev) => {
|
||||
if (ev.data && ev.data.mode) {
|
||||
setModeUI(ev.data.mode);
|
||||
}
|
||||
};
|
||||
|
||||
checkAutostart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server to determine if the console should auto-start.
|
||||
*/
|
||||
async function checkAutostart() {
|
||||
// Keep console closed by default when the web UI loads.
|
||||
// It can still be opened manually by the user.
|
||||
closeConsole();
|
||||
}
|
||||