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

Commit

Permalink
wip: dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
ppacher committed Aug 8, 2023
1 parent 7720cf7 commit 038bd81
Show file tree
Hide file tree
Showing 8 changed files with 740 additions and 5 deletions.
5 changes: 5 additions & 0 deletions modules/portmaster/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppViewComponent } from './pages/app-view';
import { DashboardPageComponent } from './pages/dashboard/dashboard.component';
import { MonitorPageComponent } from './pages/monitor';
import { SettingsComponent } from './pages/settings/settings';
import { SpnPageComponent } from './pages/spn';
Expand Down Expand Up @@ -54,6 +55,10 @@ const routes: Routes = [
path: '**',
redirectTo: 'dashboard'
},
{
path: 'dashboard',
component: DashboardPageComponent
}
];

@NgModule({
Expand Down
4 changes: 3 additions & 1 deletion modules/portmaster/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import { IntroModule } from './intro';
import { NavigationComponent } from './layout/navigation/navigation';
import { SideDashComponent } from './layout/side-dash/side-dash';
import { AppOverviewComponent, AppViewComponent, QuickSettingInternetButtonComponent } from './pages/app-view';
import { QsHistoryComponent } from './pages/app-view/qs-history/qs-history.component';
import { QuickSettingUseSPNButtonComponent } from './pages/app-view/qs-use-spn/qs-use-spn';
import { DashboardPageComponent } from './pages/dashboard/dashboard.component';
import { MonitorPageComponent } from './pages/monitor';
import { SettingsComponent } from './pages/settings/settings';
import { SPNModule } from './pages/spn/spn.module';
Expand Down Expand Up @@ -58,7 +60,6 @@ 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 { QsHistoryComponent } from './pages/app-view/qs-history/qs-history.component';

@NgModule({
declarations: [
Expand Down Expand Up @@ -90,6 +91,7 @@ import { QsHistoryComponent } from './pages/app-view/qs-history/qs-history.compo
EditProfileDialog,
ProcessDetailsDialogComponent,
QsHistoryComponent,
DashboardPageComponent
],
imports: [
BrowserModule,
Expand Down
290 changes: 290 additions & 0 deletions modules/portmaster/src/app/pages/dashboard/dashboard.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
<div class="w-full gap-4 p-4 dashboard-grid">
<header class="flex flex-row items-center justify-between w-full" id="header">
<h1 class="flex flex-col flex-grow text-lg font-light text-white">
Dashboard
<span class="text-sm font-normal text-secondary">Welcome back
<ng-container *ngIf="!!profile">
, <span class="text-primary">{{ profile.username }}</span>!
</ng-container>
</span>
</h1>

<div class="flex flex-row gap-8">
<ng-container *ngIf="!!profile">
<div class="flex flex-col text-xs leading-4">
<span class="font-light text-secondary">Your current plan is <span
class="font-normal text-green-300">{{ profile.current_plan?.name || 'N/A' }}</span></span>
<span class="font-light text-secondary">and ends {{ profile.subscription ? ' in ': ''}} <span
class="font-normal text-primary">
{{ profile.subscription ? (profile.subscription.ends_at | timeAgo) : 'never'}}
</span></span>
</div>

<button *ngIf="profile.subscription"
class="text-xs font-normal text-white cursor-pointer btn bg-blue bg-opacity-80 hover:bg-opacity-100 hover:bg-blue">
Account Details
</button>

<button *ngIf="!profile.subscription"
class="text-xs font-normal text-white cursor-pointer btn bg-blue bg-opacity-80 hover:bg-opacity-100 hover:bg-blue">
Subscribe
</button>

</ng-container>
</div>
</header>

<!-- Feature Cards -->
<ng-template #featureCard let-data>
<div class="feature-card" [class.disabled]="!!data.disabled">
<ng-container *ngIf="!!data.disabled">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" class="disabled-bg">
<defs>
<pattern id="pattern_63Hoo" patternUnits="userSpaceOnUse" width="9.5" height="9.5"
patternTransform="rotate(45)">
<line x1="0" y="0" x2="0" y2="9.5" stroke="currentColor" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#pattern_63Hoo)" :opacity="1" />
</svg>
</ng-container>

<header>
<ng-container *ngTemplateOutlet="data.icon"></ng-container>
<span>
{{ data.title }}
</span>

<div class="relative flex flex-row self-start flex-grow" *ngIf="!!data.disabled || !!data.beta">
<div class="flex-grow"></div>
<!--
<svg *ngIf="!!data.disabled" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="z-10 w-6 h-6 raltive">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
-->

<div *ngIf="!!data.beta && !data.disabled"
class="absolute top-0 right-0 flex flex-col items-center justify-center gap-0 text-yellow-300">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="relative z-10 w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23-.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
</svg>
<span class="uppercase text-xxs">BETA</span>
</div>
</div>
</header>
<div>
<span
class="font-normal ml-7 text-xxs text-secondary">{{ data.plan ? (data.disabled ? 'Upgrade required' : 'Purchased') : 'Free / Forever'}}</span>
</div>

<div *ngIf="!data.disabled && !!data.hasToggle" class="absolute right-4 bottom-4">
<sfng-toggle [ngModel]="true"></sfng-toggle>
</div>

<div class="ribbon" *ngIf="!!data.disabled && !!data.plan"><span class="ribbon__content">{{ data.plan }}</span>
</div>
</div>
</ng-template>

<ng-template #spnIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" class="text-green-300">
<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path
d="M6.488 15.581c.782.781.782 2.048 0 2.829-.782.781-2.049.781-2.83 0-.782-.781-.782-2.048 0-2.829.781-.781 2.048-.781 2.83 0M13.415 3.586c.782.781.782 2.048 0 2.829-.782.781-2.049.781-2.83 0-.782-.781-.782-2.048 0-2.829.781-.781 2.049-.781 2.83 0M20.343 15.58c.782.781.782 2.048 0 2.829-.782.781-2.049.781-2.83 0-.782-.781-.782-2.048 0-2.829.781-.781 2.048-.781 2.83 0">
</path>
<path
d="M17.721 18.581C16.269 20.071 14.246 21 12 21c-1.146 0-2.231-.246-3.215-.68M4.293 15.152c-.56-1.999-.352-4.21.769-6.151.574-.995 1.334-1.814 2.205-2.449M13.975 5.254c2.017.512 3.834 1.799 4.957 3.743.569.985.899 2.041 1.018 3.103">
</path>
</g>
</svg>
</ng-template>

<ng-template #secureDns>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" />
</svg>
</ng-template>

<ng-template #history>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" />
</svg>
</ng-template>

<ng-template #bw>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" />
</svg>

</ng-template>

<ng-template #support>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
</svg>
</ng-template>

<div class="feature-card-container" id="features">
<label>
Features
<sfng-tipup></sfng-tipup>
</label>

<div class="feature-card-list">
<ng-container *ngTemplateOutlet="featureCard; context: {$implicit: {
title: 'Secure DNS',
icon: secureDns,
hasToggle: true,
}}"></ng-container>

<ng-container *ngTemplateOutlet="featureCard; context: {$implicit: {
title: 'Safing Privacy Network',
icon: spnIcon,
plan: 'unlimited',
hasToggle: true,
}}"></ng-container>

<ng-container *ngTemplateOutlet="featureCard; context: {$implicit: {
title: 'Bandwidth',
icon: bw,
disabled: false,
plan: 'plus',
beta: true,
}}"></ng-container>

<ng-container *ngTemplateOutlet="featureCard; context: {$implicit: {
title: 'History',
icon: history,
disabled: true,
plan: 'plus',
}}"></ng-container>

<ng-container *ngTemplateOutlet="featureCard; context: {$implicit: {
title: 'Priority Support',
icon: support,
disabled: true,
plan: 'unlimited'
}}"></ng-container>
</div>

<span class="font-light text-xxs text-primary">
Want more features?
<a href="#" class="font-normal underline text-primary">Upgrade your plan now! 🚀</a>
or
<a href="#" class="font-normal underline text-primary">find out more</a>
</span>
</div>


<div class="feature-card-container" id="stats">
<label>
Statistics
<sfng-tipup></sfng-tipup>
</label>

<!-- Mini Stats -->
<div class="feature-card-list">
<div class="mini-stat">
<label>Trackers Blocked</label>
<span>1.2k</span>
</div>

<div class="mini-stat">
<label>Active Connections</label>
<span>{{ activeConnections }}</span>
</div>

<div class="mini-stat">
<label>Active Apps</label>
<span>{{ activeProfiles }}</span>
</div>

<div class="mini-stat">
<label>Bandwidth</label>
<span class="grid items-center grid-flow-col gap-2 auto-cols-fr">
<span class="flex flex-row items-center gap-1 p-1 whitespace-nowrap">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 h-5 text-secondary">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 6.75L12 3m0 0l3.75 3.75M12 3v18" />
</svg>
2.5 MB/s
</span>

<span class="flex flex-row items-center gap-1 p-1 whitespace-nowrap">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 h-5 text-secondary">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25L12 21m0 0l-3.75-3.75M12 21V3" />
</svg>
1 kB/s
</span>
</span>
</div>

<div class="mini-stat">
<label>SPN Identities</label>
<span>{{ activeIdentities }}</span>
</div>
</div>
</div>

<div class="flex flex-col flex-grow gap-4" id="countries">
<div class="flex-grow feature-card-container">
<label>
Recent Connections per Country
<sfng-tipup></sfng-tipup>
</label>
<div class="block w-full">
<ul class="list-none auto-grid-4">
<li *ngFor="let country of (connectionsPerCountry | keyvalue); trackBy: trackCountry"
[routerLink]="['/monitor']" [queryParams]="{q: 'country:' + country.key}"
(mouseenter)="onCountryHover(country.key)" (mouseleave)="onCountryHover(null)"
class="flex flex-row items-center p-2 bg-gray-300 rounded-md cursor-pointer hover:bg-gray-400">
<div class="flex flex-row items-center flex-grow gap-2">
<span class="flex-shrink-0" *ngIf="!!country.key" [appCountryFlags]="country.key"></span>
<span
class="overflow-hidden text-xs text-secondary whitespace-nowrap">{{ countryNames[country.key] || country.key || 'N/A' }}</span>
</div>
<span class="ml-2">{{ country.value }}</span>
</li>
</ul>
</div>
</div>

<div class="flex-grow feature-card-container" id="blocked">
<label>
Recently Blocked Applications
<sfng-tipup></sfng-tipup>
</label>
<div class="block w-full">
<ul class="list-none auto-grid-3">
<li *ngFor="let profile of (blockedProfiles | keyvalue); trackBy: trackApp"
(mouseenter)="onProfileHover(profile.key)" (mouseleave)="onProfileHover(null)"
class="flex flex-row items-center p-2 bg-gray-300 rounded-md cursor-pointer hover:bg-gray-400">
<div class="flex flex-row items-center flex-grow gap-2">
<app-icon [profile]="profile.value.profile"></app-icon>
<span class="text-xs text-secondary">{{ profile.value.profile.Name }}</span>
</div>
{{ profile.value.count }}
</li>
</ul>
</div>
</div>
</div>

<div class="flex-grow feature-card-container" id="connmap">
<label>
Connection Targets
<sfng-tipup></sfng-tipup>
</label>
<div class="block w-full h-96" #map>
</div>
</div>
</div>
Loading

0 comments on commit 038bd81

Please sign in to comment.