feat(welcome): add changelog section to dashboard

This commit is contained in:
Yury Kossakovsky
2026-01-12 10:01:15 -07:00
parent 361a726a07
commit b28093b5cd
5 changed files with 101 additions and 14 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ volumes/
docker-compose.override.yml
docker-compose.n8n-workers.yml
welcome/data.json
welcome/changelog.json
# Custom TLS certificates
certs/*

View File

@@ -1,11 +1,9 @@
# Changelog
All notable changes to this project are documented in this file.
## [1.2.0] - 2026-01-12
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Changelog section on Welcome Page dashboard
## [1.1.0] - 2026-01-11
@@ -219,3 +217,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Langfuse - LLM observability and analytics platform
- Initial fork from coleam00/local-ai-packager with enhanced service support
---
All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

View File

@@ -541,3 +541,30 @@ EOF
log_success "Welcome page data generated at: $OUTPUT_FILE"
log_info "Access it at: https://${WELCOME_HOSTNAME:-welcome.${USER_DOMAIN_NAME}}"
# Generate changelog.json with CHANGELOG.md content
CHANGELOG_JSON_FILE="$PROJECT_ROOT/welcome/changelog.json"
CHANGELOG_SOURCE="$PROJECT_ROOT/CHANGELOG.md"
if [ -f "$CHANGELOG_SOURCE" ]; then
# Read and escape content for JSON (preserve newlines as \n)
# Using awk for cross-platform compatibility (macOS + Linux)
CHANGELOG_CONTENT=$(awk '
BEGIN { ORS="" }
{
gsub(/\\/, "\\\\") # Escape backslashes first
gsub(/"/, "\\\"") # Escape double quotes
gsub(/\t/, "\\t") # Escape tabs
gsub(/\r/, "") # Remove carriage returns (CRLF → LF)
if (NR > 1) printf "\\n"
printf "%s", $0
}
' "$CHANGELOG_SOURCE")
# Write changelog.json file
printf '{\n "content": "%s"\n}\n' "$CHANGELOG_CONTENT" > "$CHANGELOG_JSON_FILE"
log_success "Changelog JSON generated at: $CHANGELOG_JSON_FILE"
else
log_warning "CHANGELOG.md not found, skipping changelog.json generation"
fi

View File

@@ -136,6 +136,11 @@
warning: (className = '') => `
<svg class="${className}" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>`,
changelog: (className = '') => `
<svg class="${className}" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/>
</svg>`
};
@@ -844,6 +849,7 @@
const servicesContainer = document.getElementById('services-container');
const quickstartContainer = document.getElementById('quickstart-container');
const commandsContainer = document.getElementById('commands-container');
const changelogContainer = document.getElementById('changelog-container');
const domainInfo = document.getElementById('domain-info');
/**
@@ -959,6 +965,26 @@
commandsContainer.appendChild(grid);
}
/**
* Render changelog content
*/
function renderChangelog(content) {
if (!changelogContainer) return;
changelogContainer.innerHTML = '';
if (!content) {
changelogContainer.innerHTML = `
<p class="text-gray-500 text-center py-8">Changelog not available</p>
`;
return;
}
const pre = document.createElement('pre');
pre.className = 'text-sm text-gray-300 font-mono whitespace-pre-wrap break-words leading-relaxed';
pre.textContent = content;
changelogContainer.appendChild(pre);
}
/**
* Render error state in services container
*/
@@ -984,14 +1010,26 @@
// Always render commands (static content)
renderCommands();
try {
const response = await fetch('data.json');
// Fetch both JSON files in parallel for better performance
// Each fetch is handled independently - changelog failure won't affect main data
const [changelogResult, dataResult] = await Promise.allSettled([
fetch('changelog.json').then(r => r.ok ? r.json() : null),
fetch('data.json').then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
]);
if (!response.ok) {
throw new Error(`Failed to load data (${response.status})`);
// Handle changelog (independent - failures don't break the page)
if (changelogResult.status === 'fulfilled' && changelogResult.value?.content) {
renderChangelog(changelogResult.value.content);
} else {
if (changelogResult.status === 'rejected') {
console.error('Error loading changelog:', changelogResult.reason);
}
renderChangelog(null);
}
const data = await response.json();
// Handle main data
if (dataResult.status === 'fulfilled' && dataResult.value) {
const data = dataResult.value;
// Update domain info
if (domainInfo) {
@@ -1009,9 +1047,8 @@
// Render quick start
renderQuickStart(data.quick_start);
} catch (error) {
console.error('Error loading data:', error);
} else {
console.error('Error loading data:', dataResult.reason);
// Show error in UI
renderServicesError();

View File

@@ -51,7 +51,7 @@
}
::-webkit-scrollbar-track {
background: rgba(17, 17, 17, 0.8);
background: transparent;
border-radius: 5px;
}
@@ -198,6 +198,23 @@
<div class="gradient-line my-8" aria-hidden="true"></div>
<!-- Changelog Section -->
<section class="mb-16">
<div class="flex items-center gap-3 mb-6">
<div class="w-10 h-10 rounded-lg bg-brand/10 border border-brand/20 flex items-center justify-center"
data-section-icon="changelog"></div>
<h2 class="text-2xl font-semibold text-white">Changelog</h2>
</div>
<div id="changelog-container"
class="bg-surface-100 rounded-xl border border-surface-400 p-6 overflow-y-auto"
style="max-height: 444px;">
<!-- Changelog content will be injected here by JavaScript -->
<div class="animate-pulse h-32"></div>
</div>
</section>
<div class="gradient-line my-8" aria-hidden="true"></div>
<!-- Documentation Section -->
<section class="mb-16">
<div class="flex items-center gap-3 mb-6">