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:
Daniel Hååvi
2024-08-09 17:15:48 +02:00
committed by GitHub
parent 10a77498f4
commit 80664d1a27
647 changed files with 37690 additions and 3366 deletions

View File

@@ -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,

View File

@@ -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>

View File

@@ -30,9 +30,6 @@ export interface MapPin {
// whether the pin has any known issues
hasIssues: boolean;
// FIXME: remove me
collapsed?: boolean;
}
@Injectable({ providedIn: 'root' })

View File

@@ -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;

View File

@@ -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();

View 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"
}
}
];

View File

@@ -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">

View File

@@ -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)
}
*/
}

View File

@@ -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">

View File

@@ -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;

View File

@@ -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();
});
}

View File

@@ -1 +0,0 @@
export { StatusPilotComponent as PilotWidgetComponent } from "./pilot-widget";

View File

@@ -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>

View File

@@ -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;
}
}
}
}
}
}

View File

@@ -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();
}
}