Files
app-installer/templates/available_projects.html
SimolZimol 0f83f15588 new file: README.md
new file:   app.py
	new file:   config.example.json
	new file:   config.json
	new file:   docker_diagnose.py
	new file:   requirements.txt
	new file:   start.bat
	new file:   templates/available_projects.html
	new file:   templates/base.html
	new file:   templates/config.html
	new file:   templates/docker_status.html
	new file:   templates/index.html
	new file:   templates/project_details.html
	new file:   templates/project_details_fixed.html
	new file:   templates/project_details_new.html
	new file:   templates/project_details_old.html
	.gitignore
2025-07-04 23:50:04 +02:00

311 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Verfügbare Apps - {{ super() }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-cloud-download-alt me-2"></i>Verfügbare Apps</h2>
<a href="{{ url_for('refresh_projects') }}" class="btn btn-primary">
<i class="fas fa-sync-alt"></i> Liste aktualisieren
</a>
</div>
{% if not projects %}
<div class="text-center py-5">
<i class="fas fa-exclamation-triangle fa-3x text-warning mb-3"></i>
<h3 class="text-muted">Keine Projekte verfügbar</h3>
<p class="text-muted mb-4">
Stellen Sie sicher, dass eine gültige Projekt-URL in den
<a href="{{ url_for('config') }}">Einstellungen</a> konfiguriert ist.
</p>
<a href="{{ url_for('config') }}" class="btn btn-primary">
<i class="fas fa-cog"></i> Einstellungen öffnen
</a>
</div>
{% else %}
<div class="row">
{% for project in projects %}
<div class="col-lg-6 col-xl-4 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-git-alt me-2"></i>{{ project.name or 'Unbekannt' }}
</h5>
</div>
<div class="card-body d-flex flex-column">
<div class="mb-3">
<small class="text-muted">Repository URL:</small>
<p class="card-text small font-monospace">{{ project.url }}</p>
</div>
{% if project.description %}
<div class="mb-3">
<small class="text-muted">Beschreibung:</small>
<p class="card-text">{{ project.description }}</p>
</div>
{% endif %}
{% if project.tags %}
<div class="mb-3">
{% for tag in project.tags %}
<span class="badge bg-secondary me-1">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
<div class="row mb-3">
<div class="col-6">
<small class="text-muted">Sprache:</small><br>
<span class="badge bg-info">{{ project.language or 'Unbekannt' }}</span>
</div>
<div class="col-6">
<small class="text-muted">Größe:</small><br>
<span class="text-muted">{{ project.size or 'Unbekannt' }}</span>
</div>
</div>
{% if project.last_updated %}
<div class="mb-3">
<small class="text-muted">Letztes Update: {{ project.last_updated }}</small>
</div>
{% endif %}
<div class="mt-auto">
<div class="d-grid gap-2">
<button class="btn btn-success" onclick="installProject('{{ project.url }}', '{{ project.name }}')">
<i class="fas fa-download"></i> Installieren
</button>
<a href="{{ project.url }}" target="_blank" class="btn btn-outline-info btn-sm">
<i class="fas fa-external-link-alt"></i> Repository anzeigen
</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Installationsstatistiken -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-bar me-2"></i>Installationsstatistiken
</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-md-3">
<div class="stat-item">
<span class="stat-number text-primary">{{ projects|length }}</span>
<span class="text-muted">Verfügbare Apps</span>
</div>
</div>
<div class="col-md-3">
<div class="stat-item">
<span class="stat-number text-success">{{ projects|selectattr('language', 'defined')|list|length }}</span>
<span class="text-muted">Mit Sprache</span>
</div>
</div>
<div class="col-md-3">
<div class="stat-item">
<span class="stat-number text-info">{{ projects|selectattr('description', 'defined')|list|length }}</span>
<span class="text-muted">Mit Beschreibung</span>
</div>
</div>
<div class="col-md-3">
<div class="stat-item">
<span class="stat-number text-warning">{{ projects|selectattr('tags', 'defined')|list|length }}</span>
<span class="text-muted">Mit Tags</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Bulk Installation Modal -->
<div class="modal fade" id="bulkInstallModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-download me-2"></i>Masseninstallation
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Wählen Sie die Apps aus, die Sie installieren möchten:</p>
<div class="row" id="bulkInstallList">
{% for project in projects %}
<div class="col-md-6 mb-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="{{ project.url }}" id="bulk_{{ loop.index }}">
<label class="form-check-label" for="bulk_{{ loop.index }}">
{{ project.name }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-primary" onclick="startBulkInstall()">
<i class="fas fa-download"></i> Ausgewählte installieren
</button>
</div>
</div>
</div>
</div>
<!-- Filter und Sortierung -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-filter me-2"></i>Filter & Sortierung
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<label for="searchFilter" class="form-label">Suche:</label>
<input type="text" class="form-control" id="searchFilter" placeholder="App-Name oder Beschreibung...">
</div>
<div class="col-md-3">
<label for="languageFilter" class="form-label">Programmiersprache:</label>
<select class="form-select" id="languageFilter">
<option value="">Alle Sprachen</option>
{% set languages = projects|map(attribute='language')|select('defined')|unique|list %}
{% for lang in languages %}
<option value="{{ lang }}">{{ lang }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label for="sortBy" class="form-label">Sortieren nach:</label>
<select class="form-select" id="sortBy">
<option value="name">Name</option>
<option value="language">Sprache</option>
<option value="updated">Letztes Update</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<div class="d-grid">
<button class="btn btn-outline-primary" onclick="showBulkInstall()">
<i class="fas fa-layer-group"></i> Masseninstallation
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Projektfilterung
function filterProjects() {
const searchTerm = document.getElementById('searchFilter').value.toLowerCase();
const languageFilter = document.getElementById('languageFilter').value;
const sortBy = document.getElementById('sortBy').value;
const projectCards = document.querySelectorAll('.col-lg-6.col-xl-4');
projectCards.forEach(card => {
const projectName = card.querySelector('.card-title').textContent.toLowerCase();
const projectDesc = card.querySelector('.card-text')?.textContent.toLowerCase() || '';
const projectLang = card.querySelector('.badge.bg-info')?.textContent || '';
let show = true;
// Textsuche
if (searchTerm && !projectName.includes(searchTerm) && !projectDesc.includes(searchTerm)) {
show = false;
}
// Sprachfilter
if (languageFilter && projectLang !== languageFilter) {
show = false;
}
card.style.display = show ? 'block' : 'none';
});
}
// Event Listener für Filter
document.getElementById('searchFilter').addEventListener('input', filterProjects);
document.getElementById('languageFilter').addEventListener('change', filterProjects);
document.getElementById('sortBy').addEventListener('change', filterProjects);
// Masseninstallation
function showBulkInstall() {
const modal = new bootstrap.Modal(document.getElementById('bulkInstallModal'));
modal.show();
}
function startBulkInstall() {
const checkboxes = document.querySelectorAll('#bulkInstallList input[type="checkbox"]:checked');
const selectedApps = Array.from(checkboxes).map(cb => ({
url: cb.value,
name: cb.nextElementSibling.textContent.trim()
}));
if (selectedApps.length === 0) {
alert('Bitte wählen Sie mindestens eine App aus.');
return;
}
if (confirm(`${selectedApps.length} Apps werden installiert. Fortfahren?`)) {
// Schließe Modal
bootstrap.Modal.getInstance(document.getElementById('bulkInstallModal')).hide();
// Installiere Apps nacheinander
installAppsSequentially(selectedApps, 0);
}
}
function installAppsSequentially(apps, index) {
if (index >= apps.length) {
alert('Alle Apps wurden installiert!');
location.reload();
return;
}
const app = apps[index];
const progressText = `Installiere ${index + 1}/${apps.length}: ${app.name}`;
// Zeige Fortschritt (könnte eine bessere Progress-Bar sein)
console.log(progressText);
fetch('/install_project', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `project_url=${encodeURIComponent(app.url)}&project_name=${encodeURIComponent(app.name)}`
})
.then(response => response.json())
.then(data => {
console.log(`${app.name}: ${data.success ? 'Erfolgreich' : 'Fehler'} - ${data.message}`);
// Installiere nächste App
setTimeout(() => installAppsSequentially(apps, index + 1), 1000);
})
.catch(error => {
console.error(`Fehler bei ${app.name}:`, error);
// Installiere trotzdem weiter
setTimeout(() => installAppsSequentially(apps, index + 1), 1000);
});
}
</script>
{% endblock %}