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

Add support for profile import/export and update to new icons api #495

Merged
merged 4 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions modules/portmaster/.angulardoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"repoId": "be9f3092-959d-4148-98ed-ef4f9ca07250",
"lastSync": 0
}
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