Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
279 lines
12 KiB
JavaScript
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();
|
|
});
|