Restructure modules (#1572)
* Move portbase into monorepo * Add new simple module mgr * [WIP] Switch to new simple module mgr * Add StateMgr and more worker variants * [WIP] Switch more modules * [WIP] Switch more modules * [WIP] swtich more modules * [WIP] switch all SPN modules * [WIP] switch all service modules * [WIP] Convert all workers to the new module system * [WIP] add new task system to module manager * [WIP] Add second take for scheduling workers * [WIP] Add FIXME for bugs in new scheduler * [WIP] Add minor improvements to scheduler * [WIP] Add new worker scheduler * [WIP] Fix more bug related to new module system * [WIP] Fix start handing of the new module system * [WIP] Improve startup process * [WIP] Fix minor issues * [WIP] Fix missing subsystem in settings * [WIP] Initialize managers in constructor * [WIP] Move module event initialization to constrictors * [WIP] Fix setting for enabling and disabling the SPN module * [WIP] Move API registeration into module construction * [WIP] Update states mgr for all modules * [WIP] Add CmdLine operation support * Add state helper methods to module group and instance * Add notification and module status handling to status package * Fix starting issues * Remove pilot widget and update security lock to new status data * Remove debug logs * Improve http server shutdown * Add workaround for cleanly shutting down firewall+netquery * Improve logging * Add syncing states with notifications for new module system * Improve starting, stopping, shutdown; resolve FIXMEs/TODOs * [WIP] Fix most unit tests * Review new module system and fix minor issues * Push shutdown and restart events again via API * Set sleep mode via interface * Update example/template module * [WIP] Fix spn/cabin unit test * Remove deprecated UI elements * Make log output more similar for the logging transition phase * Switch spn hub and observer cmds to new module system * Fix log sources * Make worker mgr less error prone * Fix tests and minor issues * Fix observation hub * Improve shutdown and restart handling * Split up big connection.go source file * Move varint and dsd packages to structures repo * Improve expansion test * Fix linter warnings * Fix interception module on windows * Fix linter errors --------- Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
This commit is contained in:
@@ -64,7 +64,6 @@ import { SecurityLockComponent } from './shared/security-lock';
|
||||
import { SPNAccountDetailsComponent } from './shared/spn-account-details';
|
||||
import { SPNLoginComponent } from './shared/spn-login';
|
||||
import { SPNStatusComponent } from './shared/spn-status';
|
||||
import { PilotWidgetComponent } from './shared/status-pilot';
|
||||
import { PlaceholderComponent } from './shared/text-placeholder';
|
||||
import { DashboardWidgetComponent } from './pages/dashboard/dashboard-widget/dashboard-widget.component';
|
||||
import { MergeProfileDialogComponent } from './pages/app-view/merge-profile-dialog/merge-profile-dialog.component';
|
||||
@@ -133,7 +132,6 @@ const localeConfig = {
|
||||
MonitorPageComponent,
|
||||
SideDashComponent,
|
||||
NavigationComponent,
|
||||
PilotWidgetComponent,
|
||||
NotificationListComponent,
|
||||
PromptListComponent,
|
||||
FuzzySearchPipe,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div sfngTipUpTrigger="navShield" sfngTipUpPassive class="relative flex flex-row w-full gap-2 px-2 pb-4 justify-evenly">
|
||||
<app-status-pilot class="block w-32"></app-status-pilot>
|
||||
<app-security-lock routerLink="/dashboard"></app-security-lock>
|
||||
</div>
|
||||
|
||||
<app-feature-scout></app-feature-scout>
|
||||
|
||||
@@ -30,9 +30,6 @@ export interface MapPin {
|
||||
|
||||
// whether the pin has any known issues
|
||||
hasIssues: boolean;
|
||||
|
||||
// FIXME: remove me
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
|
||||
@@ -6,22 +6,6 @@ export interface CaptivePortal {
|
||||
Domain: string;
|
||||
}
|
||||
|
||||
export enum ModuleStatus {
|
||||
Off = 0,
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Operational = 3
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string represetnation of the module status.
|
||||
*
|
||||
* @param stat The module status to translate
|
||||
*/
|
||||
export function getModuleStatusString(stat: ModuleStatus): string {
|
||||
return getEnumKey(ModuleStatus, stat)
|
||||
}
|
||||
|
||||
export enum OnlineStatus {
|
||||
Unknown = 0,
|
||||
Offline = 1,
|
||||
@@ -40,55 +24,46 @@ export function getOnlineStatusString(stat: OnlineStatus): string {
|
||||
return getEnumKey(OnlineStatus, stat)
|
||||
}
|
||||
|
||||
export interface Threat<T = any> {
|
||||
ID: string;
|
||||
Name: string;
|
||||
Description: string;
|
||||
AdditionalData: T;
|
||||
MitigationLevel: SecurityLevel;
|
||||
Started: number;
|
||||
Ended: number;
|
||||
}
|
||||
|
||||
export interface CoreStatus extends Record {
|
||||
ActiveSecurityLevel: SecurityLevel;
|
||||
SelectedSecurityLevel: SecurityLevel;
|
||||
ThreatMitigationLevel: SecurityLevel;
|
||||
OnlineStatus: OnlineStatus;
|
||||
Threats: Threat[];
|
||||
CaptivePortal: CaptivePortal;
|
||||
// Modules: []ModuleState; // TODO: Do we need all modules?
|
||||
WorstState: {
|
||||
Module: string,
|
||||
ID: string,
|
||||
Name: string,
|
||||
Message: string,
|
||||
Type: ModuleStateType,
|
||||
// Time: time.Time, // TODO: How do we best use Go's time.Time?
|
||||
Data: any
|
||||
}
|
||||
}
|
||||
|
||||
export enum FailureStatus {
|
||||
Operational = 0,
|
||||
Hint = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
export enum ModuleStateType {
|
||||
Undefined = "",
|
||||
Hint = "hint",
|
||||
Warning = "warning",
|
||||
Error = "error"
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of a failure status value.
|
||||
*
|
||||
* @param stat The failure status value.
|
||||
* @param stateType The module state type value.
|
||||
*/
|
||||
export function getFailureStatusString(stat: FailureStatus): string {
|
||||
return getEnumKey(FailureStatus, stat)
|
||||
export function getModuleStateString(stateType: ModuleStateType): string {
|
||||
return getEnumKey(ModuleStateType, stateType)
|
||||
}
|
||||
|
||||
export interface Module {
|
||||
Enabled: boolean;
|
||||
FailureID: string;
|
||||
FailureMsg: string;
|
||||
FailureStatus: FailureStatus;
|
||||
Name: string;
|
||||
Status: ModuleStatus;
|
||||
}
|
||||
|
||||
export interface Subsystem extends Record {
|
||||
ConfigKeySpace: string;
|
||||
Description: string;
|
||||
ExpertiseLevel: string;
|
||||
FailureStatus: FailureStatus;
|
||||
ID: string;
|
||||
Modules: Module[];
|
||||
Name: string;
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from '@safing/portmaster-api';
|
||||
import { BehaviorSubject, Subscription, combineLatest } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { StatusService, Subsystem } from 'src/app/services';
|
||||
import { StatusService } from 'src/app/services';
|
||||
import {
|
||||
fadeInAnimation,
|
||||
fadeInListAnimation,
|
||||
@@ -44,6 +44,8 @@ import {
|
||||
ImportDialogComponent,
|
||||
} from './import-dialog/import-dialog.component';
|
||||
|
||||
import { subsystems, SubsystemWithExpertise } from './subsystems'
|
||||
|
||||
interface Category {
|
||||
name: string;
|
||||
settings: Setting[];
|
||||
@@ -52,12 +54,6 @@ interface Category {
|
||||
hasUserDefinedValues: boolean;
|
||||
}
|
||||
|
||||
interface SubsystemWithExpertise extends Subsystem {
|
||||
minimumExpertise: ExpertiseLevelNumber;
|
||||
isDisabled: boolean;
|
||||
hasUserDefinedValues: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-view',
|
||||
templateUrl: './config-settings.html',
|
||||
@@ -66,7 +62,7 @@ interface SubsystemWithExpertise extends Subsystem {
|
||||
})
|
||||
export class ConfigSettingsViewComponent
|
||||
implements OnInit, OnDestroy, AfterViewInit {
|
||||
subsystems: SubsystemWithExpertise[] = [];
|
||||
subsystems: SubsystemWithExpertise[] = subsystems;
|
||||
others: Setting[] | null = null;
|
||||
settings: Map<string, Category[]> = new Map();
|
||||
|
||||
@@ -207,7 +203,7 @@ export class ConfigSettingsViewComponent
|
||||
private searchService: FuzzySearchService,
|
||||
private actionIndicator: ActionIndicatorService,
|
||||
private portapi: PortapiService,
|
||||
private dialog: SfngDialogService
|
||||
private dialog: SfngDialogService,
|
||||
) { }
|
||||
|
||||
openImportDialog() {
|
||||
@@ -303,21 +299,12 @@ export class ConfigSettingsViewComponent
|
||||
ngOnInit(): void {
|
||||
this.subscription = combineLatest([
|
||||
this.onSettingsChange,
|
||||
this.statusService.querySubsystem(),
|
||||
this.onSearch.pipe(debounceTime(250)),
|
||||
this.configService.watch<StringSetting>('core/releaseLevel'),
|
||||
])
|
||||
.pipe(debounceTime(10))
|
||||
.subscribe(
|
||||
([settings, subsystems, searchTerm, currentReleaseLevelSetting]) => {
|
||||
this.subsystems = subsystems.map((s) => ({
|
||||
...s,
|
||||
// we start with developer and decrease to the lowest number required
|
||||
// while grouping the settings.
|
||||
minimumExpertise: ExpertiseLevelNumber.developer,
|
||||
isDisabled: false,
|
||||
hasUserDefinedValues: false,
|
||||
}));
|
||||
([settings, searchTerm, currentReleaseLevelSetting]) => {
|
||||
this.others = [];
|
||||
this.settings = new Map();
|
||||
|
||||
|
||||
272
desktop/angular/src/app/shared/config/subsystems.ts
Normal file
272
desktop/angular/src/app/shared/config/subsystems.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { ExpertiseLevelNumber } from "@safing/portmaster-api";
|
||||
import { Subsystem } from "src/app/services/status.types";
|
||||
|
||||
export interface SubsystemWithExpertise extends Subsystem {
|
||||
minimumExpertise: ExpertiseLevelNumber;
|
||||
isDisabled: boolean;
|
||||
hasUserDefinedValues: boolean;
|
||||
}
|
||||
|
||||
export var subsystems : SubsystemWithExpertise[] = [
|
||||
{
|
||||
minimumExpertise: ExpertiseLevelNumber.developer,
|
||||
isDisabled: false,
|
||||
hasUserDefinedValues: false,
|
||||
ID: "core",
|
||||
Name: "Core",
|
||||
Description: "Base Structure and System Integration",
|
||||
Modules: [
|
||||
{
|
||||
Name: "core",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "subsystems",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "runtime",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "status",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "ui",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "compat",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "broadcasts",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "sync",
|
||||
Enabled: true
|
||||
}
|
||||
],
|
||||
ToggleOptionKey: "",
|
||||
ExpertiseLevel: "user",
|
||||
ReleaseLevel: 0,
|
||||
ConfigKeySpace: "config:core/",
|
||||
_meta: {
|
||||
Created: 0,
|
||||
Modified: 0,
|
||||
Expires: 0,
|
||||
Deleted: 0,
|
||||
Key: "runtime:subsystems/core"
|
||||
}
|
||||
},
|
||||
{
|
||||
minimumExpertise: ExpertiseLevelNumber.developer,
|
||||
isDisabled: false,
|
||||
hasUserDefinedValues: false,
|
||||
ID: "dns",
|
||||
Name: "Secure DNS",
|
||||
Description: "DNS resolver with scoping and DNS-over-TLS",
|
||||
Modules: [
|
||||
{
|
||||
Name: "nameserver",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "resolver",
|
||||
Enabled: true
|
||||
}
|
||||
],
|
||||
ToggleOptionKey: "",
|
||||
ExpertiseLevel: "user",
|
||||
ReleaseLevel: 0,
|
||||
ConfigKeySpace: "config:dns/",
|
||||
_meta: {
|
||||
Created: 0,
|
||||
Modified: 0,
|
||||
Expires: 0,
|
||||
Deleted: 0,
|
||||
Key: "runtime:subsystems/dns"
|
||||
}
|
||||
},
|
||||
{
|
||||
minimumExpertise: ExpertiseLevelNumber.developer,
|
||||
isDisabled: false,
|
||||
hasUserDefinedValues: false,
|
||||
ID: "filter",
|
||||
Name: "Privacy Filter",
|
||||
Description: "DNS and Network Filter",
|
||||
Modules: [
|
||||
{
|
||||
Name: "filter",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "interception",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "base",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "database",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "config",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "rng",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "metrics",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "api",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "updates",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "network",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "netenv",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "processes",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "profiles",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "notifications",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "intel",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "geoip",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "filterlists",
|
||||
Enabled: true
|
||||
},
|
||||
{
|
||||
Name: "customlists",
|
||||
Enabled: true
|
||||
}
|
||||
],
|
||||
ToggleOptionKey: "",
|
||||
ExpertiseLevel: "user",
|
||||
ReleaseLevel: 0,
|
||||
ConfigKeySpace: "config:filter/",
|
||||
_meta: {
|
||||
Created: 0,
|
||||
Modified: 0,
|
||||
Expires: 0,
|
||||
Deleted: 0,
|
||||
Key: "runtime:subsystems/filter"
|
||||
}
|
||||
},
|
||||
{
|
||||
minimumExpertise: ExpertiseLevelNumber.developer,
|
||||
isDisabled: false,
|
||||
hasUserDefinedValues: false,
|
||||
ID: "history",
|
||||
Name: "Network History",
|
||||
Description: "Keep Network History Data",
|
||||
Modules: [
|
||||
{
|
||||
Name: "netquery",
|
||||
Enabled: true
|
||||
}
|
||||
],
|
||||
ToggleOptionKey: "",
|
||||
ExpertiseLevel: "user",
|
||||
ReleaseLevel: 0,
|
||||
ConfigKeySpace: "config:history/",
|
||||
_meta: {
|
||||
Created: 0,
|
||||
Modified: 0,
|
||||
Expires: 0,
|
||||
Deleted: 0,
|
||||
Key: "runtime:subsystems/history"
|
||||
}
|
||||
},
|
||||
{
|
||||
minimumExpertise: ExpertiseLevelNumber.developer,
|
||||
isDisabled: false,
|
||||
hasUserDefinedValues: false,
|
||||
ID: "spn",
|
||||
Name: "SPN",
|
||||
Description: "Safing Privacy Network",
|
||||
Modules: [
|
||||
{
|
||||
Name: "captain",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "terminal",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "cabin",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "ships",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "docks",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "access",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "crew",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "navigator",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "sluice",
|
||||
Enabled: false
|
||||
},
|
||||
{
|
||||
Name: "patrol",
|
||||
Enabled: false
|
||||
}
|
||||
],
|
||||
ToggleOptionKey: "spn/enable",
|
||||
ExpertiseLevel: "user",
|
||||
ReleaseLevel: 0,
|
||||
ConfigKeySpace: "config:spn/",
|
||||
_meta: {
|
||||
Created: 0,
|
||||
Modified: 0,
|
||||
Expires: 0,
|
||||
Deleted: 0,
|
||||
Key: "runtime:subsystems/spn"
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -219,15 +219,6 @@
|
||||
"Global" }} Settings</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- FIXME
|
||||
<span *ngIf="conn.profile_revision !== helper.profile?.currentProfileRevision">
|
||||
<span>Notice:</span>
|
||||
<span>
|
||||
The settings used for this connection have been superseded.
|
||||
</span>
|
||||
</span>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div *ngIf="conn.scope === scopes.Global">
|
||||
|
||||
@@ -510,28 +510,4 @@ export class NetqueryHelper {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates of all outgoing rules and collects which domains are blocked.
|
||||
* It stops collecting domains as soon as the first "allow something" rule
|
||||
* is hit.
|
||||
*/
|
||||
// FIXME
|
||||
/*
|
||||
private collectBlockedDomains() {
|
||||
let blockedDomains = new Set<string>();
|
||||
|
||||
const rules = getAppSetting<string[]>(this.profile!.profile!.Config, 'filter/endpoints') || [];
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const rule = rules[i];
|
||||
if (rule.startsWith('+ ')) {
|
||||
break;
|
||||
}
|
||||
|
||||
blockedDomains.add(rule.slice(2))
|
||||
}
|
||||
|
||||
this.blockedDomains = Array.from(blockedDomains)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="flex flex-row items-center gap-2" name="first">
|
||||
<span class="flex-shrink-0 verdict" [class.outdated]="isOutdated" [ngClass]="helper.getVerdictClass(conn)"
|
||||
<span class="flex-shrink-0 verdict" [ngClass]="helper.getVerdictClass(conn)"
|
||||
[sfng-tooltip]="conn.extra_data?.reason?.Msg || null"></span>
|
||||
|
||||
<ng-container *ngIf="conn.domain as domain; else scopeTranslation">
|
||||
|
||||
@@ -30,21 +30,6 @@ export class SfngNetqueryConnectionRowComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
activeRevision: number | undefined = 0;
|
||||
|
||||
get isOutdated() {
|
||||
// FIXME(ppacher)
|
||||
return false;
|
||||
/*
|
||||
if (!this.conn || !this.helper.profile) {
|
||||
return false;
|
||||
}
|
||||
if (this.helper.profile.currentProfileRevision === -1) {
|
||||
// we don't know the revision counter yet ...
|
||||
return false;
|
||||
}
|
||||
return this.conn.profile_revision !== this.helper.profile.currentProfileRevision;
|
||||
*/
|
||||
}
|
||||
|
||||
/* timeAgoTicker ticks every 10000 seconds to force a refresh
|
||||
of the timeAgo pipes */
|
||||
timeAgoTicker: number = 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Input, OnInit, inject } from "@angular/core";
|
||||
import { SecurityLevel } from "@safing/portmaster-api";
|
||||
import { combineLatest } from "rxjs";
|
||||
import { FailureStatus, StatusService, Subsystem } from "src/app/services";
|
||||
import { StatusService, ModuleStateType } from "src/app/services";
|
||||
import { fadeInAnimation, fadeOutAnimation } from "../animations";
|
||||
|
||||
interface SecurityOption {
|
||||
@@ -36,14 +36,7 @@ export class SecurityLockComponent implements OnInit {
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
combineLatest([
|
||||
this.statusService.status$,
|
||||
this.statusService.watchSubsystems()
|
||||
])
|
||||
.subscribe(([status, subsystems]) => {
|
||||
const activeLevel = status.ActiveSecurityLevel;
|
||||
const suggestedLevel = status.ThreatMitigationLevel;
|
||||
|
||||
this.statusService.status$.subscribe(status => {
|
||||
// By default the lock is green and we are "Secure"
|
||||
this.lockLevel = {
|
||||
level: SecurityLevel.Normal,
|
||||
@@ -51,28 +44,16 @@ export class SecurityLockComponent implements OnInit {
|
||||
displayText: 'Secure',
|
||||
}
|
||||
|
||||
// Find the highest failure-status reported by any module
|
||||
// of any subsystem.
|
||||
const failureStatus = subsystems.reduce((value: FailureStatus, system: Subsystem) => {
|
||||
if (system.FailureStatus != 0) {
|
||||
console.log(system);
|
||||
}
|
||||
return system.FailureStatus > value
|
||||
? system.FailureStatus
|
||||
: value;
|
||||
}, FailureStatus.Operational)
|
||||
|
||||
// update the failure level depending on the highest
|
||||
// failure status.
|
||||
switch (failureStatus) {
|
||||
case FailureStatus.Warning:
|
||||
// update the shield depending on the worst state.
|
||||
switch (status.WorstState.Type) {
|
||||
case ModuleStateType.Warning:
|
||||
this.lockLevel = {
|
||||
level: SecurityLevel.High,
|
||||
class: 'text-yellow-300',
|
||||
displayText: 'Warning'
|
||||
}
|
||||
break;
|
||||
case FailureStatus.Error:
|
||||
case ModuleStateType.Error:
|
||||
this.lockLevel = {
|
||||
level: SecurityLevel.Extreme,
|
||||
class: 'text-red-300',
|
||||
@@ -81,16 +62,6 @@ export class SecurityLockComponent implements OnInit {
|
||||
break;
|
||||
}
|
||||
|
||||
// if the auto-pilot would suggest a higher (mitigation) level
|
||||
// we are always Insecure
|
||||
if (activeLevel < suggestedLevel) {
|
||||
this.lockLevel = {
|
||||
level: SecurityLevel.High,
|
||||
class: 'high',
|
||||
displayText: 'Insecure'
|
||||
}
|
||||
}
|
||||
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { StatusPilotComponent as PilotWidgetComponent } from "./pilot-widget";
|
||||
@@ -1,57 +0,0 @@
|
||||
<app-security-lock routerLink="/dashboard"></app-security-lock>
|
||||
|
||||
|
||||
<div *ngIf="networkRatingEnabled$ | async" (click)="levelDropdown.toggle(origin)" cdkOverlayOrigin
|
||||
#origin="cdkOverlayOrigin"
|
||||
class="flex flex-row items-center gap-0.5 px-2 py-1 -mt-1 rounded-md cursor-pointer text-xxs text-secondary hover:text-primary hover:bg-gray-200">
|
||||
{{ activeLevelText }}
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
|
||||
<sfng-dropdown externalTrigger="true" #levelDropdown>
|
||||
<div sfngTipUpAnchor class="network-rating-level-list" [class.auto-pilot]="mode === 'auto'">
|
||||
<div class="rate-header">
|
||||
<label style="white-space: nowrap; margin-right: 5px; opacity: 0.9; display: flex; width: 100%;">
|
||||
Network Rating
|
||||
<sfng-tipup style="margin-left: 0.5rem; align-items: center;display: flex;" key="pilot-widget-NetworkRating">
|
||||
</sfng-tipup>
|
||||
</label>
|
||||
<sfng-select *appExpertiseLevel="'developer'" [ngModel]="mode" (ngModelChange)="updateMode($event)">
|
||||
<sfng-select-item *sfngSelectValue="'auto'">
|
||||
<span>
|
||||
<span class="auto-detect low"></span>
|
||||
<span>Auto Detect</span>
|
||||
</span>
|
||||
</sfng-select-item>
|
||||
<sfng-select-item *sfngSelectValue="'manual'">
|
||||
<span>
|
||||
<span class="off"></span>
|
||||
<span>Manual</span>
|
||||
</span>
|
||||
</sfng-select-item>
|
||||
</sfng-select>
|
||||
</div>
|
||||
|
||||
<ng-container *ngFor="let opt of options">
|
||||
<div sfngTipUpAnchor class="level" [class.selected]="activeLevel === opt.level"
|
||||
[class.suggested]="suggestedLevel === opt.level && suggestedLevel > activeLevel"
|
||||
(click)="selectLevel(opt.level)">
|
||||
<div style="display: flex;align-items: center;">
|
||||
<span>
|
||||
{{opt.displayText}}
|
||||
</span>
|
||||
<span class="situation">
|
||||
{{opt.subText || ''}}
|
||||
</span>
|
||||
<sfng-tipup style="margin-left: auto;" [key]="'pilot-widget-NetworkRating-' + opt.displayText"></sfng-tipup>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</sfng-dropdown>
|
||||
@@ -1,208 +0,0 @@
|
||||
:host {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: none;
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
flex-direction: column;
|
||||
|
||||
|
||||
@keyframes shield-pulse {
|
||||
0% {
|
||||
transform: scale(.62);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-opacity {
|
||||
0% {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spn-status {
|
||||
background-color: var(--info-blue);
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 1 !important;
|
||||
padding: 0.2rem;
|
||||
transform: scale(0.8);
|
||||
position: absolute;
|
||||
bottom: 42px;
|
||||
right: 18px;
|
||||
|
||||
&.connected {
|
||||
background-color: theme('colors.info.blue');
|
||||
}
|
||||
|
||||
&.connecting,
|
||||
&.failed {
|
||||
background-color: theme('colors.info.gray');
|
||||
}
|
||||
|
||||
svg {
|
||||
stroke: white;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
|
||||
.network-rating-level-list {
|
||||
@apply p-3 rounded;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
label {
|
||||
opacity: 0.6;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
div.rate-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 0 0.3rem 0;
|
||||
margin-right: 0.11rem;
|
||||
|
||||
.auto-detect {
|
||||
height: 5px;
|
||||
width: 5px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 1px;
|
||||
background-color: #4995f3;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.auto-pilot) {
|
||||
div.level.selected {
|
||||
div {
|
||||
background-color: #292929;
|
||||
}
|
||||
|
||||
&:after {
|
||||
transition: none;
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.level {
|
||||
position: relative;
|
||||
padding: 2px;
|
||||
margin-top: 0.155rem;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
fa-icon[icon*="question-circle"] {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&:after {
|
||||
transition: all cubic-bezier(0.19, 1, 0.82, 1) .2s;
|
||||
@apply rounded;
|
||||
content: "";
|
||||
filter: saturate(1.3);
|
||||
background-image: linear-gradient(90deg, #226ab79f 0%, rgba(2, 0, 36, 0) 45%);
|
||||
transform: translateX(100%);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
div {
|
||||
background-color: #202020;
|
||||
border-radius: 2px;
|
||||
padding: 9px 17px 10px 18px;
|
||||
display: block;
|
||||
opacity: 0.55;
|
||||
|
||||
span {
|
||||
font-size: 0.725rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.situation {
|
||||
@apply text-tertiary;
|
||||
@apply ml-2;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
svg.help {
|
||||
width: 0.95rem;
|
||||
float: right;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: 1.5px;
|
||||
|
||||
.inner {
|
||||
stroke: var(--text-secondary);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
.inner {
|
||||
stroke: var(--text-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
div {
|
||||
background-color: #292929;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected,
|
||||
&.suggested {
|
||||
&:after {
|
||||
transform: translateX(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.suggested {
|
||||
&:after {
|
||||
animation: pulse-opacity 1s ease-in-out infinite alternate;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
div {
|
||||
opacity: 1;
|
||||
|
||||
span {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { ConfigService, SecurityLevel } from '@safing/portmaster-api';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { FailureStatus, StatusService, Subsystem } from 'src/app/services';
|
||||
|
||||
interface SecurityOption {
|
||||
level: SecurityLevel;
|
||||
displayText: string;
|
||||
class: string;
|
||||
subText?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-status-pilot',
|
||||
templateUrl: './pilot-widget.html',
|
||||
styleUrls: [
|
||||
'./pilot-widget.scss'
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class StatusPilotComponent implements OnInit {
|
||||
activeLevel: SecurityLevel = SecurityLevel.Off;
|
||||
selectedLevel: SecurityLevel = SecurityLevel.Off;
|
||||
suggestedLevel: SecurityLevel = SecurityLevel.Off;
|
||||
activeOption: SecurityOption | null = null;
|
||||
selectedOption: SecurityOption | null = null;
|
||||
|
||||
mode: 'auto' | 'manual' = 'auto';
|
||||
|
||||
get activeLevelText() {
|
||||
return this.options.find(opt => opt.level === this.activeLevel)?.displayText || '';
|
||||
}
|
||||
|
||||
readonly options: SecurityOption[] = [
|
||||
{
|
||||
level: SecurityLevel.Normal,
|
||||
displayText: 'Trusted',
|
||||
class: 'low',
|
||||
subText: 'Home Network'
|
||||
},
|
||||
{
|
||||
level: SecurityLevel.High,
|
||||
displayText: 'Untrusted',
|
||||
class: 'medium',
|
||||
subText: 'Public Network'
|
||||
},
|
||||
{
|
||||
level: SecurityLevel.Extreme,
|
||||
displayText: 'Danger',
|
||||
class: 'high',
|
||||
subText: 'Hacked Network'
|
||||
},
|
||||
];
|
||||
|
||||
get networkRatingEnabled$() { return this.configService.networkRatingEnabled$ }
|
||||
|
||||
constructor(
|
||||
private statusService: StatusService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private configService: ConfigService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
combineLatest([
|
||||
this.statusService.status$,
|
||||
this.statusService.watchSubsystems()
|
||||
])
|
||||
.subscribe(([status, subsystems]) => {
|
||||
this.activeLevel = status.ActiveSecurityLevel;
|
||||
this.selectedLevel = status.SelectedSecurityLevel;
|
||||
this.suggestedLevel = status.ThreatMitigationLevel;
|
||||
|
||||
if (this.selectedLevel === SecurityLevel.Off) {
|
||||
this.mode = 'auto';
|
||||
} else {
|
||||
this.mode = 'manual';
|
||||
}
|
||||
|
||||
this.selectedOption = this.options.find(opt => opt.level === this.selectedLevel) || null;
|
||||
this.activeOption = this.options.find(opt => opt.level === this.activeLevel) || null;
|
||||
|
||||
// Find the highest failure-status reported by any module
|
||||
// of any subsystem.
|
||||
const failureStatus = subsystems.reduce((value: FailureStatus, system: Subsystem) => {
|
||||
if (system.FailureStatus != 0) {
|
||||
console.log(system);
|
||||
}
|
||||
return system.FailureStatus > value
|
||||
? system.FailureStatus
|
||||
: value;
|
||||
}, FailureStatus.Operational)
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
updateMode(mode: 'auto' | 'manual') {
|
||||
this.mode = mode;
|
||||
|
||||
if (mode === 'auto') {
|
||||
this.selectLevel(SecurityLevel.Off);
|
||||
} else {
|
||||
this.selectLevel(this.activeLevel);
|
||||
}
|
||||
}
|
||||
|
||||
selectLevel(level: SecurityLevel) {
|
||||
if (this.mode === 'auto' && level !== SecurityLevel.Off) {
|
||||
this.mode = 'manual';
|
||||
}
|
||||
|
||||
this.statusService.selectLevel(level).subscribe();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user