feat: deselect all button, static serving for production, cloudflare tunnel setup
This commit is contained in:
1
api/package-lock.json
generated
1
api/package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"express": "4.21.2",
|
"express": "4.21.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
|
"serve-static": "^1.16.2",
|
||||||
"sql.js": "^1.11.0"
|
"sql.js": "^1.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"express": "4.21.2",
|
"express": "4.21.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
|
"serve-static": "^1.16.2",
|
||||||
"sql.js": "^1.11.0"
|
"sql.js": "^1.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { config } from 'dotenv';
|
import { config } from 'dotenv';
|
||||||
|
import serveStatic from 'serve-static';
|
||||||
|
|
||||||
config();
|
config();
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const DATA_DIR = join(__dirname, 'data');
|
const DATA_DIR = join(__dirname, 'data');
|
||||||
|
const DIST_DIR = join(__dirname, '..', 'dist');
|
||||||
mkdirSync(DATA_DIR, { recursive: true });
|
mkdirSync(DATA_DIR, { recursive: true });
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -240,6 +242,14 @@ app.get('/api/analytics/stats', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============ STATIC FILES + SPA FALLBACK ============
|
||||||
|
app.use(serveStatic(DIST_DIR));
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
if (!req.path.startsWith('/api')) {
|
||||||
|
res.sendFile(join(DIST_DIR, 'index.html'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ============ START ============
|
// ============ START ============
|
||||||
const PORT = process.env.PORT || 3052;
|
const PORT = process.env.PORT || 3052;
|
||||||
app.listen(PORT, () => { console.log(`StackPilot API running on port ${PORT}`); });
|
app.listen(PORT, () => { console.log(`StackPilot running on port ${PORT}`); });
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
<button class="preset-btn" data-preset="dev">💻 Dev Stack</button>
|
<button class="preset-btn" data-preset="dev">💻 Dev Stack</button>
|
||||||
<button class="preset-btn" data-preset="homelab">🏠 Homelab Essentials</button>
|
<button class="preset-btn" data-preset="homelab">🏠 Homelab Essentials</button>
|
||||||
<button class="preset-btn" data-preset="gaming">🎮 Game Servers</button>
|
<button class="preset-btn" data-preset="gaming">🎮 Game Servers</button>
|
||||||
|
<button class="preset-btn preset-clear" id="deselect-all-btn">✕ Deselect All</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="dependency-warnings" class="dependency-warnings"></div>
|
<div id="dependency-warnings" class="dependency-warnings"></div>
|
||||||
<div id="port-conflicts" class="port-conflicts"></div>
|
<div id="port-conflicts" class="port-conflicts"></div>
|
||||||
|
|||||||
11
src/main.js
11
src/main.js
@@ -419,7 +419,7 @@ function initSearch() {
|
|||||||
|
|
||||||
// Presets
|
// Presets
|
||||||
function initPresets() {
|
function initPresets() {
|
||||||
document.querySelectorAll('.preset-btn').forEach(btn => {
|
document.querySelectorAll('.preset-btn[data-preset]').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
const preset = presets[btn.dataset.preset];
|
const preset = presets[btn.dataset.preset];
|
||||||
if (!preset) return;
|
if (!preset) return;
|
||||||
@@ -433,6 +433,15 @@ function initPresets() {
|
|||||||
updateUrlState();
|
updateUrlState();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('deselect-all-btn').addEventListener('click', () => {
|
||||||
|
state.selected.clear();
|
||||||
|
renderAppGrid();
|
||||||
|
checkDependencies();
|
||||||
|
checkPortConflicts();
|
||||||
|
updatePreview();
|
||||||
|
updateUrlState();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode switching (builder vs analyser vs gallery)
|
// Mode switching (builder vs analyser vs gallery)
|
||||||
|
|||||||
@@ -202,6 +202,8 @@ body {
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
.preset-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-glow); }
|
.preset-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-glow); }
|
||||||
|
.preset-btn.preset-clear { border-color: rgba(239,68,68,0.3); color: var(--danger); }
|
||||||
|
.preset-btn.preset-clear:hover { background: rgba(239,68,68,0.1); border-color: var(--danger); }
|
||||||
.dependency-warnings, .port-conflicts { margin-bottom: 1rem; }
|
.dependency-warnings, .port-conflicts { margin-bottom: 1rem; }
|
||||||
.dep-warning, .port-conflict {
|
.dep-warning, .port-conflict {
|
||||||
background: rgba(245,158,11,0.06);
|
background: rgba(245,158,11,0.06);
|
||||||
|
|||||||
Reference in New Issue
Block a user