prole/www/installer.html
2025-12-01 14:52:18 -08:00

763 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Prole Service Dependencies Installer</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #1a1a1a;
color: #e0e0e0;
overflow: hidden;
height: 100vh;
position: relative;
}
#background-image {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('images/prole-type.gif');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
opacity: 0.15;
z-index: 0;
}
.installer-container {
position: relative;
z-index: 1;
height: 100vh;
display: flex;
flex-direction: column;
background: rgba(26, 26, 26, 0.85);
backdrop-filter: blur(10px);
}
.installer-header {
padding: 30px;
text-align: center;
border-bottom: 2px solid #333;
}
.installer-header h1 {
font-size: 32px;
color: #4a9eff;
margin-bottom: 10px;
}
.installer-header p {
color: #aaa;
font-size: 14px;
}
.installer-content {
flex: 1;
padding: 40px;
overflow-y: auto;
}
.step {
display: none;
max-width: 900px;
margin: 0 auto;
}
.step.active {
display: block;
}
.checklist-item {
background: rgba(255, 255, 255, 0.05);
border: 1px solid #333;
border-radius: 8px;
padding: 20px;
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: space-between;
}
.checklist-item-content {
flex: 1;
}
.checklist-item-title {
font-size: 18px;
font-weight: 600;
color: #fff;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 10px;
}
.checklist-item-description {
font-size: 14px;
color: #aaa;
margin-top: 5px;
}
.sub-checklist {
margin-top: 15px;
margin-left: 30px;
padding-left: 20px;
border-left: 2px solid #444;
}
.sub-checklist-item {
background: rgba(255, 255, 255, 0.03);
border: 1px solid #2a2a2a;
border-radius: 6px;
padding: 12px;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.status-indicator {
display: flex;
align-items: center;
gap: 15px;
}
.checkmark {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid #4a9eff;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.checkmark.installed {
background: #4a9eff;
border-color: #4a9eff;
}
.checkmark.installed::after {
content: '✓';
color: white;
font-size: 16px;
font-weight: bold;
}
.checkmark.not-installed {
background: transparent;
border-color: #666;
}
.checkmark.not-installed::after {
content: '✗';
color: #666;
font-size: 16px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: #4a9eff;
color: white;
}
.btn-primary:hover {
background: #3a8eef;
transform: translateY(-2px);
}
.btn-secondary {
background: #555;
color: white;
}
.btn-secondary:hover {
background: #666;
}
.btn-download {
background: #28a745;
color: white;
}
.btn-download:hover {
background: #218838;
}
.btn-update {
background: #ffc107;
color: #1a1a1a;
}
.btn-update:hover {
background: #e0a800;
}
.command-display {
background: #0a0a0a;
border: 1px solid #333;
border-radius: 6px;
padding: 20px;
margin: 20px 0;
font-family: 'Courier New', monospace;
font-size: 14px;
color: #4a9eff;
overflow-x: auto;
}
.command-display code {
color: #4a9eff;
white-space: pre-wrap;
}
.installer-footer {
padding: 20px 40px;
border-top: 2px solid #333;
display: flex;
justify-content: space-between;
align-items: center;
}
.btn-large {
padding: 12px 30px;
font-size: 16px;
}
.terminal-window {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 800px;
height: 500px;
background: rgba(10, 10, 10, 0.95);
border: 2px solid #4a9eff;
border-radius: 8px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
z-index: 1000;
display: none;
flex-direction: column;
backdrop-filter: blur(10px);
}
.terminal-window.active {
display: flex;
}
.terminal-window::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('images/prole-type.gif');
background-size: cover;
background-position: center;
opacity: 0.2;
z-index: -1;
border-radius: 6px;
}
.terminal-header {
padding: 15px;
background: rgba(74, 158, 255, 0.2);
border-bottom: 1px solid #4a9eff;
display: flex;
justify-content: space-between;
align-items: center;
}
.terminal-title {
color: #4a9eff;
font-weight: 600;
}
.terminal-close {
background: #ff4444;
color: white;
border: none;
width: 24px;
height: 24px;
border-radius: 50%;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
}
.terminal-body {
flex: 1;
padding: 20px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 14px;
color: #4a9eff;
}
.terminal-prompt {
color: #4a9eff;
margin-bottom: 10px;
}
.terminal-input {
background: transparent;
border: none;
color: #4a9eff;
font-family: 'Courier New', monospace;
font-size: 14px;
width: 100%;
outline: none;
}
.terminal-output {
color: #aaa;
margin-top: 10px;
}
.cluster-status {
background: #0a0a0a;
border: 1px solid #333;
border-radius: 6px;
padding: 20px;
margin: 20px 0;
font-family: 'Courier New', monospace;
font-size: 14px;
color: #4a9eff;
min-height: 200px;
max-height: 400px;
overflow-y: auto;
}
.cluster-status pre {
margin: 0;
color: #4a9eff;
}
.status-text {
color: #aaa;
font-style: italic;
}
</style>
</head>
<body>
<div id="background-image"></div>
<div class="installer-container">
<div class="installer-header">
<h1>Prole Service Dependencies Installer</h1>
<p>Install and configure required dependencies for Prole services</p>
</div>
<div class="installer-content">
<!-- Step 1: Dependency Installation -->
<div class="step active" id="step1">
<h2 style="margin-bottom: 30px; color: #4a9eff;">Step 1: Install Dependencies</h2>
<!-- Docker Check -->
<div class="checklist-item">
<div class="checklist-item-content">
<div class="checklist-item-title">
<span>Docker</span>
</div>
<div class="checklist-item-description">
Container platform required for running Prole services
</div>
</div>
<div class="status-indicator">
<div class="checkmark not-installed" id="docker-check"></div>
<a href="https://www.docker.com/products/docker-desktop" target="_blank" class="btn btn-download">Click here to download</a>
</div>
</div>
<!-- Homebrew Check -->
<div class="checklist-item">
<div class="checklist-item-content">
<div class="checklist-item-title">
<span>Homebrew</span>
</div>
<div class="checklist-item-description">
Package manager for macOS (required for k3d installation)
</div>
<div class="sub-checklist">
<div class="sub-checklist-item">
<div class="checklist-item-content">
<div class="checklist-item-title" style="font-size: 16px;">
<span>Homebrew Installed & Current</span>
</div>
</div>
<div class="status-indicator">
<div class="checkmark not-installed" id="homebrew-check"></div>
<button class="btn btn-update" id="update-homebrew-btn">Update Homebrew</button>
</div>
</div>
</div>
</div>
</div>
<!-- k3d Check -->
<div class="checklist-item">
<div class="checklist-item-content">
<div class="checklist-item-title">
<span>k3d</span>
</div>
<div class="checklist-item-description">
Lightweight wrapper to run k3s in Docker (installed via Homebrew)
</div>
</div>
<div class="status-indicator">
<div class="checkmark not-installed" id="k3d-check"></div>
<a href="https://k3d.io/" target="_blank" class="btn btn-download">Click here to download</a>
</div>
</div>
<!-- Command Display -->
<div class="command-display">
<strong style="color: #fff; margin-bottom: 10px; display: block;">Commands to execute:</strong>
<code id="install-commands"># Commands will appear here after checking dependencies</code>
</div>
<div style="text-align: center; margin-top: 30px;">
<button class="btn btn-primary btn-large" id="install-btn">Install</button>
</div>
</div>
<!-- Step 2: Create Home Cluster -->
<div class="step" id="step2">
<h2 style="margin-bottom: 30px; color: #4a9eff;">Step 2: Create Home Cluster</h2>
<!-- Docker Running Check -->
<div class="checklist-item">
<div class="checklist-item-content">
<div class="checklist-item-title">
<span>Docker Installed and Running</span>
</div>
<div class="checklist-item-description">
Verify Docker is installed and the daemon is running
</div>
</div>
<div class="status-indicator">
<div class="checkmark not-installed" id="docker-running-check"></div>
</div>
</div>
<!-- k3d Ready Check -->
<div class="checklist-item">
<div class="checklist-item-content">
<div class="checklist-item-title">
<span>k3d Command and Dependencies Ready</span>
</div>
<div class="checklist-item-description">
Verify k3d and all related dependencies are installed and ready
</div>
</div>
<div class="status-indicator">
<div class="checkmark not-installed" id="k3d-ready-check"></div>
</div>
</div>
<!-- k3d Cluster List -->
<div class="cluster-status">
<strong style="color: #fff; margin-bottom: 10px; display: block;">k3d cluster list output:</strong>
<pre id="cluster-list-output" class="status-text">Running k3d cluster list...</pre>
</div>
</div>
</div>
<div class="installer-footer">
<button class="btn btn-secondary" id="prev-btn" style="display: none;">Previous</button>
<button class="btn btn-primary btn-large" id="next-btn">Next</button>
</div>
</div>
<!-- Terminal Window -->
<div class="terminal-window" id="terminal-window">
<div class="terminal-header">
<div class="terminal-title">Prole Terminal</div>
<button class="terminal-close" id="terminal-close">×</button>
</div>
<div class="terminal-body" id="terminal-body">
<div class="terminal-prompt">$ what is my name?</div>
<div class="terminal-output" id="terminal-output"></div>
<div class="terminal-prompt" id="terminal-input-line" style="display: none;">
$ <input type="text" class="terminal-input" id="terminal-input" autocomplete="off">
</div>
</div>
</div>
<script>
let currentStep = 1;
let userName = '';
let terminalActive = false;
// Check dependencies on load
async function checkDependencies() {
// Simulate checking dependencies
// In a real implementation, this would call a backend API
// Check Docker
try {
const dockerCheck = await checkCommand('docker --version');
updateCheckmark('docker-check', dockerCheck);
} catch (e) {
updateCheckmark('docker-check', false);
}
// Check Homebrew
try {
const brewCheck = await checkCommand('brew --version');
updateCheckmark('homebrew-check', brewCheck);
} catch (e) {
updateCheckmark('homebrew-check', false);
}
// Check k3d
try {
const k3dCheck = await checkCommand('k3d --version');
updateCheckmark('k3d-check', k3dCheck);
} catch (e) {
updateCheckmark('k3d-check', false);
}
updateInstallCommands();
}
// Simulate command checking (in real app, this would use a backend)
async function checkCommand(command) {
// This is a simulation - in a real app, you'd call a backend API
// For now, we'll simulate based on common scenarios
return new Promise((resolve) => {
setTimeout(() => {
// Simulate: docker and brew are often installed, k3d less so
if (command.includes('docker')) {
resolve(Math.random() > 0.3); // 70% chance installed
} else if (command.includes('brew')) {
resolve(Math.random() > 0.2); // 80% chance installed
} else if (command.includes('k3d')) {
resolve(Math.random() > 0.7); // 30% chance installed
} else {
resolve(false);
}
}, 500);
});
}
function updateCheckmark(id, installed) {
const checkmark = document.getElementById(id);
if (installed) {
checkmark.classList.remove('not-installed');
checkmark.classList.add('installed');
} else {
checkmark.classList.remove('installed');
checkmark.classList.add('not-installed');
}
}
function updateInstallCommands() {
const dockerInstalled = document.getElementById('docker-check').classList.contains('installed');
const brewInstalled = document.getElementById('homebrew-check').classList.contains('installed');
const k3dInstalled = document.getElementById('k3d-check').classList.contains('installed');
let commands = [];
if (!brewInstalled) {
commands.push('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"');
} else {
commands.push('brew update');
commands.push('brew upgrade');
}
if (!k3dInstalled && brewInstalled) {
commands.push('brew install k3d');
}
const commandsDisplay = document.getElementById('install-commands');
if (commands.length > 0) {
commandsDisplay.textContent = commands.join('\n');
} else {
commandsDisplay.textContent = '# All dependencies are already installed!';
}
}
// Update Homebrew button
document.getElementById('update-homebrew-btn').addEventListener('click', () => {
const commandsDisplay = document.getElementById('install-commands');
commandsDisplay.textContent = 'brew update\nbrew upgrade';
alert('Click the Install button to update Homebrew');
});
// Install button
document.getElementById('install-btn').addEventListener('click', () => {
const commands = document.getElementById('install-commands').textContent;
if (commands.includes('All dependencies')) {
alert('All dependencies are already installed!');
return;
}
// Show commands that will be executed
const confirmed = confirm(`The following commands will be executed:\n\n${commands}\n\nProceed?`);
if (confirmed) {
// In a real app, this would execute the commands via a backend
alert('Installation commands executed! (This is a simulation - in production, commands would run via backend)');
// Simulate installation success
setTimeout(() => {
checkDependencies();
}, 2000);
}
});
// Next button
document.getElementById('next-btn').addEventListener('click', () => {
if (currentStep === 1) {
// Move to step 2
document.getElementById('step1').classList.remove('active');
document.getElementById('step2').classList.add('active');
document.getElementById('prev-btn').style.display = 'block';
currentStep = 2;
// Check cluster status
checkClusterStatus();
} else if (currentStep === 2) {
// Open terminal window
openTerminal();
}
});
// Previous button
document.getElementById('prev-btn').addEventListener('click', () => {
if (currentStep === 2) {
document.getElementById('step2').classList.remove('active');
document.getElementById('step1').classList.add('active');
document.getElementById('prev-btn').style.display = 'none';
currentStep = 1;
}
});
// Check cluster status
async function checkClusterStatus() {
// Check Docker running
try {
const dockerRunning = await checkCommand('docker ps');
updateCheckmark('docker-running-check', dockerRunning);
} catch (e) {
updateCheckmark('docker-running-check', false);
}
// Check k3d ready
try {
const k3dReady = await checkCommand('k3d --version');
updateCheckmark('k3d-ready-check', k3dReady);
} catch (e) {
updateCheckmark('k3d-ready-check', false);
}
// Run k3d cluster list
const clusterOutput = document.getElementById('cluster-list-output');
clusterOutput.textContent = 'Running: k3d cluster list\n\n';
// Simulate k3d cluster list output
setTimeout(() => {
const output = `$ k3d cluster list
NAME CLUSTER SERVERS AGENTS LOADBALANCER
prole-data-cluster running 1 0 true
Clusters found: 1`;
clusterOutput.textContent = output;
}, 1000);
}
// Terminal functions
function openTerminal() {
const terminal = document.getElementById('terminal-window');
terminal.classList.add('active');
terminalActive = true;
// Start the prompt sequence
setTimeout(() => {
const inputLine = document.getElementById('terminal-input-line');
inputLine.style.display = 'block';
document.getElementById('terminal-input').focus();
}, 500);
}
document.getElementById('terminal-close').addEventListener('click', () => {
document.getElementById('terminal-window').classList.remove('active');
terminalActive = false;
userName = '';
document.getElementById('terminal-output').innerHTML = '';
document.getElementById('terminal-input-line').style.display = 'none';
document.getElementById('terminal-input').value = '';
});
document.getElementById('terminal-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const input = document.getElementById('terminal-input');
const value = input.value.trim();
const output = document.getElementById('terminal-output');
if (!userName) {
// First question: "what is my name?"
userName = value || 'Prole';
output.innerHTML = `<div style="color: #4a9eff;">$ ${value}</div>`;
output.innerHTML += `<div style="color: #aaa; margin-top: 10px;">Hi, I'm ${userName}. What should I call you?</div>`;
input.value = '';
// Update prompt
setTimeout(() => {
const promptLine = document.querySelector('.terminal-prompt');
if (promptLine) {
promptLine.textContent = `$ `;
}
}, 100);
} else {
// Second question: "What should I call you?"
const userResponse = value || 'User';
output.innerHTML += `<div style="color: #4a9eff;">$ ${value}</div>`;
output.innerHTML += `<div style="color: #aaa; margin-top: 10px;">Nice to meet you, ${userResponse}! I'm ${userName}.</div>`;
input.value = '';
// Close terminal after a moment
setTimeout(() => {
document.getElementById('terminal-window').classList.remove('active');
terminalActive = false;
}, 2000);
}
}
});
// Initialize
checkDependencies();
</script>
</body>
</html>