mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 08:44:58 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5860c5747b | ||
|
|
d3c2d55706 | ||
|
|
ac2ff47a9c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ coverage.out
|
||||
*.hold/
|
||||
/deploy/
|
||||
.vscode/
|
||||
tmp/
|
||||
|
||||
@@ -34,7 +34,7 @@ let OBJECTS = #APIObjects & {
|
||||
containers: [
|
||||
{
|
||||
name: Holos
|
||||
image: "271053619184.dkr.ecr.us-east-2.amazonaws.com/holos-run/holos-server/holos:0.74.0"
|
||||
image: "271053619184.dkr.ecr.us-east-2.amazonaws.com/holos-run/holos-server/holos:v0.76.0"
|
||||
imagePullPolicy: "Always"
|
||||
env: [
|
||||
{
|
||||
|
||||
2
go.mod
2
go.mod
@@ -20,6 +20,7 @@ require (
|
||||
github.com/lmittmann/tint v1.0.4
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
github.com/mennanov/fieldmask-utils v1.1.2
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
@@ -157,7 +158,6 @@ require (
|
||||
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mennanov/fieldmask-utils v1.1.2 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/dns v1.1.58 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
|
||||
43
internal/cli/generate/generate.go
Normal file
43
internal/cli/generate/generate.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/generate"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// New returns a new generate command.
|
||||
func New(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("generate")
|
||||
cmd.Aliases = []string{"gen"}
|
||||
cmd.Short = "generate local resources"
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
cmd.AddCommand(NewPlatform(cfg))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatform(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("platform")
|
||||
cmd.Use = "platform [flags] PLATFORM"
|
||||
cmd.Short = "generate a platform from an embedded schematic"
|
||||
cmd.Long = fmt.Sprintf("Embedded platforms available to generate:\n\n %s", strings.Join(generate.Platforms(), "\n "))
|
||||
cmd.Args = cobra.ExactArgs(1)
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
for _, name := range args {
|
||||
if err := generate.GeneratePlatform(ctx, name); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -5,11 +5,16 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/holos-run/holos/version"
|
||||
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
"github.com/holos-run/holos/internal/server"
|
||||
|
||||
"github.com/holos-run/holos/internal/cli/build"
|
||||
"github.com/holos-run/holos/internal/cli/controller"
|
||||
"github.com/holos-run/holos/internal/cli/create"
|
||||
"github.com/holos-run/holos/internal/cli/generate"
|
||||
"github.com/holos-run/holos/internal/cli/get"
|
||||
"github.com/holos-run/holos/internal/cli/kv"
|
||||
"github.com/holos-run/holos/internal/cli/login"
|
||||
@@ -19,9 +24,6 @@ import (
|
||||
"github.com/holos-run/holos/internal/cli/rpc"
|
||||
"github.com/holos-run/holos/internal/cli/token"
|
||||
"github.com/holos-run/holos/internal/cli/txtar"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
"github.com/holos-run/holos/version"
|
||||
)
|
||||
|
||||
// New returns a new root *cobra.Command for command line execution.
|
||||
@@ -41,7 +43,7 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
log := cfg.Logger()
|
||||
c.SetContext(logger.NewContext(c.Context(), log))
|
||||
c.Root().SetContext(logger.NewContext(c.Context(), log))
|
||||
// Set the default logger after flag parsing.
|
||||
slog.SetDefault(log)
|
||||
return nil
|
||||
@@ -65,6 +67,7 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
rootCmd.AddCommand(logout.New(cfg))
|
||||
rootCmd.AddCommand(token.New(cfg))
|
||||
rootCmd.AddCommand(rpc.New(cfg))
|
||||
rootCmd.AddCommand(generate.New(cfg))
|
||||
|
||||
// Maybe not needed?
|
||||
rootCmd.AddCommand(txtar.New(cfg))
|
||||
|
||||
@@ -10,6 +10,7 @@ import { UserService } from './gen/holos/user/v1alpha1/user_service_connect';
|
||||
import { OrganizationService } from './gen/holos/organization/v1alpha1/organization_service_connect';
|
||||
import { PlatformService } from './gen/holos/platform/v1alpha1/platform_service_connect';
|
||||
import { HolosPanelWrapperComponent } from '../wrappers/holos-panel-wrapper/holos-panel-wrapper.component';
|
||||
import { SystemService } from './gen/holos/system/v1alpha1/system_service_connect';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
@@ -19,6 +20,7 @@ export const appConfig: ApplicationConfig = {
|
||||
provideClient(UserService),
|
||||
provideClient(OrganizationService),
|
||||
provideClient(PlatformService),
|
||||
provideClient(SystemService),
|
||||
importProvidersFrom(
|
||||
ConnectModule.forRoot({
|
||||
baseUrl: window.location.origin
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated from file holos/system/v1alpha1/system.proto (package holos.system.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
|
||||
import { Message, proto3 } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
* @generated from message holos.system.v1alpha1.Version
|
||||
*/
|
||||
export class Version extends Message<Version> {
|
||||
/**
|
||||
* @generated from field: string version = 1;
|
||||
*/
|
||||
version = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string git_commit = 2;
|
||||
*/
|
||||
gitCommit = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string git_tree_state = 3;
|
||||
*/
|
||||
gitTreeState = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string go_version = 4;
|
||||
*/
|
||||
goVersion = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string build_date = 5;
|
||||
*/
|
||||
buildDate = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string os = 6;
|
||||
*/
|
||||
os = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string arch = 7;
|
||||
*/
|
||||
arch = "";
|
||||
|
||||
constructor(data?: PartialMessage<Version>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.system.v1alpha1.Version";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "version", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 2, name: "git_commit", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 3, name: "git_tree_state", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 4, name: "go_version", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 5, name: "build_date", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 6, name: "os", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 7, name: "arch", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Version {
|
||||
return new Version().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Version {
|
||||
return new Version().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Version {
|
||||
return new Version().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: Version | PlainMessage<Version> | undefined, b: Version | PlainMessage<Version> | undefined): boolean {
|
||||
return proto3.util.equals(Version, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { DropTablesRequest, DropTablesResponse, SeedDatabaseRequest, SeedDatabaseResponse } from "./system_service_pb.js";
|
||||
import { DropTablesRequest, DropTablesResponse, GetVersionRequest, GetVersionResponse, SeedDatabaseRequest, SeedDatabaseResponse } from "./system_service_pb.js";
|
||||
import { MethodKind } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
@@ -13,12 +13,12 @@ export const SystemService = {
|
||||
typeName: "holos.system.v1alpha1.SystemService",
|
||||
methods: {
|
||||
/**
|
||||
* @generated from rpc holos.system.v1alpha1.SystemService.SeedDatabase
|
||||
* @generated from rpc holos.system.v1alpha1.SystemService.GetVersion
|
||||
*/
|
||||
seedDatabase: {
|
||||
name: "SeedDatabase",
|
||||
I: SeedDatabaseRequest,
|
||||
O: SeedDatabaseResponse,
|
||||
getVersion: {
|
||||
name: "GetVersion",
|
||||
I: GetVersionRequest,
|
||||
O: GetVersionResponse,
|
||||
kind: MethodKind.Unary,
|
||||
},
|
||||
/**
|
||||
@@ -30,6 +30,15 @@ export const SystemService = {
|
||||
O: DropTablesResponse,
|
||||
kind: MethodKind.Unary,
|
||||
},
|
||||
/**
|
||||
* @generated from rpc holos.system.v1alpha1.SystemService.SeedDatabase
|
||||
*/
|
||||
seedDatabase: {
|
||||
name: "SeedDatabase",
|
||||
I: SeedDatabaseRequest,
|
||||
O: SeedDatabaseResponse,
|
||||
kind: MethodKind.Unary,
|
||||
},
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -4,7 +4,84 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
|
||||
import { Message, proto3 } from "@bufbuild/protobuf";
|
||||
import { FieldMask, Message, proto3 } from "@bufbuild/protobuf";
|
||||
import { Version } from "./system_pb.js";
|
||||
|
||||
/**
|
||||
* @generated from message holos.system.v1alpha1.GetVersionRequest
|
||||
*/
|
||||
export class GetVersionRequest extends Message<GetVersionRequest> {
|
||||
/**
|
||||
* FieldMask represents the fields to include in the response.
|
||||
*
|
||||
* @generated from field: google.protobuf.FieldMask field_mask = 1;
|
||||
*/
|
||||
fieldMask?: FieldMask;
|
||||
|
||||
constructor(data?: PartialMessage<GetVersionRequest>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.system.v1alpha1.GetVersionRequest";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "field_mask", kind: "message", T: FieldMask },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetVersionRequest {
|
||||
return new GetVersionRequest().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetVersionRequest {
|
||||
return new GetVersionRequest().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetVersionRequest {
|
||||
return new GetVersionRequest().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: GetVersionRequest | PlainMessage<GetVersionRequest> | undefined, b: GetVersionRequest | PlainMessage<GetVersionRequest> | undefined): boolean {
|
||||
return proto3.util.equals(GetVersionRequest, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @generated from message holos.system.v1alpha1.GetVersionResponse
|
||||
*/
|
||||
export class GetVersionResponse extends Message<GetVersionResponse> {
|
||||
/**
|
||||
* @generated from field: holos.system.v1alpha1.Version version = 1;
|
||||
*/
|
||||
version?: Version;
|
||||
|
||||
constructor(data?: PartialMessage<GetVersionResponse>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.system.v1alpha1.GetVersionResponse";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "version", kind: "message", T: Version },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetVersionResponse {
|
||||
return new GetVersionResponse().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetVersionResponse {
|
||||
return new GetVersionResponse().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetVersionResponse {
|
||||
return new GetVersionResponse().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: GetVersionResponse | PlainMessage<GetVersionResponse> | undefined, b: GetVersionResponse | PlainMessage<GetVersionResponse> | undefined): boolean {
|
||||
return proto3.util.equals(GetVersionResponse, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @generated from message holos.system.v1alpha1.SeedDatabaseRequest
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</button>
|
||||
}
|
||||
</span>
|
||||
<app-version-button></app-version-button>
|
||||
<app-profile-button [user$]="user$"></app-profile-button>
|
||||
</mat-toolbar>
|
||||
<main class="main-content">
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ProfileButtonComponent } from '../profile-button/profile-button.component';
|
||||
import { User } from '../gen/holos/user/v1alpha1/user_pb';
|
||||
import { UserService } from '../services/user.service';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { map, shareReplay, takeUntil } from 'rxjs/operators';
|
||||
import { Organization } from '../gen/holos/organization/v1alpha1/organization_pb';
|
||||
import { User } from '../gen/holos/user/v1alpha1/user_pb';
|
||||
import { ProfileButtonComponent } from '../profile-button/profile-button.component';
|
||||
import { OrganizationService } from '../services/organization.service';
|
||||
import { UserService } from '../services/user.service';
|
||||
import { VersionButtonComponent } from '../version-button/version-button.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-nav',
|
||||
@@ -34,28 +35,35 @@ import { OrganizationService } from '../services/organization.service';
|
||||
RouterOutlet,
|
||||
MatCardModule,
|
||||
ProfileButtonComponent,
|
||||
VersionButtonComponent,
|
||||
]
|
||||
})
|
||||
export class NavComponent implements OnInit {
|
||||
export class NavComponent implements OnInit, OnDestroy {
|
||||
private breakpointObserver = inject(BreakpointObserver);
|
||||
private userService = inject(UserService);
|
||||
private orgService = inject(OrganizationService);
|
||||
private destroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
user$!: Observable<User | null>;
|
||||
org$!: Observable<Organization | undefined>;
|
||||
|
||||
refreshOrg(): void {
|
||||
this.orgService.refreshOrganizations()
|
||||
}
|
||||
|
||||
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
|
||||
.pipe(
|
||||
map(result => result.matches),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
refreshOrg(): void {
|
||||
this.orgService.refreshOrganizations()
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.user$ = this.userService.getUser();
|
||||
this.org$ = this.orgService.activeOrg();
|
||||
this.user$ = this.userService.getUser().pipe(takeUntil(this.destroy$));
|
||||
this.org$ = this.orgService.activeOrg().pipe(takeUntil(this.destroy$));
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.destroy$.next(true);
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { FieldMask, JsonValue, Struct } from '@bufbuild/protobuf';
|
||||
import { Observable, of, switchMap } from 'rxjs';
|
||||
import { ObservableClient } from '../../connect/observable-client';
|
||||
import { Organization } from '../gen/holos/organization/v1alpha1/organization_pb';
|
||||
import { Platform } from '../gen/holos/platform/v1alpha1/platform_pb';
|
||||
import { PlatformService as ConnectPlatformService } from '../gen/holos/platform/v1alpha1/platform_service_connect';
|
||||
import { Platform, Spec } from '../gen/holos/platform/v1alpha1/platform_pb';
|
||||
import { GetPlatformRequest, ListPlatformsRequest, UpdatePlatformOperation, UpdatePlatformRequest } from '../gen/holos/platform/v1alpha1/platform_service_pb';
|
||||
import { FieldMask, JsonValue, Struct } from '@bufbuild/protobuf';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SystemService } from './system.service';
|
||||
|
||||
describe('SystemService', () => {
|
||||
let service: SystemService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(SystemService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
22
internal/frontend/holos/src/app/services/system.service.ts
Normal file
22
internal/frontend/holos/src/app/services/system.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Observable, of, switchMap } from 'rxjs';
|
||||
import { ObservableClient } from '../../connect/observable-client';
|
||||
import { Version } from '../gen/holos/system/v1alpha1/system_pb';
|
||||
import { SystemService as ConnectSystemService } from '../gen/holos/system/v1alpha1/system_service_connect';
|
||||
import { GetVersionRequest } from '../gen/holos/system/v1alpha1/system_service_pb';
|
||||
import { FieldMask } from '@bufbuild/protobuf';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SystemService {
|
||||
getVersion(): Observable<Version | undefined> {
|
||||
const fieldMask = new FieldMask({ paths: ["version", "git_commit", "go_version", "os", "arch"] })
|
||||
const req = new GetVersionRequest({ fieldMask: fieldMask })
|
||||
return this.client.getVersion(req).pipe(
|
||||
switchMap(resp => { return of(resp.version) })
|
||||
)
|
||||
}
|
||||
|
||||
constructor(@Inject(ConnectSystemService) private client: ObservableClient<typeof ConnectSystemService>) { }
|
||||
}
|
||||
8
internal/frontend/holos/src/app/truncate.pipe.spec.ts
Normal file
8
internal/frontend/holos/src/app/truncate.pipe.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { TruncatePipe } from './truncate.pipe';
|
||||
|
||||
describe('TruncatePipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new TruncatePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
13
internal/frontend/holos/src/app/truncate.pipe.ts
Normal file
13
internal/frontend/holos/src/app/truncate.pipe.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'truncate',
|
||||
standalone: true
|
||||
})
|
||||
export class TruncatePipe implements PipeTransform {
|
||||
|
||||
transform(value: string, limit: number = 8): string {
|
||||
if (!value) return '';
|
||||
return value.length > limit ? value.substring(0, limit) : value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@if (version$ | async; as version) {
|
||||
<button mat-button [matMenuTriggerFor]="menu">
|
||||
{{ version.version }}
|
||||
</button>
|
||||
|
||||
<mat-menu class="version-menu" #menu="matMenu">
|
||||
<mat-card class="version-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ version.version }}</mat-card-title>
|
||||
<mat-card-subtitle>Server version info</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<pre>Git: {{ version.gitCommit | truncate }}</pre>
|
||||
<pre>Go: {{ version.goVersion | truncate }}</pre>
|
||||
<pre>OS: {{ version.os | truncate }}</pre>
|
||||
<pre>Arch: {{ version.arch | truncate }}</pre>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button (click)="refreshVersion()" [disabled]="isLoading">Refresh</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</mat-menu>
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VersionButtonComponent } from './version-button.component';
|
||||
|
||||
describe('VersionButtonComponent', () => {
|
||||
let component: VersionButtonComponent;
|
||||
let fixture: ComponentFixture<VersionButtonComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VersionButtonComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VersionButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { AsyncPipe, NgIf, NgStyle } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { Observable, Subject, of, startWith, switchMap, takeUntil } from 'rxjs';
|
||||
import { Version } from '../gen/holos/system/v1alpha1/system_pb';
|
||||
import { SystemService } from '../services/system.service';
|
||||
import { TruncatePipe } from '../truncate.pipe';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
|
||||
@Component({
|
||||
selector: 'app-version-button',
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDivider,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
NgIf,
|
||||
NgStyle,
|
||||
TruncatePipe,
|
||||
],
|
||||
templateUrl: './version-button.component.html',
|
||||
styleUrl: './version-button.component.scss'
|
||||
})
|
||||
export class VersionButtonComponent implements OnInit, OnDestroy {
|
||||
private destroy$: Subject<boolean> = new Subject<boolean>();
|
||||
private refreshVersion$ = new Subject<boolean>();
|
||||
private systemService = inject(SystemService);
|
||||
version$!: Observable<Version | undefined>;
|
||||
isLoading = false;
|
||||
|
||||
refreshVersion(): void {
|
||||
this.refreshVersion$.next(true);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.version$ = this.refreshVersion$.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
startWith(true),
|
||||
switchMap(() => {
|
||||
this.isLoading = true;
|
||||
return this.systemService.getVersion().pipe(
|
||||
switchMap((version) => { this.isLoading = false; return of(version); })
|
||||
);
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.destroy$.next(true);
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,14 @@ import { Component, Input, OnDestroy, inject } from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatTab, MatTabGroup } from '@angular/material/tabs';
|
||||
import { JsonValue } from '@bufbuild/protobuf';
|
||||
import { FormlyFieldConfig, FormlyFormOptions, FormlyModule } from '@ngx-formly/core';
|
||||
import { FormlyMaterialModule } from '@ngx-formly/material';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { PlatformService } from '../../services/platform.service';
|
||||
import { Platform } from '../../gen/holos/platform/v1alpha1/platform_pb';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { PlatformService } from '../../services/platform.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-platform-detail',
|
||||
|
||||
97
internal/generate/generate.go
Normal file
97
internal/generate/generate.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
)
|
||||
|
||||
//go:embed all:platforms
|
||||
var platforms embed.FS
|
||||
|
||||
// root is the root path to copy platform cue code from.
|
||||
const root = "platforms"
|
||||
|
||||
// Platforms returns a slice of embedded platforms or nil if there are none.
|
||||
func Platforms() []string {
|
||||
entries, err := fs.ReadDir(platforms, root)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
dirs := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() && entry.Name() != "cue.mod" {
|
||||
dirs = append(dirs, entry.Name())
|
||||
}
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
// GeneratePlatform writes the cue code for a platform to the local working
|
||||
// directory.
|
||||
func GeneratePlatform(ctx context.Context, name string) error {
|
||||
// Check for a valid platform
|
||||
platformPath := filepath.Join(root, name)
|
||||
if !dirExists(platforms, platformPath) {
|
||||
return errors.Wrap(fmt.Errorf("cannot generate: have: [%s] want: %+v", name, Platforms()))
|
||||
}
|
||||
|
||||
// Copy the cue.mod directory
|
||||
if err := copyEmbedFS(ctx, platforms, filepath.Join(root, "cue.mod"), "cue.mod"); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// Copy the named platform
|
||||
if err := copyEmbedFS(ctx, platforms, platformPath, "."); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dirExists(srcFS embed.FS, path string) bool {
|
||||
entries, err := fs.ReadDir(srcFS, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(entries) > 0
|
||||
}
|
||||
|
||||
func copyEmbedFS(ctx context.Context, srcFS embed.FS, srcPath, dstPath string) error {
|
||||
log := logger.FromContext(ctx)
|
||||
return fs.WalkDir(srcFS, srcPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(srcPath, path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
dstFullPath := filepath.Join(dstPath, relPath)
|
||||
|
||||
if d.IsDir() {
|
||||
if err := os.MkdirAll(dstFullPath, os.ModePerm); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.DebugContext(ctx, "created", "directory", dstFullPath)
|
||||
} else {
|
||||
data, err := srcFS.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
if err := os.WriteFile(dstFullPath, data, os.ModePerm); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.DebugContext(ctx, "wrote", "file", dstFullPath)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func (h *PlatformHandler) GetPlatform(ctx context.Context, req *connect.Request[
|
||||
}
|
||||
rpcPlatform := PlatformToRPC(dbPlatform)
|
||||
|
||||
mask, err := fieldmask_utils.MaskFromPaths(req.Msg.GetFieldMask().GetPaths(), strings.PascalCase)
|
||||
mask, err := fieldmask_utils.MaskFromProtoFieldMask(req.Msg.GetFieldMask(), strings.PascalCase)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err))
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func (h *PlatformHandler) ListPlatforms(ctx context.Context, req *connect.Reques
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
|
||||
mask, err := fieldmask_utils.MaskFromPaths(req.Msg.GetFieldMask().GetPaths(), strings.PascalCase)
|
||||
mask, err := fieldmask_utils.MaskFromProtoFieldMask(req.Msg.GetFieldMask(), strings.PascalCase)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err))
|
||||
}
|
||||
@@ -348,7 +348,9 @@ func rpcPlatforms(reqDBOrg *ent.Organization, mask fieldmask_utils.Mask) ([]*pla
|
||||
// fieldmask_utils.StructToMap. This is here largely as an placeholder to
|
||||
// remember we can mutate the value if we want.
|
||||
func newVisitor(ctx context.Context) func(filter fieldmask_utils.FieldFilter, src, dst reflect.Value, srcFieldName, dstFieldName string, srcFieldValue reflect.Value) fieldmask_utils.MapVisitorResult {
|
||||
log := logger.FromContext(ctx)
|
||||
return func(filter fieldmask_utils.FieldFilter, src, dst reflect.Value, srcFieldName, dstFieldName string, srcFieldValue reflect.Value) fieldmask_utils.MapVisitorResult {
|
||||
log.DebugContext(ctx, "visitor", "srcFieldName", srcFieldName, "dstFieldName", dstFieldName)
|
||||
return fieldmask_utils.MapVisitorResult{
|
||||
UpdatedDst: &dst,
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@ import (
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/authn"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
holosstrings "github.com/holos-run/holos/internal/strings"
|
||||
storage "github.com/holos-run/holos/service/gen/holos/storage/v1alpha1"
|
||||
system "github.com/holos-run/holos/service/gen/holos/system/v1alpha1"
|
||||
"github.com/holos-run/holos/version"
|
||||
fieldmask_utils "github.com/mennanov/fieldmask-utils"
|
||||
)
|
||||
|
||||
const AdminEmail = "jeff@openinfrastructure.co"
|
||||
@@ -41,6 +44,26 @@ func (h *SystemHandler) checkAdmin(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SystemHandler) GetVersion(ctx context.Context, req *connect.Request[system.GetVersionRequest]) (*connect.Response[system.GetVersionResponse], error) {
|
||||
_, err := authn.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
|
||||
}
|
||||
|
||||
mask, err := fieldmask_utils.MaskFromProtoFieldMask(req.Msg.GetFieldMask(), holosstrings.PascalCase)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
|
||||
srcVersion := version.NewVersionInfo()
|
||||
var rpcVersion system.Version
|
||||
if err := fieldmask_utils.StructToStruct(mask, &srcVersion, &rpcVersion); err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&system.GetVersionResponse{Version: &rpcVersion}), nil
|
||||
}
|
||||
|
||||
func (h *SystemHandler) DropTables(ctx context.Context, req *connect.Request[system.DropTablesRequest]) (*connect.Response[system.DropTablesResponse], error) {
|
||||
if err := h.checkAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user