6c788b3e7d5bde0959c52c2eec1a85cb942ead55
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
abd9f73405 |
fix(git): Seitengroesse an der Antwort messen, nicht an der Anfrage
CI / pruefen (pull_request) Canceled after 0s
Behebt florian.wilde/nebula#22. Gitea deckelt jede Listenantwort auf MAX_RESPONSE_ITEMS (Voreinstellung 50), egal was limit sagt. calendarByPaging verglich gegen die angefragten 100 und hielt damit bei jedem Gitea-Repository die erste Seite fuer die letzte. Die Heatmap kannte nur die juengsten 50 Commits - und gab deren aeltesten als Anfang des Zeitraums aus, ohne truncated zu setzen. Sie behauptete also Vollstaendigkeit, die sie nicht hatte. Maßstab ist jetzt die erste tatsaechlich gelieferte Seite. Kuerzer als die heisst letzte Seite, leer heisst Ende, und der Deckel gilt weiterhin nach CALENDAR_MAX_PAGES - dann aber ehrlich mit truncated. Am echten Repository nachgesehen, dem aus dem Issue: vorher Seite 1: 50 -> 50 Commits jetzt Seite 1: 50 | Seite 2: 26 -> 76 Commits Gitea selbst weist 76 aus. WAS DAS KOSTET Bei Gitea macht der Kalender jetzt bis zu fuenf Aufrufe statt einem, und ein Repository mit kurzer Historie fragt eine Seite mehr, um zu erfahren, dass nichts mehr kommt. Das laeuft hoechstens einmal am Tag; der Preis dafuer, dass die Zahl stimmt, ist ihn wert. VIER BESTEHENDE TESTS ANGEFASST Ihr Fixture lieferte fuer jede Seite dieselben Daten und hielt nur deshalb an, weil die alte Regel eine kurze Seite fuer die letzte hielt - also wegen genau des Fehlers, der hier behoben wird. Kein Server verhaelt sich so. Sie holen ihre Daten jetzt ueber eineSeite() und pruefen unveraendert das, worum es ihnen geht: Zaehlen und Filtern. Vier neue Tests stellen eine deckelnde Gegenseite nach. Gegengeprobt: mit der alten Zeile fallen genau diese vier. Refs #22 |
||
|
|
b062eb36a1 |
fix(sync): Teil-Abrufe verschlucken ihre Fehler nicht mehr
CI / pruefen (pull_request) Canceled after 0s
Behebt florian.wilde/nebula#19 und florian.wilde/nebula#20.
getJsonSafe machte aus jedem Fehlschlag eine leere Liste. Damit war ein
403 nicht mehr von "das Repository hat keine Branches" zu unterscheiden:
Der Abgleich lief durch, meldete "Snapshot aktualisiert: 0 Branches,
0 Tags", setzte kein syncError - und ueberschrieb dabei den letzten guten
Stand mit Nullen. Ein Repository mit 65 Commits sah danach aus wie ein
leeres, und die Ursache war nicht zu ermitteln, weil die Auskunft, die
sie benannt haette, gerade weggeworfen worden war.
Teilabrufe sammelt jetzt je Feld, was schiefging - mit HTTP-Status, denn
daran haengt die Ursache: 403 heisst Scopes, 429 Rate-Limit, 408
Zeitueberschreitung, 0 nicht erreicht. Der Fallback bleibt, ein einzelner
Fehlschlag darf den ganzen Abgleich nicht abbrechen.
Daraus folgt der zweite Teil: persistSnapshot laesst stehen, was nicht
geholt werden konnte. Der Snapshot ist ein Zwischenspeicher - ein alter
Stand mit sichtbarem Datum ist immer brauchbarer als eine leere Liste,
die aussieht wie eine Aussage. Die abgeleiteten Felder gehen mit ihrer
Liste: lastCommit* haengt an commits, latestTag an tags UND releases,
sonst waeren sie in sich widerspruechlich.
Bewusst kein Fehlschlag: syncError bleibt leer und der Zaehler fuers
Zurueckhalten steigt nicht. Der Rest des Standes ist frisch, und ein
Repository deswegen stundenlang auszusetzen waere die falsche Antwort.
GIT_API_TIMEOUT_MS wird geprueft. Number("") ist 0 und Number("abc") ist
NaN; setTimeout loest bei beidem sofort aus, und dann bricht jeder Aufruf
ab, bevor er beginnt. Eine verrutschte Zeile in der .env legte damit den
ganzen Abgleich still - lautlos, versteht sich.
NACHGEWIESEN, NICHT BEHAUPTET
Gegen einen nachgebauten Gitea-Server, der genau den beobachteten Fall
erzeugt - Basis-Aufruf antwortet, Teillisten geben 403 - und eine
Wegwerf-Datenbank:
gesund branchCount=2 tagCount=3 commits=2 releases=1 missing={}
kaputt branchCount=2 tagCount=3 commits=2 releases=1
missing={8 Felder mit HTTP 403}, completeAt unveraendert
wieder gut missing={} , completeAt fortgeschrieben
Nichts verloren, die Luecke steht in der Meldung und am Repository, und
nach dem naechsten gesunden Durchlauf raeumt sie sich von selbst weg.
axe-core 0 Verstoesse auf 1280 und 375 px, kein seitlicher Ueberlauf.
Der eigentliche Grund der Ausfaelle auf der Instanz ist damit noch nicht
gefunden - aber ab jetzt steht er auf der Projektseite.
Refs #19
Refs #20
|
||
|
|
e9f01ba8fa |
docs(git): jeden ausgehenden Aufruf einzeln mit seinem Recht belegen
CI / pruefen (pull_request) Canceled after 0s
Die Faelle A und B beantworteten die Frage auf Ebene der Scopes, nicht auf Ebene der Aufrufe. Wer nachpruefen wollte, ob ein Recht wirklich noetig ist, musste die drei Clients selbst lesen. Jetzt steht in der README je System eine vollstaendige Tabelle: Aufruf, Zweck, benoetigtes Recht. Die Zuordnung bei GitHub folgt der offiziellen Berechtigungsuebersicht - dass /tags und /stats/commit_activity unter Metadata fallen und nicht unter Contents, ist nicht zu erraten. Fuer GitLab ist mit Quellenangabe belegt, dass es unterhalb von 'api' keinen Schreibzugriff auf Issues gibt. Ausserdem war offen, woher die Kommentare an den Issues stammen: Nebula schreibt keine. Der Schreibzugriff besteht aus genau zwei Aufrufen. Ein Test klopft dafuer die Clients ab - kaeme ein dritter dazu, wuerde die Empfehlung 'nimm ein lesendes Token' still falsch, ohne dass jemand einen Fehler saehe. Der Hinweis am Eingabefeld nannte Actions nicht, obwohl der CI-Zustand bei GitHub genau daran haengt. Refs #10 |
||
|
|
7920b6a20f |
Token-Rechte vollstaendig dokumentiert
CI / pruefen (pull_request) Canceled after 0s
Die Angaben stammten aus der Zeit, als Nebula nur gelesen hat. Seit v1.7.0 legt es Issues an und schliesst sie - wer sich an README oder den Hinweis im Verbindungsdialog hielt, bekam dabei einen 403. Erhoben wurde es nicht aus dem Gedaechtnis, sondern aus dem Code: jeder ausgehende Aufruf in lib/git/gitea.ts, github.ts und gitlab.ts, abgebildet auf das noetige Recht. Aufgeschluesselt nach Funktion statt nach System, damit erkennbar ist, was sich weglassen laesst: der Abgleich allein kommt rein lesend aus, nur die Issue-Anbindung schreibt. **GitLab ist der unangenehme Fall.** Es kennt keinen schmalen Schreib-Scope fuer Issues - dort geht nur "api", also alles, was das Konto darf. Das steht jetzt ausdruecklich da, samt der Alternative: bei read_api bleiben und auf die Issue-Anbindung verzichten. Der Abgleich laeuft dann vollstaendig, und geschlossene Issues werden weiterhin erkannt, nur nicht zurueckgeschrieben. Ebenfalls festgehalten, was Nebula *nicht* braucht: Schreibrechte auf Code, Adminrechte, Rechte zum Anlegen von Webhooks. Letzteres ist der Grund, warum der Haken von Hand eingetragen wird - so bleibt der Token davon frei. Der Hinweis in lib/git/meta.ts steht direkt am Eingabefeld und ist die Stelle, die tatsaechlich gelesen wird; er ist deshalb mitgezogen. Dazu ein Waechter in meta.test.ts: jeder Provider muss in GitClient createIssue und setIssueState koennen, folglich muss auch jeder Hinweis sagen, was das an Rechten kostet. Genau diese Kopplung war hier gerissen. Geprueft: 267 Unit-Tests (6 neue), 36 E2E-Tests, Hinweistext je System gegen die laufende Anwendung gelesen, axe-core auf 375px und 1280px ohne Beanstandung. Refs #10 |
||
|
|
7b60667633 |
feat(sync): geschlossene Issues haken ihre Aufgabe auch ohne Webhook ab
CI / pruefen (push) Canceled after 0s
Behebt florian.wilde/nebula#1. Der Rueckweg "Issue geschlossen -> Aufgabe erledigt" gab es seit v1.7.0 schon, aber nur ueber den Webhook. Ist keiner eingerichtet - und am Repository war keiner eingetragen -, blieb er ganz aus. Damit hing die Funktion an einer Voraussetzung, die man ihr von aussen nicht ansieht. Der Abgleich fragt jetzt den Zustand verknuepfter Issues nach und hakt ab, was in Git geschlossen ist. Beides ergaenzt sich: der Webhook wirkt sofort, der Abgleich verlaesslich. Gespart wird, wo es geht: Nummern, die der gerade geholte Snapshot als offen fuehrt, werden nicht einzeln nachgefragt. Umgekehrt gilt das nicht - die Liste ist gedeckelt, aus dem Fehlen einer Nummer folgt nichts. Deshalb ein Deckel von 25 Nachfragen je Durchgang. **Nur abhaken, nie zuruecknehmen.** Der Abgleich sieht Zustaende, keine Ereignisse. Ein offenes Issue kann auch bedeuten, dass das Schliessen von Nebula aus fehlgeschlagen ist; ein symmetrischer Abgleich wuerde dann eine bewusst erledigte Aufgabe wieder aufmachen. Das Zuruecknehmen bleibt dem Webhook vorbehalten, der ein echtes "reopened" sieht. Ein geloeschtes Issue ist ebenfalls kein erledigtes. Nebenbei eine Unstimmigkeit aus v1.7.0 geradegezogen: der Webhook setzte nur den Status und liess aus, was die Oberflaeche tut - naechste Fassung einer wiederkehrenden Aufgabe, Eintrag im Verlauf. Beide Wege gehen jetzt durch dieselbe Funktion. Geprueft: 232 Unit-Tests, 31 E2E-Tests. Der neue E2E-Test laeuft als einziger wirklich nach draussen - er haengt eine Aufgabe an ein seit Jahren geschlossenes Issue und prueft, dass der Abgleich sie abhakt. Ist GitHub nicht erreichbar, ueberspringt er sich statt rot zu werden. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e1d435f10a |
feat(tasks): Aufgabe als Git-Issue anlegen, Zustand wandert mit
Die Zuordnung stand im Schema laengst – sourceIssue, sourceUrl und sourceRepositoryId samt Eindeutigkeit je Repository, gefuellt von der Uebernahme aus Issues seit v0.9.0. Es fehlte die Gegenrichtung. Ein Knopf im Aufgabendialog legt das Issue an; Nummer und Adresse landen in denselben Feldern, der Dublettenschutz gilt also fuer beide Wege. In den Rumpf kommt ein Rueckverweis auf das Projekt. Ausdruecklich per Knopf und nie nebenbei: was in ein fremdes System geschrieben wird, soll jemand gewollt haben. Ist die Aufgabe erst verknuepft, wandert der Zustand allerdings mit – erledigt schliesst das Issue, ein Zurueckziehen oeffnet es wieder. Fehlertolerant: ein nicht erreichbares Git-System darf das Abhaken nicht verhindern. Die Zustaende passen nicht aufeinander, und das bleibt so. Nebula kennt vier, ein Issue zwei; nach aussen geht nur die Grenze um ERLEDIGT. Ein Rundlauf verliert damit IN ARBEIT und BLOCKIERT. Labels waeren die Alternative, wuerden aber den Issue-Tracker mit Zustaenden fuellen, die dort niemand pflegt. Kein Widerspruch zu "keine eigenen Git-Operationen": Issues laufen ueber die HTTP-API der Provider. Dafuer gibt es jetzt sendJson neben getJson – durch dieselbe SSRF-Schleuse, aber ohne Umleitungen zu folgen. Ein 307 auf eine interne Adresse naehme den Rumpf und den Token im Header mit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f8e6c66876 |
feat(git): README des Repositories holen
Erspart den Wechsel ins Git, nur um zu sehen, worum ein halbvergessenes Projekt eigentlich ging. GitHub hat einen eigenen Endpunkt, der jede Schreibweise findet - ein Aufruf genuegt. GitLab und Gitea kennen keinen; dort wird erst das Wurzelverzeichnis aufgelistet und dann die gefundene Datei geholt. Erkannt werden README.md und die ueblichen Varianten, Gross- und Kleinschreibung egal; Markdown hat Vorrang, weil nur das wirklich dargestellt werden kann. Geholt wird hoechstens einmal am Tag, wie der Kalender, und mit eigenem Zeitstempel: schlaegt das eine fehl, soll das andere nicht mit blockiert werden. Bei 60 000 Zeichen wird abgeschnitten, aber an einer Zeilengrenze - sonst bliebe womoeglich ein halber Codeblock stehen. Dekodiert wird ueber Buffer und nicht ueber atob, sonst kaemen Umlaute als Kauderwelsch heraus. Kein README ist eine Auskunft, kein Fehlschlag: der Zeitstempel wird dann trotzdem gesetzt, damit nicht jeder Abgleich erneut sucht. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ca6181b58f |
feat(git): Commit-Heatmap ueber ein Jahr
Ein Feld je Tag auf der Projektseite, Wochen als Spalten, mit Farbskala und Datum im Tooltip. Woher die Zahlen kommen, unterscheidet sich je Anbieter. GitHub fuehrt die Statistik selbst und liefert 52 Wochen mit Tageswerten in einem einzigen Aufruf. GitLab und Gitea kennen nichts Vergleichbares; dort werden Commits seitenweise geholt, gedeckelt auf fuenf Seiten - genug fuer die allermeisten Repositories und wenig genug, um kein fremdes Kontingent zu sprengen. Gezeichnet wird nur der Zeitraum, fuer den wirklich Daten vorliegen. Reichen die Seiten nur ein paar Monate zurueck, faengt die Heatmap dort an und sagt es dazu. Ein leeres Feld wuerde sonst behaupten, an dem Tag sei nicht gearbeitet worden, obwohl darueber nichts bekannt ist - genau diesen Fehler hatte die Verlaufslinie schon einmal. Geholt wird hoechstens einmal am Tag und getrennt vom uebrigen Abgleich: an einem Jahresueberblick aendert sich von Stunde zu Stunde nichts Sichtbares, der Abgleich laeuft aber im Zweifel stuendlich. Der Kalender benutzt getJson statt getJsonSafe. Waere ein Rate-Limit als "keine Daten" durchgegangen, haette der gesetzte Zeitstempel die Heatmap einen ganzen Tag lang unterdrueckt. Jetzt bleibt er bei einem Fehlschlag ungesetzt, und der naechste Abgleich versucht es erneut - das deckt auch den Fall ab, dass GitHub die Statistik gerade erst berechnet und mit 202 antwortet. Nachgewiesen an vercel/swr: 49 Commits an 33 Tagen ueber ein volles Jahr aus der Statistik, und mit kurzer Historie 90 statt 365 Felder samt Hinweis auf den Deckel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a843f08211 |
feat(git): mehrere Repositories je Projekt
Ein Vorhaben besteht oft aus Frontend und Backend - zwei Repositories, aber eine Karte. Die Git-Felder wandern deshalb vom Projekt in eine eigene Tabelle, der Snapshot haengt jetzt am Repository. Das erste Repository ist das fuehrende. Wo nur eines dargestellt werden kann - Karte, Kanban -, ist es dieses. Ein Abgleichsfehler an *irgendeinem* erscheint aber trotzdem auf der Karte, sonst bliebe er unbemerkt. Der Merkstand fuer Benachrichtigungen liegt nun am Repository. Am Projekt verdeckte ein rotes Frontend die rote Pipeline des Backends. Stillstand bleibt dagegen eine Aussage ueber das Vorhaben und bemisst sich am juengsten Commit ueber alle Repos: an einem Projekt wird gearbeitet, sobald an einem seiner Teile gearbeitet wird. Der Zeitgeber zaehlt und begrenzt jetzt Repositories statt Projekte. Sonst zoege ein Projekt mit fuenf Repos fuenfmal so viele Anfragen nach sich, wie sein Platz im Kontingent hergibt. "Alle abgleichen" laeuft nacheinander - ein Schwung gleichzeitiger Anfragen an denselben Anbieter ist genau das, worauf Rate-Limits reagieren. Die Issue-Nummer allein reicht als Dublettenschutz nicht mehr: Frontend und Backend haben beide ein Issue 5, und das sind zwei Aufgaben. Die Herkunft gehoert deshalb mit in die Eindeutigkeit. In Postgres nachgewiesen: dieselbe Nummer in zwei Repos geht durch, zweimal im selben Repo nicht, mehrere Aufgaben ohne Herkunft bleiben erlaubt. Die Projekt-API nimmt die Git-Felder weiterhin an und setzt damit das fuehrende Repository - so kommt man mit einem Aufruf zu Projekt und Repo, und der Import muss von der neuen Tabelle nichts wissen. Die Exportdatei zaehlt auf 2 hoch und traegt eine Liste. Version 1 bleibt lesbar: dort stand genau ein Repository flach am Projekt. Die Datenmigration ist von Hand geschrieben - migrate diff kennt nur den Zielzustand und haette die Spalten samt Snapshots ersatzlos geloescht. Gegen eine Kopie mit echten Daten geprueft: Verknuepfung, Verbindung, Fehlerzaehler und Snapshot kommen vollstaendig mit, Projekte ohne Repo bekommen keine leere Zeile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
03ce74faa2 |
feat(git): offene PRs und Issues, CI-Status und Commit-Aktivitaet
Bisher standen nur Zahlen da. Jetzt kommen die Inhalte mit. Pull Requests und Issues: bis zu 50 offene je Liste, mit Nummer, Titel, Autor, Alter, Labels und Link. Bei GitHub und Gitea liefert der Issue-Endpunkt auch Pull Requests mit - die werden herausgefiltert. CI-Status des letzten Laufs auf dem Default-Branch: - GitHub ueber actions/runs; dort sind Lauf-Status und Ergebnis getrennt und werden auf einen Wert zusammengefuehrt - GitLab ueber pipelines - Gitea ueber den kombinierten Commit-Status. Das deckt Gitea Actions und externe Systeme wie Woodpecker gleichermassen ab - ein reiner Actions-Endpunkt existiert auf aelteren Instanzen nicht. Commit-Aktivitaet als Balkenreihe. Berechnet aus den ohnehin geholten Commits (jetzt 100 statt 15, angezeigt bleiben 15) statt ueber eigene Statistik-Endpunkte: die gibt es nicht bei allen drei Systemen, und bei GitHub antworten sie beim ersten Aufruf mit 202 und ohne Daten. Beim Testen gegen gitlab-runner fiel auf, dass alle 100 Commits in die letzten 30 Tage fielen - die frueheren Tage haetten faelschlich Null gezeigt, obwohl wir ueber sie nichts wissen. Die Kurve wird deshalb auf den tatsaechlich belegten Zeitraum gekuerzt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
dbcf30efc1 |
feat(security): SSRF-Schutz, Ratenbegrenzung und harte Body-Pruefung
Die Basis-URL einer Git-Verbindung ist eine Benutzereingabe, die der Server anschliessend selbst abruft. Ohne Pruefung liesse sich darueber jede vom Server erreichbare Adresse ansprechen - Cloud-Metadaten unter 169.254.169.254, lokal gebundene Dienste oder ein Portscan im Heimnetz. ssrf.ts prueft deshalb vor jedem ausgehenden Aufruf: - Loopback, Link-Local samt Metadaten-Endpunkt, unspezifizierte Adressen, Multicast und CGNAT sind gesperrt - das private LAN bleibt erlaubt, weil dort ueblicherweise die eigene Gitea-Instanz steht; abschaltbar ueber GIT_BLOCK_PRIVATE - GIT_ALLOW_LOOPBACK fuer eine Instanz auf demselben Rechner, GIT_ALLOWED_HOSTS als strengster Modus - Hostnamen werden aufgeloest und *jede* zurueckgegebene Adresse geprueft, sonst genuegte ein zweiter A-Record fuer DNS-Rebinding - nach einem Redirect wird das tatsaechliche Ziel erneut geprueft Dazu: - rateLimit.ts, ein Zaehler im Arbeitsspeicher mit vorkonfigurierten Kontingenten fuer Anmeldung, ausgehende Aufrufe und WebAuthn - request.ts liest JSON-Bodies nur mit Content-Type application/json und hoechstens 256 KB. Die Content-Type-Pflicht ist zugleich CSRF-Schutz in der Tiefe: ein Cross-Site-Formular kommt ohne Preflight nur mit text/plain, multipart oder urlencoded durch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9ea6bb8d36 |
feat(git): Adapter fuer GitHub, GitLab und Gitea
Ein gemeinsames Interface (GitClient) mit drei Implementierungen. Jeder
Adapter liefert dieselbe normalisierte Struktur, sodass die Oberflaeche die
Unterschiede der APIs nicht kennen muss:
- GitHub – REST v3, Bearer-Token, funktioniert auch mit Enterprise (/api/v3).
open_issues_count enthaelt dort PRs, das wird herausgerechnet.
- GitLab – API v4, PRIVATE-TOKEN, Projekte ueber den URL-kodierten Pfad,
damit verschachtelte Gruppen funktionieren.
- Gitea – API v1, token-Header, deckt auch Forgejo ab.
Geholt werden Repo-Eckdaten, Branches, Tags, Commits, Releases, Sprachen und
offene PRs/MRs. Die Teilabfragen laufen parallel; eine fehlschlagende Liste
bricht den restlichen Sync nicht ab.
Weiteres:
- http.ts uebersetzt HTTP-Status in verstaendliche Meldungen (401 Token,
403 Scopes/Rate-Limit, 404 Pfad) und erzwingt ein Timeout.
- parseRepoUrl versteht https-, ssh- und GitLab-Weboberflaechen-URLs.
- meta.ts haelt die Provider-Metadaten frei von node:crypto, damit
Client-Komponenten sie importieren koennen.
- sync.ts schreibt das Ergebnis als Snapshot und vermerkt Fehler am Projekt,
statt eine Ausnahme bis in die Seite durchzureichen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|