feat(frontend): wire app shell framework with dynamic providers
Replace the standalone IDE/Tray components with the framework shell from core/gui/ui. The IDE frontend is now minimal routing config that uses: - ApplicationFrameComponent as the HLCRF layout (header, sidebar, content, footer) - ProviderHostComponent for dynamic custom element rendering via :provider route - SystemTrayFrameComponent for the 380x480 tray panel Go side: add GET /api/v1/providers endpoint (ProvidersAPI) that returns all registered providers from the Registry plus runtime providers from the RuntimeManager. The Angular frontend calls this on startup to populate navigation and load custom elements. Also bumps @angular/build and @angular/cli to 21.x to match @angular/core. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
fe90224de6
commit
b9500bf866
7 changed files with 1803 additions and 1347 deletions
2991
frontend/package-lock.json
generated
2991
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -40,8 +40,8 @@
|
|||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/build": "^20.3.6",
|
||||
"@angular/cli": "^20.3.15",
|
||||
"@angular/build": "^21.1.2",
|
||||
"@angular/cli": "^21.1.3",
|
||||
"@angular/compiler-cli": "^20.3.0",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
|
||||
// SPDX-Licence-Identifier: EUPL-1.2
|
||||
|
||||
import {
|
||||
ApplicationConfig,
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
provideBrowserGlobalErrorListeners,
|
||||
provideZoneChangeDetection,
|
||||
} from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes), provideClientHydration(withEventReplay())
|
||||
]
|
||||
provideRouter(routes),
|
||||
provideClientHydration(withEventReplay()),
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,17 +1,26 @@
|
|||
// SPDX-Licence-Identifier: EUPL-1.2
|
||||
|
||||
import { Routes } from '@angular/router';
|
||||
import { TrayComponent } from './pages/tray/tray.component';
|
||||
import { IdeComponent } from './pages/ide/ide.component';
|
||||
import {
|
||||
ApplicationFrameComponent,
|
||||
ProviderHostComponent,
|
||||
SystemTrayFrameComponent,
|
||||
} from '@core/gui-ui';
|
||||
|
||||
export const routes: Routes = [
|
||||
// System tray panel - standalone compact UI
|
||||
{ path: 'tray', component: TrayComponent },
|
||||
// System tray panel — standalone compact UI (380x480 frameless)
|
||||
{ path: 'tray', component: SystemTrayFrameComponent },
|
||||
|
||||
// Full IDE interface
|
||||
{ path: 'ide', component: IdeComponent },
|
||||
// Main application frame with HLCRF layout
|
||||
{
|
||||
path: '',
|
||||
component: ApplicationFrameComponent,
|
||||
children: [
|
||||
// Dynamic provider rendering via route parameter
|
||||
{ path: ':provider', component: ProviderHostComponent },
|
||||
|
||||
// Default to tray for the root (tray panel is the default view)
|
||||
{ path: '', redirectTo: 'tray', pathMatch: 'full' },
|
||||
|
||||
// Catch-all
|
||||
{ path: '**', redirectTo: 'tray' },
|
||||
// Default to process provider (first registered)
|
||||
{ path: '', redirectTo: 'process', pathMatch: 'full' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@
|
|||
"paths": {
|
||||
"@bindings/*": [
|
||||
"bindings/*"
|
||||
],
|
||||
"@core/gui-ui": [
|
||||
"../../gui/ui/src/index.ts"
|
||||
],
|
||||
"@core/gui-ui/*": [
|
||||
"../../gui/ui/src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
|||
4
main.go
4
main.go
|
|
@ -81,6 +81,10 @@ func main() {
|
|||
// ── Runtime Provider Manager ──────────────────────────────
|
||||
rm := NewRuntimeManager(engine)
|
||||
|
||||
// ── Providers API ─────────────────────────────────────────
|
||||
// Exposes GET /api/v1/providers for the Angular frontend
|
||||
engine.Register(NewProvidersAPI(reg, rm))
|
||||
|
||||
// ── Core framework ─────────────────────────────────────────
|
||||
c, err := core.New(
|
||||
core.WithName("ws", func(c *core.Core) (any, error) {
|
||||
|
|
|
|||
98
providers.go
Normal file
98
providers.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// SPDX-Licence-Identifier: EUPL-1.2
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.lthn.ai/core/api/pkg/provider"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ProvidersAPI exposes registered provider information via GET /api/v1/providers.
|
||||
// The Angular frontend uses this endpoint to discover providers and load their
|
||||
// custom elements dynamically.
|
||||
type ProvidersAPI struct {
|
||||
registry *provider.Registry
|
||||
runtime *RuntimeManager
|
||||
}
|
||||
|
||||
func NewProvidersAPI(reg *provider.Registry, rm *RuntimeManager) *ProvidersAPI {
|
||||
return &ProvidersAPI{registry: reg, runtime: rm}
|
||||
}
|
||||
|
||||
func (p *ProvidersAPI) Name() string { return "providers-api" }
|
||||
func (p *ProvidersAPI) BasePath() string { return "/api/v1/providers" }
|
||||
|
||||
func (p *ProvidersAPI) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
rg.GET("", p.list)
|
||||
}
|
||||
|
||||
// list godoc
|
||||
//
|
||||
// @Summary List registered providers
|
||||
// @Description Returns all registered providers with their capabilities
|
||||
// @Tags providers
|
||||
// @Produce json
|
||||
// @Success 200 {object} providersResponse
|
||||
// @Router /api/v1/providers [get]
|
||||
func (p *ProvidersAPI) list(c *gin.Context) {
|
||||
registryInfo := p.registry.Info()
|
||||
runtimeInfo := p.runtime.List()
|
||||
|
||||
// Merge runtime provider info with registry info
|
||||
providers := make([]providerDTO, 0, len(registryInfo)+len(runtimeInfo))
|
||||
|
||||
for _, info := range registryInfo {
|
||||
dto := providerDTO{
|
||||
Name: info.Name,
|
||||
BasePath: info.BasePath,
|
||||
Channels: info.Channels,
|
||||
Status: "active",
|
||||
}
|
||||
if info.Element != nil {
|
||||
dto.Element = &elementDTO{
|
||||
Tag: info.Element.Tag,
|
||||
Source: info.Element.Source,
|
||||
}
|
||||
}
|
||||
providers = append(providers, dto)
|
||||
}
|
||||
|
||||
// Add runtime providers not already in registry
|
||||
for _, ri := range runtimeInfo {
|
||||
found := false
|
||||
for _, p := range providers {
|
||||
if p.Name == ri.Code {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
providers = append(providers, providerDTO{
|
||||
Name: ri.Code,
|
||||
BasePath: ri.Namespace,
|
||||
Status: "active",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, providersResponse{Providers: providers})
|
||||
}
|
||||
|
||||
type providersResponse struct {
|
||||
Providers []providerDTO `json:"providers"`
|
||||
}
|
||||
|
||||
type providerDTO struct {
|
||||
Name string `json:"name"`
|
||||
BasePath string `json:"basePath"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Element *elementDTO `json:"element,omitempty"`
|
||||
Channels []string `json:"channels,omitempty"`
|
||||
}
|
||||
|
||||
type elementDTO struct {
|
||||
Tag string `json:"tag"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue