Skip to content
This repository has been archived by the owner on Mar 29, 2024. It is now read-only.

Commit

Permalink
Add support for profile import/export and update to new icons api
Browse files Browse the repository at this point in the history
  • Loading branch information
ppacher committed Nov 20, 2023
1 parent 423fe35 commit 7461512
Show file tree
Hide file tree
Showing 18 changed files with 2,310 additions and 1,324 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter, finalize, map, mergeMap, share, take } from 'rxjs/operators';
import { AppProfile, FlatConfigObject, LayeredProfile, TagDescription, flattenProfileConfig } from './app-profile.types';
import { PORTMASTER_HTTP_API_ENDPOINT, PortapiService } from './portapi.service';
import {
AppProfile,
FlatConfigObject,
LayeredProfile,
TagDescription,
flattenProfileConfig,
} from './app-profile.types';
import {
PORTMASTER_HTTP_API_ENDPOINT,
PortapiService,
} from './portapi.service';

@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AppProfileService {
private watchedProfiles = new Map<string, Observable<AppProfile>>();

constructor(
private portapi: PortapiService,
private http: HttpClient,
@Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string,
) { }
@Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string
) {}

/**
* Returns the database key of a profile.
Expand All @@ -41,7 +50,7 @@ export class AppProfileService {

if (!!id) {
key = `core:profiles/${idOrSourceOrProfile}/${id}`;
};
}

return key;
}
Expand All @@ -61,46 +70,61 @@ export class AppProfileService {
*/
getAppProfile(source: string, id: string): Observable<AppProfile>;

getAppProfile(sourceOrSourceAndID: string, id?: string): Observable<AppProfile> {
getAppProfile(
sourceOrSourceAndID: string,
id?: string
): Observable<AppProfile> {
let source = sourceOrSourceAndID;
if (id !== undefined) {
source += "/" + id
source += '/' + id;
}
const key = `core:profiles/${source}`
const key = `core:profiles/${source}`;

if (this.watchedProfiles.has(key)) {
return this.watchedProfiles.get(key)!
.pipe(
take(1)
)
return this.watchedProfiles.get(key)!.pipe(take(1));
}

return this.getAppProfileFromKey(key);
}

setProfileIcon(
content: string | ArrayBuffer,
mimeType: string
): Observable<{ filename: string }> {
return this.http.post<{ filename: string }>(
`${this.httpAPI}/v1/profile/icon`,
content,
{
headers: new HttpHeaders({
'Content-Type': mimeType,
}),
}
);
}

/**
* Loads an application profile by it's database key.
*
* @param key The key of the application profile.
*/
getAppProfileFromKey(key: string): Observable<AppProfile> {
return this.portapi.get(key)
return this.portapi.get(key);
}

/**
* Loads the global-configuration profile.
*/
globalConfig(): Observable<FlatConfigObject> {
return this.getAppProfile('special', 'global-config')
.pipe(
map(profile => flattenProfileConfig(profile.Config)),
)
return this.getAppProfile('special', 'global-config').pipe(
map((profile) => flattenProfileConfig(profile.Config))
);
}

/** Returns all possible process tags. */
tagDescriptions(): Observable<TagDescription[]> {
return this.http.get<{ Tags: TagDescription[] }>(`${this.httpAPI}/v1/process/tags`)
.pipe(map(result => result.Tags))
return this.http
.get<{ Tags: TagDescription[] }>(`${this.httpAPI}/v1/process/tags`)
.pipe(map((result) => result.Tags));
}

/**
Expand All @@ -122,9 +146,9 @@ export class AppProfileService {
let key = '';

if (id === undefined) {
key = sourceAndId
if (!key.startsWith("core:profiles/")) {
key = `core:profiles/${key}`
key = sourceAndId;
if (!key.startsWith('core:profiles/')) {
key = `core:profiles/${key}`;
}
} else {
key = `core:profiles/${sourceAndId}/${id}`;
Expand All @@ -134,17 +158,20 @@ export class AppProfileService {
return this.watchedProfiles.get(key)!;
}

const stream =
this.portapi.get<AppProfile>(key)
.pipe(
mergeMap(() => this.portapi.watch<AppProfile>(key)),
finalize(() => {
console.log("watchAppProfile: removing cached profile stream for " + key)
this.watchedProfiles.delete(key);
}),
share({ connector: () => new BehaviorSubject<AppProfile | null>(null), resetOnRefCountZero: true }),
filter(profile => profile !== null),
) as Observable<AppProfile>;
const stream = this.portapi.get<AppProfile>(key).pipe(
mergeMap(() => this.portapi.watch<AppProfile>(key)),
finalize(() => {
console.log(
'watchAppProfile: removing cached profile stream for ' + key
);
this.watchedProfiles.delete(key);
}),
share({
connector: () => new BehaviorSubject<AppProfile | null>(null),
resetOnRefCountZero: true,
}),
filter((profile) => profile !== null)
) as Observable<AppProfile>;

this.watchedProfiles.set(key, stream);

Expand All @@ -162,15 +189,18 @@ export class AppProfileService {
* @param profile The profile to save
*/
saveProfile(profile: AppProfile): Observable<void> {
profile.LastEdited = Math.floor((new Date()).getTime() / 1000);
return this.portapi.update(`core:profiles/${profile.Source}/${profile.ID}`, profile);
profile.LastEdited = Math.floor(new Date().getTime() / 1000);
return this.portapi.update(
`core:profiles/${profile.Source}/${profile.ID}`,
profile
);
}

/**
* Watch all application profiles
*/
watchProfiles(): Observable<AppProfile[]> {
return this.portapi.watchAll<AppProfile>('core:profiles/')
return this.portapi.watchAll<AppProfile>('core:profiles/');
}

watchLayeredProfile(source: string, id: string): Observable<LayeredProfile>;
Expand All @@ -183,8 +213,11 @@ export class AppProfileService {
*/
watchLayeredProfile(profile: AppProfile): Observable<LayeredProfile>;

watchLayeredProfile(profileOrSource: string | AppProfile, id?: string): Observable<LayeredProfile> {
if (typeof profileOrSource == "object") {
watchLayeredProfile(
profileOrSource: string | AppProfile,
id?: string
): Observable<LayeredProfile> {
if (typeof profileOrSource == 'object') {
id = profileOrSource.ID;
profileOrSource = profileOrSource.Source;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ export interface LayeredProfile extends Record {
}

export enum FingerprintType {
Tag = "tag",
Cmdline = "cmdline",
Env = "env",
Path = "path"
Tag = 'tag',
Cmdline = 'cmdline',
Env = 'env',
Path = 'path',
}

export enum FingerpringOperation {
Equal = "equals",
Prefix = "prefix",
Regex = "regex",
Equal = 'equals',
Prefix = 'prefix',
Regex = 'regex',
}

export interface Fingerprint {
Expand All @@ -49,7 +49,7 @@ export interface TagDescription {
}

export interface Icon {
Type: 'database' | 'path';
Type: 'database' | 'path' | 'api';
Value: string;
}

Expand All @@ -74,13 +74,14 @@ export interface AppProfile extends Record {

// flattenProfileConfig returns a flat version of a nested ConfigMap where each property
// can be used as the database key for the associated setting.
export function flattenProfileConfig(p: ConfigMap, prefix = ''): FlatConfigObject {
export function flattenProfileConfig(
p: ConfigMap,
prefix = ''
): FlatConfigObject {
let result: FlatConfigObject = {};

Object.keys(p).forEach(key => {
const childPrefix = prefix === ''
? key
: `${prefix}/${key}`;
Object.keys(p).forEach((key) => {
const childPrefix = prefix === '' ? key : `${prefix}/${key}`;

const prop = p[key];

Expand All @@ -91,7 +92,7 @@ export function flattenProfileConfig(p: ConfigMap, prefix = ''): FlatConfigObjec
}

result[childPrefix] = prop;
})
});

return result;
}
Expand All @@ -103,7 +104,10 @@ export function flattenProfileConfig(p: ConfigMap, prefix = ''): FlatConfigObjec
* @param obj The ConfigMap object
* @param path The path of the setting separated by foward slashes.
*/
export function getAppSetting<T extends OptionValueType>(obj: ConfigMap, path: string): T | null {
export function getAppSetting<T extends OptionValueType>(
obj: ConfigMap,
path: string
): T | null {
const parts = path.split('/');

let iter = obj;
Expand All @@ -124,12 +128,13 @@ export function getAppSetting<T extends OptionValueType>(obj: ConfigMap, path: s
}

iter = value;

}
return null;
}

export function getActualValue<S extends BaseSetting<any, any>>(s: S): SettingValueType<S> {
export function getActualValue<S extends BaseSetting<any, any>>(
s: S
): SettingValueType<S> {
if (s.Value !== undefined) {
return s.Value;
}
Expand All @@ -139,7 +144,6 @@ export function getActualValue<S extends BaseSetting<any, any>>(s: S): SettingVa
return s.DefaultValue;
}


/**
* Sets the value of a settings inside the nested config object.
*
Expand All @@ -159,11 +163,11 @@ export function setAppSetting(obj: ConfigObject, path: string, value: any) {

if (idx === parts.length - 1) {
if (value === undefined) {
delete (iter[propName])
delete iter[propName];
} else {
iter[propName] = value;
}
return
return;
}

if (iter[propName] === undefined) {
Expand All @@ -186,13 +190,16 @@ function isConfigMap(v: any): v is ConfigMap {
* @param a The first config object
* @param b The second config object
*/
function mergeObjects(a: FlatConfigObject, b: FlatConfigObject): FlatConfigObject {
function mergeObjects(
a: FlatConfigObject,
b: FlatConfigObject
): FlatConfigObject {
var res: FlatConfigObject = {};
Object.keys(a).forEach(key => {
Object.keys(a).forEach((key) => {
res[key] = a[key];
});
Object.keys(b).forEach(key => {
Object.keys(b).forEach((key) => {
res[key] = b[key];
})
});
return res;
}
Loading

0 comments on commit 7461512

Please sign in to comment.