瀏覽代碼

init

develop
Robin Thoni 6 年之前
父節點
當前提交
14363eb2c2
共有 56 個檔案被更改,包括 1095 行新增273 行删除
  1. 54
    4
      backend/SiteStatus/SiteStatus/Business/DnsBusiness.cs
  2. 54
    0
      backend/SiteStatus/SiteStatus/Business/VpnBusiness.cs
  3. 11
    41
      backend/SiteStatus/SiteStatus/Controllers/StatusController.cs
  4. 9
    0
      backend/SiteStatus/SiteStatus/Dbo/DnsStatusDbo.cs
  5. 11
    0
      backend/SiteStatus/SiteStatus/Dbo/DnsStatusViewDbo.cs
  6. 1
    3
      backend/SiteStatus/SiteStatus/Dbo/SettingsDbo.cs
  7. 1
    3
      backend/SiteStatus/SiteStatus/Dbo/SettingsDnsDbo.cs
  8. 13
    0
      backend/SiteStatus/SiteStatus/Dbo/SettingsDnsViewDbo.cs
  9. 2
    2
      backend/SiteStatus/SiteStatus/Dbo/SiteStatusDbo.cs
  10. 9
    0
      backend/SiteStatus/SiteStatus/Dbo/VpnStatusDbo.cs
  11. 1
    0
      backend/SiteStatus/SiteStatus/Startup.cs
  12. 57
    37
      backend/SiteStatus/SiteStatus/appsettings.Development.json
  13. 7
    8
      frontend/Dockerfile
  14. 26
    7
      frontend/SiteStatus/package-lock.json
  15. 1
    0
      frontend/SiteStatus/package.json
  16. 5
    3
      frontend/SiteStatus/src/app/app-routing.module.ts
  17. 5
    4
      frontend/SiteStatus/src/app/app.component.html
  18. 18
    18
      frontend/SiteStatus/src/app/app.component.ts
  19. 19
    5
      frontend/SiteStatus/src/app/app.module.ts
  20. 5
    0
      frontend/SiteStatus/src/app/models/dns/dns-status.model.ts
  21. 6
    0
      frontend/SiteStatus/src/app/models/dns/dns-view-status.model.ts
  22. 4
    0
      frontend/SiteStatus/src/app/models/dns/dns-zone-server-status.model.ts
  23. 6
    0
      frontend/SiteStatus/src/app/models/dns/dns-zone-status.model.ts
  24. 0
    6
      frontend/SiteStatus/src/app/models/host.model.ts
  25. 0
    6
      frontend/SiteStatus/src/app/models/site.model.ts
  26. 0
    0
      frontend/SiteStatus/src/app/models/vpn/host-status.model.ts
  27. 6
    0
      frontend/SiteStatus/src/app/models/vpn/site-status.model.ts
  28. 5
    0
      frontend/SiteStatus/src/app/models/vpn/vpn-status.model.ts
  29. 403
    0
      frontend/SiteStatus/src/app/services/app-state.service.ts
  30. 18
    5
      frontend/SiteStatus/src/app/services/status.service.ts
  31. 8
    0
      frontend/SiteStatus/src/app/view-models/dns/by-server/dns-server-per-server.view-model.ts
  32. 7
    0
      frontend/SiteStatus/src/app/view-models/dns/by-server/dns-server-zone-per-server.view-model.ts
  33. 5
    0
      frontend/SiteStatus/src/app/view-models/dns/by-server/dns-status-per-server.view-model.ts
  34. 6
    0
      frontend/SiteStatus/src/app/view-models/dns/by-server/dns-view-status-per-server.view-model.ts
  35. 5
    0
      frontend/SiteStatus/src/app/view-models/dns/by-zone/dns-status.view-model.ts
  36. 6
    0
      frontend/SiteStatus/src/app/view-models/dns/by-zone/dns-view-status.view-model.ts
  37. 7
    0
      frontend/SiteStatus/src/app/view-models/dns/by-zone/dns-zone-server-status.view-model.ts
  38. 8
    0
      frontend/SiteStatus/src/app/view-models/dns/by-zone/dns-zone-status.view-model.ts
  39. 4
    0
      frontend/SiteStatus/src/app/view-models/ui/ui-dns-view-type-enum.view-model.ts
  40. 6
    0
      frontend/SiteStatus/src/app/view-models/ui/ui-dns-view-type.view-model.ts
  41. 19
    0
      frontend/SiteStatus/src/app/view-models/ui/ui-state.view-model.ts
  42. 1
    1
      frontend/SiteStatus/src/app/view-models/vpn/host-status.view-model.ts
  43. 2
    2
      frontend/SiteStatus/src/app/view-models/vpn/site-status.view-model.ts
  44. 5
    0
      frontend/SiteStatus/src/app/view-models/vpn/vpn-status.view-model.ts
  45. 0
    0
      frontend/SiteStatus/src/app/views/dns-status-component/dns-status.component.css
  46. 84
    0
      frontend/SiteStatus/src/app/views/dns-status-component/dns-status.component.html
  47. 80
    0
      frontend/SiteStatus/src/app/views/dns-status-component/dns-status.component.ts
  48. 0
    99
      frontend/SiteStatus/src/app/views/status-component/status.component.ts
  49. 0
    0
      frontend/SiteStatus/src/app/views/vpn-status-component/vpn-status.component.css
  50. 2
    2
      frontend/SiteStatus/src/app/views/vpn-status-component/vpn-status.component.html
  51. 43
    0
      frontend/SiteStatus/src/app/views/vpn-status-component/vpn-status.component.ts
  52. 二進制
      frontend/SiteStatus/src/favicon.ico
  53. 1
    3
      frontend/SiteStatus/src/index.html
  54. 20
    5
      frontend/SiteStatus/src/styles.scss
  55. 17
    7
      frontend/SiteStatus/src/theme.scss
  56. 2
    2
      frontend/apache2.conf

+ 54
- 4
backend/SiteStatus/SiteStatus/Business/DnsBusiness.cs 查看文件

@@ -2,14 +2,23 @@
2 2
 using System.Collections.Generic;
3 3
 using System.Linq;
4 4
 using System.Net;
5
+using System.Runtime.CompilerServices;
5 6
 using System.Threading.Tasks;
6 7
 using ARSoft.Tools.Net.Dns;
8
+using Microsoft.Extensions.Options;
7 9
 using SiteStatus.Dbo;
8 10
 
9 11
 namespace SiteStatus.Business
10 12
 {
11 13
     public class DnsBusiness
12 14
     {
15
+        public SettingsDbo Options { get; }
16
+
17
+        public DnsBusiness(IOptions<SettingsDbo> options)
18
+        {
19
+            Options = options.Value;
20
+        }
21
+
13 22
         public async Task<DnsZoneStatusDbo> GetZoneStatusAsync(string zone, IEnumerable<string> servers)
14 23
         {
15 24
             var dnsStatus = new DnsZoneStatusDbo
@@ -18,16 +27,33 @@ namespace SiteStatus.Business
18 27
                 ServerStatus = new List<DnsZoneServerStatusDbo>()
19 28
             };
20 29
 
30
+            var list = new List<KeyValuePair<string, Task<List<SoaRecord>>>>();
31
+            var tasks = new List<Task<List<SoaRecord>>>();
21 32
             foreach (var server in servers)
22 33
             {
23 34
                 try
24 35
                 {
25 36
                     var resolver = new DnsStubResolver(new []{IPAddress.Parse(server)});
26
-                    var records = resolver.Resolve<SoaRecord>(zone, RecordType.Soa);
27
-                    var record = records.FirstOrDefault();
37
+                    var recordsTask = resolver.ResolveAsync<SoaRecord>(zone, RecordType.Soa);
38
+                    list.Add(new KeyValuePair<string, Task<List<SoaRecord>>>(server, recordsTask));
39
+                    tasks.Add(recordsTask);
40
+                }
41
+                catch (Exception e)
42
+                {
43
+                    tasks.Add(Task.FromResult<List<SoaRecord>>(null));
44
+                }
45
+            }
46
+
47
+            await Task.WhenAll(tasks.ToArray());
48
+
49
+            foreach (var recordResult in list)
50
+            {
51
+                try
52
+                {
53
+                    var record = recordResult.Value.Result.FirstOrDefault();
28 54
                     dnsStatus.ServerStatus.Add(new DnsZoneServerStatusDbo
29 55
                     {
30
-                        Server = server,
56
+                        Server = recordResult.Key,
31 57
                         Soa = record?.ToString()
32 58
                     });
33 59
                 }
@@ -35,7 +61,7 @@ namespace SiteStatus.Business
35 61
                 {
36 62
                     dnsStatus.ServerStatus.Add(new DnsZoneServerStatusDbo
37 63
                     {
38
-                        Server = server,
64
+                        Server = recordResult.Key,
39 65
                         Soa = null
40 66
                     });
41 67
                 }
@@ -43,5 +69,29 @@ namespace SiteStatus.Business
43 69
 
44 70
             return dnsStatus;
45 71
         }
72
+
73
+        public DnsStatusDbo GetDnsStatus()
74
+        {
75
+            var status = new DnsStatusDbo
76
+            {
77
+                Views = Options.Dns.Views.Select(view =>
78
+                {
79
+                    var servers = view.Servers.Select(server => server.Ip).ToList();
80
+                    var tasks = view.Zones.Select(zone => GetZoneStatusAsync(zone.Name, servers)).ToArray();
81
+
82
+                    Task.WaitAll(tasks);
83
+
84
+                    var results = tasks.Select(task => task.Result).ToList();
85
+
86
+                    return new DnsStatusViewDbo
87
+                    {
88
+                        Name = view.Name,
89
+                        Zones = results
90
+                    };
91
+                }).ToList()
92
+            };
93
+
94
+            return status;
95
+        }
46 96
     }
47 97
 }

+ 54
- 0
backend/SiteStatus/SiteStatus/Business/VpnBusiness.cs 查看文件

@@ -0,0 +1,54 @@
1
+using System.Linq;
2
+using System.Threading.Tasks;
3
+using Microsoft.Extensions.Options;
4
+using SiteStatus.Dbo;
5
+
6
+namespace SiteStatus.Business
7
+{
8
+    public class VpnBusiness
9
+    {
10
+        public HostBusiness HostBusiness { get; }
11
+        public SettingsDbo Options { get; }
12
+
13
+        public VpnBusiness(HostBusiness hostBusiness, IOptions<SettingsDbo> options)
14
+        {
15
+            HostBusiness = hostBusiness;
16
+            Options = options.Value;
17
+        }
18
+
19
+        public VpnStatusDbo GetVpnStatus()
20
+        {
21
+            var sites = Options.VpnS2s.Sites.Select(site => new SiteStatusDbo
22
+            {
23
+                Name = string.IsNullOrEmpty(site.Name) ? null : site.Name,
24
+                Hosts = site.Hosts.Select(host => new HostStatusDbo
25
+                {
26
+                    Ip = host.Ip
27
+                }).ToList()
28
+            }).ToList();
29
+
30
+            var hosts = sites.SelectMany(site => site.Hosts).ToList();
31
+            var tasks = hosts.Select(host => HostBusiness.GetHostStatusAsync(host.Ip, Options.VpnS2s.Ping.Count, Options.VpnS2s.Ping.Timeout)).ToArray();
32
+
33
+            Task.WaitAll(tasks);
34
+
35
+            foreach (var task in tasks)
36
+            {
37
+                foreach (var site in sites)
38
+                {
39
+                    var hostStatus = site.Hosts.FirstOrDefault(host => host.Ip == task.Result.Ip.ToString());
40
+                    if (hostStatus != null)
41
+                    {
42
+                        var idx = site.Hosts.IndexOf(hostStatus);
43
+                        site.Hosts[idx] = task.Result;
44
+                    }
45
+                }
46
+            }
47
+
48
+            return new VpnStatusDbo
49
+            {
50
+                Sites = sites
51
+            };
52
+        }
53
+    }
54
+}

+ 11
- 41
backend/SiteStatus/SiteStatus/Controllers/StatusController.cs 查看文件

@@ -1,8 +1,4 @@
1
-using System.Collections.Generic;
2
-using System.Linq;
3
-using System.Threading.Tasks;
4
-using Microsoft.AspNetCore.Mvc;
5
-using Microsoft.Extensions.Options;
1
+using Microsoft.AspNetCore.Mvc;
6 2
 using SiteStatus.Business;
7 3
 using SiteStatus.Dbo;
8 4
 
@@ -13,55 +9,29 @@ namespace SiteStatus.Controllers
13 9
     [ApiController]
14 10
     public class StatusController : SSController
15 11
     {
16
-        protected HostBusiness HostBusiness { get; }
17 12
         public DnsBusiness DnsBusiness { get; }
13
+        public VpnBusiness VpnBusiness { get; }
18 14
 
19
-        public SettingsDbo Options { get; }
20
-
21
-        public StatusController(HostBusiness hostBusiness, DnsBusiness dnsBusiness, IOptions<SettingsDbo> options)
15
+        public StatusController(DnsBusiness dnsBusiness, VpnBusiness vpnBusiness)
22 16
         {
23
-            HostBusiness = hostBusiness;
24 17
             DnsBusiness = dnsBusiness;
25
-            Options = options.Value;
18
+            VpnBusiness = vpnBusiness;
26 19
         }
27 20
 
28
-        [HttpGet("/api/[controller]/hosts")]
29
-        public ApiResultDbo<List<SiteDbo>> GetHosts()
21
+        [HttpGet("/api/[controller]/vpn")]
22
+        public ApiResultDbo<VpnStatusDbo> GetVpnStatus()
30 23
         {
31
-            var sites = Options.VpnS2s.Sites.Select(site => new SiteDbo
32
-            {
33
-                Name = string.IsNullOrEmpty(site.Name) ? null : site.Name,
34
-                Hosts = site.Hosts.Select(host => new HostDbo
35
-                {
36
-                    HostStatus = null,
37
-                    Ip = host.Ip
38
-                }).ToList()
39
-            }).ToList();
40
-
41
-            var hosts = sites.SelectMany(site => site.Hosts).ToList();
42
-            var tasks = hosts.Select(host => HostBusiness.GetHostStatusAsync(host.Ip, Options.VpnS2s.Ping.Count, Options.VpnS2s.Ping.Timeout)).ToArray();
43
-
44
-            Task.WaitAll(tasks);
24
+            var result = VpnBusiness.GetVpnStatus();
45 25
 
46
-            foreach (var task in tasks)
47
-            {
48
-                hosts.First(host => host.Ip == task.Result.Ip.ToString()).HostStatus = task.Result;
49
-            }
50
-
51
-            return Handle(sites);
26
+            return Handle(result);
52 27
         }
53 28
 
54 29
         [HttpGet("/api/[controller]/dns")]
55
-        public ApiResultDbo<List<DnsZoneStatusDbo>> GetDns()
30
+        public ApiResultDbo<DnsStatusDbo> GetDns()
56 31
         {
57
-            var servers = Options.Dns.Servers.Select(server => server.Ip).ToList();
58
-            var tasks = Options.Dns.Zones.Select(zone => DnsBusiness.GetZoneStatusAsync(zone.Name, servers)).ToArray();
59
-
60
-            Task.WaitAll(tasks);
61
-
62
-            var results = tasks.Select(task => task.Result).ToList();
32
+            var result = DnsBusiness.GetDnsStatus();
63 33
 
64
-            return Handle(results);
34
+            return Handle(result);
65 35
         }
66 36
     }
67 37
 }

+ 9
- 0
backend/SiteStatus/SiteStatus/Dbo/DnsStatusDbo.cs 查看文件

@@ -0,0 +1,9 @@
1
+using System.Collections.Generic;
2
+
3
+namespace SiteStatus.Dbo
4
+{
5
+    public class DnsStatusDbo
6
+    {
7
+        public IList<DnsStatusViewDbo> Views { get; set; }
8
+    }
9
+}

+ 11
- 0
backend/SiteStatus/SiteStatus/Dbo/DnsStatusViewDbo.cs 查看文件

@@ -0,0 +1,11 @@
1
+using System.Collections.Generic;
2
+
3
+namespace SiteStatus.Dbo
4
+{
5
+    public class DnsStatusViewDbo
6
+    {
7
+        public string Name { get; set; }
8
+
9
+        public IList<DnsZoneStatusDbo> Zones { get; set; }
10
+    }
11
+}

+ 1
- 3
backend/SiteStatus/SiteStatus/Dbo/SettingsDbo.cs 查看文件

@@ -1,6 +1,4 @@
1
-using System.Collections.Generic;
2
-
3
-namespace SiteStatus.Dbo
1
+namespace SiteStatus.Dbo
4 2
 {
5 3
     public class SettingsDbo
6 4
     {

+ 1
- 3
backend/SiteStatus/SiteStatus/Dbo/SettingsDnsDbo.cs 查看文件

@@ -4,8 +4,6 @@ namespace SiteStatus.Dbo
4 4
 {
5 5
     public class SettingsDnsDbo
6 6
     {
7
-        public IList<SettingsHostDbo> Servers { get; set; }
8
-
9
-        public IList<SettingsDnsZoneDbo> Zones { get; set; }
7
+        public IList<SettingsDnsViewDbo> Views { get; set; }
10 8
     }
11 9
 }

+ 13
- 0
backend/SiteStatus/SiteStatus/Dbo/SettingsDnsViewDbo.cs 查看文件

@@ -0,0 +1,13 @@
1
+using System.Collections.Generic;
2
+
3
+namespace SiteStatus.Dbo
4
+{
5
+    public class SettingsDnsViewDbo
6
+    {
7
+        public string Name { get; set; }
8
+
9
+        public IList<SettingsHostDbo> Servers { get; set; }
10
+
11
+        public IList<SettingsDnsZoneDbo> Zones { get; set; }
12
+    }
13
+}

backend/SiteStatus/SiteStatus/Dbo/SiteDbo.cs → backend/SiteStatus/SiteStatus/Dbo/SiteStatusDbo.cs 查看文件

@@ -2,10 +2,10 @@
2 2
 
3 3
 namespace SiteStatus.Dbo
4 4
 {
5
-    public class SiteDbo
5
+    public class SiteStatusDbo
6 6
     {
7 7
         public string Name { get; set; }
8 8
 
9
-        public IList<HostDbo> Hosts { get; set; }
9
+        public IList<HostStatusDbo> Hosts { get; set; }
10 10
     }
11 11
 }

+ 9
- 0
backend/SiteStatus/SiteStatus/Dbo/VpnStatusDbo.cs 查看文件

@@ -0,0 +1,9 @@
1
+using System.Collections.Generic;
2
+
3
+namespace SiteStatus.Dbo
4
+{
5
+    public class VpnStatusDbo
6
+    {
7
+        public IList<SiteStatusDbo> Sites { get; set; }
8
+    }
9
+}

+ 1
- 0
backend/SiteStatus/SiteStatus/Startup.cs 查看文件

@@ -33,6 +33,7 @@ namespace SiteStatus
33 33
 
34 34
             services.AddSingleton<HostBusiness>();
35 35
             services.AddSingleton<DnsBusiness>();
36
+            services.AddSingleton<VpnBusiness>();
36 37
         }
37 38
 
38 39
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

+ 57
- 37
backend/SiteStatus/SiteStatus/appsettings.Development.json 查看文件

@@ -38,15 +38,13 @@
38 38
           "Name": "aws-eu-central1",
39 39
           "Hosts": [
40 40
             { "Ip": "10.15.120.10" },
41
-            { "Ip": "10.15.120.11" },
42
-            { "Ip": "10.15.120.15" }
41
+            { "Ip": "10.15.120.11" }
43 42
           ]
44 43
         },
45 44
         {
46 45
           "Name": "ovh-rbx5",
47 46
           "Hosts": [
48
-            { "Ip": "10.15.128.10" },
49
-            { "Ip": "10.15.128.15" }
47
+            { "Ip": "10.15.128.10" }
50 48
           ]
51 49
         },
52 50
         {
@@ -59,39 +57,61 @@
59 57
     },
60 58
     "Dns":
61 59
     {
62
-      "Servers":
63
-              [
64
-                { "Ip": "10.15.0.3" }, { "Ip": "10.15.0.20" },
65
-                { "Ip": "10.15.8.3" }, { "Ip": "10.15.8.20" },
66
-                { "Ip": "10.15.16.3" },
67
-                { "Ip": "10.15.120.10" },
68
-                { "Ip": "10.15.128.10" }
69
-              ],
70
-      "Zones":
71
-              [
72
-                { "Name": "2drt.fr" },
73
-                { "Name": "bc3r.fr" },
74
-                { "Name": "betaclean.fr" },
75
-                { "Name": "rthoni.com" },
76
-                { "Name": "internal.rthoni.com" },
77
-                { "Name": "internal.rthoni.com" },
78
-                { "Name": "ad.sso.rthoni.com" },
79
-                { "Name": "_msdcs.ad.sso.rthoni.com" },
80
-                { "Name": "ars.rthoni.com" },
81
-                { "Name": "ars.rthoni.com" },
82
-                { "Name": "dhcp.ars.rthoni.com" },
83
-                { "Name": "0.15.10.in-addr.arpa" },
84
-                { "Name": "lesse.rthoni.com" },
85
-                { "Name": "lesse.rthoni.com" },
86
-                { "Name": "dhcp.lesse.rthoni.com" },
87
-                { "Name": "8.15.10.in-addr.arpa" },
88
-                { "Name": "aws-eu-central1.rthoni.com" },
89
-                { "Name": "aws-eu-central1.rthoni.com" },
90
-                { "Name": "120.15.10.in-addr.arpa" },
91
-                { "Name": "ovh-rbx5.rthoni.com" },
92
-                { "Name": "ovh-rbx5.rthoni.com" },
93
-                { "Name": "128.15.10.in-addr.arpa" }
94
-              ]
60
+      "Views": [
61
+        {
62
+          "Name": "internal",
63
+          "Servers": [
64
+            { "Ip": "10.15.0.3" }, { "Ip": "10.15.0.20" },
65
+            { "Ip": "10.15.8.3" }, { "Ip": "10.15.8.20" },
66
+            { "Ip": "10.15.16.3" },
67
+            { "Ip": "10.15.120.10" },
68
+            { "Ip": "10.15.128.10" }
69
+          ],
70
+          "Zones": [
71
+            { "Name": "2drt.fr" },
72
+            { "Name": "bc3r.fr" },
73
+            { "Name": "betaclean.fr" },
74
+            { "Name": "rthoni.com" },
75
+
76
+            { "Name": "internal.rthoni.com" },
77
+
78
+            { "Name": "ad.sso.rthoni.com" },
79
+            { "Name": "_msdcs.ad.sso.rthoni.com" },
80
+
81
+            { "Name": "ars.rthoni.com" },
82
+            { "Name": "dhcp.ars.rthoni.com" },
83
+            { "Name": "0.15.10.in-addr.arpa" },
84
+
85
+            { "Name": "lesse.rthoni.com" },
86
+            { "Name": "dhcp.lesse.rthoni.com" },
87
+            { "Name": "8.15.10.in-addr.arpa" },
88
+
89
+            { "Name": "cambridge.rthoni.com" },
90
+            { "Name": "dhcp.cambridge.rthoni.com" },
91
+            { "Name": "16.15.10.in-addr.arpa" },
92
+
93
+            { "Name": "aws-eu-central1.rthoni.com" },
94
+            { "Name": "120.15.10.in-addr.arpa" },
95
+
96
+            { "Name": "ovh-rbx5.rthoni.com" },
97
+            { "Name": "128.15.10.in-addr.arpa" }
98
+          ]
99
+        },
100
+        {
101
+          "Name": "external",
102
+          "Servers": [
103
+            { "Ip": "5.39.80.77" }, { "Ip": "52.58.80.7" },
104
+            { "Ip": "8.8.8.8" },
105
+            { "Ip": "208.67.222.222" }
106
+          ],
107
+          "Zones": [
108
+            { "Name": "2drt.fr" },
109
+            { "Name": "bc3r.fr" },
110
+            { "Name": "betaclean.fr" },
111
+            { "Name": "rthoni.com" }
112
+          ]
113
+        }
114
+      ]
95 115
     }
96 116
   }
97 117
 }

+ 7
- 8
frontend/Dockerfile 查看文件

@@ -1,4 +1,4 @@
1
-FROM debian:jessie AS builder
1
+FROM robinthoni/debian-multiarch:jessie AS builder
2 2
 
3 3
 # FROM https://github.com/ZHAJOR/Docker-Apache-2.4-Php-5.6-for-Laravel
4 4
 MAINTAINER Robin Thoni <robin@rthoni.com>
@@ -7,27 +7,26 @@ RUN apt-get update && apt-get -y install \
7 7
         git \
8 8
         curl && \
9 9
         apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/ && \
10
-        curl https://deb.nodesource.com/setup_7.x | bash && \
10
+        curl https://deb.nodesource.com/setup_10.x | bash && \
11 11
         ln -s /usr/bin/nodejs /usr/bin/node
12 12
 
13 13
 RUN apt-get update && apt-get -y install \
14 14
         nodejs && \
15 15
         apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/
16 16
 
17
-RUN npm install -g bower && \
18
-        npm install -g grunt
17
+RUN npm install -g @angular/cli
19 18
 
20 19
 COPY ./SiteStatus/ /tmp/frontend
21 20
 
22 21
 RUN cd /tmp/frontend && \
22
+    rm -rf node_modules dist && \
23 23
     npm install && \
24
-    bower --allow-root install && \
25
-    for gruntfile in Gruntfile_*; do grunt --gruntfile "${gruntfile}"; done && \
24
+    ng build && \
26 25
     mkdir -p /var/www/ && \
27
-    cp -r /tmp/frontend/build/release/ /var/www/html && \
26
+    cp -r /tmp/frontend/dist/SiteStatus/ /var/www/html && \
28 27
     rm -rf /tmp/frontend
29 28
 
30
-FROM debian:jessie
29
+FROM robinthoni/debian-multiarch:jessie
31 30
 
32 31
 RUN apt-get update && apt-get -y install \
33 32
         apache2=2.4.* && \

+ 26
- 7
frontend/SiteStatus/package-lock.json 查看文件

@@ -415,6 +415,11 @@
415 415
         "tslib": "^1.9.0"
416 416
       }
417 417
     },
418
+    "@ngrx/core": {
419
+      "version": "1.2.0",
420
+      "resolved": "https://registry.npmjs.org/@ngrx/core/-/core-1.2.0.tgz",
421
+      "integrity": "sha1-iCtGq6+i4ObYh8txobLC+j5tDcY="
422
+    },
418 423
     "@ngrx/store": {
419 424
       "version": "6.1.0",
420 425
       "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-6.1.0.tgz",
@@ -3734,12 +3739,14 @@
3734 3739
         "balanced-match": {
3735 3740
           "version": "1.0.0",
3736 3741
           "bundled": true,
3737
-          "dev": true
3742
+          "dev": true,
3743
+          "optional": true
3738 3744
         },
3739 3745
         "brace-expansion": {
3740 3746
           "version": "1.1.11",
3741 3747
           "bundled": true,
3742 3748
           "dev": true,
3749
+          "optional": true,
3743 3750
           "requires": {
3744 3751
             "balanced-match": "^1.0.0",
3745 3752
             "concat-map": "0.0.1"
@@ -3754,17 +3761,20 @@
3754 3761
         "code-point-at": {
3755 3762
           "version": "1.1.0",
3756 3763
           "bundled": true,
3757
-          "dev": true
3764
+          "dev": true,
3765
+          "optional": true
3758 3766
         },
3759 3767
         "concat-map": {
3760 3768
           "version": "0.0.1",
3761 3769
           "bundled": true,
3762
-          "dev": true
3770
+          "dev": true,
3771
+          "optional": true
3763 3772
         },
3764 3773
         "console-control-strings": {
3765 3774
           "version": "1.1.0",
3766 3775
           "bundled": true,
3767
-          "dev": true
3776
+          "dev": true,
3777
+          "optional": true
3768 3778
         },
3769 3779
         "core-util-is": {
3770 3780
           "version": "1.0.2",
@@ -3881,7 +3891,8 @@
3881 3891
         "inherits": {
3882 3892
           "version": "2.0.3",
3883 3893
           "bundled": true,
3884
-          "dev": true
3894
+          "dev": true,
3895
+          "optional": true
3885 3896
         },
3886 3897
         "ini": {
3887 3898
           "version": "1.3.5",
@@ -3893,6 +3904,7 @@
3893 3904
           "version": "1.0.0",
3894 3905
           "bundled": true,
3895 3906
           "dev": true,
3907
+          "optional": true,
3896 3908
           "requires": {
3897 3909
             "number-is-nan": "^1.0.0"
3898 3910
           }
@@ -3907,6 +3919,7 @@
3907 3919
           "version": "3.0.4",
3908 3920
           "bundled": true,
3909 3921
           "dev": true,
3922
+          "optional": true,
3910 3923
           "requires": {
3911 3924
             "brace-expansion": "^1.1.7"
3912 3925
           }
@@ -3914,12 +3927,14 @@
3914 3927
         "minimist": {
3915 3928
           "version": "0.0.8",
3916 3929
           "bundled": true,
3917
-          "dev": true
3930
+          "dev": true,
3931
+          "optional": true
3918 3932
         },
3919 3933
         "minipass": {
3920 3934
           "version": "2.2.4",
3921 3935
           "bundled": true,
3922 3936
           "dev": true,
3937
+          "optional": true,
3923 3938
           "requires": {
3924 3939
             "safe-buffer": "^5.1.1",
3925 3940
             "yallist": "^3.0.0"
@@ -3938,6 +3953,7 @@
3938 3953
           "version": "0.5.1",
3939 3954
           "bundled": true,
3940 3955
           "dev": true,
3956
+          "optional": true,
3941 3957
           "requires": {
3942 3958
             "minimist": "0.0.8"
3943 3959
           }
@@ -4018,7 +4034,8 @@
4018 4034
         "number-is-nan": {
4019 4035
           "version": "1.0.1",
4020 4036
           "bundled": true,
4021
-          "dev": true
4037
+          "dev": true,
4038
+          "optional": true
4022 4039
         },
4023 4040
         "object-assign": {
4024 4041
           "version": "4.1.1",
@@ -4030,6 +4047,7 @@
4030 4047
           "version": "1.4.0",
4031 4048
           "bundled": true,
4032 4049
           "dev": true,
4050
+          "optional": true,
4033 4051
           "requires": {
4034 4052
             "wrappy": "1"
4035 4053
           }
@@ -4151,6 +4169,7 @@
4151 4169
           "version": "1.0.2",
4152 4170
           "bundled": true,
4153 4171
           "dev": true,
4172
+          "optional": true,
4154 4173
           "requires": {
4155 4174
             "code-point-at": "^1.0.0",
4156 4175
             "is-fullwidth-code-point": "^1.0.0",

+ 1
- 0
frontend/SiteStatus/package.json 查看文件

@@ -23,6 +23,7 @@
23 23
     "@angular/platform-browser": "^6.1.0",
24 24
     "@angular/platform-browser-dynamic": "^6.1.0",
25 25
     "@angular/router": "^6.1.0",
26
+    "@ngrx/core": "^1.2.0",
26 27
     "@ngrx/store": "^6.1.0",
27 28
     "@ngx-translate/core": "^10.0.2",
28 29
     "@ngx-translate/http-loader": "^3.0.1",

+ 5
- 3
frontend/SiteStatus/src/app/app-routing.module.ts 查看文件

@@ -1,10 +1,12 @@
1 1
 import { NgModule } from '@angular/core';
2 2
 import { RouterModule, Routes } from '@angular/router';
3
-import {StatusComponent} from './views/status-component/status.component';
3
+import {VpnStatusComponent} from './views/vpn-status-component/vpn-status.component';
4
+import {DnsStatusComponent} from './views/dns-status-component/dns-status.component';
4 5
 
5 6
 const routes: Routes = [
6
-    { path: '', redirectTo: '/status', pathMatch: 'full' },
7
-    { path: 'status', component: StatusComponent },
7
+    { path: '', redirectTo: '/status/vpn', pathMatch: 'full' },
8
+    { path: 'status/vpn', component: VpnStatusComponent },
9
+    { path: 'status/dns', component: DnsStatusComponent },
8 10
 ];
9 11
 
10 12
 @NgModule({

+ 5
- 4
frontend/SiteStatus/src/app/app.component.html 查看文件

@@ -1,5 +1,5 @@
1 1
 <mat-sidenav-container class="all-wrap" fullscreen>
2
-    <mat-sidenav [mode]="uiStatus.sideMenu.mode" [opened]="uiStatus.sideMenu.opened" #sidenav>
2
+    <mat-sidenav [mode]="(uiState$ | async)?.sidenav.mode" [opened]="(uiState$ | async)?.sidenav.opened" #sidenav>
3 3
         <header>
4 4
             <mat-toolbar color="primary">
5 5
                 {{ 'common.appName' | translate }}
@@ -8,7 +8,8 @@
8 8
         <!--<h4>Test</h4>-->
9 9
         <mat-list class="sidenav-menu">
10 10
             <mat-list-item class="sidenav-menu-title">{{ 'common.appName' | translate }}</mat-list-item>
11
-            <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-item">HOME</a></mat-list-item>
11
+            <mat-list-item><a [routerLink]="['/status/vpn']" mat-button class="sidenav-menu-btn-item">VPN</a></mat-list-item>
12
+            <mat-list-item><a [routerLink]="['/status/dns']" mat-button class="sidenav-menu-btn-item">DNS</a></mat-list-item>
12 13
             <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-item">SOME STUFF  <mat-icon>keyboard_arrow_down</mat-icon></a></mat-list-item>
13 14
             <div style="height: auto; overflow: hidden">
14 15
                 <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-sub-item">SUB STUFF 1</a></mat-list-item>
@@ -27,14 +28,14 @@
27 28
     <div class="page-wrap">
28 29
         <header role="banner">
29 30
             <mat-toolbar color="primary">
30
-                <button *ngIf="uiStatus.toolbar.displayMenuButton"
31
+                <button *ngIf="(uiState$ | async)?.sidenav.displayMenuButton"
31 32
                         type="button"
32 33
                         mat-icon-button
33 34
                         (click)="sidenav.open()"
34 35
                         title="Open sidenav">
35 36
                     <mat-icon>menu</mat-icon>
36 37
                 </button>
37
-                {{ 'common.appName' | translate }}
38
+                {{ (uiState$ | async)?.toolbar.title }}
38 39
             </mat-toolbar>
39 40
         </header>
40 41
         <main class="content">

+ 18
- 18
frontend/SiteStatus/src/app/app.component.ts 查看文件

@@ -1,6 +1,10 @@
1 1
 import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core';
2 2
 import {MediaChange, ObservableMedia} from '@angular/flex-layout';
3 3
 import {CgBusyDefaults} from 'angular-busy2';
4
+import {AppStateService} from './services/app-state.service';
5
+import {UiStateViewModel} from './view-models/ui/ui-state.view-model';
6
+import {Observable} from 'rxjs';
7
+import {Title} from '@angular/platform-browser';
4 8
 
5 9
 @Component({
6 10
   selector: 'app-root',
@@ -9,32 +13,28 @@ import {CgBusyDefaults} from 'angular-busy2';
9 13
 })
10 14
 export class AppComponent implements OnInit {
11 15
     title = 'Site Status';
12
-    uiStatus = {
13
-      sideMenu: {
14
-        opened: false,
15
-        mode: 'over'
16
-      },
17
-      toolbar: {
18
-          displayMenuButton: true
19
-      }
20
-    };
21
-    mediaWatcher = null;
22 16
 
23 17
     @ViewChild('cgBusyTemplate')
24 18
     private cgBusyTemplate: TemplateRef<any>;
25 19
 
26
-    constructor(media: ObservableMedia, private busyDefaults: CgBusyDefaults) {
27
-        this.mediaWatcher = media.subscribe((change: MediaChange) => {
20
+    public uiState$: Observable<UiStateViewModel>;
21
+
22
+    constructor(private busyDefaults: CgBusyDefaults, private titleService: Title,
23
+                media: ObservableMedia, appStateService: AppStateService) {
24
+        media.subscribe((change: MediaChange) => {
28 25
             if (change.mqAlias === 'sm' || change.mqAlias === 'xs') {
29
-                this.uiStatus.sideMenu.opened = false;
30
-                this.uiStatus.sideMenu.mode = 'over';
31
-                this.uiStatus.toolbar.displayMenuButton = true;
26
+                appStateService.setUiStateMobile();
32 27
             } else {
33
-                this.uiStatus.sideMenu.opened = true;
34
-                this.uiStatus.sideMenu.mode = 'side';
35
-                this.uiStatus.toolbar.displayMenuButton = false;
28
+                appStateService.setUiStateDesktop();
36 29
             }
37 30
         });
31
+
32
+        this.uiState$ = appStateService.onUiStateUpdated();
33
+        this.uiState$.subscribe(this.onUiStateChange.bind(this));
34
+    }
35
+
36
+    onUiStateChange(state: UiStateViewModel) {
37
+        this.titleService.setTitle(state.page.title);
38 38
     }
39 39
 
40 40
     ngOnInit() {

+ 19
- 5
frontend/SiteStatus/src/app/app.module.ts 查看文件

@@ -1,10 +1,10 @@
1
-import { BrowserModule } from '@angular/platform-browser';
1
+import {BrowserModule, Title} from '@angular/platform-browser';
2 2
 import { NgModule } from '@angular/core';
3 3
 
4 4
 import { AppComponent } from './app.component';
5 5
 import {AppRoutingModule} from './app-routing.module';
6 6
 import {HttpClient, HttpClientModule} from '@angular/common/http';
7
-import {StatusComponent} from './views/status-component/status.component';
7
+import {VpnStatusComponent} from './views/vpn-status-component/vpn-status.component';
8 8
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
9 9
 import {
10 10
     MatButtonModule,
@@ -17,11 +17,15 @@ import {
17 17
 import {MatProgressBarModule} from '@angular/material/progress-bar';
18 18
 import {MatExpansionModule} from '@angular/material/expansion';
19 19
 import {MatTableModule} from '@angular/material/table';
20
+import {MatSelectModule} from '@angular/material/select';
20 21
 import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
21 22
 import {TranslateHttpLoader} from '@ngx-translate/http-loader';
22 23
 import {FlexLayoutModule} from '@angular/flex-layout';
23 24
 import {CgBusyModule} from 'angular-busy2';
24 25
 import {StatusLedDirective} from './directives/status-led.directive';
26
+import {VpnStatusUpdated, UiStateUpdated, DnsStatusUpdated} from './services/app-state.service';
27
+import {StoreModule} from '@ngrx/store';
28
+import {DnsStatusComponent} from './views/dns-status-component/dns-status.component';
25 29
 
26 30
 export function HttpLoaderFactory(http: HttpClient) {
27 31
     return new TranslateHttpLoader(http, './assets/languages/');
@@ -30,7 +34,8 @@ export function HttpLoaderFactory(http: HttpClient) {
30 34
 @NgModule({
31 35
   declarations: [
32 36
     AppComponent,
33
-    StatusComponent,
37
+    VpnStatusComponent,
38
+    DnsStatusComponent,
34 39
     StatusLedDirective
35 40
   ],
36 41
   imports: [
@@ -47,6 +52,7 @@ export function HttpLoaderFactory(http: HttpClient) {
47 52
     MatProgressBarModule,
48 53
     MatExpansionModule,
49 54
     MatTableModule,
55
+    MatSelectModule,
50 56
     TranslateModule.forRoot({
51 57
         loader: {
52 58
             provide: TranslateLoader,
@@ -55,9 +61,17 @@ export function HttpLoaderFactory(http: HttpClient) {
55 61
         }
56 62
     }),
57 63
     FlexLayoutModule,
58
-    CgBusyModule
64
+    CgBusyModule,
65
+    StoreModule.forRoot({
66
+        uiState: UiStateUpdated.uiStateVMReducer,
67
+        vpnStatus: VpnStatusUpdated.vpnStatusReducer,
68
+        vpnStatusVM: VpnStatusUpdated.vpnStatusVMReducer,
69
+        dnsStatus: DnsStatusUpdated.dnsStatusReducer,
70
+        dnsStatusVM: DnsStatusUpdated.dnsStatusVMReducer,
71
+        dnsStatusPerServerVM: DnsStatusUpdated.dnsStatusPerServerVMReducer
72
+    })
59 73
   ],
60
-  providers: [],
74
+  providers: [Title],
61 75
   bootstrap: [AppComponent]
62 76
 })
63 77
 export class AppModule {

+ 5
- 0
frontend/SiteStatus/src/app/models/dns/dns-status.model.ts 查看文件

@@ -0,0 +1,5 @@
1
+import {DnsViewStatusModel} from './dns-view-status.model';
2
+
3
+export class DnsStatusModel {
4
+    views: DnsViewStatusModel[];
5
+}

+ 6
- 0
frontend/SiteStatus/src/app/models/dns/dns-view-status.model.ts 查看文件

@@ -0,0 +1,6 @@
1
+import {DnsZoneStatusModel} from './dns-zone-status.model';
2
+
3
+export class DnsViewStatusModel {
4
+    name: string;
5
+    zones: DnsZoneStatusModel[];
6
+}

+ 4
- 0
frontend/SiteStatus/src/app/models/dns/dns-zone-server-status.model.ts 查看文件

@@ -0,0 +1,4 @@
1
+export class DnsZoneServerStatusModel {
2
+    server: string;
3
+    soa: string;
4
+}

+ 6
- 0
frontend/SiteStatus/src/app/models/dns/dns-zone-status.model.ts 查看文件

@@ -0,0 +1,6 @@
1
+import {DnsZoneServerStatusModel} from './dns-zone-server-status.model';
2
+
3
+export class DnsZoneStatusModel {
4
+    zone: string;
5
+    serverStatus: DnsZoneServerStatusModel[];
6
+}

+ 0
- 6
frontend/SiteStatus/src/app/models/host.model.ts 查看文件

@@ -1,6 +0,0 @@
1
-import {HostStatusModel} from './host-status.model';
2
-
3
-export class HostModel {
4
-    ip: string;
5
-    hostStatus: HostStatusModel;
6
-}

+ 0
- 6
frontend/SiteStatus/src/app/models/site.model.ts 查看文件

@@ -1,6 +0,0 @@
1
-import {HostModel} from './host.model';
2
-
3
-export class SiteModel {
4
-    name: string;
5
-    hosts: HostModel[];
6
-}

frontend/SiteStatus/src/app/models/host-status.model.ts → frontend/SiteStatus/src/app/models/vpn/host-status.model.ts 查看文件


+ 6
- 0
frontend/SiteStatus/src/app/models/vpn/site-status.model.ts 查看文件

@@ -0,0 +1,6 @@
1
+import {HostStatusModel} from './host-status.model';
2
+
3
+export class SiteStatusModel {
4
+    name: string;
5
+    hosts: HostStatusModel[];
6
+}

+ 5
- 0
frontend/SiteStatus/src/app/models/vpn/vpn-status.model.ts 查看文件

@@ -0,0 +1,5 @@
1
+import {SiteStatusModel} from './site-status.model';
2
+
3
+export class VpnStatusModel {
4
+    sites: SiteStatusModel[];
5
+}

+ 403
- 0
frontend/SiteStatus/src/app/services/app-state.service.ts 查看文件

@@ -0,0 +1,403 @@
1
+import {Action, select, Store} from '@ngrx/store';
2
+import {Observable} from 'rxjs';
3
+import {Injectable} from '@angular/core';
4
+import {HostStatusViewModel} from '../view-models/vpn/host-status.view-model';
5
+import {StatusEnumViewModel} from '../view-models/status-enum.view-model';
6
+import {UiStateViewModel} from '../view-models/ui/ui-state.view-model';
7
+import {VpnStatusModel} from '../models/vpn/vpn-status.model';
8
+import {SiteStatusViewModel} from '../view-models/vpn/site-status.view-model';
9
+import {VpnStatusViewModel} from '../view-models/vpn/vpn-status.view-model';
10
+import {DnsStatusModel} from '../models/dns/dns-status.model';
11
+import {DnsStatusViewModel} from '../view-models/dns/by-zone/dns-status.view-model';
12
+import {DnsZoneStatusViewModel} from '../view-models/dns/by-zone/dns-zone-status.view-model';
13
+import {DnsZoneServerStatusViewModel} from '../view-models/dns/by-zone/dns-zone-server-status.view-model';
14
+import {DnsViewStatusViewModel} from '../view-models/dns/by-zone/dns-view-status.view-model';
15
+import {DnsStatusPerServerViewModel} from '../view-models/dns/by-server/dns-status-per-server.view-model';
16
+import {DnsViewStatusPerServerViewModel} from '../view-models/dns/by-server/dns-view-status-per-server.view-model';
17
+import {DnsServerPerServerViewModel} from '../view-models/dns/by-server/dns-server-per-server.view-model';
18
+
19
+export class VpnStatusUpdated implements Action {
20
+    static TYPE = 'VpnStatusUpdated';
21
+
22
+    readonly type = VpnStatusUpdated.TYPE;
23
+
24
+    constructor(public payload: VpnStatusModel) { }
25
+
26
+    static vpnStatusReducer(state: VpnStatusModel, action: Action): VpnStatusModel {
27
+        switch (action.type) {
28
+            case VpnStatusUpdated.TYPE:
29
+                return (action as VpnStatusUpdated).payload;
30
+        }
31
+        return state;
32
+    }
33
+
34
+    static vpnStatusVMReducer(state: VpnStatusViewModel, action: Action): VpnStatusViewModel {
35
+        switch (action.type) {
36
+            case VpnStatusUpdated.TYPE:
37
+                const hostStatusVM = (action as VpnStatusUpdated).payload.sites.map(site => {
38
+                    const siteVm = new SiteStatusViewModel();
39
+                    siteVm.name = site.name;
40
+                    siteVm.hosts = site.hosts.map(host => {
41
+                        const hostVM = new HostStatusViewModel();
42
+                        hostVM.ip = host.ip;
43
+                        hostVM.hostname = host.hostname;
44
+                        hostVM.times = host.times;
45
+
46
+                        let allNull = true;
47
+                        let oneNull = false;
48
+                        hostVM.timesMin = Number.MAX_SAFE_INTEGER;
49
+                        hostVM.timesAvg = 0;
50
+                        hostVM.timesMax = 0;
51
+                        hostVM.timesLost = 0;
52
+                        hostVM.times.forEach(p => {
53
+                            if (p == null) {
54
+                                ++hostVM.timesLost;
55
+                                oneNull = true;
56
+                            } else {
57
+                                allNull = false;
58
+                                if (p < hostVM.timesMin) {
59
+                                    hostVM.timesMin = p;
60
+                                }
61
+                                if (p > hostVM.timesMax) {
62
+                                    hostVM.timesMax = p;
63
+                                }
64
+                                hostVM.timesAvg += p;
65
+                            }
66
+                        });
67
+                        if (hostVM.times.length - hostVM.timesLost !== 0) {
68
+                            hostVM.timesAvg = hostVM.timesAvg / (hostVM.times.length - hostVM.timesLost);
69
+                        } else {
70
+                            hostVM.timesMin = null;
71
+                            hostVM.timesAvg = null;
72
+                            hostVM.timesMax = null;
73
+                        }
74
+
75
+                        if (allNull) {
76
+                            hostVM.status = StatusEnumViewModel.Error;
77
+                        } else if (oneNull) {
78
+                            hostVM.status = StatusEnumViewModel.Warning;
79
+                        } else {
80
+                            hostVM.status = StatusEnumViewModel.Ok;
81
+                        }
82
+
83
+                        return hostVM;
84
+                    });
85
+
86
+                    const status: { [id: number]: number } = {};
87
+                    status[StatusEnumViewModel.Ok] = 0;
88
+                    status[StatusEnumViewModel.Warning] = 0;
89
+                    status[StatusEnumViewModel.Error] = 0;
90
+
91
+                    siteVm.hosts.forEach(host => {
92
+                        ++status[host.status];
93
+                    });
94
+                    if (status[StatusEnumViewModel.Error] === siteVm.hosts.length) {
95
+                        siteVm.status = StatusEnumViewModel.Error;
96
+                    } else if (status[StatusEnumViewModel.Error] !== 0 || status[StatusEnumViewModel.Warning] !== 0) {
97
+                        siteVm.status = StatusEnumViewModel.Warning;
98
+                    } else {
99
+                        siteVm.status = StatusEnumViewModel.Ok;
100
+                    }
101
+
102
+                    return siteVm;
103
+                });
104
+                return {
105
+                    sites: hostStatusVM
106
+                };
107
+        }
108
+        return state;
109
+    }
110
+}
111
+
112
+export class DnsStatusUpdated implements Action {
113
+    static TYPE = 'DnsStatusUpdated';
114
+
115
+    readonly type = DnsStatusUpdated.TYPE;
116
+
117
+    constructor(public payload: DnsStatusModel) { }
118
+
119
+    static convertDnsStatusModel(model: DnsStatusModel): DnsStatusViewModel {
120
+        const viewsVM: DnsViewStatusViewModel[] = model.views.map(view => {
121
+            const zonesVM = view.zones.map(zone => {
122
+                const zoneVM = new DnsZoneStatusViewModel();
123
+                zoneVM.zone = zone.zone;
124
+
125
+                let mostRecentSoaSerial = 0;
126
+                zone.serverStatus.forEach(server => {
127
+                    if (server.soa != null) {
128
+                        const serial = Number(server.soa.split(' ')[6]);
129
+                        if (serial > mostRecentSoaSerial) {
130
+                            mostRecentSoaSerial = serial;
131
+                        }
132
+                    }
133
+                });
134
+
135
+                zoneVM.serverStatus = zone.serverStatus.map(server => {
136
+                    const serverVM = new DnsZoneServerStatusViewModel();
137
+                    serverVM.server = server.server;
138
+                    serverVM.soa = server.soa;
139
+                    if (serverVM.soa == null) {
140
+                        serverVM.status = StatusEnumViewModel.Error;
141
+                    } else if (serverVM.soa.split(' ')[6] !== String(mostRecentSoaSerial)) {
142
+                        serverVM.status = StatusEnumViewModel.Warning;
143
+                    } else {
144
+                        serverVM.status = StatusEnumViewModel.Ok;
145
+                    }
146
+                    return serverVM;
147
+                });
148
+
149
+                const status: { [id: number]: number } = {};
150
+                status[StatusEnumViewModel.Ok] = 0;
151
+                status[StatusEnumViewModel.Warning] = 0;
152
+                status[StatusEnumViewModel.Error] = 0;
153
+
154
+                zoneVM.serverStatus.forEach(server => {
155
+                    ++status[server.status];
156
+                });
157
+                if (status[StatusEnumViewModel.Error] === zoneVM.serverStatus.length) {
158
+                    zoneVM.status = StatusEnumViewModel.Error;
159
+                } else if (status[StatusEnumViewModel.Error] !== 0 || status[StatusEnumViewModel.Warning] !== 0) {
160
+                    zoneVM.status = StatusEnumViewModel.Warning;
161
+                } else {
162
+                    zoneVM.status = StatusEnumViewModel.Ok;
163
+                }
164
+
165
+                return zoneVM;
166
+            });
167
+            return {
168
+                zones: zonesVM,
169
+                name: view.name
170
+            };
171
+        });
172
+
173
+        return {
174
+            views: viewsVM
175
+        };
176
+    }
177
+
178
+    static dnsStatusReducer(state: DnsStatusModel, action: Action): DnsStatusModel {
179
+        switch (action.type) {
180
+            case DnsStatusUpdated.TYPE:
181
+                return (action as DnsStatusUpdated).payload;
182
+        }
183
+        return state;
184
+    }
185
+
186
+    static dnsStatusVMReducer(state: DnsStatusViewModel, action: Action): DnsStatusViewModel {
187
+        switch (action.type) {
188
+            case DnsStatusUpdated.TYPE:
189
+                return DnsStatusUpdated.convertDnsStatusModel((action as DnsStatusUpdated).payload);
190
+        }
191
+        return state;
192
+    }
193
+
194
+    static dnsStatusPerServerVMReducer(state: DnsStatusPerServerViewModel, action: Action): DnsStatusPerServerViewModel {
195
+        switch (action.type) {
196
+            case DnsStatusUpdated.TYPE:
197
+                const dnsStatusVm = DnsStatusUpdated.convertDnsStatusModel((action as DnsStatusUpdated).payload);
198
+
199
+                const viewsPSVM: DnsViewStatusPerServerViewModel[] = dnsStatusVm.views.map(view => {
200
+                    const serversPSVM: DnsServerPerServerViewModel[] = view.zones[0].serverStatus.map(server => {
201
+                        const zonesPSVM = view.zones.map(zone => {
202
+                            let soa: string = null;
203
+                            zone.serverStatus.forEach(serverStatus => {
204
+                                if (serverStatus.server === server.server) {
205
+                                    soa = serverStatus.soa;
206
+                                }
207
+                            });
208
+
209
+                            let zoneVM: DnsZoneStatusViewModel = null;
210
+                            view.zones.forEach(z => {
211
+                                if (z.zone === zone.zone) {
212
+                                    zoneVM = z;
213
+                                }
214
+                            });
215
+
216
+                            return {
217
+                                zone: zone.zone,
218
+                                soa: soa,
219
+                                status: zoneVM.status
220
+                            };
221
+                        });
222
+
223
+                        const status: { [id: number]: number } = {};
224
+                        status[StatusEnumViewModel.Ok] = 0;
225
+                        status[StatusEnumViewModel.Warning] = 0;
226
+                        status[StatusEnumViewModel.Error] = 0;
227
+
228
+                        let statusPSVM: StatusEnumViewModel = null;
229
+                        zonesPSVM.forEach(zone => {
230
+                            ++status[zone.status];
231
+                        });
232
+                        if (status[StatusEnumViewModel.Error] === zonesPSVM.length) {
233
+                            statusPSVM = StatusEnumViewModel.Error;
234
+                        } else if (status[StatusEnumViewModel.Error] !== 0 || status[StatusEnumViewModel.Warning] !== 0) {
235
+                            statusPSVM = StatusEnumViewModel.Warning;
236
+                        } else {
237
+                            statusPSVM = StatusEnumViewModel.Ok;
238
+                        }
239
+
240
+                        return {
241
+                            ip: server.server,
242
+                            zones: zonesPSVM,
243
+                            status: statusPSVM
244
+                        };
245
+                    });
246
+
247
+                    return {
248
+                        name: view.name,
249
+                        servers: serversPSVM
250
+                    };
251
+                });
252
+
253
+                return {
254
+                    views: viewsPSVM
255
+                };
256
+        }
257
+        return state;
258
+    }
259
+}
260
+
261
+export class UiStateUpdatedMobile implements Action {
262
+    static TYPE = 'UiStateUpdatedMobile';
263
+
264
+    readonly type = UiStateUpdatedMobile.TYPE;
265
+}
266
+
267
+export class UiStateUpdatedDesktop implements Action {
268
+    static TYPE = 'UiStateUpdatedDesktop';
269
+
270
+    readonly type = UiStateUpdatedDesktop.TYPE;
271
+}
272
+
273
+export class UiStateUpdatedTitle implements Action {
274
+    static TYPE = 'UiStateUpdatedTitle';
275
+
276
+    readonly type = UiStateUpdatedTitle.TYPE;
277
+
278
+    constructor(public payload: string) {}
279
+}
280
+
281
+export class UiStateUpdatedToolbarTitle implements Action {
282
+    static TYPE = 'UiStateUpdatedToolbarTitle';
283
+
284
+    readonly type = UiStateUpdatedToolbarTitle.TYPE;
285
+
286
+    constructor(public payload: string) {}
287
+}
288
+
289
+export class UiStateUpdated implements Action {
290
+    static INITIAL_STATE: UiStateViewModel = {
291
+        sidenav: {
292
+            displayMenuButton: true,
293
+            mode: 'over',
294
+            opened: false
295
+        },
296
+        page: {
297
+            title: 'Loading...'
298
+        },
299
+        toolbar: {
300
+            title: 'Loading...'
301
+        }
302
+    };
303
+
304
+    static TYPE = 'UiStateUpdated';
305
+
306
+    readonly type = UiStateUpdated.TYPE;
307
+
308
+    constructor(public payload: UiStateViewModel) { }
309
+
310
+    static uiStateVMReducer(state: UiStateViewModel = UiStateUpdated.INITIAL_STATE, action: Action): UiStateViewModel {
311
+        switch (action.type) {
312
+            case UiStateUpdated.TYPE:
313
+                return (action as UiStateUpdated).payload;
314
+            case UiStateUpdatedMobile.TYPE:
315
+                return {...state, ...{
316
+                        sidenav: {
317
+                            opened: false,
318
+                            mode: 'over',
319
+                            displayMenuButton: true
320
+                        }
321
+                    }};
322
+            case UiStateUpdatedDesktop.TYPE:
323
+                return {...state, ...{
324
+                    sidenav: {
325
+                        opened: true,
326
+                        mode: 'side',
327
+                        displayMenuButton: false
328
+                    }
329
+                }};
330
+            case UiStateUpdatedTitle.TYPE:
331
+                return {...state, ...{
332
+                    page: {
333
+                        title: (action as UiStateUpdatedTitle).payload
334
+                    }
335
+                }};
336
+            case UiStateUpdatedToolbarTitle.TYPE:
337
+                return {...state, ...{
338
+                    toolbar: {
339
+                        title: (action as UiStateUpdatedToolbarTitle).payload
340
+                    }
341
+                }};
342
+        }
343
+        return state;
344
+    }
345
+}
346
+
347
+export interface AppStateData {
348
+    uiState: UiStateViewModel;
349
+    vpnStatus: VpnStatusModel;
350
+    vpnStatusVM: VpnStatusViewModel;
351
+    dnsStatus: DnsStatusModel;
352
+    dnsStatusVM: DnsStatusViewModel;
353
+    dnsStatusPerServerVM: DnsStatusPerServerViewModel;
354
+}
355
+
356
+@Injectable({ providedIn: 'root' })
357
+export class AppStateService {
358
+    constructor(public store: Store<AppStateData>) { }
359
+
360
+    public updateDnsStatus(dnsStatus: DnsStatusModel): void {
361
+        this.store.dispatch(new DnsStatusUpdated(dnsStatus));
362
+    }
363
+
364
+    public onDnsStatusUpdated(): Observable<DnsStatusViewModel> {
365
+        return this.store.pipe(select('dnsStatusVM'));
366
+    }
367
+
368
+    public onDnsStatusPerServerUpdated(): Observable<DnsStatusPerServerViewModel> {
369
+        return this.store.pipe(select('dnsStatusPerServerVM'));
370
+    }
371
+
372
+    public updateVpnStatus(vpnStatus: VpnStatusModel): void {
373
+        this.store.dispatch(new VpnStatusUpdated(vpnStatus));
374
+    }
375
+
376
+    public onVpnStatusUpdated(): Observable<VpnStatusViewModel> {
377
+        return this.store.pipe(select('vpnStatusVM'));
378
+    }
379
+
380
+    public updateUiState(uiState: UiStateViewModel): void {
381
+        this.store.dispatch(new UiStateUpdated(uiState));
382
+    }
383
+
384
+    public setUiStateMobile(): void {
385
+        this.store.dispatch(new UiStateUpdatedMobile());
386
+    }
387
+
388
+    public setUiStateDesktop(): void {
389
+        this.store.dispatch(new UiStateUpdatedDesktop());
390
+    }
391
+
392
+    public setUiStateTitle(title: string): void {
393
+        this.store.dispatch(new UiStateUpdatedTitle(title));
394
+    }
395
+
396
+    public setUiStateToolbarTitle(title: string): void {
397
+        this.store.dispatch(new UiStateUpdatedToolbarTitle(title));
398
+    }
399
+
400
+    public onUiStateUpdated(): Observable<UiStateViewModel> {
401
+        return this.store.pipe(select('uiState'));
402
+    }
403
+}

+ 18
- 5
frontend/SiteStatus/src/app/services/status.service.ts 查看文件

@@ -1,8 +1,10 @@
1 1
 import {HttpClient, HttpHeaders} from '@angular/common/http';
2 2
 import {Injectable} from '@angular/core';
3 3
 import {Observable} from 'rxjs';
4
-import {SiteModel} from '../models/site.model';
5 4
 import {ApiResultModel} from '../models/api-result.model';
5
+import {AppStateService} from './app-state.service';
6
+import {VpnStatusModel} from '../models/vpn/vpn-status.model';
7
+import {DnsStatusModel} from '../models/dns/dns-status.model';
6 8
 
7 9
 const httpPostOptions = {
8 10
     headers: new HttpHeaders({ 'Content-Type': 'application/json' })
@@ -12,10 +14,21 @@ const httpPostOptions = {
12 14
 export class StatusService {
13 15
     private urlBase = 'api/status';
14 16
 
15
-    constructor(
16
-        private http: HttpClient) { }
17
+    constructor(private http: HttpClient, private appStateService: AppStateService) { }
17 18
 
18
-        getHosts(): Observable<ApiResultModel<SiteModel[]>> {
19
-            return this.http.get<ApiResultModel<SiteModel[]>>(`${this.urlBase}/hosts`);
19
+        getVpnStatus(): Observable<ApiResultModel<VpnStatusModel>> {
20
+            const obs = this.http.get<ApiResultModel<VpnStatusModel>>(`${this.urlBase}/vpn`);
21
+            obs.subscribe(vpnStatus => {
22
+                this.appStateService.updateVpnStatus(vpnStatus.data);
23
+            });
24
+            return obs;
25
+        }
26
+
27
+        getDnsStatus(): Observable<ApiResultModel<DnsStatusModel>> {
28
+            const obs = this.http.get<ApiResultModel<DnsStatusModel>>(`${this.urlBase}/dns`);
29
+            obs.subscribe(dnsStatus => {
30
+                this.appStateService.updateDnsStatus(dnsStatus.data);
31
+            });
32
+            return obs;
20 33
         }
21 34
 }

+ 8
- 0
frontend/SiteStatus/src/app/view-models/dns/by-server/dns-server-per-server.view-model.ts 查看文件

@@ -0,0 +1,8 @@
1
+import {DnsServerZonePerServerViewModel} from './dns-server-zone-per-server.view-model';
2
+import {StatusEnumViewModel} from '../../status-enum.view-model';
3
+
4
+export class DnsServerPerServerViewModel {
5
+    ip: string;
6
+    zones: DnsServerZonePerServerViewModel[];
7
+    status: StatusEnumViewModel;
8
+}

+ 7
- 0
frontend/SiteStatus/src/app/view-models/dns/by-server/dns-server-zone-per-server.view-model.ts 查看文件

@@ -0,0 +1,7 @@
1
+import {StatusEnumViewModel} from '../../status-enum.view-model';
2
+
3
+export class DnsServerZonePerServerViewModel {
4
+    zone: string;
5
+    soa: string;
6
+    status: StatusEnumViewModel;
7
+}

+ 5
- 0
frontend/SiteStatus/src/app/view-models/dns/by-server/dns-status-per-server.view-model.ts 查看文件

@@ -0,0 +1,5 @@
1
+import {DnsViewStatusPerServerViewModel} from './dns-view-status-per-server.view-model';
2
+
3
+export class DnsStatusPerServerViewModel {
4
+    views: DnsViewStatusPerServerViewModel[];
5
+}

+ 6
- 0
frontend/SiteStatus/src/app/view-models/dns/by-server/dns-view-status-per-server.view-model.ts 查看文件

@@ -0,0 +1,6 @@
1
+import {DnsServerPerServerViewModel} from './dns-server-per-server.view-model';
2
+
3
+export class DnsViewStatusPerServerViewModel {
4
+    name: string;
5
+    servers: DnsServerPerServerViewModel[];
6
+}

+ 5
- 0
frontend/SiteStatus/src/app/view-models/dns/by-zone/dns-status.view-model.ts 查看文件

@@ -0,0 +1,5 @@
1
+import {DnsViewStatusViewModel} from './dns-view-status.view-model';
2
+
3
+export class DnsStatusViewModel {
4
+    views: DnsViewStatusViewModel[];
5
+}

+ 6
- 0
frontend/SiteStatus/src/app/view-models/dns/by-zone/dns-view-status.view-model.ts 查看文件

@@ -0,0 +1,6 @@
1
+import {DnsZoneStatusViewModel} from './dns-zone-status.view-model';
2
+
3
+export class DnsViewStatusViewModel {
4
+    name: string;
5
+    zones: DnsZoneStatusViewModel[];
6
+}

+ 7
- 0
frontend/SiteStatus/src/app/view-models/dns/by-zone/dns-zone-server-status.view-model.ts 查看文件

@@ -0,0 +1,7 @@
1
+import {StatusEnumViewModel} from '../../status-enum.view-model';
2
+
3
+export class DnsZoneServerStatusViewModel {
4
+    server: string;
5
+    soa: string;
6
+    status: StatusEnumViewModel;
7
+}

+ 8
- 0
frontend/SiteStatus/src/app/view-models/dns/by-zone/dns-zone-status.view-model.ts 查看文件

@@ -0,0 +1,8 @@
1
+import {DnsZoneServerStatusViewModel} from './dns-zone-server-status.view-model';
2
+import {StatusEnumViewModel} from '../../status-enum.view-model';
3
+
4
+export class DnsZoneStatusViewModel {
5
+    zone: string;
6
+    serverStatus: DnsZoneServerStatusViewModel[];
7
+    status: StatusEnumViewModel;
8
+}

+ 4
- 0
frontend/SiteStatus/src/app/view-models/ui/ui-dns-view-type-enum.view-model.ts 查看文件

@@ -0,0 +1,4 @@
1
+export enum UiDnsViewTypeEnumViewModel {
2
+    ByZone,
3
+    ByServer
4
+}

+ 6
- 0
frontend/SiteStatus/src/app/view-models/ui/ui-dns-view-type.view-model.ts 查看文件

@@ -0,0 +1,6 @@
1
+import {UiDnsViewTypeEnumViewModel} from './ui-dns-view-type-enum.view-model';
2
+
3
+export class UiDnsViewTypeViewModel {
4
+    name: string;
5
+    value: UiDnsViewTypeEnumViewModel;
6
+}

+ 19
- 0
frontend/SiteStatus/src/app/view-models/ui/ui-state.view-model.ts 查看文件

@@ -0,0 +1,19 @@
1
+export class UiStateSideNavViewModel {
2
+    mode: string;
3
+    opened: boolean;
4
+    displayMenuButton: boolean;
5
+}
6
+
7
+export class UiStateToolbarViewModel {
8
+    title: string;
9
+}
10
+
11
+export class UiStatePageViewModel {
12
+    title: string;
13
+}
14
+
15
+export class UiStateViewModel {
16
+    sidenav: UiStateSideNavViewModel;
17
+    page: UiStatePageViewModel;
18
+    toolbar: UiStateToolbarViewModel;
19
+}

frontend/SiteStatus/src/app/view-models/host-status.view-model.ts → frontend/SiteStatus/src/app/view-models/vpn/host-status.view-model.ts 查看文件

@@ -1,4 +1,4 @@
1
-import {StatusEnumViewModel} from './status-enum.view-model';
1
+import {StatusEnumViewModel} from '../status-enum.view-model';
2 2
 
3 3
 export class HostStatusViewModel {
4 4
     ip: string;

frontend/SiteStatus/src/app/view-models/site.view-model.ts → frontend/SiteStatus/src/app/view-models/vpn/site-status.view-model.ts 查看文件

@@ -1,7 +1,7 @@
1 1
 import {HostStatusViewModel} from './host-status.view-model';
2
-import {StatusEnumViewModel} from './status-enum.view-model';
2
+import {StatusEnumViewModel} from '../status-enum.view-model';
3 3
 
4
-export class SiteViewModel {
4
+export class SiteStatusViewModel {
5 5
     name: string;
6 6
     hosts: HostStatusViewModel[];
7 7
     status: StatusEnumViewModel;

+ 5
- 0
frontend/SiteStatus/src/app/view-models/vpn/vpn-status.view-model.ts 查看文件

@@ -0,0 +1,5 @@
1
+import {SiteStatusViewModel} from './site-status.view-model';
2
+
3
+export class VpnStatusViewModel {
4
+    sites: SiteStatusViewModel[];
5
+}

frontend/SiteStatus/src/app/views/status-component/status.component.css → frontend/SiteStatus/src/app/views/dns-status-component/dns-status.component.css 查看文件


+ 84
- 0
frontend/SiteStatus/src/app/views/dns-status-component/dns-status.component.html 查看文件

@@ -0,0 +1,84 @@
1
+<div [cgBusy]="promise">
2
+
3
+    <mat-form-field>
4
+        <mat-select placeholder="View by" [(value)]="viewType">
5
+            <mat-option *ngFor="let viewType of viewTypes" [value]="viewType.value">
6
+                {{ viewType.name }}
7
+            </mat-option>
8
+        </mat-select>
9
+    </mat-form-field>
10
+
11
+    <div *ngIf="viewType === viewTypes[0].value">
12
+        <div *ngFor="let view of (dnsStatus$ | async)?.views;trackBy: trackViewByName">
13
+
14
+            <h3>{{ view.name }}</h3>
15
+
16
+            <mat-expansion-panel *ngFor="let zoneStatus of view.zones;trackBy: trackZoneByZone">
17
+                <mat-expansion-panel-header>
18
+                    <mat-panel-title>
19
+                        <span [appStatusLed]="zoneStatus.status"></span>&nbsp;<strong>{{ zoneStatus.zone }}</strong>
20
+                    </mat-panel-title>
21
+                    <mat-panel-description>
22
+                    </mat-panel-description>
23
+                </mat-expansion-panel-header>
24
+
25
+                <table mat-table [dataSource]="zoneStatus.serverStatus" class="mat-elevation-z0" [trackBy]="trackZoneServerByServer">
26
+                    <ng-container matColumnDef="status">
27
+                        <th mat-header-cell *matHeaderCellDef> Status </th>
28
+                        <td mat-cell *matCellDef="let element">  <span [appStatusLed]="element.status"></span> </td>
29
+                    </ng-container>
30
+                    <ng-container matColumnDef="server">
31
+                        <th mat-header-cell *matHeaderCellDef> Server </th>
32
+                        <td mat-cell *matCellDef="let element"> {{element.server}} </td>
33
+                    </ng-container>
34
+                    <ng-container matColumnDef="soa">
35
+                        <th mat-header-cell *matHeaderCellDef> SOA </th>
36
+                        <td mat-cell *matCellDef="let element"> {{element.soa}} </td>
37
+                    </ng-container>
38
+
39
+                    <tr mat-header-row *matHeaderRowDef="['status', 'server', 'soa']"></tr>
40
+                    <tr mat-row *matRowDef="let row; columns: ['status', 'server', 'soa'];"></tr>
41
+                </table>
42
+
43
+            </mat-expansion-panel>
44
+        </div>
45
+    </div>
46
+
47
+    <div *ngIf="viewType === viewTypes[1].value">
48
+        <div *ngFor="let view of (dnsStatusPerServer$ | async)?.views;trackBy: trackViewPerServerByName">
49
+
50
+            <h3>{{ view.name }}</h3>
51
+
52
+            <mat-expansion-panel *ngFor="let server of view.servers;trackBy: trackServerByIp">
53
+                <mat-expansion-panel-header>
54
+                    <mat-panel-title>
55
+                        <span [appStatusLed]="server.status"></span>&nbsp;<strong>{{ server.ip }}</strong>
56
+                    </mat-panel-title>
57
+                    <mat-panel-description>
58
+                    </mat-panel-description>
59
+                </mat-expansion-panel-header>
60
+
61
+                <table mat-table [dataSource]="server.zones" class="mat-elevation-z0" [trackBy]="trackZonePerServerByZone">
62
+                    <ng-container matColumnDef="status">
63
+                        <th mat-header-cell *matHeaderCellDef> Status </th>
64
+                        <td mat-cell *matCellDef="let element">  <span [appStatusLed]="element.status"></span> </td>
65
+                    </ng-container>
66
+                    <ng-container matColumnDef="zone">
67
+                        <th mat-header-cell *matHeaderCellDef> Zone </th>
68
+                        <td mat-cell *matCellDef="let element"> {{element.zone}} </td>
69
+                    </ng-container>
70
+                    <ng-container matColumnDef="soa">
71
+                        <th mat-header-cell *matHeaderCellDef> SOA </th>
72
+                        <td mat-cell *matCellDef="let element"> {{element.soa}} </td>
73
+                    </ng-container>
74
+
75
+                    <tr mat-header-row *matHeaderRowDef="['status', 'zone', 'soa']"></tr>
76
+                    <tr mat-row *matRowDef="let row; columns: ['status', 'zone', 'soa'];"></tr>
77
+                </table>
78
+
79
+            </mat-expansion-panel>
80
+        </div>
81
+    </div>
82
+
83
+    <button (click)="onclick()" mat-button mat-raised-button>Reload</button>
84
+</div>

+ 80
- 0
frontend/SiteStatus/src/app/views/dns-status-component/dns-status.component.ts 查看文件

@@ -0,0 +1,80 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import {StatusService} from '../../services/status.service';
3
+import {AppStateService} from '../../services/app-state.service';
4
+import {Observable} from 'rxjs';
5
+import {DnsStatusViewModel} from '../../view-models/dns/by-zone/dns-status.view-model';
6
+import {DnsZoneStatusViewModel} from '../../view-models/dns/by-zone/dns-zone-status.view-model';
7
+import {DnsZoneServerStatusViewModel} from '../../view-models/dns/by-zone/dns-zone-server-status.view-model';
8
+import {DnsViewStatusViewModel} from '../../view-models/dns/by-zone/dns-view-status.view-model';
9
+import {UiDnsViewTypeViewModel} from '../../view-models/ui/ui-dns-view-type.view-model';
10
+import {UiDnsViewTypeEnumViewModel} from '../../view-models/ui/ui-dns-view-type-enum.view-model';
11
+import {DnsStatusPerServerViewModel} from '../../view-models/dns/by-server/dns-status-per-server.view-model';
12
+import {DnsViewStatusPerServerViewModel} from '../../view-models/dns/by-server/dns-view-status-per-server.view-model';
13
+import {DnsServerPerServerViewModel} from '../../view-models/dns/by-server/dns-server-per-server.view-model';
14
+import {DnsServerZonePerServerViewModel} from '../../view-models/dns/by-server/dns-server-zone-per-server.view-model';
15
+
16
+@Component({
17
+    selector: 'app-dns-status',
18
+    templateUrl: './dns-status.component.html',
19
+    styleUrls: [ './dns-status.component.css' ]
20
+})
21
+export class DnsStatusComponent implements OnInit {
22
+    promise = null;
23
+    dnsStatus$: Observable<DnsStatusViewModel> = null;
24
+    dnsStatusPerServer$: Observable<DnsStatusPerServerViewModel> = null;
25
+
26
+    viewTypes: UiDnsViewTypeViewModel[] = [
27
+        {
28
+            name: 'Zone',
29
+            value: UiDnsViewTypeEnumViewModel.ByZone
30
+        },
31
+        {
32
+            name: 'Server',
33
+            value: UiDnsViewTypeEnumViewModel.ByServer
34
+        }
35
+    ];
36
+    viewType: UiDnsViewTypeEnumViewModel = this.viewTypes[0].value;
37
+
38
+    constructor(private appStateService: AppStateService, private statusService: StatusService) {
39
+        this.dnsStatus$ = appStateService.onDnsStatusUpdated();
40
+        this.dnsStatusPerServer$ = appStateService.onDnsStatusPerServerUpdated();
41
+        this.appStateService.setUiStateTitle('DNS Status');
42
+        this.appStateService.setUiStateToolbarTitle('DNS Status');
43
+    }
44
+
45
+    ngOnInit() {
46
+        this.getStatus();
47
+    }
48
+
49
+    getStatus(): void {
50
+        this.promise = this.statusService.getDnsStatus();
51
+    }
52
+
53
+    onclick(): void {
54
+        this.getStatus();
55
+    }
56
+
57
+    trackViewByName(index: number, view: DnsViewStatusViewModel): string {
58
+        return view.name;
59
+    }
60
+
61
+    trackZoneByZone(index: number, zone: DnsZoneStatusViewModel): string {
62
+        return zone.zone;
63
+    }
64
+
65
+    trackZoneServerByServer(index: number, server: DnsZoneServerStatusViewModel): string {
66
+        return server.server;
67
+    }
68
+
69
+    trackViewPerServerByName(index: number, view: DnsViewStatusPerServerViewModel): string {
70
+        return view.name;
71
+    }
72
+
73
+    trackServerByIp(index: number, server: DnsServerPerServerViewModel): string {
74
+        return server.ip;
75
+    }
76
+
77
+    trackZonePerServerByZone(index: number, zone: DnsServerZonePerServerViewModel): string {
78
+        return zone.zone;
79
+    }
80
+}

+ 0
- 99
frontend/SiteStatus/src/app/views/status-component/status.component.ts 查看文件

@@ -1,99 +0,0 @@
1
-import { Component, OnInit } from '@angular/core';
2
-import {StatusService} from '../../services/status.service';
3
-import {SiteViewModel} from '../../view-models/site.view-model';
4
-import {HostStatusViewModel} from '../../view-models/host-status.view-model';
5
-import {StatusEnumViewModel} from '../../view-models/status-enum.view-model';
6
-
7
-@Component({
8
-    selector: 'app-status',
9
-    templateUrl: './status.component.html',
10
-    styleUrls: [ './status.component.css' ]
11
-})
12
-export class StatusComponent implements OnInit {
13
-    hostsStatus: SiteViewModel[] = [];
14
-    promise = null;
15
-
16
-    constructor(private statusService: StatusService) { }
17
-
18
-    ngOnInit() {
19
-        this.getStatus();
20
-    }
21
-
22
-    getStatus(): void {
23
-        this.promise = this.statusService.getHosts()
24
-            .subscribe(hostsStatus => {
25
-                const hostsStatusData = hostsStatus.data;
26
-                this.hostsStatus = hostsStatusData.map(site => {
27
-                    const siteVm = new SiteViewModel();
28
-                    siteVm.name = site.name;
29
-                    siteVm.hosts = site.hosts.map(host => {
30
-                        const hostVM = new HostStatusViewModel();
31
-                        hostVM.ip = host.hostStatus.ip;
32
-                        hostVM.hostname = host.hostStatus.hostname;
33
-                        hostVM.times = host.hostStatus.times;
34
-
35
-                        let allNull = true;
36
-                        let oneNull = false;
37
-                        hostVM.timesMin = Number.MAX_SAFE_INTEGER;
38
-                        hostVM.timesAvg = 0;
39
-                        hostVM.timesMax = 0;
40
-                        hostVM.timesLost = 0;
41
-                        hostVM.times.forEach(p => {
42
-                            if (p == null) {
43
-                                ++hostVM.timesLost;
44
-                                oneNull = true;
45
-                            } else {
46
-                                allNull = false;
47
-                                if (p < hostVM.timesMin) {
48
-                                    hostVM.timesMin = p;
49
-                                }
50
-                                if (p > hostVM.timesMax) {
51
-                                    hostVM.timesMax = p;
52
-                                }
53
-                                hostVM.timesAvg += p;
54
-                            }
55
-                        });
56
-                        if (hostVM.times.length - hostVM.timesLost !== 0) {
57
-                            hostVM.timesAvg = hostVM.timesAvg / (hostVM.times.length - hostVM.timesLost);
58
-                        } else {
59
-                            hostVM.timesMin = null;
60
-                            hostVM.timesAvg = null;
61
-                            hostVM.timesMax = null;
62
-                        }
63
-
64
-                        if (allNull) {
65
-                            hostVM.status = StatusEnumViewModel.Error;
66
-                        } else if (oneNull) {
67
-                            hostVM.status = StatusEnumViewModel.Warning;
68
-                        } else {
69
-                            hostVM.status = StatusEnumViewModel.Ok;
70
-                        }
71
-
72
-                        return hostVM;
73
-                    });
74
-
75
-                    const status: {[id: number]: number} = {};
76
-                    status[StatusEnumViewModel.Ok] = 0;
77
-                    status[StatusEnumViewModel.Warning] = 0;
78
-                    status[StatusEnumViewModel.Error] = 0;
79
-
80
-                    siteVm.hosts.forEach(host => {
81
-                        ++status[host.status];
82
-                    });
83
-                    if (status[StatusEnumViewModel.Error] === siteVm.hosts.length) {
84
-                        siteVm.status = StatusEnumViewModel.Error;
85
-                    } else if (status[StatusEnumViewModel.Error] !== 0 || status[StatusEnumViewModel.Warning] !== 0) {
86
-                        siteVm.status = StatusEnumViewModel.Warning;
87
-                    } else {
88
-                        siteVm.status = StatusEnumViewModel.Ok;
89
-                    }
90
-
91
-                    return siteVm;
92
-                });
93
-            });
94
-    }
95
-
96
-    onclick(): void {
97
-        this.getStatus();
98
-    }
99
-}

+ 0
- 0
frontend/SiteStatus/src/app/views/vpn-status-component/vpn-status.component.css 查看文件


frontend/SiteStatus/src/app/views/status-component/status.component.html → frontend/SiteStatus/src/app/views/vpn-status-component/vpn-status.component.html 查看文件

@@ -1,6 +1,6 @@
1 1
 <div [cgBusy]="promise">
2 2
 
3
-    <mat-expansion-panel *ngFor="let hostStatus of hostsStatus">
3
+    <mat-expansion-panel *ngFor="let hostStatus of (vpnStatus$ | async)?.sites;trackBy: trackSiteByName">
4 4
         <mat-expansion-panel-header>
5 5
             <mat-panel-title>
6 6
                 <span [appStatusLed]="hostStatus.status"></span>&nbsp;<strong>{{ hostStatus.name == null ? "Internet" : hostStatus.name }}</strong>
@@ -9,7 +9,7 @@
9 9
             </mat-panel-description>
10 10
         </mat-expansion-panel-header>
11 11
 
12
-        <table mat-table [dataSource]="hostStatus.hosts" class="mat-elevation-z0">
12
+        <table mat-table [dataSource]="hostStatus.hosts" class="mat-elevation-z0" [trackBy]="trackHostStatusByIp">
13 13
             <ng-container matColumnDef="status">
14 14
                 <th mat-header-cell *matHeaderCellDef> Status </th>
15 15
                 <td mat-cell *matCellDef="let element">  <span [appStatusLed]="element.status"></span> </td>

+ 43
- 0
frontend/SiteStatus/src/app/views/vpn-status-component/vpn-status.component.ts 查看文件

@@ -0,0 +1,43 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import {StatusService} from '../../services/status.service';
3
+import {AppStateService} from '../../services/app-state.service';
4
+import {Observable} from 'rxjs';
5
+import {VpnStatusViewModel} from '../../view-models/vpn/vpn-status.view-model';
6
+import {SiteStatusViewModel} from '../../view-models/vpn/site-status.view-model';
7
+import {HostStatusViewModel} from '../../view-models/vpn/host-status.view-model';
8
+
9
+@Component({
10
+    selector: 'app-vpn-status',
11
+    templateUrl: './vpn-status.component.html',
12
+    styleUrls: [ './vpn-status.component.css' ]
13
+})
14
+export class VpnStatusComponent implements OnInit {
15
+    promise = null;
16
+    vpnStatus$: Observable<VpnStatusViewModel> = null;
17
+
18
+    constructor(private appStateService: AppStateService, private statusService: StatusService) {
19
+        this.vpnStatus$ = appStateService.onVpnStatusUpdated();
20
+        this.appStateService.setUiStateTitle('VPN Status');
21
+        this.appStateService.setUiStateToolbarTitle('VPN Status');
22
+    }
23
+
24
+    ngOnInit() {
25
+        this.getStatus();
26
+    }
27
+
28
+    getStatus(): void {
29
+        this.promise = this.statusService.getVpnStatus();
30
+    }
31
+
32
+    onclick(): void {
33
+        this.getStatus();
34
+    }
35
+
36
+    trackSiteByName(index: number, site: SiteStatusViewModel): string {
37
+        return site.name;
38
+    }
39
+
40
+    trackHostStatusByIp(index: number, hostStatus: HostStatusViewModel): string {
41
+        return hostStatus.ip;
42
+    }
43
+}

二進制
frontend/SiteStatus/src/favicon.ico 查看文件


+ 1
- 3
frontend/SiteStatus/src/index.html 查看文件

@@ -7,10 +7,8 @@
7 7
 
8 8
   <meta name="viewport" content="width=device-width, initial-scale=1">
9 9
   <link rel="icon" type="image/x-icon" href="favicon.ico">
10
-
11
-  <!--<link rel="stylesheet" href="@angular/material/prebuilt-themes/indigo-pink.css"/>-->
12 10
 </head>
13
-<body class="app-theme">
11
+<body class="dark-app-theme">
14 12
   <app-root></app-root>
15 13
 </body>
16 14
 </html>

+ 20
- 5
frontend/SiteStatus/src/styles.scss 查看文件

@@ -3,8 +3,23 @@
3 3
 
4 4
 @import 'theme.scss';
5 5
 
6
-.app-theme {
7
-    @include angular-material-theme($app-theme);
6
+.dark-app-theme {
7
+    @include angular-material-theme($dark-app-theme);
8
+    .sidenav-menu-title {
9
+        color: rgb(140, 158, 255) !important;
10
+    }
11
+
12
+    .sidenav-menu-btn-item {
13
+        color: white !important;
14
+    }
15
+
16
+    .sidenav-menu-btn-sub-item {
17
+        color: white !important;
18
+    }
19
+}
20
+
21
+.light-app-theme {
22
+    @include angular-material-theme($light-app-theme);
8 23
 }
9 24
 
10 25
 /*
@@ -28,20 +43,20 @@
28 43
 
29 44
 .sidenav-menu-title {
30 45
     padding-left: 16px !important;
31
-    color: rgb(140, 158, 255) !important;
46
+    //color: rgb(140, 158, 255) !important;
32 47
 }
33 48
 
34 49
 .sidenav-menu-btn-item {
35 50
     width: 100%;
36 51
     text-align: start !important;
37
-    color: white !important;
52
+    //color: white !important;
38 53
 }
39 54
 
40 55
 .sidenav-menu-btn-sub-item {
41 56
     width: 100%;
42 57
     padding-left: 32px !important;
43 58
     text-align: start !important;
44
-    color: white !important;
59
+    //color: white !important;
45 60
 }
46 61
 
47 62
 .sidenav-menu .mat-list-item-content {

+ 17
- 7
frontend/SiteStatus/src/theme.scss 查看文件

@@ -1,9 +1,19 @@
1
-$primaryPalette: mat-palette($mat-grey, 700, 300, 900);
2
-$accentPalette: mat-palette($mat-blue-grey, 400);
3
-$warnPalette: mat-palette($mat-red, 500);
1
+$darkPrimaryPalette: mat-palette($mat-grey, 700, 300, 900);
2
+$darkAccentPalette: mat-palette($mat-blue-grey, 400);
3
+$darkWarnPalette: mat-palette($mat-red, 500);
4 4
 
5
-$app-theme: mat-dark-theme(
6
-                $primaryPalette,
7
-                $accentPalette,
8
-                $warnPalette
5
+$dark-app-theme: mat-dark-theme(
6
+                $darkPrimaryPalette,
7
+                $darkAccentPalette,
8
+                $darkWarnPalette
9
+);
10
+
11
+$lightPrimaryPalette: mat-palette($mat-indigo, 800, 300, 900);
12
+$lightAccentPalette: mat-palette($mat-light-blue);
13
+$lightWarnPalette: mat-palette($mat-pink, 600);
14
+
15
+$light-app-theme: mat-light-theme(
16
+                $lightPrimaryPalette,
17
+                $lightAccentPalette,
18
+                $lightWarnPalette
9 19
 );

+ 2
- 2
frontend/apache2.conf 查看文件

@@ -39,8 +39,8 @@ DocumentRoot "/var/www/html/"
39 39
 </Directory>
40 40
 
41 41
 <Location "/api/">
42
-  ProxyPass http://BACKEND_HOST:BACKEND_PORT/ retry=0
43
-  ProxyPassReverse http://BACKEND_HOST:BACKEND_PORT/
42
+  ProxyPass http://BACKEND_HOST:BACKEND_PORT/api/ retry=0
43
+  ProxyPassReverse http://BACKEND_HOST:BACKEND_PORT/api/
44 44
 </Location>
45 45
 <Location "/signalr/">
46 46
   ProxyPass http://BACKEND_HOST:BACKEND_PORT/signalr/ retry=0

Loading…
取消
儲存