Add Linux App Store Web Portal with Kubernetes deployment

- React-style static web portal with Tailwind CSS
- OS detection (Debian/Ubuntu, Fedora, Arch, Generic)
- Dynamic install commands (apt://, appstream://, flatpak)
- 9 pre-configured applications
- Kubernetes: 2 replicas, LoadBalancer service, Nginx ingress
- GitLab Container Registry (git.giaco.net/capitano/webapplinux)
- Namespace: linuxwebapp
- Added Dockerfile, nginx.conf, deploy.sh, docker-compose.yml
- Updated README.md with deployment instructions
This commit is contained in:
capitano
2026-03-18 22:20:20 +01:00
parent d13597e6b2
commit 533cbed8d3
13 changed files with 560 additions and 19 deletions

112
frontend/apps.json Normal file
View File

@@ -0,0 +1,112 @@
{
"apps": [
{
"id": "vlc",
"name": "VLC Media Player",
"description": "The most complete media player ever created. Play all your favorite media formats.",
"icon": "fa-film",
"colors": "from-purple-500 to-indigo-600",
"packages": {
"apt": "vlc",
"appstream": "org.videolan.VLC",
"flatpak": "https://flathub.org-repo/vlc.flatpakref"
}
},
{
"id": "gimp",
"name": "GIMP",
"description": "GNU Image Manipulation Program - professional-grade photo editing tool.",
"icon": "fa-image",
"colors": "from-blue-500 to-cyan-600",
"packages": {
"apt": "gimp",
"appstream": "org.gimp.GIMP",
"flatpak": "https://flathub.org-repo/gimp.flatpakref"
}
},
{
"id": "vscode",
"name": "VS Code",
"description": "Visual Studio Code - the code editor redefined for modern development.",
"icon": "fa-code",
"colors": "from-blue-600 to-blue-800",
"packages": {
"apt": "code",
"appstream": "com.visualstudio.code",
"flatpak": "https://flathub.org-repo/code.flatpakref"
}
},
{
"id": "thunderbird",
"name": "Thunderbird",
"description": "Email client that puts control back in your hands with powerful features.",
"icon": "fa-envelope",
"colors": "from-orange-500 to-red-600",
"packages": {
"apt": "thunderbird",
"appstream": "org.mozilla.thunderbird",
"flatpak": "https://flathub.org-repo/thunderbird.flatpakref"
}
},
{
"id": "libreoffice",
"name": "LibreOffice",
"description": "The-free-and-open-source productivity suite for documents, spreadsheets, and presentations.",
"icon": "fa-file-alt",
"colors": "from-teal-500 to-green-600",
"packages": {
"apt": "libreoffice",
"appstream": "org.libreoffice.LibreOffice",
"flatpak": "https://flathub.org-repo/libreoffice.flatpakref"
}
},
{
"id": "firefox",
"name": "Firefox",
"description": "The web browser built for privacy, speed, and customization.",
"icon": "fa-globe",
"colors": "from-orange-600 to-red-700",
"packages": {
"apt": "firefox",
"appstream": "org.mozilla.firefox",
"flatpak": "https://flathub.org-repo/firefox.flatpakref"
}
},
{
"id": "inkscape",
"name": "Inkscape",
"description": "Professional vector graphics editor for illustrations, diagrams, and logos.",
"icon": "fa-pen-nib",
"colors": "from-blue-400 to-indigo-500",
"packages": {
"apt": "inkscape",
"appstream": "org.inkscape.Inkscape",
"flatpak": "https://flathub.org-repo/inkscape.flatpakref"
}
},
{
"id": "discord",
"name": "Discord",
"description": "All-in-one voice and text chat for gamers and communities.",
"icon": "fa-comments",
"colors": "from-indigo-500 to-purple-600",
"packages": {
"apt": "discord",
"appstream": "com.discordapp.Discord",
"flatpak": "https://flathub.org-repo/discord.flatpakref"
}
},
{
"id": "github-desktop",
"name": "GitHub Desktop",
"description": "Simple collaboration from your desktop - git made easy.",
"icon": "fa-github",
"colors": "from-gray-600 to-gray-800",
"packages": {
"apt": "github-desktop",
"appstream": "com.githubDesktop.githubDesktop",
"flatpak": "https://flathub.org-repo/github-desktop.flatpakref"
}
}
]
}

49
frontend/index.html Normal file
View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Linux App Store</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
gray: {
850: '#1f2937',
900: '#111827',
}
}
}
}
}
</script>
</head>
<body class="bg-gray-900 text-white">
<nav class="bg-gray-850 border-b border-gray-700">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<i class="fas fa-store text-blue-500 text-2xl mr-3"></i>
<span class="text-xl font-bold">Linux App Store</span>
</div>
<div id="os-display" class="hidden text-sm text-gray-400"></div>
</div>
</div>
</nav>
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="mb-8">
<h1 class="text-3xl font-bold mb-2">Discovery</h1>
<p class="text-gray-400">Discover and install applications directly on your Linux system</p>
</div>
<div id="apps-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
</div>
</main>
<script src="script.js"></script>
</body>
</html>

192
frontend/script.js Normal file
View File

@@ -0,0 +1,192 @@
const OS_TYPE = {
DEB: 'deb',
RPM: 'rpm',
ARCH: 'arch',
GENERIC: 'generic'
};
function detectOS() {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('ubuntu') ||
userAgent.includes('debian') ||
userAgent.includes('linuxmint') ||
userAgent.includes('pop!_os')) {
return { type: OS_TYPE.DEB, name: 'Debian/Ubuntu-based' };
}
if (userAgent.includes('fedora') ||
userAgent.includes('rhel') ||
userAgent.includes('redhat') ||
userAgent.includes('centos') ||
userAgent.includes('opensuse') ||
userAgent.includes('suse')) {
return { type: OS_TYPE.RPM, name: 'Fedora/RedHat-based' };
}
if (userAgent.includes('arch') ||
userAgent.includes('endeavouros') ||
userAgent.includes('manjaro')) {
return { type: OS_TYPE.ARCH, name: 'Arch Linux' };
}
return { type: OS_TYPE.GENERIC, name: 'Generic Linux' };
}
function renderApps(apps) {
const container = document.getElementById('apps-container');
apps.forEach(app => {
const appCard = document.createElement('div');
appCard.className = 'bg-gray-850 rounded-xl overflow-hidden shadow-lg hover:shadow-2xl transition-shadow duration-300 border border-gray-700';
appCard.innerHTML = `
<div class="p-6">
<div class="flex items-start justify-between mb-4">
<div class="flex items-center">
<div class="w-12 h-12 rounded-lg bg-gradient-to-br ${app.colors} flex items-center justify-center mr-4">
<i class="${app.icon} fa-xl text-white"></i>
</div>
<h3 class="text-xl font-bold">${app.name}</h3>
</div>
</div>
<p class="text-gray-400 text-sm mb-6 line-clamp-2">${app.description}</p>
<div class="bg-gray-900 rounded-lg p-3 mb-4 text-xs text-gray-500 font-mono">
<div class="mb-1"><i class="fas fa-terminal mr-2"></i>Installazione:</div>
<div id="install-${app.id}">Caricamento...</div>
</div>
<button onclick="installApp('${app.id}', '${app.packages.apt}', '${app.packages.appstream}', '${app.packages.flatpak}')"
class="w-full bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-bold py-3 px-4 rounded-lg transition-all duration-200 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
<i class="fas fa-download mr-2"></i>Installa
</button>
</div>
`;
container.appendChild(appCard);
});
}
function updateInstallLink(appId, aptPackage, appstreamId, flatpakUrl) {
const osInfo = detectOS();
const installDiv = document.getElementById(`install-${appId}`);
let installCommand = '';
let protocol = '';
switch (osInfo.type) {
case OS_TYPE.DEB:
protocol = 'apt://';
installCommand = `${protocol}${aptPackage}`;
break;
case OS_TYPE.RPM:
protocol = 'appstream://';
installCommand = `${protocol}${appstreamId}`;
break;
case OS_TYPE.ARCH:
protocol = 'appstream://';
installCommand = `${protocol}${appstreamId} (pacman -S ${aptPackage})`;
break;
case OS_TYPE.GENERIC:
protocol = 'flatpak';
installCommand = `Download: ${flatpakUrl}`;
break;
}
installDiv.innerHTML = `<i class="fas fa-terminal mr-2"></i> ${installCommand}`;
const installBtn = document.querySelector(`button[onclick="installApp('${appId}', '${aptPackage}', '${appstreamId}', '${flatpakUrl}')"]`);
installBtn.onclick = function() {
handleInstall(appId, aptPackage, appstreamId, flatpakUrl);
};
}
function handleInstall(appId, aptPackage, appstreamId, flatpakUrl) {
const osInfo = detectOS();
let url = '';
switch (osInfo.type) {
case OS_TYPE.DEB:
url = `apt://${aptPackage}`;
break;
case OS_TYPE.RPM:
url = `appstream://${appstreamId}`;
break;
case OS_TYPE.ARCH:
url = `appstream://${appstreamId}`;
break;
case OS_TYPE.GENERIC:
url = flatpakUrl;
break;
}
if (url.startsWith('apt://') || url.startsWith('appstream://')) {
const protocol = url.split(':')[0];
let command = url.replace(`${protocol}://`, '');
if (protocol === 'apt') {
command = `<span class="text-green-400">sudo apt install ${command}</span>`;
} else {
command = `<span class="text-green-400">${protocol} install ${command}</span>`;
}
const notification = createNotification(`Comando generato per ${osInfo.name}:<br>${command}`, 'success');
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 5000);
} else if (url) {
window.open(url, '_blank');
}
}
function createNotification(message, type) {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 bg-gray-800 border-l-4 border-${type === 'success' ? 'green' : 'red'}-500 text-white px-6 py-4 rounded shadow-lg z-50 transition-all duration-500 transform translate-x-full`;
notification.innerHTML = `
<div class="flex items-center">
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-circle'} text-${type === 'success' ? 'green' : 'red'}-500 mr-3"></i>
<div>${message}</div>
</div>
`;
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
return notification;
}
function highlightOS(osInfo) {
const osDisplay = document.getElementById('os-display');
if (osDisplay) {
osDisplay.innerHTML = `<i class="fab fa-${osInfo.name.includes('Ubuntu') ? 'ubuntu' : osInfo.name.includes('Fedora') ? 'fedora' : osInfo.name.includes('Arch') ? 'linux' : 'linux'} mr-2"></i>${osInfo.name}`;
osDisplay.classList.remove('hidden');
}
}
function init() {
fetch('apps.json')
.then(response => response.json())
.then(data => {
const osInfo = detectOS();
highlightOS(osInfo);
renderApps(data.apps);
data.apps.forEach(app => {
updateInstallLink(app.id, app.packages.apt, app.packages.appstream, app.packages.flatpak);
});
})
.catch(error => {
console.error('Error loading apps:', error);
const container = document.getElementById('apps-container');
container.innerHTML = `<div class="col-span-full text-center py-10 text-red-500">
<i class="fas fa-exclamation-triangle text-3xl mb-2"></i>
<p>Errore nel caricamento delle applicazioni</p>
</div>`;
});
}
document.addEventListener('DOMContentLoaded', init);