Gitea:
Push für Serveranwendung in Gitea implementiert
Dieser Commit ist enthalten in:
@ -87,6 +87,15 @@ class ApiClient {
|
||||
this.setCsrfToken(newCsrfToken);
|
||||
}
|
||||
|
||||
// Update auth token if refreshed by server
|
||||
const newAuthToken = response.headers.get('X-New-Token');
|
||||
if (newAuthToken) {
|
||||
this.setToken(newAuthToken);
|
||||
window.dispatchEvent(new CustomEvent('auth:token-refreshed', {
|
||||
detail: { token: newAuthToken }
|
||||
}));
|
||||
}
|
||||
|
||||
// Handle 401 Unauthorized
|
||||
if (response.status === 401) {
|
||||
this.setToken(null);
|
||||
@ -827,6 +836,54 @@ class ApiClient {
|
||||
async gitRenameBranch(projectId, oldName, newName) {
|
||||
return this.post(`/git/rename-branch/${projectId}`, { oldName, newName });
|
||||
}
|
||||
|
||||
// =====================
|
||||
// SERVER GIT ENDPOINTS (Server-Modus)
|
||||
// =====================
|
||||
|
||||
async getServerGitInfo() {
|
||||
return this.get('/git/server/info');
|
||||
}
|
||||
|
||||
async getServerGitStatus() {
|
||||
return this.get('/git/server/status');
|
||||
}
|
||||
|
||||
async getServerGitBranches() {
|
||||
return this.get('/git/server/branches');
|
||||
}
|
||||
|
||||
async getServerGitCommits(limit = 20) {
|
||||
return this.get(`/git/server/commits?limit=${limit}`);
|
||||
}
|
||||
|
||||
async getServerGitRemote() {
|
||||
return this.get('/git/server/remote');
|
||||
}
|
||||
|
||||
async serverGitStage() {
|
||||
return this.post('/git/server/stage', {});
|
||||
}
|
||||
|
||||
async serverGitCommit(message, stageAll = true) {
|
||||
return this.post('/git/server/commit', { message, stageAll });
|
||||
}
|
||||
|
||||
async serverGitPush(branch = null, force = false) {
|
||||
return this.post('/git/server/push', { branch, force });
|
||||
}
|
||||
|
||||
async serverGitPull(branch = null) {
|
||||
return this.post('/git/server/pull', { branch });
|
||||
}
|
||||
|
||||
async serverGitFetch() {
|
||||
return this.post('/git/server/fetch', {});
|
||||
}
|
||||
|
||||
async serverGitCheckout(branch) {
|
||||
return this.post('/git/server/checkout', { branch });
|
||||
}
|
||||
}
|
||||
|
||||
// Custom API Error Class
|
||||
|
||||
@ -370,6 +370,258 @@ class UserMenuHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Session Timer Handler
|
||||
class SessionTimerHandler {
|
||||
constructor(authManager) {
|
||||
this.auth = authManager;
|
||||
this.timerElement = null;
|
||||
this.countdownElement = null;
|
||||
this.intervalId = null;
|
||||
this.expiresAt = null;
|
||||
this.warningThreshold = 60; // Warnung bei 60 Sekunden verbleibend
|
||||
this.refreshDebounceTimer = null;
|
||||
this.refreshDebounceDelay = 1000; // 1 Sekunde Debounce
|
||||
this.isRefreshing = false;
|
||||
this.isActive = false; // Nur aktiv wenn eingeloggt und Timer läuft
|
||||
}
|
||||
|
||||
init() {
|
||||
this.timerElement = $('#session-timer');
|
||||
this.countdownElement = $('#session-countdown');
|
||||
|
||||
// Bei Login neu initialisieren
|
||||
window.addEventListener('auth:login', () => {
|
||||
// Kurze Verzögerung um sicherzustellen, dass Token gespeichert ist
|
||||
setTimeout(() => {
|
||||
this.updateFromToken();
|
||||
this.start();
|
||||
this.isActive = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Bei Logout stoppen
|
||||
window.addEventListener('auth:logout', () => {
|
||||
this.isActive = false;
|
||||
this.stop();
|
||||
this.hide();
|
||||
});
|
||||
|
||||
// Bei Interaktionen Session refreshen (mit Debouncing)
|
||||
this.bindInteractionEvents();
|
||||
}
|
||||
|
||||
// Interaktions-Events binden für Session-Refresh
|
||||
bindInteractionEvents() {
|
||||
const refreshOnInteraction = (e) => {
|
||||
// Nicht refreshen wenn nicht aktiv (nicht eingeloggt oder Timer läuft nicht)
|
||||
if (!this.isActive) return;
|
||||
|
||||
// Nicht refreshen bei Klicks auf Login-Formular
|
||||
if (e.target.closest('#login-form') || e.target.closest('.login-container')) return;
|
||||
|
||||
// Nur refreshen wenn Token existiert
|
||||
if (!localStorage.getItem('auth_token')) return;
|
||||
|
||||
// Debounce: Nur alle X ms refreshen
|
||||
if (this.refreshDebounceTimer) {
|
||||
clearTimeout(this.refreshDebounceTimer);
|
||||
}
|
||||
|
||||
this.refreshDebounceTimer = setTimeout(() => {
|
||||
this.refreshSession();
|
||||
}, this.refreshDebounceDelay);
|
||||
};
|
||||
|
||||
// Click-Events auf dem gesamten Dokument
|
||||
document.addEventListener('click', refreshOnInteraction);
|
||||
|
||||
// Keyboard-Events
|
||||
document.addEventListener('keydown', refreshOnInteraction);
|
||||
}
|
||||
|
||||
// Session beim Server refreshen
|
||||
async refreshSession() {
|
||||
if (this.isRefreshing) return;
|
||||
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) return;
|
||||
|
||||
this.isRefreshing = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.token) {
|
||||
// Wichtig: api.setToken() verwenden, um den Cache zu aktualisieren
|
||||
api.setToken(data.token);
|
||||
this.expiresAt = this.parseToken(data.token);
|
||||
this.timerElement?.classList.remove('warning', 'critical');
|
||||
|
||||
// CSRF-Token auch aktualisieren
|
||||
if (data.csrfToken) {
|
||||
api.setCsrfToken(data.csrfToken);
|
||||
}
|
||||
}
|
||||
} else if (response.status === 401) {
|
||||
// Token ungültig - ausloggen
|
||||
this.auth.logout();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session refresh error:', error);
|
||||
} finally {
|
||||
this.isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// JWT-Token parsen und Ablaufzeit extrahieren
|
||||
parseToken(token) {
|
||||
if (!token) return null;
|
||||
try {
|
||||
const payload = token.split('.')[1];
|
||||
const decoded = JSON.parse(atob(payload));
|
||||
return decoded.exp ? decoded.exp * 1000 : null; // exp ist in Sekunden, wir brauchen ms
|
||||
} catch (e) {
|
||||
console.error('Token parsing error:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
updateFromToken() {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
this.expiresAt = this.parseToken(token);
|
||||
}
|
||||
|
||||
// Beim Seiten-Reload aufrufen
|
||||
async initFromExistingSession() {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) {
|
||||
this.hide();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prüfen ob Token noch gültig ist
|
||||
const expiresAt = this.parseToken(token);
|
||||
if (!expiresAt || expiresAt <= Date.now()) {
|
||||
// Token abgelaufen
|
||||
this.hide();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Session refreshen um neues Token zu bekommen
|
||||
this.isActive = true; // Aktivieren damit refreshSession funktioniert
|
||||
await this.refreshSession();
|
||||
|
||||
// Timer mit neuem Token starten
|
||||
this.updateFromToken();
|
||||
if (this.expiresAt) {
|
||||
this.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
this.isActive = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.stop(); // Bestehenden Timer stoppen
|
||||
|
||||
if (!this.expiresAt) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.show();
|
||||
this.update(); // Sofort updaten
|
||||
|
||||
// Jede Sekunde aktualisieren
|
||||
this.intervalId = setInterval(() => this.update(), 1000);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (!this.expiresAt || !this.countdownElement) return;
|
||||
|
||||
const now = Date.now();
|
||||
const remaining = Math.max(0, this.expiresAt - now);
|
||||
const seconds = Math.floor(remaining / 1000);
|
||||
|
||||
// Zeit formatieren
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
const timeStr = `${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
||||
|
||||
this.countdownElement.textContent = timeStr;
|
||||
|
||||
// Warnung bei wenig Zeit
|
||||
if (seconds <= this.warningThreshold && seconds > 0) {
|
||||
this.timerElement?.classList.add('warning');
|
||||
} else {
|
||||
this.timerElement?.classList.remove('warning');
|
||||
}
|
||||
|
||||
// Kritisch bei < 30 Sekunden
|
||||
if (seconds <= 30 && seconds > 0) {
|
||||
this.timerElement?.classList.add('critical');
|
||||
} else {
|
||||
this.timerElement?.classList.remove('critical');
|
||||
}
|
||||
|
||||
// Session abgelaufen
|
||||
if (seconds <= 0) {
|
||||
this.stop();
|
||||
this.handleExpired();
|
||||
}
|
||||
}
|
||||
|
||||
handleExpired() {
|
||||
// Toast anzeigen
|
||||
window.dispatchEvent(new CustomEvent('toast:show', {
|
||||
detail: {
|
||||
message: 'Sitzung abgelaufen. Bitte erneut anmelden.',
|
||||
type: 'warning',
|
||||
duration: 5000
|
||||
}
|
||||
}));
|
||||
|
||||
// Automatisch ausloggen
|
||||
this.auth.logout();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.timerElement?.classList.remove('hidden');
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.timerElement?.classList.add('hidden');
|
||||
if (this.countdownElement) {
|
||||
this.countdownElement.textContent = '--:--';
|
||||
}
|
||||
}
|
||||
|
||||
// Token wurde erneuert (z.B. durch API-Response mit X-New-Token)
|
||||
refreshToken(newToken) {
|
||||
if (newToken) {
|
||||
// api.setToken() wird bereits in api.js aufgerufen, hier nur Timer aktualisieren
|
||||
this.expiresAt = this.parseToken(newToken);
|
||||
this.timerElement?.classList.remove('warning', 'critical');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change Password Modal Handler
|
||||
class ChangePasswordHandler {
|
||||
constructor(authManager) {
|
||||
@ -516,12 +768,21 @@ const authManager = new AuthManager();
|
||||
let loginFormHandler = null;
|
||||
let userMenuHandler = null;
|
||||
let changePasswordHandler = null;
|
||||
let sessionTimerHandler = null;
|
||||
|
||||
// Initialize handlers when DOM is ready
|
||||
function initAuthHandlers() {
|
||||
async function initAuthHandlers() {
|
||||
loginFormHandler = new LoginFormHandler(authManager);
|
||||
userMenuHandler = new UserMenuHandler(authManager);
|
||||
changePasswordHandler = new ChangePasswordHandler(authManager);
|
||||
sessionTimerHandler = new SessionTimerHandler(authManager);
|
||||
sessionTimerHandler.init();
|
||||
|
||||
// Bei bestehendem Token: Session refreshen und Timer starten
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
await sessionTimerHandler.initFromExistingSession();
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for DOM ready
|
||||
@ -536,11 +797,17 @@ window.addEventListener('auth:login', () => {
|
||||
userMenuHandler?.update();
|
||||
});
|
||||
|
||||
// Listen for token refresh event to update session timer
|
||||
window.addEventListener('auth:token-refreshed', (e) => {
|
||||
sessionTimerHandler?.refreshToken(e.detail?.token);
|
||||
});
|
||||
|
||||
export {
|
||||
authManager,
|
||||
loginFormHandler,
|
||||
userMenuHandler,
|
||||
changePasswordHandler
|
||||
changePasswordHandler,
|
||||
sessionTimerHandler
|
||||
};
|
||||
|
||||
export default authManager;
|
||||
|
||||
@ -1225,7 +1225,9 @@ class BoardManager {
|
||||
}
|
||||
|
||||
getTasksForDay(tasks, dateStr) {
|
||||
const result = [];
|
||||
// Gruppiere Aufgaben nach Spalte
|
||||
const columnGroups = new Map();
|
||||
const columns = store.get('columns');
|
||||
|
||||
tasks.forEach(task => {
|
||||
const startDate = task.startDate ? task.startDate.split('T')[0] : null;
|
||||
@ -1235,7 +1237,15 @@ class BoardManager {
|
||||
const isEnd = dueDate === dateStr;
|
||||
|
||||
if (isStart || isEnd) {
|
||||
result.push({
|
||||
const columnId = task.columnId;
|
||||
if (!columnGroups.has(columnId)) {
|
||||
const column = columns.find(c => c.id === columnId);
|
||||
columnGroups.set(columnId, {
|
||||
column,
|
||||
tasks: []
|
||||
});
|
||||
}
|
||||
columnGroups.get(columnId).tasks.push({
|
||||
task,
|
||||
isStart,
|
||||
isEnd
|
||||
@ -1243,70 +1253,99 @@ class BoardManager {
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
return columnGroups;
|
||||
}
|
||||
|
||||
renderDayDots(dayTasks, dateStr) {
|
||||
if (dayTasks.length === 0) return '';
|
||||
renderDayDots(columnGroups, dateStr) {
|
||||
if (columnGroups.size === 0) return '';
|
||||
|
||||
const columns = store.get('columns');
|
||||
const users = store.get('users');
|
||||
const dots = [];
|
||||
|
||||
return dayTasks.map(({ task, isStart, isEnd }) => {
|
||||
const column = columns.find(c => c.id === task.columnId);
|
||||
columnGroups.forEach(({ column, tasks }, columnId) => {
|
||||
const color = column?.color || '#6B7280';
|
||||
const taskIds = tasks.map(t => t.task.id).join(',');
|
||||
|
||||
// Bestimme Dot-Typ basierend auf allen Aufgaben der Spalte
|
||||
const hasStart = tasks.some(t => t.isStart);
|
||||
const hasEnd = tasks.some(t => t.isEnd);
|
||||
|
||||
let typeClass = '';
|
||||
if (isStart && isEnd) {
|
||||
if (hasStart && hasEnd) {
|
||||
typeClass = 'both';
|
||||
} else if (isStart) {
|
||||
} else if (hasStart) {
|
||||
typeClass = 'start';
|
||||
} else {
|
||||
typeClass = 'end';
|
||||
}
|
||||
|
||||
return `<span class="week-strip-dot ${typeClass}"
|
||||
style="color: ${color}"
|
||||
data-task-id="${task.id}"
|
||||
data-is-start="${isStart}"
|
||||
data-is-end="${isEnd}"></span>`;
|
||||
}).join('');
|
||||
dots.push(`<span class="week-strip-dot ${typeClass}"
|
||||
style="color: ${color}"
|
||||
data-column-id="${columnId}"
|
||||
data-task-ids="${taskIds}"
|
||||
data-column-name="${this.escapeHtml(column?.name || '')}"></span>`);
|
||||
});
|
||||
|
||||
return dots.join('');
|
||||
}
|
||||
|
||||
showTaskTooltip(event, dot) {
|
||||
const taskId = parseInt(dot.dataset.taskId);
|
||||
const isStart = dot.dataset.isStart === 'true';
|
||||
const isEnd = dot.dataset.isEnd === 'true';
|
||||
const taskIds = dot.dataset.taskIds ? dot.dataset.taskIds.split(',').map(id => parseInt(id)) : [];
|
||||
const columnName = dot.dataset.columnName || 'Unbekannt';
|
||||
const columnId = parseInt(dot.dataset.columnId);
|
||||
|
||||
const task = store.get('tasks').find(t => t.id === taskId);
|
||||
if (!task) return;
|
||||
if (taskIds.length === 0) return;
|
||||
|
||||
const allTasks = store.get('tasks');
|
||||
const columns = store.get('columns');
|
||||
const column = columns.find(c => c.id === task.columnId);
|
||||
const column = columns.find(c => c.id === columnId);
|
||||
const color = column?.color || '#6B7280';
|
||||
|
||||
let typeLabel = '';
|
||||
if (isStart && isEnd) {
|
||||
typeLabel = 'Start & Ende';
|
||||
} else if (isStart) {
|
||||
typeLabel = 'Startdatum';
|
||||
} else {
|
||||
typeLabel = 'Enddatum';
|
||||
}
|
||||
// Sammle alle Aufgaben mit ihren Start/Ende-Infos
|
||||
const tasksWithDates = taskIds.map(taskId => {
|
||||
const task = allTasks.find(t => t.id === taskId);
|
||||
if (!task) return null;
|
||||
|
||||
const dayDate = dot.closest('.week-strip-day')?.dataset.date;
|
||||
const startDate = task.startDate ? task.startDate.split('T')[0] : null;
|
||||
const dueDate = task.dueDate ? task.dueDate.split('T')[0] : null;
|
||||
const isStart = startDate === dayDate;
|
||||
const isEnd = dueDate === dayDate;
|
||||
|
||||
let typeLabel = '';
|
||||
if (isStart && isEnd) {
|
||||
typeLabel = 'Start & Ende';
|
||||
} else if (isStart) {
|
||||
typeLabel = 'Start';
|
||||
} else if (isEnd) {
|
||||
typeLabel = 'Ende';
|
||||
}
|
||||
|
||||
return { task, typeLabel, isStart, isEnd };
|
||||
}).filter(Boolean);
|
||||
|
||||
if (tasksWithDates.length === 0) return;
|
||||
|
||||
// Create tooltip
|
||||
this.hideTooltip();
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'week-strip-tooltip';
|
||||
|
||||
const taskCount = tasksWithDates.length;
|
||||
const taskListHtml = tasksWithDates.map(({ task, typeLabel, isStart }) => `
|
||||
<div class="week-strip-tooltip-task">
|
||||
<span class="week-strip-tooltip-task-dot ${isStart ? 'start' : 'end'}" style="color: ${color}"></span>
|
||||
<span class="week-strip-tooltip-task-title">${this.escapeHtml(task.title)}</span>
|
||||
<span class="week-strip-tooltip-task-type">(${typeLabel})</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
tooltip.innerHTML = `
|
||||
<div class="week-strip-tooltip-title">${this.escapeHtml(task.title)}</div>
|
||||
<div class="week-strip-tooltip-meta">
|
||||
<div class="week-strip-tooltip-type" style="color: ${color}">
|
||||
<span class="dot ${isStart ? 'start' : 'end'}"></span>
|
||||
${typeLabel}
|
||||
</div>
|
||||
${task.startDate ? `<div>Start: ${formatDate(task.startDate)}</div>` : ''}
|
||||
${task.dueDate ? `<div>Ende: ${formatDate(task.dueDate)}</div>` : ''}
|
||||
<div class="week-strip-tooltip-header" style="border-left-color: ${color}">
|
||||
<span class="week-strip-tooltip-column-name">${this.escapeHtml(columnName)}</span>
|
||||
<span class="week-strip-tooltip-count">${taskCount} Aufgabe${taskCount > 1 ? 'n' : ''}</span>
|
||||
</div>
|
||||
<div class="week-strip-tooltip-task-list">
|
||||
${taskListHtml}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -1342,7 +1381,12 @@ class BoardManager {
|
||||
|
||||
openTaskFromDot(event, dot) {
|
||||
event.stopPropagation();
|
||||
const taskId = parseInt(dot.dataset.taskId);
|
||||
const taskIds = dot.dataset.taskIds ? dot.dataset.taskIds.split(',').map(id => parseInt(id)) : [];
|
||||
|
||||
if (taskIds.length === 0) return;
|
||||
|
||||
// Öffne die erste Aufgabe
|
||||
const taskId = taskIds[0];
|
||||
|
||||
window.dispatchEvent(new CustomEvent('modal:open', {
|
||||
detail: { modalId: 'task-modal', mode: 'edit', data: { taskId } }
|
||||
|
||||
@ -10,16 +10,27 @@ import store from './store.js';
|
||||
|
||||
class GiteaManager {
|
||||
constructor() {
|
||||
// Gemeinsame Eigenschaften
|
||||
this.initialized = false;
|
||||
this.refreshInterval = null;
|
||||
this.isLoading = false;
|
||||
this.currentMode = 'server'; // 'server' oder 'project'
|
||||
|
||||
// Projekt-Modus Eigenschaften
|
||||
this.application = null;
|
||||
this.gitStatus = null;
|
||||
this.branches = [];
|
||||
this.commits = [];
|
||||
this.giteaRepos = [];
|
||||
this.giteaConnected = false;
|
||||
this.initialized = false;
|
||||
this.refreshInterval = null;
|
||||
this.isLoading = false;
|
||||
this.hiddenCommits = new Set(); // Ausgeblendete Commits
|
||||
this.hiddenCommits = new Set();
|
||||
|
||||
// Server-Modus Eigenschaften
|
||||
this.serverInfo = null;
|
||||
this.serverStatus = null;
|
||||
this.serverBranches = [];
|
||||
this.serverCommits = [];
|
||||
this.hiddenServerCommits = new Set();
|
||||
}
|
||||
|
||||
async init() {
|
||||
@ -27,19 +38,31 @@ class GiteaManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// DOM Elements
|
||||
// DOM Elements - Gemeinsam
|
||||
this.giteaView = $('#view-gitea');
|
||||
this.modeSwitch = $('#gitea-mode-switch');
|
||||
|
||||
// DOM Elements - Server-Modus
|
||||
this.serverModeSection = $('#gitea-server-mode');
|
||||
|
||||
// DOM Elements - Projekt-Modus
|
||||
this.noProjectSection = $('#gitea-no-project');
|
||||
this.configSection = $('#gitea-config-section');
|
||||
this.mainSection = $('#gitea-main-section');
|
||||
this.connectionStatus = $('#gitea-connection-status');
|
||||
|
||||
this.bindEvents();
|
||||
this.bindServerEvents();
|
||||
this.subscribeToStore();
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// Modus-Schalter
|
||||
$$('.gitea-mode-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => this.handleModeSwitch(e));
|
||||
});
|
||||
|
||||
// Konfiguration speichern
|
||||
$('#gitea-config-form')?.addEventListener('submit', (e) => this.handleConfigSave(e));
|
||||
|
||||
@ -86,6 +109,425 @@ class GiteaManager {
|
||||
$('#git-commits-list')?.addEventListener('click', (e) => this.handleCommitListClick(e));
|
||||
}
|
||||
|
||||
bindServerEvents() {
|
||||
// Server Git-Operationen
|
||||
$('#btn-server-fetch')?.addEventListener('click', () => this.handleServerFetch());
|
||||
$('#btn-server-pull')?.addEventListener('click', () => this.handleServerPull());
|
||||
$('#btn-server-push')?.addEventListener('click', () => this.openServerPushModal());
|
||||
$('#btn-server-commit')?.addEventListener('click', () => this.openServerCommitModal());
|
||||
|
||||
// Server Branch wechseln
|
||||
$('#server-branch-select')?.addEventListener('change', (e) => this.handleServerBranchChange(e));
|
||||
|
||||
// Server Commits ausblenden
|
||||
$('#btn-server-clear-commits')?.addEventListener('click', () => this.clearAllServerCommits());
|
||||
$('#server-commits-list')?.addEventListener('click', (e) => this.handleServerCommitListClick(e));
|
||||
}
|
||||
|
||||
// Modus-Wechsel Handler
|
||||
handleModeSwitch(e) {
|
||||
const btn = e.currentTarget;
|
||||
const newMode = btn.dataset.mode;
|
||||
|
||||
if (newMode === this.currentMode) return;
|
||||
|
||||
// Button-Status aktualisieren
|
||||
$$('.gitea-mode-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
this.currentMode = newMode;
|
||||
this.updateModeView();
|
||||
}
|
||||
|
||||
updateModeView() {
|
||||
if (this.currentMode === 'server') {
|
||||
// Server-Modus
|
||||
this.serverModeSection?.classList.remove('hidden');
|
||||
this.noProjectSection?.classList.add('hidden');
|
||||
this.configSection?.classList.add('hidden');
|
||||
this.mainSection?.classList.add('hidden');
|
||||
this.loadServerData();
|
||||
} else {
|
||||
// Projekt-Modus
|
||||
this.serverModeSection?.classList.add('hidden');
|
||||
this.loadApplication();
|
||||
}
|
||||
}
|
||||
|
||||
// =====================
|
||||
// SERVER-MODUS METHODEN
|
||||
// =====================
|
||||
|
||||
async loadServerData() {
|
||||
this.showLoading();
|
||||
|
||||
try {
|
||||
// Server-Info laden
|
||||
const infoResult = await api.getServerGitInfo();
|
||||
this.serverInfo = infoResult;
|
||||
|
||||
if (infoResult.accessible && infoResult.isRepository) {
|
||||
// Remote-URL anzeigen
|
||||
if (infoResult.remoteUrl) {
|
||||
const repoUrl = $('#server-repo-url');
|
||||
if (repoUrl) {
|
||||
// Gitea HTML URL generieren (ohne .git)
|
||||
const htmlUrl = infoResult.remoteUrl.replace(/\.git$/, '');
|
||||
repoUrl.href = htmlUrl;
|
||||
repoUrl.textContent = htmlUrl;
|
||||
repoUrl.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Git-Daten laden
|
||||
await this.loadServerGitData();
|
||||
} else {
|
||||
this.showToast('Server-Verzeichnis ist kein Git-Repository', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Gitea Server] Fehler beim Laden:', error);
|
||||
this.showToast('Fehler beim Laden der Server-Daten', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async loadServerGitData() {
|
||||
try {
|
||||
const [statusResult, branchesResult, commitsResult] = await Promise.allSettled([
|
||||
api.getServerGitStatus(),
|
||||
api.getServerGitBranches(),
|
||||
api.getServerGitCommits(10)
|
||||
]);
|
||||
|
||||
if (statusResult.status === 'fulfilled') {
|
||||
this.serverStatus = statusResult.value;
|
||||
} else {
|
||||
this.serverStatus = null;
|
||||
console.error('[Gitea Server] Status-Fehler:', statusResult.reason);
|
||||
}
|
||||
|
||||
if (branchesResult.status === 'fulfilled') {
|
||||
this.serverBranches = branchesResult.value.branches || [];
|
||||
} else {
|
||||
this.serverBranches = [];
|
||||
}
|
||||
|
||||
if (commitsResult.status === 'fulfilled') {
|
||||
this.serverCommits = commitsResult.value.commits || [];
|
||||
} else {
|
||||
this.serverCommits = [];
|
||||
}
|
||||
|
||||
this.renderServerStatus();
|
||||
this.renderServerBranches();
|
||||
this.renderServerCommits();
|
||||
this.renderServerChanges();
|
||||
} catch (error) {
|
||||
console.error('[Gitea Server] Git-Daten laden fehlgeschlagen:', error);
|
||||
}
|
||||
}
|
||||
|
||||
renderServerStatus() {
|
||||
const statusBadge = $('#server-status-indicator');
|
||||
const changesCount = $('#server-changes-count');
|
||||
|
||||
if (!this.serverStatus) {
|
||||
if (statusBadge) {
|
||||
statusBadge.textContent = 'Fehler';
|
||||
statusBadge.className = 'status-badge error';
|
||||
}
|
||||
if (changesCount) changesCount.textContent = '-';
|
||||
return;
|
||||
}
|
||||
|
||||
if (statusBadge) {
|
||||
if (!this.serverStatus.success) {
|
||||
statusBadge.textContent = 'Fehler';
|
||||
statusBadge.className = 'status-badge error';
|
||||
} else if (this.serverStatus.isClean) {
|
||||
statusBadge.textContent = 'Sauber';
|
||||
statusBadge.className = 'status-badge clean';
|
||||
} else if (this.serverStatus.hasChanges) {
|
||||
statusBadge.textContent = 'Geändert';
|
||||
statusBadge.className = 'status-badge dirty';
|
||||
} else {
|
||||
statusBadge.textContent = 'OK';
|
||||
statusBadge.className = 'status-badge clean';
|
||||
}
|
||||
}
|
||||
|
||||
const changes = this.serverStatus.changes || [];
|
||||
if (changesCount) changesCount.textContent = changes.length;
|
||||
}
|
||||
|
||||
renderServerBranches() {
|
||||
const select = $('#server-branch-select');
|
||||
if (!select) return;
|
||||
|
||||
const currentBranch = this.serverStatus?.branch || 'main';
|
||||
|
||||
select.innerHTML = '';
|
||||
|
||||
const localBranches = this.serverBranches.filter(b => !b.isRemote);
|
||||
localBranches.forEach(branch => {
|
||||
const option = document.createElement('option');
|
||||
option.value = branch.name;
|
||||
option.textContent = branch.name;
|
||||
if (branch.name === currentBranch) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
renderServerCommits() {
|
||||
const listEl = $('#server-commits-list');
|
||||
const clearBtn = $('#btn-server-clear-commits');
|
||||
if (!listEl) return;
|
||||
|
||||
const visibleCommits = this.serverCommits.filter(commit => {
|
||||
const hash = commit.hash || commit.sha || commit.shortHash;
|
||||
return !this.hiddenServerCommits.has(hash);
|
||||
});
|
||||
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = visibleCommits.length > 0 ? '' : 'none';
|
||||
}
|
||||
|
||||
if (visibleCommits.length === 0) {
|
||||
const message = this.serverCommits.length > 0
|
||||
? 'Alle Commits ausgeblendet'
|
||||
: 'Keine Commits gefunden';
|
||||
listEl.innerHTML = `<div class="gitea-empty-state" style="padding: var(--spacing-4);"><p>${message}</p></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
listEl.innerHTML = visibleCommits.map(commit => {
|
||||
const hash = commit.hash || commit.sha || '';
|
||||
const shortHash = commit.shortHash || hash.substring(0, 7);
|
||||
return `
|
||||
<div class="commit-item" data-hash="${escapeHtml(hash)}">
|
||||
<span class="commit-hash">${escapeHtml(shortHash)}</span>
|
||||
<div class="commit-info">
|
||||
<div class="commit-message">${escapeHtml(commit.message?.split('\n')[0] || '')}</div>
|
||||
<div class="commit-meta">
|
||||
<span class="author">${escapeHtml(commit.author)}</span> · ${this.formatDate(commit.date)}
|
||||
</div>
|
||||
</div>
|
||||
<button class="commit-delete" title="Ausblenden" data-hash="${escapeHtml(hash)}">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
`}).join('');
|
||||
}
|
||||
|
||||
renderServerChanges() {
|
||||
const changesSection = $('#server-changes-section');
|
||||
const listEl = $('#server-changes-list');
|
||||
|
||||
if (!changesSection || !listEl) return;
|
||||
|
||||
const changes = this.serverStatus?.changes || [];
|
||||
|
||||
if (changes.length === 0) {
|
||||
changesSection.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
changesSection.classList.remove('hidden');
|
||||
|
||||
listEl.innerHTML = changes.map(change => {
|
||||
const statusClass = this.getChangeStatusClass(change.status);
|
||||
const statusLabel = this.getChangeStatusLabel(change.status);
|
||||
|
||||
return `
|
||||
<div class="change-item">
|
||||
<span class="change-status ${statusClass}" title="${statusLabel}">${change.status}</span>
|
||||
<span class="change-file">${escapeHtml(change.file)}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Server Git-Operationen
|
||||
async handleServerFetch() {
|
||||
this.setServerOperationLoading('fetch', true);
|
||||
|
||||
try {
|
||||
const result = await api.serverGitFetch();
|
||||
if (result.success) {
|
||||
this.showToast('Fetch erfolgreich', 'success');
|
||||
await this.loadServerGitData();
|
||||
} else {
|
||||
this.showToast(result.error || 'Fetch fehlgeschlagen', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Fetch fehlgeschlagen', 'error');
|
||||
} finally {
|
||||
this.setServerOperationLoading('fetch', false);
|
||||
}
|
||||
}
|
||||
|
||||
async handleServerPull() {
|
||||
this.setServerOperationLoading('pull', true);
|
||||
|
||||
try {
|
||||
const branch = $('#server-branch-select')?.value || null;
|
||||
const result = await api.serverGitPull(branch);
|
||||
|
||||
if (result.success) {
|
||||
this.showToast('Pull erfolgreich', 'success');
|
||||
await this.loadServerGitData();
|
||||
} else {
|
||||
this.showToast(result.error || 'Pull fehlgeschlagen', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Pull fehlgeschlagen', 'error');
|
||||
} finally {
|
||||
this.setServerOperationLoading('pull', false);
|
||||
}
|
||||
}
|
||||
|
||||
openServerPushModal() {
|
||||
const modal = $('#git-push-modal');
|
||||
if (!modal) return;
|
||||
|
||||
const currentBranch = $('#server-branch-select')?.value || this.serverStatus?.branch || 'main';
|
||||
$('#push-local-branch').textContent = currentBranch;
|
||||
$('#push-target-branch').value = '';
|
||||
$('#push-force').checked = false;
|
||||
|
||||
// Temporär Attribut setzen um Server-Modus zu markieren
|
||||
modal.dataset.serverMode = 'true';
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('visible');
|
||||
$('#modal-overlay')?.classList.remove('hidden');
|
||||
|
||||
store.openModal('git-push-modal');
|
||||
}
|
||||
|
||||
async executeServerPush(force = false) {
|
||||
this.setServerOperationLoading('push', true);
|
||||
|
||||
try {
|
||||
const targetBranch = $('#push-target-branch')?.value || null;
|
||||
const localBranch = $('#server-branch-select')?.value || this.serverStatus?.branch || 'main';
|
||||
|
||||
const result = await api.serverGitPush(targetBranch || localBranch, force);
|
||||
|
||||
if (result.success) {
|
||||
const pushedBranch = result.branch || targetBranch || localBranch;
|
||||
this.showToast(`Push erfolgreich nach Branch "${pushedBranch}"`, 'success');
|
||||
await this.loadServerGitData();
|
||||
} else {
|
||||
this.showToast(result.error || 'Push fehlgeschlagen', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Push fehlgeschlagen', 'error');
|
||||
} finally {
|
||||
this.setServerOperationLoading('push', false);
|
||||
}
|
||||
}
|
||||
|
||||
openServerCommitModal() {
|
||||
const modal = $('#git-commit-modal');
|
||||
if (!modal) return;
|
||||
|
||||
$('#commit-message').value = '';
|
||||
$('#commit-stage-all').checked = true;
|
||||
|
||||
// Temporär Attribut setzen um Server-Modus zu markieren
|
||||
modal.dataset.serverMode = 'true';
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('visible');
|
||||
$('#modal-overlay')?.classList.remove('hidden');
|
||||
|
||||
store.openModal('git-commit-modal');
|
||||
}
|
||||
|
||||
async executeServerCommit(message, stageAll = true) {
|
||||
try {
|
||||
const result = await api.serverGitCommit(message, stageAll);
|
||||
|
||||
if (result.success) {
|
||||
this.showToast('Commit erstellt', 'success');
|
||||
await this.loadServerGitData();
|
||||
return true;
|
||||
} else {
|
||||
this.showToast(result.error || 'Commit fehlgeschlagen', 'error');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Commit fehlgeschlagen', 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async handleServerBranchChange(e) {
|
||||
const branch = e.target.value;
|
||||
if (!branch) return;
|
||||
|
||||
try {
|
||||
const result = await api.serverGitCheckout(branch);
|
||||
|
||||
if (result.success) {
|
||||
this.showToast(`Gewechselt zu ${branch}`, 'success');
|
||||
await this.loadServerGitData();
|
||||
} else {
|
||||
this.showToast(result.error || 'Branch-Wechsel fehlgeschlagen', 'error');
|
||||
await this.loadServerGitData();
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Branch-Wechsel fehlgeschlagen', 'error');
|
||||
await this.loadServerGitData();
|
||||
}
|
||||
}
|
||||
|
||||
handleServerCommitListClick(e) {
|
||||
const deleteBtn = e.target.closest('.commit-delete');
|
||||
if (deleteBtn) {
|
||||
const hash = deleteBtn.dataset.hash;
|
||||
if (hash) {
|
||||
this.hideServerCommit(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hideServerCommit(hash) {
|
||||
this.hiddenServerCommits.add(hash);
|
||||
this.renderServerCommits();
|
||||
this.showToast('Commit ausgeblendet', 'info');
|
||||
}
|
||||
|
||||
clearAllServerCommits() {
|
||||
this.serverCommits.forEach(commit => {
|
||||
const hash = commit.hash || commit.sha || commit.shortHash;
|
||||
if (hash) {
|
||||
this.hiddenServerCommits.add(hash);
|
||||
}
|
||||
});
|
||||
this.renderServerCommits();
|
||||
this.showToast('Alle Commits ausgeblendet', 'info');
|
||||
}
|
||||
|
||||
setServerOperationLoading(operation, loading) {
|
||||
const buttonId = `btn-server-${operation}`;
|
||||
const button = $(`#${buttonId}`);
|
||||
if (button) {
|
||||
button.disabled = loading;
|
||||
if (loading) {
|
||||
button.classList.add('loading');
|
||||
} else {
|
||||
button.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================
|
||||
// PROJEKT-MODUS METHODEN
|
||||
// =====================
|
||||
|
||||
subscribeToStore() {
|
||||
// Bei Projektwechsel neu laden
|
||||
store.subscribe('currentProjectId', async (projectId) => {
|
||||
@ -444,58 +886,68 @@ class GiteaManager {
|
||||
async executePush(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const projectId = store.get('currentProjectId');
|
||||
if (!projectId) return;
|
||||
const modal = $('#git-push-modal');
|
||||
const isServerMode = modal?.dataset.serverMode === 'true';
|
||||
|
||||
// Modal schließen
|
||||
const modal = $('#git-push-modal');
|
||||
modal?.classList.add('hidden');
|
||||
modal?.classList.remove('visible');
|
||||
$('#modal-overlay')?.classList.add('hidden');
|
||||
store.closeModal();
|
||||
|
||||
this.setOperationLoading('push', true);
|
||||
|
||||
try {
|
||||
const targetBranch = $('#push-target-branch')?.value || null;
|
||||
if (isServerMode) {
|
||||
// Server-Modus: Push für Server-Dateien
|
||||
delete modal.dataset.serverMode;
|
||||
const forcePush = $('#push-force')?.checked || false;
|
||||
const localBranch = $('#branch-select')?.value || this.gitStatus?.branch || 'master';
|
||||
await this.executeServerPush(forcePush);
|
||||
} else {
|
||||
// Projekt-Modus: Push für Projekt-Repository
|
||||
const projectId = store.get('currentProjectId');
|
||||
if (!projectId) return;
|
||||
|
||||
let result;
|
||||
this.setOperationLoading('push', true);
|
||||
|
||||
// Wenn ein anderer Target-Branch ausgewählt wurde oder Force-Push, verwende init-push
|
||||
if ((targetBranch && targetBranch !== localBranch) || forcePush) {
|
||||
const forceText = forcePush ? ' (Force)' : '';
|
||||
const branchText = targetBranch && targetBranch !== localBranch
|
||||
? `Push von "${localBranch}" nach "${targetBranch}"${forceText}...`
|
||||
: `Push nach "${localBranch}"${forceText}...`;
|
||||
this.showToast(branchText, 'info');
|
||||
result = await api.gitInitPush(projectId, targetBranch, forcePush);
|
||||
} else {
|
||||
// Normaler Push (gleicher Branch-Name)
|
||||
result = await api.gitPush(projectId, localBranch);
|
||||
try {
|
||||
const targetBranch = $('#push-target-branch')?.value || null;
|
||||
const forcePush = $('#push-force')?.checked || false;
|
||||
const localBranch = $('#branch-select')?.value || this.gitStatus?.branch || 'master';
|
||||
|
||||
// Falls Push wegen fehlendem Upstream/Remote fehlschlägt, versuche init-push
|
||||
if (!result.success && result.error &&
|
||||
(result.error.includes('No configured push destination') ||
|
||||
result.error.includes('no upstream') ||
|
||||
result.error.includes('Kein Remote'))) {
|
||||
this.showToast('Kein Upstream konfiguriert, führe initialen Push durch...', 'info');
|
||||
result = await api.gitInitPush(projectId, targetBranch, false);
|
||||
let result;
|
||||
|
||||
// Wenn ein anderer Target-Branch ausgewählt wurde oder Force-Push, verwende init-push
|
||||
if ((targetBranch && targetBranch !== localBranch) || forcePush) {
|
||||
const forceText = forcePush ? ' (Force)' : '';
|
||||
const branchText = targetBranch && targetBranch !== localBranch
|
||||
? `Push von "${localBranch}" nach "${targetBranch}"${forceText}...`
|
||||
: `Push nach "${localBranch}"${forceText}...`;
|
||||
this.showToast(branchText, 'info');
|
||||
result = await api.gitInitPush(projectId, targetBranch, forcePush);
|
||||
} else {
|
||||
// Normaler Push (gleicher Branch-Name)
|
||||
result = await api.gitPush(projectId, localBranch);
|
||||
|
||||
// Falls Push wegen fehlendem Upstream/Remote fehlschlägt, versuche init-push
|
||||
if (!result.success && result.error &&
|
||||
(result.error.includes('No configured push destination') ||
|
||||
result.error.includes('no upstream') ||
|
||||
result.error.includes('Kein Remote'))) {
|
||||
this.showToast('Kein Upstream konfiguriert, führe initialen Push durch...', 'info');
|
||||
result = await api.gitInitPush(projectId, targetBranch, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
const pushedBranch = result.branch || targetBranch || localBranch;
|
||||
this.showToast(`Push erfolgreich nach Branch "${pushedBranch}"`, 'success');
|
||||
await this.loadGitData();
|
||||
} else {
|
||||
this.showToast(result.error || 'Push fehlgeschlagen', 'error');
|
||||
if (result.success) {
|
||||
const pushedBranch = result.branch || targetBranch || localBranch;
|
||||
this.showToast(`Push erfolgreich nach Branch "${pushedBranch}"`, 'success');
|
||||
await this.loadGitData();
|
||||
} else {
|
||||
this.showToast(result.error || 'Push fehlgeschlagen', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Push fehlgeschlagen', 'error');
|
||||
} finally {
|
||||
this.setOperationLoading('push', false);
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Push fehlgeschlagen', 'error');
|
||||
} finally {
|
||||
this.setOperationLoading('push', false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -574,8 +1026,8 @@ class GiteaManager {
|
||||
async handleCommit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const projectId = store.get('currentProjectId');
|
||||
if (!projectId) return;
|
||||
const modal = $('#git-commit-modal');
|
||||
const isServerMode = modal?.dataset.serverMode === 'true';
|
||||
|
||||
const message = $('#commit-message')?.value.trim();
|
||||
const stageAll = $('#commit-stage-all')?.checked ?? true;
|
||||
@ -585,18 +1037,31 @@ class GiteaManager {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await api.gitCommit(projectId, message, stageAll);
|
||||
|
||||
if (result.success) {
|
||||
this.showToast('Commit erstellt', 'success');
|
||||
if (isServerMode) {
|
||||
// Server-Modus: Commit für Server-Dateien
|
||||
const success = await this.executeServerCommit(message, stageAll);
|
||||
if (success) {
|
||||
this.closeModal('git-commit-modal');
|
||||
await this.loadGitData();
|
||||
} else {
|
||||
this.showToast(result.error || 'Commit fehlgeschlagen', 'error');
|
||||
delete modal.dataset.serverMode;
|
||||
}
|
||||
} else {
|
||||
// Projekt-Modus: Commit für Projekt-Repository
|
||||
const projectId = store.get('currentProjectId');
|
||||
if (!projectId) return;
|
||||
|
||||
try {
|
||||
const result = await api.gitCommit(projectId, message, stageAll);
|
||||
|
||||
if (result.success) {
|
||||
this.showToast('Commit erstellt', 'success');
|
||||
this.closeModal('git-commit-modal');
|
||||
await this.loadGitData();
|
||||
} else {
|
||||
this.showToast(result.error || 'Commit fehlgeschlagen', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Commit fehlgeschlagen', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast(error.message || 'Commit fehlgeschlagen', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@ -976,7 +1441,7 @@ class GiteaManager {
|
||||
show() {
|
||||
this.giteaView?.classList.remove('hidden');
|
||||
this.giteaView?.classList.add('active');
|
||||
this.loadApplication();
|
||||
this.updateModeView();
|
||||
this.startAutoRefresh();
|
||||
}
|
||||
|
||||
@ -990,7 +1455,13 @@ class GiteaManager {
|
||||
this.stopAutoRefresh();
|
||||
// Status alle 30 Sekunden aktualisieren
|
||||
this.refreshInterval = setInterval(() => {
|
||||
if (this.application?.configured && !this.isLoading) {
|
||||
if (this.isLoading) return;
|
||||
|
||||
if (this.currentMode === 'server') {
|
||||
// Server-Modus: Server-Daten aktualisieren
|
||||
this.loadServerGitData();
|
||||
} else if (this.application?.configured) {
|
||||
// Projekt-Modus: Projekt-Daten aktualisieren
|
||||
this.loadGitData();
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren