Files
Jiří Stachandaider 77febb993e feat: add backend caching and pagination for resources
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 18:01:48 +02:00

279 lines
12 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const contextSelect = document.getElementById('context');
const namespaceSelect = document.getElementById('namespace');
const resourceSelect = document.getElementById('resource-type');
const resourceList = document.getElementById('resource-list');
const resourceTitle = document.getElementById('resource-title');
const searchInput = document.getElementById('search-input');
const searchBtn = document.getElementById('search-btn');
const refreshBtn = document.getElementById('refresh-btn');
const setDefaultBtn = document.getElementById('set-default');
const actionName = document.getElementById('action-name');
const actionOutput = document.getElementById('action-output');
let currentPage = 1;
const pageSize = 10;
function saveSession() {
sessionStorage.setItem('k8s_context', contextSelect.value);
sessionStorage.setItem('k8s_namespace', namespaceSelect.value);
sessionStorage.setItem('k8s_resource', resourceSelect.value);
}
function loadSession() {
const ctx = sessionStorage.getItem('k8s_context');
const ns = sessionStorage.getItem('k8s_namespace');
const res = sessionStorage.getItem('k8s_resource');
if (ctx) contextSelect.value = ctx;
if (ns) namespaceSelect.value = ns;
if (res) resourceSelect.value = res;
}
function updateActionVisibility() {
const isNode = resourceSelect.value === 'nodes';
const isConfigmap = resourceSelect.value === 'configmaps';
document.getElementById('btn-delete').style.display = (isNode || isConfigmap) ? 'none' : 'inline-block';
document.getElementById('btn-logs').style.display = (isNode || isConfigmap) ? 'none' : 'inline-block';
document.getElementById('btn-exec').style.display = (isNode || isConfigmap) ? 'none' : 'inline-block';
document.getElementById('btn-cordon').style.display = isNode ? 'inline-block' : 'none';
document.getElementById('btn-uncordon').style.display = isNode ? 'inline-block' : 'none';
}
async function loadContexts() {
try {
const res = await fetch('http://localhost:8000/contexts');
const data = await res.json();
contextSelect.innerHTML = '';
data.contexts.forEach(ctx => {
const opt = document.createElement('option');
opt.value = ctx;
opt.textContent = ctx;
if (ctx === data.current) opt.selected = true;
contextSelect.appendChild(opt);
});
loadSession();
loadNamespaces();
} catch (e) {
console.error('Failed to load contexts', e);
}
}
async function loadNamespaces() {
try {
const ctx = contextSelect.value || null;
const url = new URL('http://localhost:8000/namespaces');
if (ctx) url.searchParams.set('context', ctx);
const res = await fetch(url);
const data = await res.json();
namespaceSelect.innerHTML = '';
data.namespaces.forEach(ns => {
const opt = document.createElement('option');
opt.value = ns;
opt.textContent = ns;
namespaceSelect.appendChild(opt);
});
loadSession();
loadResources();
} catch (e) {
console.error('Failed to load namespaces', e);
}
}
async function loadResources(bypass = false, page = 1) {
const resType = resourceSelect.value;
const ns = namespaceSelect.value || 'default';
const ctx = contextSelect.value || null;
const search = searchInput.value.trim();
resourceTitle.textContent = resType.charAt(0).toUpperCase() + resType.slice(1);
resourceList.innerHTML = '<li>Loading...</li>';
currentPage = page;
try {
let url;
if (resType === 'pods') {
url = `http://localhost:8000/pods?namespace=${encodeURIComponent(ns)}&page=${page}&page_size=${pageSize}&bypass=${bypass}`;
} else if (resType === 'nodes') {
url = `http://localhost:8000/nodes?page=${page}&page_size=${pageSize}&bypass=${bypass}`;
} else if (resType === 'pvcs') {
url = `http://localhost:8000/pvcs?namespace=${encodeURIComponent(ns)}&page=${page}&page_size=${pageSize}&bypass=${bypass}`;
} else if (resType === 'configmaps') {
url = `http://localhost:8000/configmaps?namespace=${encodeURIComponent(ns)}&page=${page}&page_size=${pageSize}&bypass=${bypass}`;
} else {
url = `http://localhost:8000/pods?namespace=${encodeURIComponent(ns)}&page=${page}&page_size=${pageSize}&bypass=${bypass}`;
}
if (search) url += `&search=${encodeURIComponent(search)}`;
if (ctx) url += `&context=${encodeURIComponent(ctx)}`;
const res = await fetch(url);
const data = await res.json();
renderList(data);
updateActionVisibility();
} catch (e) {
console.error('Failed to load resources', e);
resourceList.innerHTML = '<li>Error loading resources</li>';
}
}
function renderList(data) {
const items = data.items || [];
const total = data.total || 0;
const page = data.page || 1;
const pageSize = data.page_size || 10;
const totalPages = Math.max(1, Math.ceil(total / pageSize));
resourceList.innerHTML = '';
if (items.length === 0) {
resourceList.innerHTML = '<li>No results</li>';
} else {
items.forEach(item => {
const li = document.createElement('li');
li.style.cursor = 'pointer';
li.innerHTML = `<strong>${item.name}</strong> <span style="color:#666">(${item.namespace})</span>`;
li.addEventListener('click', () => {
actionName.value = item.name;
document.getElementById('exec-pod').value = item.name;
Array.from(resourceList.children).forEach(child => {
child.classList.remove('selected');
child.style.background = '';
});
li.classList.add('selected');
li.style.background = '#ebf8ff';
});
resourceList.appendChild(li);
});
}
const pageInfo = document.getElementById('page-info');
if (pageInfo) pageInfo.textContent = `Page ${page} of ${totalPages} (${total} total)`;
const prevBtn = document.getElementById('prev-page');
const nextBtn = document.getElementById('next-page');
if (prevBtn) prevBtn.disabled = page <= 1;
if (nextBtn) nextBtn.disabled = page >= totalPages;
}
contextSelect.addEventListener('change', () => { saveSession(); loadNamespaces(); });
namespaceSelect.addEventListener('change', () => { saveSession(); loadResources(); });
resourceSelect.addEventListener('change', () => { saveSession(); loadResources(); });
searchBtn.addEventListener('click', () => loadResources(false, 1));
refreshBtn.addEventListener('click', () => loadResources(true, 1));
setDefaultBtn.addEventListener('click', () => { saveSession(); alert('Defaults saved for session'); });
document.getElementById('prev-page').addEventListener('click', () => {
if (currentPage > 1) loadResources(false, currentPage - 1);
});
document.getElementById('next-page').addEventListener('click', () => {
loadResources(false, currentPage + 1);
});
document.getElementById('btn-describe').addEventListener('click', async () => {
const name = actionName.value.trim();
if (!name) return;
const resType = resourceSelect.value === 'pvcs' ? 'pvc' : (resourceSelect.value === 'nodes' ? 'node' : (resourceSelect.value === 'configmaps' ? 'configmap' : 'pod'));
const ns = namespaceSelect.value || 'default';
const ctx = contextSelect.value || null;
try {
const url = new URL('http://localhost:8000/describe');
url.searchParams.set('resource', resType);
url.searchParams.set('name', name);
url.searchParams.set('namespace', ns);
if (ctx) url.searchParams.set('context', ctx);
const res = await fetch(url);
const data = await res.json();
actionOutput.textContent = data.output || data.error || JSON.stringify(data);
} catch (e) {
actionOutput.textContent = 'Error: ' + e;
}
});
document.getElementById('btn-logs').addEventListener('click', async () => {
const name = actionName.value.trim();
if (!name) return;
const ns = namespaceSelect.value || 'default';
const ctx = contextSelect.value || null;
try {
const url = new URL('http://localhost:8000/logs');
url.searchParams.set('pod', name);
url.searchParams.set('namespace', ns);
if (ctx) url.searchParams.set('context', ctx);
const res = await fetch(url);
const data = await res.json();
actionOutput.textContent = data.output || data.error || JSON.stringify(data);
} catch (e) {
actionOutput.textContent = 'Error: ' + e;
}
});
document.getElementById('btn-delete').addEventListener('click', async () => {
const name = actionName.value.trim();
if (!name || !confirm('Delete ' + name + '?')) return;
const resType = resourceSelect.value === 'pvcs' ? 'pvc' : (resourceSelect.value === 'nodes' ? 'node' : (resourceSelect.value === 'configmaps' ? 'configmap' : 'pod'));
const ns = namespaceSelect.value || 'default';
const ctx = contextSelect.value || null;
try {
const url = `http://localhost:8000/delete?resource=${encodeURIComponent(resType)}&name=${encodeURIComponent(name)}&namespace=${encodeURIComponent(ns)}${ctx ? '&context=' + encodeURIComponent(ctx) : ''}`;
const res = await fetch(url, { method: 'POST' });
const data = await res.json();
actionOutput.textContent = data.output || data.error || JSON.stringify(data);
loadResources();
} catch (e) {
actionOutput.textContent = 'Error: ' + e;
}
});
document.getElementById('btn-exec').addEventListener('click', async () => {
const pod = document.getElementById('exec-pod').value.trim();
if (!pod) return;
const ns = namespaceSelect.value || 'default';
const ctx = contextSelect.value || null;
const container = document.getElementById('exec-container').value.trim() || null;
const cmd = document.getElementById('exec-cmd').value.trim() || 'ls';
try {
const url = new URL('http://localhost:8000/exec');
url.searchParams.set('pod', pod);
url.searchParams.set('namespace', ns);
url.searchParams.set('command', cmd);
if (container) url.searchParams.set('container', container);
if (ctx) url.searchParams.set('context', ctx);
const res = await fetch(url);
const data = await res.json();
actionOutput.textContent = data.output || data.error || JSON.stringify(data);
} catch (e) {
actionOutput.textContent = 'Error: ' + e;
}
});
document.getElementById('btn-cordon').addEventListener('click', async () => {
const node = actionName.value.trim();
if (!node) return;
const ctx = contextSelect.value || null;
try {
const url = new URL('http://localhost:8000/cordon');
url.searchParams.set('node', node);
if (ctx) url.searchParams.set('context', ctx);
const res = await fetch(url);
const data = await res.json();
actionOutput.textContent = data.output || data.error || JSON.stringify(data);
} catch (e) {
actionOutput.textContent = 'Error: ' + e;
}
});
document.getElementById('btn-uncordon').addEventListener('click', async () => {
const node = actionName.value.trim();
if (!node) return;
const ctx = contextSelect.value || null;
try {
const url = new URL('http://localhost:8000/uncordon');
url.searchParams.set('node', node);
if (ctx) url.searchParams.set('context', ctx);
const res = await fetch(url);
const data = await res.json();
actionOutput.textContent = data.output || data.error || JSON.stringify(data);
} catch (e) {
actionOutput.textContent = 'Error: ' + e;
}
});
loadContexts();
});