Files
Krow-workspace/firebase/internal-launchpad/index.html
bwnyasse 9bdb250714 feat: add firebase configuration and deployment scripts
This commit introduces Firebase configuration files (.firebaserc,
firebase.json) and updates the Makefile to include deployment
commands for different environments (dev, staging).

The .firebaserc file defines Firebase projects for development and
staging, along with hosting targets.

The firebase.json file configures hosting settings, including
rewrites and ignores. It defines hosting targets for the main app
in dev and staging, and a separate target for an internal launchpad.

The Makefile is updated to include:
- GCP project IDs for dev and staging.
- Environment detection (ENV variable).
- Conditional variables based on the environment (GCP_PROJECT_ID,
 FIREBASE_ALIAS, HOSTING_TARGET).
- Deployment commands for the launchpad and the main app.
- The build command now passes the environment variable to the
 frontend build process.

The internal launchpad is added to firebase/internal-launchpad/index.html
to provide quick access to application URLs and Firebase/GCP
consoles for different environments.

A patch script is added to inject the environment label into the
Dashboard page.

The index.html title is changed to KROW.

These changes enable streamlined deployment and environment
management for the KROW application.
2025-11-14 08:53:00 -05:00

503 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KROW Launchpad</title>
<link rel="icon" type="image/x-icon" href="favicon.svg">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
<!-- Mermaid -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.6.1/mermaid.min.js"></script>
<!-- Custom CSS -->
<style>
body {
background-color: #f8f9fa;
}
.main-layout {
display: grid;
grid-template-columns: 280px 1fr;
min-height: 100vh;
}
.sidebar {
background-color: #ffffff;
border-right: 1px solid #dee2e6;
padding: 1.5rem;
overflow-y: auto;
}
.sidebar .nav-link {
color: #495057;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
transition: color 0.2s, background-color 0.2s;
}
.sidebar .nav-link.active,
.sidebar .nav-link:hover {
color: #0d6efd;
background-color: #e9ecef;
}
.sidebar-heading {
font-size: 0.75rem;
font-weight: 600;
color: #6c757d;
text-transform: uppercase;
padding: 0 1rem;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
}
.sidebar-subheading {
font-size: 0.7rem;
font-weight: 600;
color: #868e96;
padding: 0 1rem 0 2rem;
margin-top: 0.75rem;
margin-bottom: 0.25rem;
}
.nav-link.sub-item {
padding-left: 2.5rem;
font-size: 0.9rem;
}
.nav-link.sub-sub-item {
padding-left: 3.5rem;
font-size: 0.85rem;
}
.content-area {
padding: 2rem;
overflow-y: auto;
}
#diagram-viewer {
display: none;
height: calc(100vh - 4rem);
display: flex;
flex-direction: column;
}
#diagram-container {
cursor: grab;
overflow: hidden;
flex-grow: 1;
position: relative;
background-color: #ffffff;
border-radius: 0.5rem;
border: 1px solid #dee2e6;
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
}
#diagram-container:active {
cursor: grabbing;
}
#diagram-container:focus {
outline: none;
}
#diagram-container svg {
max-width: 100%;
height: auto;
}
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
</style>
</head>
<body>
<div class="main-layout">
<!-- Sidebar -->
<nav class="sidebar">
<div class="d-flex justify-content-center align-items-center mb-4">
<img src="logo.svg" alt="Krow Logo" style="height: 60px;" onerror="this.style.display='none'">
</div>
<div class="nav flex-column nav-pills" id="sidebar-nav">
<a class="nav-link active" href="#" id="nav-home" onclick="showView('home', this)">
<i class="bi bi-house-door"></i>Home
</a>
<div class="sidebar-heading">Static Diagrams</div>
<a class="nav-link" href="#" onclick="showView('diagram', this, './assets/diagrams/high-level-overview.svg', 'Apps High-Level Overview', 'svg')">
<i class="bi bi-diagram-3"></i>High-Level Overview
</a>
<a class="nav-link" href="#" onclick="showView('diagram', this, './assets/diagrams/shift-lifecycle-workflow.svg', 'Core Workflow - Shift Lifecycle', 'svg')">
<i class="bi bi-arrow-repeat"></i>Shift Lifecycle
</a>
<a class="nav-link" href="#" onclick="showView('diagram', this, './assets/diagrams/invoice-workflow.svg', 'Invoice Workflow - Complete', 'svg')">
<i class="bi bi-receipt"></i>Invoice Workflow
</a>
<a class="nav-link" href="#" onclick="showView('diagram', this, './assets/diagrams/complete-workflow.svg', 'Complete Workflow', 'svg')">
<i class="bi bi-infinity"></i>Complete Workflow
</a>
<!-- Dynamic Mermaid diagrams will be inserted here -->
<div id="dynamic-diagrams-section"></div>
</div>
</nav>
<!-- Main Content Area -->
<main class="content-area">
<!-- View 1: Home Content -->
<div id="home-view">
<h2 class="mb-4">Welcome to the Project Launchpad</h2>
<div class="row">
<div class="col-lg-6">
<div class="card shadow-sm mb-4">
<div class="card-header"><h3>Applications (Hosting URLs)</h3></div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
<a target="_blank" href="#">Control Tower (Dev)</a>
<span class="badge bg-primary rounded-pill">Dev</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<a target="_blank" href="#">Control Tower (Staging)</a>
<span class="badge bg-warning text-dark rounded-pill">Staging</span>
</li>
</ul>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header"><h3>Legacy Mobile Apps</h3></div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
<a target="_blank" href="https://play.google.com/store/apps/dev?id=9163719228191263405&hl=en">Google Play Store</a>
<span class="badge bg-success rounded-pill">Live</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<a target="_blank" href="https://apps.apple.com/us/developer/thinkloops-llc/id1719034287">Apple App Store</a>
<span class="badge bg-success rounded-pill">Live</span>
</li>
</ul>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card shadow-sm mb-4">
<div class="card-header"><h3>Google Cloud & Firebase Consoles</h3></div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
<a target="_blank" href="https://console.firebase.google.com/project/krow-workforce-dev/overview">Firebase Console (Dev)</a>
<span class="badge bg-primary rounded-pill">Dev</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<a target="_blank" href="https://console.firebase.google.com/project/krow-workforce-staging/overview">Firebase Console (Staging)</a>
<span class="badge bg-warning text-dark rounded-pill">Staging</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<a target="_blank" href="https://console.cloud.google.com/welcome/new?project=krow-workforce-dev">Google Cloud Console (Dev)</a>
<span class="badge bg-primary rounded-pill">Dev</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<a target="_blank" href="https://console.cloud.google.com/welcome/new?project=krow-workforce-staging">Google Cloud Console (Staging)</a>
<span class="badge bg-warning text-dark rounded-pill">Staging</span>
</li>
</ul>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header"><h3>Access & Resources</h3></div>
<div class="card-body">
<div class="list-group list-group-flush">
<a href="https://github.com/Oloodi/krow-workforce" target="_blank" class="list-group-item list-group-item-action">
<div class="fw-bold">GitHub Repository</div>
</a>
<a href="https://chat.google.com/" target="_blank" class="list-group-item list-group-item-action">
<div class="fw-bold">Team Communication</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- View 2: Diagram Viewer -->
<div id="diagram-viewer">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 id="diagram-title" class="mb-0"></h3>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" id="zoomInBtn" title="Zoom In"><i class="bi bi-zoom-in"></i></button>
<button type="button" class="btn btn-outline-secondary" id="zoomOutBtn" title="Zoom Out"><i class="bi bi-zoom-out"></i></button>
<button type="button" class="btn btn-outline-secondary" id="resetBtn" title="Reset View"><i class="bi bi-aspect-ratio"></i></button>
</div>
</div>
<div id="diagram-container" tabindex="-1">
<!-- SVG will be loaded here -->
</div>
</div>
</main>
</div>
<!-- JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@panzoom/panzoom@4.5.1/dist/panzoom.min.js"></script>
<script>
let mermaidDiagrams = [];
const homeView = document.getElementById('home-view');
const diagramViewer = document.getElementById('diagram-viewer');
const diagramContainer = document.getElementById('diagram-container');
const diagramTitle = document.getElementById('diagram-title');
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const resetBtn = document.getElementById('resetBtn');
let panzoomInstance = null;
let currentScale = 1;
// Initialize Mermaid
mermaid.initialize({
startOnLoad: false,
theme: 'default',
flowchart: {
useMaxWidth: false,
htmlLabels: true,
curve: 'basis'
}
});
// Build hierarchical structure from paths
function buildDiagramHierarchy(diagrams) {
const hierarchy = {};
diagrams.forEach(diagram => {
const parts = diagram.path.split('/');
// Remove 'assets/diagrams/' prefix
const relevantParts = parts.slice(2, -1); // Everything except filename
const filename = parts[parts.length - 1].replace('.mermaid', '');
let current = hierarchy;
relevantParts.forEach(part => {
if (!current[part]) {
current[part] = { _items: [], _children: {} };
}
current = current[part]._children;
});
// Add the item to the last level
const lastLevel = relevantParts.length > 0 ?
relevantParts.reduce((acc, part) => acc[part]._children, hierarchy) :
hierarchy;
if (relevantParts.length > 0) {
const parentKey = relevantParts[relevantParts.length - 1];
if (!hierarchy[relevantParts[0]]) {
hierarchy[relevantParts[0]] = { _items: [], _children: {} };
}
let parent = hierarchy[relevantParts[0]];
for (let i = 1; i < relevantParts.length; i++) {
parent = parent._children[relevantParts[i]];
}
parent._items.push(diagram);
} else {
if (!hierarchy._root) {
hierarchy._root = { _items: [], _children: {} };
}
hierarchy._root._items.push(diagram);
}
});
return hierarchy;
}
// Create navigation from hierarchy
function createNavigation(hierarchy, parentElement, level = 0) {
Object.keys(hierarchy).forEach(key => {
if (key === '_items' || key === '_children' || key === '_root') return;
const section = hierarchy[key];
const heading = document.createElement('div');
heading.className = level === 0 ? 'sidebar-heading' : 'sidebar-subheading';
heading.textContent = key.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
parentElement.appendChild(heading);
// Add items in this section
if (section._items && section._items.length > 0) {
section._items.forEach(diagram => {
const link = document.createElement('a');
link.className = `nav-link ${level > 0 ? 'sub-item' : ''}`;
link.href = '#';
link.onclick = (e) => {
e.preventDefault();
showView('diagram', link, diagram.path, diagram.title, 'mermaid');
};
link.innerHTML = `<i class="bi bi-file-earmark-code"></i>${diagram.title}`;
parentElement.appendChild(link);
});
}
// Recursively add children
if (section._children && Object.keys(section._children).length > 0) {
createNavigation(section._children, parentElement, level + 1);
}
});
}
// Load and render dynamic diagrams navigation
async function loadDynamicDiagrams() {
const dynamicSection = document.getElementById('dynamic-diagrams-section');
try {
// Fetch the diagrams configuration from JSON file
const response = await fetch('./assets/diagrams/diagrams-config.json');
if (!response.ok) {
throw new Error(`Failed to load diagrams config: ${response.status}`);
}
mermaidDiagrams = await response.json();
if (mermaidDiagrams.length > 0) {
const mainHeading = document.createElement('div');
mainHeading.className = 'sidebar-heading';
mainHeading.textContent = 'Mermaid Diagrams';
dynamicSection.appendChild(mainHeading);
const hierarchy = buildDiagramHierarchy(mermaidDiagrams);
createNavigation(hierarchy, dynamicSection);
}
} catch (error) {
console.error('Error loading diagrams configuration:', error);
dynamicSection.innerHTML = `<div class="alert alert-warning m-2 small">No Mermaid diagrams configuration found</div>`;
}
}
function setActiveNav(activeLink) {
document.querySelectorAll('.sidebar .nav-link').forEach(link => {
link.classList.remove('active');
});
activeLink.classList.add('active');
}
async function showView(viewName, navLink, filePath, title, type = 'svg') {
setActiveNav(navLink);
if (panzoomInstance) {
panzoomInstance.destroy();
panzoomInstance = null;
}
diagramContainer.innerHTML = '';
currentScale = 1;
if (viewName === 'home') {
homeView.style.display = 'block';
diagramViewer.style.display = 'none';
} else if (viewName === 'diagram') {
homeView.style.display = 'none';
diagramViewer.style.display = 'flex';
diagramTitle.textContent = title;
diagramContainer.innerHTML = '<div class="loading-spinner"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
try {
if (type === 'svg') {
// Load static SVG files
const response = await fetch(filePath);
if (!response.ok) throw new Error(`Network error: ${response.status}`);
const svgContent = await response.text();
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svgContent, "image/svg+xml");
const svgElement = svgDoc.querySelector('svg');
if (svgElement) {
diagramContainer.innerHTML = '';
diagramContainer.appendChild(svgElement);
panzoomInstance = Panzoom(svgElement, {
canvas: true,
maxScale: 10,
minScale: 0.3,
startScale: 1
});
diagramContainer.addEventListener('wheel', panzoomInstance.zoomWithWheel);
diagramContainer.focus();
} else {
throw new Error('No SVG element found.');
}
} else if (type === 'mermaid') {
// Load and render Mermaid files
const response = await fetch(filePath);
if (!response.ok) throw new Error(`Network error: ${response.status}`);
const mermaidCode = await response.text();
const { svg } = await mermaid.render('mermaidDiagram_' + Date.now(), mermaidCode);
diagramContainer.innerHTML = svg;
const svgElement = diagramContainer.querySelector('svg');
if (svgElement) {
svgElement.style.maxWidth = 'none';
svgElement.style.height = 'auto';
panzoomInstance = Panzoom(svgElement, {
canvas: true,
maxScale: 10,
minScale: 0.3,
startScale: 1
});
diagramContainer.addEventListener('wheel', panzoomInstance.zoomWithWheel);
diagramContainer.focus();
} else {
throw new Error('Failed to render Mermaid diagram.');
}
}
} catch (error) {
diagramContainer.innerHTML = `<div class="alert alert-danger m-3">Failed to load diagram: ${error.message}</div>`;
}
}
}
zoomInBtn.addEventListener('click', () => {
if (panzoomInstance) {
panzoomInstance.zoomIn();
diagramContainer.focus();
}
});
zoomOutBtn.addEventListener('click', () => {
if (panzoomInstance) {
panzoomInstance.zoomOut();
diagramContainer.focus();
}
});
resetBtn.addEventListener('click', () => {
if (panzoomInstance) {
panzoomInstance.reset();
diagramContainer.focus();
}
});
document.addEventListener('DOMContentLoaded', () => {
loadDynamicDiagrams();
showView('home', document.getElementById('nav-home'));
});
</script>
</body>
</html>