Files
people_database/index.html
ZockerKatze 0e5913a7ce initial
2025-05-13 10:33:11 +02:00

1417 lines
60 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CLASSIFIED: Personnel Database</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap');
body {
font-family: 'Roboto Mono', monospace;
background-color: #0a0a0a;
color: #00ff00;
}
.terminal-effect {
text-shadow: 0 0 5px rgba(0, 255, 0, 0.7);
}
.glow-border {
box-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
}
.data-row:hover {
background-color: rgba(0, 255, 0, 0.1);
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #111;
}
::-webkit-scrollbar-thumb {
background: #00ff00;
border-radius: 4px;
}
.tab-button {
transition: all 0.3s ease;
}
.tab-button.active {
background-color: rgba(0, 255, 0, 0.2);
border-color: #00ff00;
}
</style>
</head>
<body class="min-h-screen p-4">
<div class="max-w-6xl mx-auto">
<!-- Header -->
<div class="flex justify-between items-center mb-6 border-b border-green-500 pb-4">
<div class="flex items-center">
<i class="fas fa-user-secret text-3xl text-green-500 mr-3"></i>
<h1 class="text-2xl font-bold terminal-effect">PERSONNEL DATABASE</h1>
</div>
<div class="text-xs text-green-400">
<span id="datetime" class="terminal-effect"></span>
<div class="mt-1">SECURITY CLEARANCE: <span class="font-bold">LEVEL 5</span></div>
</div>
</div>
<!-- Database Connection Panel -->
<div id="connectionPanel" class="bg-black p-6 rounded-lg glow-border mb-6">
<h2 class="text-xl font-bold mb-4 terminal-effect border-b border-green-500 pb-2">
<i class="fas fa-database mr-2"></i>DATABASE CONNECTION
</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div>
<label class="block text-sm text-green-400 mb-1">CONNECTION TYPE</label>
<select id="connectionType" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500">
<option value="local">Local Browser Storage</option>
<option value="sqlite">SQLite Database</option>
</select>
</div>
<div id="dbFileContainer" class="hidden">
<label class="block text-sm text-green-400 mb-1">DATABASE FILE</label>
<input type="file" id="dbFile" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500 file:mr-4 file:py-1 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-green-700 file:text-white hover:file:bg-green-600">
</div>
<div id="dbActionContainer" class="flex items-end space-x-2">
<button id="connectBtn" class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded font-bold">
<i class="fas fa-plug mr-1"></i> CONNECT
</button>
<button id="newDbBtn" class="bg-blue-700 hover:bg-blue-600 text-white px-4 py-2 rounded font-bold">
<i class="fas fa-plus mr-1"></i> NEW DB
</button>
</div>
</div>
<div id="connectionStatus" class="text-sm p-3 rounded hidden">
<i class="fas fa-circle mr-2"></i> <span>Status message</span>
</div>
</div>
<!-- Main Content Tabs -->
<div class="flex border-b border-green-700 mb-4">
<button class="tab-button active px-4 py-2 border-b-2 border-green-500 font-medium" data-tab="dataEntry">
<i class="fas fa-user-plus mr-2"></i> DATA ENTRY
</button>
<button class="tab-button px-4 py-2 border-b-2 border-transparent font-medium" data-tab="dataView">
<i class="fas fa-database mr-2"></i> RECORDS
</button>
<button class="tab-button px-4 py-2 border-b-2 border-transparent font-medium" data-tab="sqlQuery">
<i class="fas fa-terminal mr-2"></i> SQL QUERY
</button>
</div>
<!-- Tab Content -->
<div id="tabContent">
<!-- Data Entry Tab -->
<div id="dataEntry" class="tab-pane active">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Input Form -->
<div class="lg:col-span-1 bg-black p-6 rounded-lg glow-border">
<h2 class="text-xl font-bold mb-4 terminal-effect border-b border-green-500 pb-2">
<i class="fas fa-user-plus mr-2"></i>ADD NEW SUBJECT
</h2>
<form id="personForm" class="space-y-4">
<div>
<label class="block text-sm text-green-400 mb-1">IDENTIFICATION</label>
<input type="text" id="id" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="ID Number" required>
</div>
<div>
<label class="block text-sm text-green-400 mb-1">FULL NAME</label>
<input type="text" id="name" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Last, First Middle" required>
</div>
<div>
<label class="block text-sm text-green-400 mb-1">DATE OF BIRTH</label>
<input type="date" id="dob" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" required>
</div>
<div>
<label class="block text-sm text-green-400 mb-1">NATIONALITY</label>
<input type="text" id="nationality" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Country of origin">
</div>
<div>
<label class="block text-sm text-green-400 mb-1">KNOWN ASSOCIATES</label>
<input type="text" id="associates" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Separate with commas">
</div>
<div>
<label class="block text-sm text-green-400 mb-1">STATUS</label>
<select id="status" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500">
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
<option value="Deceased">Deceased</option>
<option value="Missing">Missing</option>
<option value="Classified">Classified</option>
</select>
</div>
<div id="customFieldsContainer" class="space-y-2">
<!-- Dynamic fields will be added here -->
</div>
<div class="flex justify-between pt-2">
<button type="button" id="addFieldBtn" class="bg-green-900 hover:bg-green-800 text-green-100 px-3 py-1 rounded text-sm">
<i class="fas fa-plus mr-1"></i> Add Field
</button>
<button type="submit" class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded font-bold">
<i class="fas fa-save mr-1"></i> RECORD SUBJECT
</button>
</div>
</form>
</div>
<!-- Database Info -->
<div class="lg:col-span-2 bg-black p-6 rounded-lg glow-border">
<h2 class="text-xl font-bold mb-4 terminal-effect border-b border-green-500 pb-2">
<i class="fas fa-info-circle mr-2"></i>DATABASE INFORMATION
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-medium text-green-400 mb-2">DATABASE STATISTICS</h3>
<div class="bg-gray-900 p-4 rounded">
<div class="flex justify-between mb-2">
<span>Records Count:</span>
<span id="recordCount" class="font-bold">0</span>
</div>
<div class="flex justify-between mb-2">
<span>Database Type:</span>
<span id="dbType" class="font-bold">Not connected</span>
</div>
<div class="flex justify-between">
<span>Last Updated:</span>
<span id="lastUpdated" class="font-bold">Never</span>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-medium text-green-400 mb-2">QUICK ACTIONS</h3>
<div class="grid grid-cols-2 gap-2">
<button id="exportDbBtn" class="bg-blue-700 hover:bg-blue-600 text-white px-3 py-2 rounded text-sm">
<i class="fas fa-download mr-1"></i> Export DB
</button>
<button id="importDbBtn" class="bg-purple-700 hover:bg-purple-600 text-white px-3 py-2 rounded text-sm">
<i class="fas fa-upload mr-1"></i> Import DB
</button>
<button id="backupDbBtn" class="bg-yellow-700 hover:bg-yellow-600 text-white px-3 py-2 rounded text-sm">
<i class="fas fa-copy mr-1"></i> Backup
</button>
<button id="clearDbBtn" class="bg-red-700 hover:bg-red-600 text-white px-3 py-2 rounded text-sm">
<i class="fas fa-trash mr-1"></i> Clear DB
</button>
</div>
<div class="mt-4">
<h3 class="text-lg font-medium text-green-400 mb-2">SQLITE COMMANDS</h3>
<div class="bg-gray-900 p-3 rounded text-xs font-mono overflow-x-auto">
<p class="text-green-300">CREATE TABLE personnel (</p>
<p class="ml-4">id TEXT PRIMARY KEY,</p>
<p class="ml-4">name TEXT NOT NULL,</p>
<p class="ml-4">dob TEXT,</p>
<p class="ml-4">nationality TEXT,</p>
<p class="ml-4">associates TEXT,</p>
<p class="ml-4">status TEXT,</p>
<p class="ml-4">custom_fields TEXT</p>
<p class="text-green-300">);</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Data View Tab -->
<div id="dataView" class="tab-pane hidden">
<div class="bg-black p-6 rounded-lg glow-border">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold terminal-effect border-b border-green-500 pb-2">
<i class="fas fa-database mr-2"></i>SUBJECT RECORDS
</h2>
<div class="relative">
<input type="text" id="searchInput" placeholder="Search records..." class="bg-gray-900 border border-green-700 rounded px-3 py-1 pr-8 text-green-100 focus:outline-none focus:border-green-500 text-sm">
<i class="fas fa-search absolute right-2 top-2 text-green-500"></i>
</div>
</div>
<div class="overflow-auto max-h-[600px]">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-green-700 text-green-400">
<th class="text-left py-2 px-3">ID</th>
<th class="text-left py-2 px-3">NAME</th>
<th class="text-left py-2 px-3">DOB</th>
<th class="text-left py-2 px-3">STATUS</th>
<th class="text-left py-2 px-3">ACTIONS</th>
</tr>
</thead>
<tbody id="databaseTable" class="divide-y divide-green-900">
<!-- Database entries will be added here dynamically -->
<tr>
<td colspan="5" class="py-4 text-center text-green-500">
<i class="fas fa-exclamation-circle mr-2"></i> NO DATABASE CONNECTION
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- SQL Query Tab -->
<div id="sqlQuery" class="tab-pane hidden">
<div class="bg-black p-6 rounded-lg glow-border">
<h2 class="text-xl font-bold mb-4 terminal-effect border-b border-green-500 pb-2">
<i class="fas fa-terminal mr-2"></i>SQL QUERY INTERFACE
</h2>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2">
<div class="mb-4">
<label class="block text-sm text-green-400 mb-1">SQL COMMAND</label>
<textarea id="sqlCommand" class="w-full h-32 bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500 font-mono" placeholder="SELECT * FROM personnel;"></textarea>
</div>
<div class="flex space-x-2">
<button id="executeQueryBtn" class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded font-bold">
<i class="fas fa-play mr-1"></i> EXECUTE
</button>
<button id="clearQueryBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded font-bold">
<i class="fas fa-eraser mr-1"></i> CLEAR
</button>
</div>
</div>
<div>
<label class="block text-sm text-green-400 mb-1">QUERY HISTORY</label>
<div class="bg-gray-900 p-3 rounded max-h-48 overflow-y-auto">
<ul id="queryHistory" class="space-y-1 text-xs font-mono">
<li class="text-gray-500">No query history</li>
</ul>
</div>
</div>
</div>
<div class="mt-6">
<label class="block text-sm text-green-400 mb-1">RESULTS</label>
<div id="queryResults" class="bg-gray-900 p-3 rounded min-h-32 max-h-96 overflow-auto">
<p class="text-gray-500">Execute a query to see results</p>
</div>
</div>
</div>
</div>
</div>
<!-- Modal for viewing full record -->
<div id="recordModal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center hidden z-50">
<div class="bg-black p-6 rounded-lg glow-border max-w-2xl w-full max-h-[80vh] overflow-auto">
<div class="flex justify-between items-center mb-4 border-b border-green-500 pb-2">
<h3 class="text-lg font-bold terminal-effect">SUBJECT DETAILS</h3>
<button id="closeModalBtn" class="text-green-500 hover:text-green-300">
<i class="fas fa-times"></i>
</button>
</div>
<div id="modalContent" class="space-y-3">
<!-- Record details will be displayed here -->
</div>
</div>
</div>
<!-- Modal for SQLite initialization -->
<div id="initModal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center hidden z-50">
<div class="bg-black p-6 rounded-lg glow-border max-w-md w-full">
<div class="flex justify-between items-center mb-4 border-b border-green-500 pb-2">
<h3 class="text-lg font-bold terminal-effect">INITIALIZING SQLITE</h3>
</div>
<div class="space-y-4">
<p class="text-green-300">Loading SQLite database engine...</p>
<div class="w-full bg-gray-900 rounded-full h-2.5">
<div id="initProgress" class="bg-green-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
<p id="initStatus" class="text-sm text-green-400">Downloading SQLite WASM module</p>
</div>
</div>
</div>
</div>
<script>
// Global variables
let db = null;
let sqliteInitialized = false;
let currentDatabase = 'none';
let database = []; // Fallback for localStorage mode
// DOM elements
const connectionPanel = document.getElementById('connectionPanel');
const connectionType = document.getElementById('connectionType');
const dbFileContainer = document.getElementById('dbFileContainer');
const dbFile = document.getElementById('dbFile');
const connectBtn = document.getElementById('connectBtn');
const newDbBtn = document.getElementById('newDbBtn');
const connectionStatus = document.getElementById('connectionStatus');
const recordCount = document.getElementById('recordCount');
const dbType = document.getElementById('dbType');
const lastUpdated = document.getElementById('lastUpdated');
const exportDbBtn = document.getElementById('exportDbBtn');
const importDbBtn = document.getElementById('importDbBtn');
const backupDbBtn = document.getElementById('backupDbBtn');
const clearDbBtn = document.getElementById('clearDbBtn');
const initModal = document.getElementById('initModal');
const initProgress = document.getElementById('initProgress');
const initStatus = document.getElementById('initStatus');
// Tab management
const tabButtons = document.querySelectorAll('.tab-button');
const tabPanes = document.querySelectorAll('.tab-pane');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tabId = button.getAttribute('data-tab');
// Update active tab button
tabButtons.forEach(btn => {
btn.classList.remove('active');
btn.classList.add('border-transparent');
});
button.classList.add('active');
button.classList.remove('border-transparent');
// Show corresponding tab pane
tabPanes.forEach(pane => {
pane.classList.add('hidden');
});
document.getElementById(tabId).classList.remove('hidden');
});
});
// Update datetime
function updateDateTime() {
const now = new Date();
document.getElementById('datetime').textContent = now.toLocaleString();
}
setInterval(updateDateTime, 1000);
updateDateTime();
// Connection type change handler
connectionType.addEventListener('change', (e) => {
if (e.target.value === 'sqlite') {
dbFileContainer.classList.remove('hidden');
} else {
dbFileContainer.classList.add('hidden');
}
});
// Initialize SQLite
async function initSQLite() {
if (sqliteInitialized) return true;
initModal.classList.remove('hidden');
initStatus.textContent = "Downloading SQLite WASM module...";
initProgress.style.width = "20%";
try {
// Load SQL.js
const SQL = await initSqlJs({
locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}`
});
initStatus.textContent = "Initializing database engine...";
initProgress.style.width = "60%";
// Create a new database if none exists
db = new SQL.Database();
initStatus.textContent = "Creating schema...";
initProgress.style.width = "80%";
// Create tables if they don't exist
db.run(`
CREATE TABLE IF NOT EXISTS personnel (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
dob TEXT,
nationality TEXT,
associates TEXT,
status TEXT,
custom_fields TEXT
);
`);
initStatus.textContent = "Initialization complete!";
initProgress.style.width = "100%";
setTimeout(() => {
initModal.classList.add('hidden');
}, 500);
sqliteInitialized = true;
return true;
} catch (err) {
console.error("SQLite initialization error:", err);
initStatus.textContent = `Initialization failed: ${err.message}`;
initProgress.style.width = "0%";
return false;
}
}
// Connect to database
connectBtn.addEventListener('click', async () => {
const type = connectionType.value;
if (type === 'local') {
// Fallback to localStorage
database = JSON.parse(localStorage.getItem('personnelDatabase')) || [];
currentDatabase = 'local';
// Update UI
connectionStatus.className = "text-sm p-3 rounded bg-green-900 text-green-100";
connectionStatus.innerHTML = '<i class="fas fa-circle mr-2"></i> <span>Connected to local browser storage</span>';
connectionStatus.classList.remove('hidden');
dbType.textContent = "Local Browser Storage";
recordCount.textContent = database.length;
lastUpdated.textContent = new Date().toLocaleString();
renderDatabase();
} else if (type === 'sqlite') {
if (!dbFile.files.length) {
showStatus("Please select a database file", "error");
return;
}
const file = dbFile.files[0];
const reader = new FileReader();
reader.onload = async function() {
try {
// Initialize SQLite first if needed
const initialized = await initSQLite();
if (!initialized) {
showStatus("Failed to initialize SQLite", "error");
return;
}
// Load the database file
const uint8Array = new Uint8Array(reader.result);
db = new SQL.Database(uint8Array);
// Verify the database has our table
try {
db.exec("SELECT 1 FROM personnel LIMIT 1;");
} catch (err) {
// Table doesn't exist, create it
db.run(`
CREATE TABLE personnel (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
dob TEXT,
nationality TEXT,
associates TEXT,
status TEXT,
custom_fields TEXT
);
`);
}
currentDatabase = 'sqlite';
// Update UI
showStatus(`Connected to SQLite database: ${file.name}`, "success");
updateDatabaseStats();
renderDatabase();
} catch (err) {
console.error("Database load error:", err);
showStatus(`Database error: ${err.message}`, "error");
}
};
reader.readAsArrayBuffer(file);
}
});
// Create new database
newDbBtn.addEventListener('click', async () => {
const type = connectionType.value;
if (type === 'local') {
if (confirm("Create new local database? This will clear all existing data.")) {
database = [];
localStorage.setItem('personnelDatabase', JSON.stringify(database));
showStatus("New local database created", "success");
recordCount.textContent = "0";
lastUpdated.textContent = new Date().toLocaleString();
renderDatabase();
}
} else if (type === 'sqlite') {
if (confirm("Create new SQLite database? This will clear all existing data.")) {
try {
const initialized = await initSQLite();
if (!initialized) {
showStatus("Failed to initialize SQLite", "error");
return;
}
// Create a fresh database
db = new SQL.Database();
db.run(`
CREATE TABLE personnel (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
dob TEXT,
nationality TEXT,
associates TEXT,
status TEXT,
custom_fields TEXT
);
`);
currentDatabase = 'sqlite';
// Update UI
showStatus("New SQLite database created", "success");
updateDatabaseStats();
renderDatabase();
} catch (err) {
console.error("Database creation error:", err);
showStatus(`Database error: ${err.message}`, "error");
}
}
}
});
// Export database
exportDbBtn.addEventListener('click', () => {
if (currentDatabase === 'none') {
showStatus("No database connected", "error");
return;
}
if (currentDatabase === 'local') {
// Export localStorage data
const dataStr = JSON.stringify(database, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportName = 'personnel-database-' + new Date().toISOString().slice(0,10) + '.json';
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportName);
linkElement.click();
showStatus("Local database exported as JSON", "success");
} else if (currentDatabase === 'sqlite' && db) {
// Export SQLite database
const data = db.export();
const blob = new Blob([data], {type: 'application/x-sqlite3'});
const url = URL.createObjectURL(blob);
const exportName = 'personnel-database-' + new Date().toISOString().slice(0,10) + '.sqlite';
const linkElement = document.createElement('a');
linkElement.setAttribute('href', url);
linkElement.setAttribute('download', exportName);
linkElement.click();
showStatus("SQLite database exported", "success");
}
});
// Import database
importDbBtn.addEventListener('click', () => {
if (currentDatabase === 'none') {
showStatus("Please connect to a database first", "error");
return;
}
const input = document.createElement('input');
input.type = 'file';
if (currentDatabase === 'local') {
input.accept = '.json';
input.onchange = e => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function() {
try {
const data = JSON.parse(reader.result);
if (Array.isArray(data)) {
if (confirm(`Import ${data.length} records? This will overwrite current data.`)) {
database = data;
localStorage.setItem('personnelDatabase', JSON.stringify(database));
showStatus(`Imported ${data.length} records`, "success");
updateDatabaseStats();
renderDatabase();
}
} else {
showStatus("Invalid data format", "error");
}
} catch (err) {
showStatus("Error parsing JSON file", "error");
}
};
reader.readAsText(file);
};
} else if (currentDatabase === 'sqlite') {
input.accept = '.sqlite,.db';
input.onchange = e => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = async function() {
try {
const uint8Array = new Uint8Array(reader.result);
// Verify the database
const tempDb = new SQL.Database(uint8Array);
try {
tempDb.exec("SELECT 1 FROM personnel LIMIT 1;");
if (confirm("Import this SQLite database? This will overwrite current data.")) {
db = tempDb;
showStatus(`Imported SQLite database: ${file.name}`, "success");
updateDatabaseStats();
renderDatabase();
}
} catch (err) {
showStatus("The database doesn't have the required table structure", "error");
}
} catch (err) {
showStatus("Error reading SQLite database", "error");
}
};
reader.readAsArrayBuffer(file);
};
}
input.click();
});
// Backup database
backupDbBtn.addEventListener('click', () => {
if (currentDatabase === 'none') {
showStatus("No database connected", "error");
return;
}
if (currentDatabase === 'local') {
// Simple clone for localStorage
const backup = JSON.parse(JSON.stringify(database));
localStorage.setItem('personnelDatabaseBackup', JSON.stringify(backup));
showStatus("Local database backup created", "success");
} else if (currentDatabase === 'sqlite' && db) {
// Clone SQLite database
const backupData = db.export();
localStorage.setItem('sqliteDatabaseBackup', JSON.stringify(Array.from(backupData)));
showStatus("SQLite database backup created", "success");
}
});
// Clear database
clearDbBtn.addEventListener('click', () => {
if (currentDatabase === 'none') {
showStatus("No database connected", "error");
return;
}
if (confirm("WARNING: This will permanently delete all data. Continue?")) {
if (currentDatabase === 'local') {
database = [];
localStorage.setItem('personnelDatabase', JSON.stringify(database));
showStatus("Local database cleared", "success");
updateDatabaseStats();
renderDatabase();
} else if (currentDatabase === 'sqlite') {
// Create a fresh database
db = new SQL.Database();
db.run(`
CREATE TABLE personnel (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
dob TEXT,
nationality TEXT,
associates TEXT,
status TEXT,
custom_fields TEXT
);
`);
showStatus("SQLite database cleared", "success");
updateDatabaseStats();
renderDatabase();
}
}
});
// Show status message
function showStatus(message, type) {
connectionStatus.className = "text-sm p-3 rounded";
if (type === 'success') {
connectionStatus.classList.add('bg-green-900', 'text-green-100');
} else if (type === 'error') {
connectionStatus.classList.add('bg-red-900', 'text-red-100');
} else {
connectionStatus.classList.add('bg-gray-900', 'text-gray-100');
}
connectionStatus.innerHTML = `<i class="fas fa-circle mr-2"></i> <span>${message}</span>`;
connectionStatus.classList.remove('hidden');
}
// Update database statistics
function updateDatabaseStats() {
if (currentDatabase === 'local') {
dbType.textContent = "Local Browser Storage";
recordCount.textContent = database.length;
lastUpdated.textContent = new Date().toLocaleString();
} else if (currentDatabase === 'sqlite' && db) {
dbType.textContent = "SQLite Database";
try {
const stmt = db.prepare("SELECT COUNT(*) as count FROM personnel;");
stmt.step();
const count = stmt.getAsObject().count;
stmt.free();
recordCount.textContent = count;
lastUpdated.textContent = new Date().toLocaleString();
} catch (err) {
console.error("Error getting record count:", err);
recordCount.textContent = "Error";
}
} else {
dbType.textContent = "Not connected";
recordCount.textContent = "0";
lastUpdated.textContent = "Never";
}
}
// Render database table
function renderDatabase() {
const databaseTable = document.getElementById('databaseTable');
if (currentDatabase === 'none') {
databaseTable.innerHTML = `
<tr>
<td colspan="5" class="py-4 text-center text-green-500">
<i class="fas fa-exclamation-circle mr-2"></i> NO DATABASE CONNECTION
</td>
</tr>
`;
return;
}
if (currentDatabase === 'local') {
if (database.length === 0) {
databaseTable.innerHTML = `
<tr>
<td colspan="5" class="py-4 text-center text-green-500">
<i class="fas fa-exclamation-circle mr-2"></i> NO RECORDS FOUND
</td>
</tr>
`;
return;
}
databaseTable.innerHTML = '';
database.forEach((person, index) => {
const row = document.createElement('tr');
row.className = 'data-row hover:bg-gray-900 cursor-pointer';
row.innerHTML = `
<td class="py-2 px-3 text-green-300">${person.id}</td>
<td class="py-2 px-3">${person.name}</td>
<td class="py-2 px-3">${person.dob}</td>
<td class="py-2 px-3">
<span class="inline-block px-2 py-0.5 rounded text-xs
${person.status === 'Active' ? 'bg-green-900 text-green-300' :
person.status === 'Deceased' ? 'bg-red-900 text-red-300' :
person.status === 'Missing' ? 'bg-yellow-900 text-yellow-300' :
'bg-gray-800 text-gray-300'}">
${person.status}
</span>
</td>
<td class="py-2 px-3">
<button class="view-btn mr-2 text-green-500 hover:text-green-300" data-id="${index}">
<i class="fas fa-eye"></i>
</button>
<button class="delete-btn text-red-500 hover:text-red-300" data-id="${index}">
<i class="fas fa-trash"></i>
</button>
</td>
`;
databaseTable.appendChild(row);
});
} else if (currentDatabase === 'sqlite' && db) {
try {
const stmt = db.prepare("SELECT * FROM personnel;");
let rows = [];
while (stmt.step()) {
rows.push(stmt.getAsObject());
}
stmt.free();
if (rows.length === 0) {
databaseTable.innerHTML = `
<tr>
<td colspan="5" class="py-4 text-center text-green-500">
<i class="fas fa-exclamation-circle mr-2"></i> NO RECORDS FOUND
</td>
</tr>
`;
return;
}
databaseTable.innerHTML = '';
rows.forEach((person, index) => {
const row = document.createElement('tr');
row.className = 'data-row hover:bg-gray-900 cursor-pointer';
row.innerHTML = `
<td class="py-2 px-3 text-green-300">${person.id}</td>
<td class="py-2 px-3">${person.name}</td>
<td class="py-2 px-3">${person.dob}</td>
<td class="py-2 px-3">
<span class="inline-block px-2 py-0.5 rounded text-xs
${person.status === 'Active' ? 'bg-green-900 text-green-300' :
person.status === 'Deceased' ? 'bg-red-900 text-red-300' :
person.status === 'Missing' ? 'bg-yellow-900 text-yellow-300' :
'bg-gray-800 text-gray-300'}">
${person.status}
</span>
</td>
<td class="py-2 px-3">
<button class="view-btn mr-2 text-green-500 hover:text-green-300" data-id="${person.id}">
<i class="fas fa-eye"></i>
</button>
<button class="delete-btn text-red-500 hover:text-red-300" data-id="${person.id}">
<i class="fas fa-trash"></i>
</button>
</td>
`;
databaseTable.appendChild(row);
});
} catch (err) {
console.error("Error rendering database:", err);
databaseTable.innerHTML = `
<tr>
<td colspan="5" class="py-4 text-center text-red-500">
<i class="fas fa-exclamation-circle mr-2"></i> ERROR LOADING DATA
</td>
</tr>
`;
}
}
// Add event listeners to view and delete buttons
document.querySelectorAll('.view-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const id = btn.getAttribute('data-id');
viewRecord(id);
});
});
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const id = btn.getAttribute('data-id');
deleteRecord(id);
});
});
// Add click event to rows for viewing record
document.querySelectorAll('.data-row').forEach(row => {
row.addEventListener('click', () => {
const btn = row.querySelector('.view-btn');
const id = btn.getAttribute('data-id');
viewRecord(id);
});
});
}
// View full record
function viewRecord(id) {
const recordModal = document.getElementById('recordModal');
const modalContent = document.getElementById('modalContent');
if (currentDatabase === 'local') {
const person = database[parseInt(id)];
if (!person) {
modalContent.innerHTML = '<p class="text-red-500">Record not found</p>';
recordModal.classList.remove('hidden');
return;
}
let html = `
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">BASIC INFORMATION</h4>
<p><span class="text-green-400">ID:</span> ${person.id}</p>
<p><span class="text-green-400">Name:</span> ${person.name}</p>
<p><span class="text-green-400">DOB:</span> ${person.dob}</p>
<p><span class="text-green-400">Nationality:</span> ${person.nationality || 'N/A'}</p>
<p><span class="text-green-400">Status:</span> ${person.status}</p>
</div>
<div>
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">ASSOCIATES</h4>
`;
if (person.associates && person.associates.length > 0) {
html += `<ul class="list-disc list-inside">`;
person.associates.split(',').forEach(associate => {
html += `<li>${associate.trim()}</li>`;
});
html += `</ul>`;
} else {
html += `<p>No known associates</p>`;
}
html += `</div></div>`;
// Add custom fields if they exist
if (person.customFields && Object.keys(person.customFields).length > 0) {
html += `
<div class="mt-4">
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">ADDITIONAL INFORMATION</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
`;
for (const [key, value] of Object.entries(person.customFields)) {
html += `
<div>
<p><span class="text-green-400">${key}:</span> ${value || 'N/A'}</p>
</div>
`;
}
html += `</div></div>`;
}
modalContent.innerHTML = html;
recordModal.classList.remove('hidden');
} else if (currentDatabase === 'sqlite' && db) {
try {
const stmt = db.prepare("SELECT * FROM personnel WHERE id = ?;");
stmt.bind([id]);
stmt.step();
const person = stmt.getAsObject();
stmt.free();
if (!person.id) {
modalContent.innerHTML = '<p class="text-red-500">Record not found</p>';
recordModal.classList.remove('hidden');
return;
}
let html = `
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">BASIC INFORMATION</h4>
<p><span class="text-green-400">ID:</span> ${person.id}</p>
<p><span class="text-green-400">Name:</span> ${person.name}</p>
<p><span class="text-green-400">DOB:</span> ${person.dob}</p>
<p><span class="text-green-400">Nationality:</span> ${person.nationality || 'N/A'}</p>
<p><span class="text-green-400">Status:</span> ${person.status}</p>
</div>
<div>
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">ASSOCIATES</h4>
`;
if (person.associates && person.associates.length > 0) {
html += `<ul class="list-disc list-inside">`;
person.associates.split(',').forEach(associate => {
html += `<li>${associate.trim()}</li>`;
});
html += `</ul>`;
} else {
html += `<p>No known associates</p>`;
}
html += `</div></div>`;
// Add custom fields if they exist
if (person.custom_fields) {
try {
const customFields = JSON.parse(person.custom_fields);
if (Object.keys(customFields).length > 0) {
html += `
<div class="mt-4">
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">ADDITIONAL INFORMATION</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
`;
for (const [key, value] of Object.entries(customFields)) {
html += `
<div>
<p><span class="text-green-400">${key}:</span> ${value || 'N/A'}</p>
</div>
`;
}
html += `</div></div>`;
}
} catch (e) {
console.error("Error parsing custom fields:", e);
}
}
modalContent.innerHTML = html;
recordModal.classList.remove('hidden');
} catch (err) {
console.error("Error viewing record:", err);
modalContent.innerHTML = '<p class="text-red-500">Error loading record</p>';
recordModal.classList.remove('hidden');
}
}
}
// Delete record
function deleteRecord(id) {
if (!confirm('CONFIRM RECORD DELETION?\nThis action cannot be undone.')) {
return;
}
if (currentDatabase === 'local') {
database.splice(parseInt(id), 1);
localStorage.setItem('personnelDatabase', JSON.stringify(database));
showStatus("Record deleted", "success");
updateDatabaseStats();
renderDatabase();
} else if (currentDatabase === 'sqlite' && db) {
try {
db.run("DELETE FROM personnel WHERE id = ?;", [id]);
showStatus("Record deleted", "success");
updateDatabaseStats();
renderDatabase();
} catch (err) {
console.error("Error deleting record:", err);
showStatus("Error deleting record", "error");
}
}
}
// Form submission
document.getElementById('personForm').addEventListener('submit', (e) => {
e.preventDefault();
// Get form values
const person = {
id: document.getElementById('id').value,
name: document.getElementById('name').value,
dob: document.getElementById('dob').value,
nationality: document.getElementById('nationality').value,
associates: document.getElementById('associates').value,
status: document.getElementById('status').value
};
// Get custom fields
const customFields = {};
document.querySelectorAll('.field-name').forEach(field => {
const name = field.value.trim();
if (name) {
const value = field.parentElement.nextElementSibling.querySelector('.field-value').value;
customFields[name] = value;
}
});
if (currentDatabase === 'local') {
// Add custom fields to person object
person.customFields = customFields;
// Check if ID already exists
const existingIndex = database.findIndex(p => p.id === person.id);
if (existingIndex !== -1) {
if (confirm('ID already exists. Update existing record?')) {
database[existingIndex] = person;
} else {
return;
}
} else {
database.push(person);
}
localStorage.setItem('personnelDatabase', JSON.stringify(database));
showStatus("Record saved to local storage", "success");
updateDatabaseStats();
renderDatabase();
} else if (currentDatabase === 'sqlite' && db) {
try {
// Check if record exists
const checkStmt = db.prepare("SELECT 1 FROM personnel WHERE id = ?;");
checkStmt.bind([person.id]);
const exists = checkStmt.step();
checkStmt.free();
// Prepare custom fields as JSON
const customFieldsJson = JSON.stringify(customFields);
if (exists) {
// Update existing record
db.run(`
UPDATE personnel SET
name = ?,
dob = ?,
nationality = ?,
associates = ?,
status = ?,
custom_fields = ?
WHERE id = ?;
`, [
person.name,
person.dob,
person.nationality,
person.associates,
person.status,
customFieldsJson,
person.id
]);
showStatus("Record updated in SQLite database", "success");
} else {
// Insert new record
db.run(`
INSERT INTO personnel
(id, name, dob, nationality, associates, status, custom_fields)
VALUES (?, ?, ?, ?, ?, ?, ?);
`, [
person.id,
person.name,
person.dob,
person.nationality,
person.associates,
person.status,
customFieldsJson
]);
showStatus("Record added to SQLite database", "success");
}
updateDatabaseStats();
renderDatabase();
} catch (err) {
console.error("Error saving record:", err);
showStatus("Error saving record to database", "error");
}
} else {
showStatus("No database connected", "error");
}
// Reset form
e.target.reset();
document.getElementById('customFieldsContainer').innerHTML = '';
});
// Add custom field
document.getElementById('addFieldBtn').addEventListener('click', () => {
const fieldId = Date.now();
const fieldDiv = document.createElement('div');
fieldDiv.className = 'flex items-end space-x-2';
fieldDiv.innerHTML = `
<div class="flex-grow">
<label class="block text-sm text-green-400 mb-1">FIELD NAME</label>
<input type="text" class="field-name w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Field name">
</div>
<div class="flex-grow">
<label class="block text-sm text-green-400 mb-1">VALUE</label>
<input type="text" class="field-value w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Field value">
</div>
<button type="button" class="remove-field-btn bg-red-900 hover:bg-red-800 text-red-100 px-3 py-2 rounded">
<i class="fas fa-times"></i>
</button>
`;
document.getElementById('customFieldsContainer').appendChild(fieldDiv);
// Add event listener to remove button
fieldDiv.querySelector('.remove-field-btn').addEventListener('click', () => {
fieldDiv.remove();
});
});
// Close modal
document.getElementById('closeModalBtn').addEventListener('click', () => {
document.getElementById('recordModal').classList.add('hidden');
});
// Close modal when clicking outside
document.getElementById('recordModal').addEventListener('click', (e) => {
if (e.target === document.getElementById('recordModal')) {
document.getElementById('recordModal').classList.add('hidden');
}
});
// SQL Query Interface
const sqlCommand = document.getElementById('sqlCommand');
const executeQueryBtn = document.getElementById('executeQueryBtn');
const clearQueryBtn = document.getElementById('clearQueryBtn');
const queryResults = document.getElementById('queryResults');
const queryHistory = document.getElementById('queryHistory');
let queryHistoryList = JSON.parse(localStorage.getItem('queryHistory')) || [];
// Update query history display
function updateQueryHistory() {
queryHistory.innerHTML = '';
if (queryHistoryList.length === 0) {
queryHistory.innerHTML = '<li class="text-gray-500">No query history</li>';
return;
}
// Show last 5 queries
const recentQueries = queryHistoryList.slice(-5).reverse();
recentQueries.forEach((query, index) => {
const li = document.createElement('li');
li.className = 'cursor-pointer hover:text-green-300 truncate';
li.textContent = query;
li.addEventListener('click', () => {
sqlCommand.value = query;
});
queryHistory.appendChild(li);
});
}
// Execute SQL query
executeQueryBtn.addEventListener('click', () => {
if (currentDatabase !== 'sqlite' || !db) {
queryResults.innerHTML = '<p class="text-red-500">SQLite database not connected</p>';
return;
}
const query = sqlCommand.value.trim();
if (!query) {
queryResults.innerHTML = '<p class="text-yellow-500">Please enter a SQL query</p>';
return;
}
try {
// Add to history
queryHistoryList.push(query);
localStorage.setItem('queryHistory', JSON.stringify(queryHistoryList));
updateQueryHistory();
// Execute query
const results = db.exec(query);
if (results.length === 0) {
queryResults.innerHTML = '<p class="text-green-500">Query executed successfully (no results)</p>';
return;
}
let html = '';
results.forEach(result => {
html += '<div class="mb-4">';
html += `<h4 class="text-green-400 mb-2">${result.columns.join(' | ')}</h4>`;
html += '<div class="overflow-x-auto">';
html += '<table class="w-full border border-green-700">';
html += '<tbody>';
result.values.forEach(row => {
html += '<tr class="border-b border-green-800">';
row.forEach(cell => {
html += `<td class="px-3 py-1">${cell !== null ? cell : 'NULL'}</td>`;
});
html += '</tr>';
});
html += '</tbody>';
html += '</table>';
html += '</div>';
html += `<p class="text-xs text-green-500 mt-1">${result.values.length} rows returned</p>`;
html += '</div>';
});
queryResults.innerHTML = html;
} catch (err) {
console.error("SQL error:", err);
queryResults.innerHTML = `<p class="text-red-500">SQL Error: ${err.message}</p>`;
}
});
// Clear SQL query
clearQueryBtn.addEventListener('click', () => {
sqlCommand.value = '';
});
// Initialize query history
updateQueryHistory();
// Search functionality
document.getElementById('searchInput').addEventListener('input', (e) => {
const term = e.target.value.toLowerCase();
if (currentDatabase === 'local') {
const filtered = database.filter(person =>
person.id.toLowerCase().includes(term) ||
person.name.toLowerCase().includes(term) ||
(person.nationality && person.nationality.toLowerCase().includes(term)) ||
(person.associates && person.associates.toLowerCase().includes(term)) ||
person.status.toLowerCase().includes(term)
);
renderDatabase(filtered);
} else if (currentDatabase === 'sqlite' && db) {
try {
const stmt = db.prepare(`
SELECT * FROM personnel
WHERE
id LIKE ? OR
name LIKE ? OR
nationality LIKE ? OR
associates LIKE ? OR
status LIKE ?
`);
const searchTerm = `%${term}%`;
stmt.bind([searchTerm, searchTerm, searchTerm, searchTerm, searchTerm]);
let results = [];
while (stmt.step()) {
results.push(stmt.getAsObject());
}
stmt.free();
renderDatabase(results);
} catch (err) {
console.error("Search error:", err);
}
}
});
// Initial setup
updateDatabaseStats();
</script>
</body>
</html>