Add GitHub connection type — repo viewer with org grouping and auto-refresh

New protocol type 'github' for browsing repositories via the GitHub REST API.
Creates a non-terminal panel showing repos grouped by owner/org, sorted by
last commit date (newest first), with search filtering and auto-refresh.

Backend:
- ProtocolGitHub constant and GitHubConfig/GitHubRepo model structs
- New internal/githubclient package (REST API, pagination, token validation)
- Session manager connectGitHub() with periodic refresh goroutine
- API surface: RefreshGitHubRepos() for on-demand refresh
- Token stored via existing credential infrastructure (keychain/vault)

Frontend:
- GitHub tab in profile editor with API URL and token storage fields
- New github.js viewer component (grouped repos, badges, language dots)
- github.css with charcoal-themed repo cards and org headers
- Protocol colour tokens, tab badge, sidebar indicator for GitHub
- Event wiring for session:github:repos and session:github:error
- Cleanup on tab close

Supports GitHub Enterprise via configurable API base URL.
This commit is contained in:
2026-04-16 00:00:00 +01:00
parent 58eb1626f6
commit c98ace2396
20 changed files with 1179 additions and 21 deletions
+2
View File
@@ -65,6 +65,8 @@ export function PinSession(arg1:string,arg2:boolean):Promise<void>;
export function ReconnectSession(arg1:string):Promise<void>;
export function RefreshGitHubRepos(arg1:string):Promise<Array<models.GitHubRepo>>;
export function RegisterSecretResponseListener():Promise<void>;
export function RenameSession(arg1:string,arg2:string):Promise<void>;
+4
View File
@@ -126,6 +126,10 @@ export function ReconnectSession(arg1) {
return window['go']['api']['Service']['ReconnectSession'](arg1);
}
export function RefreshGitHubRepos(arg1) {
return window['go']['api']['Service']['RefreshGitHubRepos'](arg1);
}
export function RegisterSecretResponseListener() {
return window['go']['api']['Service']['RegisterSecretResponseListener']();
}
+52
View File
@@ -71,6 +71,56 @@ export namespace models {
return a;
}
}
export class GitHubConfig {
baseURL: string;
secretMode: string;
refreshIntervalSecs: number;
static createFrom(source: any = {}) {
return new GitHubConfig(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.baseURL = source["baseURL"];
this.secretMode = source["secretMode"];
this.refreshIntervalSecs = source["refreshIntervalSecs"];
}
}
export class GitHubRepo {
fullName: string;
name: string;
owner: string;
description: string;
htmlURL: string;
private: boolean;
fork: boolean;
archived: boolean;
language: string;
stars: number;
pushedAt: string;
updatedAt: string;
static createFrom(source: any = {}) {
return new GitHubRepo(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.fullName = source["fullName"];
this.name = source["name"];
this.owner = source["owner"];
this.description = source["description"];
this.htmlURL = source["htmlURL"];
this.private = source["private"];
this.fork = source["fork"];
this.archived = source["archived"];
this.language = source["language"];
this.stars = source["stars"];
this.pushedAt = source["pushedAt"];
this.updatedAt = source["updatedAt"];
}
}
export class JumpHost {
host: string;
port: number;
@@ -301,6 +351,7 @@ export namespace models {
ssh?: SSHConfig;
telnet?: TelnetConfig;
raw?: RAWConfig;
github?: GitHubConfig;
static createFrom(source: any = {}) {
return new Profile(source);
@@ -332,6 +383,7 @@ export namespace models {
this.ssh = this.convertValues(source["ssh"], SSHConfig);
this.telnet = this.convertValues(source["telnet"], TelnetConfig);
this.raw = this.convertValues(source["raw"], RAWConfig);
this.github = this.convertValues(source["github"], GitHubConfig);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {