Browse Source

init

master
Robin Thoni 6 years ago
commit
dc7d164080
84 changed files with 12701 additions and 0 deletions
  1. 7
    0
      .gitignore
  2. 41
    0
      backend/Dockerfile
  3. 16
    0
      backend/SiteStatus/SiteStatus.sln
  4. 47
    0
      backend/SiteStatus/SiteStatus/Business/DnsBusiness.cs
  5. 68
    0
      backend/SiteStatus/SiteStatus/Business/HostBusiness.cs
  6. 19
    0
      backend/SiteStatus/SiteStatus/Controllers/SSController.cs
  7. 67
    0
      backend/SiteStatus/SiteStatus/Controllers/StatusController.cs
  8. 13
    0
      backend/SiteStatus/SiteStatus/Dbo/ApiResultDbo.cs
  9. 9
    0
      backend/SiteStatus/SiteStatus/Dbo/DnsZoneServerStatusDbo.cs
  10. 11
    0
      backend/SiteStatus/SiteStatus/Dbo/DnsZoneStatusDbo.cs
  11. 9
    0
      backend/SiteStatus/SiteStatus/Dbo/HostDbo.cs
  12. 13
    0
      backend/SiteStatus/SiteStatus/Dbo/HostStatusDbo.cs
  13. 11
    0
      backend/SiteStatus/SiteStatus/Dbo/SettingsDbo.cs
  14. 11
    0
      backend/SiteStatus/SiteStatus/Dbo/SettingsDnsDbo.cs
  15. 7
    0
      backend/SiteStatus/SiteStatus/Dbo/SettingsDnsZoneDbo.cs
  16. 7
    0
      backend/SiteStatus/SiteStatus/Dbo/SettingsHostDbo.cs
  17. 9
    0
      backend/SiteStatus/SiteStatus/Dbo/SettingsPingDbo.cs
  18. 11
    0
      backend/SiteStatus/SiteStatus/Dbo/SettingsSiteDbo.cs
  19. 11
    0
      backend/SiteStatus/SiteStatus/Dbo/SettingsVpnS2sDbo.cs
  20. 11
    0
      backend/SiteStatus/SiteStatus/Dbo/SiteDbo.cs
  21. 24
    0
      backend/SiteStatus/SiteStatus/Program.cs
  22. 30
    0
      backend/SiteStatus/SiteStatus/Properties/launchSettings.json
  23. 17
    0
      backend/SiteStatus/SiteStatus/SiteStatus.csproj
  24. 54
    0
      backend/SiteStatus/SiteStatus/Startup.cs
  25. 97
    0
      backend/SiteStatus/SiteStatus/appsettings.Development.json
  26. 8
    0
      backend/SiteStatus/SiteStatus/appsettings.json
  27. 42
    0
      backend/run.sh
  28. 0
    0
      backend/vars-files
  29. 0
    0
      backend/vars-vars
  30. 39
    0
      docker-compose.yml
  31. 2
    0
      env
  32. 58
    0
      frontend/Dockerfile
  33. 13
    0
      frontend/SiteStatus/.editorconfig
  34. 39
    0
      frontend/SiteStatus/.gitignore
  35. 27
    0
      frontend/SiteStatus/README.md
  36. 127
    0
      frontend/SiteStatus/angular.json
  37. 28
    0
      frontend/SiteStatus/e2e/protractor.conf.js
  38. 14
    0
      frontend/SiteStatus/e2e/src/app.e2e-spec.ts
  39. 11
    0
      frontend/SiteStatus/e2e/src/app.po.ts
  40. 13
    0
      frontend/SiteStatus/e2e/tsconfig.e2e.json
  41. 10529
    0
      frontend/SiteStatus/package-lock.json
  42. 55
    0
      frontend/SiteStatus/package.json
  43. 6
    0
      frontend/SiteStatus/proxy.conf.json
  44. 14
    0
      frontend/SiteStatus/src/app/app-routing.module.ts
  45. 27
    0
      frontend/SiteStatus/src/app/app.component.css
  46. 53
    0
      frontend/SiteStatus/src/app/app.component.html
  47. 27
    0
      frontend/SiteStatus/src/app/app.component.spec.ts
  48. 43
    0
      frontend/SiteStatus/src/app/app.component.ts
  49. 72
    0
      frontend/SiteStatus/src/app/app.module.ts
  50. 35
    0
      frontend/SiteStatus/src/app/directives/status-led.directive.ts
  51. 6
    0
      frontend/SiteStatus/src/app/models/api-result.model.ts
  52. 5
    0
      frontend/SiteStatus/src/app/models/host-status.model.ts
  53. 6
    0
      frontend/SiteStatus/src/app/models/host.model.ts
  54. 6
    0
      frontend/SiteStatus/src/app/models/site.model.ts
  55. 21
    0
      frontend/SiteStatus/src/app/services/status.service.ts
  56. 12
    0
      frontend/SiteStatus/src/app/view-models/host-status.view-model.ts
  57. 8
    0
      frontend/SiteStatus/src/app/view-models/site.view-model.ts
  58. 5
    0
      frontend/SiteStatus/src/app/view-models/status-enum.view-model.ts
  59. 0
    0
      frontend/SiteStatus/src/app/views/status-component/status.component.css
  60. 49
    0
      frontend/SiteStatus/src/app/views/status-component/status.component.html
  61. 99
    0
      frontend/SiteStatus/src/app/views/status-component/status.component.ts
  62. 0
    0
      frontend/SiteStatus/src/assets/.gitkeep
  63. 6
    0
      frontend/SiteStatus/src/assets/languages/en.json
  64. BIN
      frontend/SiteStatus/src/assets/material-icons.woff2
  65. 9
    0
      frontend/SiteStatus/src/browserslist
  66. 3
    0
      frontend/SiteStatus/src/environments/environment.prod.ts
  67. 15
    0
      frontend/SiteStatus/src/environments/environment.ts
  68. BIN
      frontend/SiteStatus/src/favicon.ico
  69. 16
    0
      frontend/SiteStatus/src/index.html
  70. 31
    0
      frontend/SiteStatus/src/karma.conf.js
  71. 12
    0
      frontend/SiteStatus/src/main.ts
  72. 80
    0
      frontend/SiteStatus/src/polyfills.ts
  73. 73
    0
      frontend/SiteStatus/src/styles.scss
  74. 20
    0
      frontend/SiteStatus/src/test.ts
  75. 9
    0
      frontend/SiteStatus/src/theme.scss
  76. 11
    0
      frontend/SiteStatus/src/tsconfig.app.json
  77. 18
    0
      frontend/SiteStatus/src/tsconfig.spec.json
  78. 17
    0
      frontend/SiteStatus/src/tslint.json
  79. 21
    0
      frontend/SiteStatus/tsconfig.json
  80. 130
    0
      frontend/SiteStatus/tslint.json
  81. 73
    0
      frontend/apache2.conf
  82. 30
    0
      frontend/run.sh
  83. 1
    0
      frontend/vars-files
  84. 7
    0
      frontend/vars-vars

+ 7
- 0
.gitignore View File

@@ -0,0 +1,7 @@
1
+/data
2
+/env_override
3
+/docker-compose.override.yml
4
+
5
+.idea
6
+bin
7
+obj

+ 41
- 0
backend/Dockerfile View File

@@ -0,0 +1,41 @@
1
+FROM microsoft/dotnet:2.1-sdk AS builder
2
+
3
+ARG WEBAPI_PROJECT=WebApi
4
+ARG BUILD_CONFIG=Release
5
+ARG CONFIG_DIR=/etc/default/config-files/
6
+
7
+COPY ./SiteStatus/ /tmp/backend/
8
+
9
+RUN echo "Building ${WEBAPI_PROJECT} in ${BUILD_CONFIG} configuration" && \
10
+    mkdir "${CONFIG_DIR}" &&\
11
+    cd /tmp/backend/ && \
12
+    dotnet restore && \
13
+    mkdir /var/www && \
14
+    cd /tmp/backend/${WEBAPI_PROJECT} && \
15
+    dotnet publish --configuration "${BUILD_CONFIG}" --output /var/www && \
16
+    cp /tmp/backend/${WEBAPI_PROJECT}/appsettings.* "${CONFIG_DIR}" && \
17
+    ln -s /var/www/${WEBAPI_PROJECT}.dll /var/www/__RUN_ME.DLL &&\
18
+    ls /var/www /tmp/backend/SiteStatus/bin/Release/netcoreapp2.1
19
+
20
+FROM microsoft/dotnet:2.1-aspnetcore-runtime
21
+
22
+ARG CONFIG_DIR=/etc/default/config-files/
23
+
24
+RUN rm -rf /var/log/* && \
25
+        mkdir "${CONFIG_DIR}"
26
+
27
+COPY ./vars-vars /etc/vars-vars
28
+
29
+COPY ./vars-files /etc/vars-files
30
+
31
+COPY ./run.sh /run.sh
32
+
33
+COPY --from=builder "${CONFIG_DIR}" "${CONFIG_DIR}"
34
+
35
+COPY --from=builder /var/www /var/www
36
+
37
+EXPOSE 80
38
+
39
+VOLUME ["/data/storage"]
40
+
41
+CMD ["/run.sh"]

+ 16
- 0
backend/SiteStatus/SiteStatus.sln View File

@@ -0,0 +1,16 @@
1
+
2
+Microsoft Visual Studio Solution File, Format Version 12.00
3
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SiteStatus", "SiteStatus\SiteStatus.csproj", "{36005F73-C881-4465-B4E4-7D55FAC5FA55}"
4
+EndProject
5
+Global
6
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
7
+		Debug|Any CPU = Debug|Any CPU
8
+		Release|Any CPU = Release|Any CPU
9
+	EndGlobalSection
10
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
11
+		{36005F73-C881-4465-B4E4-7D55FAC5FA55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12
+		{36005F73-C881-4465-B4E4-7D55FAC5FA55}.Debug|Any CPU.Build.0 = Debug|Any CPU
13
+		{36005F73-C881-4465-B4E4-7D55FAC5FA55}.Release|Any CPU.ActiveCfg = Release|Any CPU
14
+		{36005F73-C881-4465-B4E4-7D55FAC5FA55}.Release|Any CPU.Build.0 = Release|Any CPU
15
+	EndGlobalSection
16
+EndGlobal

+ 47
- 0
backend/SiteStatus/SiteStatus/Business/DnsBusiness.cs View File

@@ -0,0 +1,47 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.Linq;
4
+using System.Net;
5
+using System.Threading.Tasks;
6
+using ARSoft.Tools.Net.Dns;
7
+using SiteStatus.Dbo;
8
+
9
+namespace SiteStatus.Business
10
+{
11
+    public class DnsBusiness
12
+    {
13
+        public async Task<DnsZoneStatusDbo> GetZoneStatusAsync(string zone, IEnumerable<string> servers)
14
+        {
15
+            var dnsStatus = new DnsZoneStatusDbo
16
+            {
17
+                Zone = zone,
18
+                ServerStatus = new List<DnsZoneServerStatusDbo>()
19
+            };
20
+
21
+            foreach (var server in servers)
22
+            {
23
+                try
24
+                {
25
+                    var resolver = new DnsStubResolver(new []{IPAddress.Parse(server)});
26
+                    var records = resolver.Resolve<SoaRecord>(zone, RecordType.Soa);
27
+                    var record = records.FirstOrDefault();
28
+                    dnsStatus.ServerStatus.Add(new DnsZoneServerStatusDbo
29
+                    {
30
+                        Server = server,
31
+                        Soa = record?.ToString()
32
+                    });
33
+                }
34
+                catch (Exception e)
35
+                {
36
+                    dnsStatus.ServerStatus.Add(new DnsZoneServerStatusDbo
37
+                    {
38
+                        Server = server,
39
+                        Soa = null
40
+                    });
41
+                }
42
+            }
43
+
44
+            return dnsStatus;
45
+        }
46
+    }
47
+}

+ 68
- 0
backend/SiteStatus/SiteStatus/Business/HostBusiness.cs View File

@@ -0,0 +1,68 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.Net;
4
+using System.Net.NetworkInformation;
5
+using System.Threading.Tasks;
6
+using SiteStatus.Dbo;
7
+
8
+namespace SiteStatus.Business
9
+{
10
+    public class HostBusiness
11
+    {
12
+        public async Task<HostStatusDbo> GetHostStatusAsync(string host, int count, int timeout)
13
+        {
14
+            var hostStatus = new HostStatusDbo
15
+            {
16
+                Ip = host,
17
+                Times = new List<long?>()
18
+            };
19
+
20
+            var tasks = new List<Task<PingReply>>();
21
+
22
+            for (var i = 0; i < count; ++i)
23
+            {
24
+                try
25
+                {
26
+                    var ping = new Ping();
27
+                    var pingTask = ping.SendPingAsync(host, timeout);
28
+                    tasks.Add(pingTask);
29
+                }
30
+                catch (Exception e)
31
+                {
32
+                    hostStatus.Times.Add(null);
33
+                }
34
+            }
35
+
36
+            foreach (var pingResult in await Task.WhenAll(tasks))
37
+            {
38
+                if (pingResult != null && pingResult.Status == IPStatus.Success)
39
+                {
40
+                    hostStatus.Times.Add(pingResult.RoundtripTime);
41
+                }
42
+                else
43
+                {
44
+                    hostStatus.Times.Add(null);
45
+                }
46
+            }
47
+
48
+            try
49
+            {
50
+                var dnsResult = await Dns.GetHostEntryAsync(host);
51
+                if (dnsResult != null)
52
+                {
53
+                    hostStatus.Hostname = dnsResult.HostName;
54
+                }
55
+                else
56
+                {
57
+                    hostStatus.Hostname = null;
58
+                }
59
+            }
60
+            catch (Exception e)
61
+            {
62
+                hostStatus.Hostname = null;
63
+            }
64
+
65
+            return hostStatus;
66
+        }
67
+    }
68
+}

+ 19
- 0
backend/SiteStatus/SiteStatus/Controllers/SSController.cs View File

@@ -0,0 +1,19 @@
1
+using Microsoft.AspNetCore.Mvc;
2
+using SiteStatus.Dbo;
3
+
4
+namespace SiteStatus.Controllers
5
+{
6
+    public class SSController : ControllerBase
7
+    {
8
+        public ApiResultDbo<T> Handle<T>(T data)
9
+        {
10
+            return new ApiResultDbo<T>
11
+            {
12
+                Code = 200,
13
+                Data = data,
14
+                Message = null,
15
+                Version = "1.0.0"
16
+            };
17
+        }
18
+    }
19
+}

+ 67
- 0
backend/SiteStatus/SiteStatus/Controllers/StatusController.cs View File

@@ -0,0 +1,67 @@
1
+using System.Collections.Generic;
2
+using System.Linq;
3
+using System.Threading.Tasks;
4
+using Microsoft.AspNetCore.Mvc;
5
+using Microsoft.Extensions.Options;
6
+using SiteStatus.Business;
7
+using SiteStatus.Dbo;
8
+
9
+namespace SiteStatus.Controllers
10
+{
11
+
12
+    [Route("api/[controller]")]
13
+    [ApiController]
14
+    public class StatusController : SSController
15
+    {
16
+        protected HostBusiness HostBusiness { get; }
17
+        public DnsBusiness DnsBusiness { get; }
18
+
19
+        public SettingsDbo Options { get; }
20
+
21
+        public StatusController(HostBusiness hostBusiness, DnsBusiness dnsBusiness, IOptions<SettingsDbo> options)
22
+        {
23
+            HostBusiness = hostBusiness;
24
+            DnsBusiness = dnsBusiness;
25
+            Options = options.Value;
26
+        }
27
+
28
+        [HttpGet("/api/[controller]/hosts")]
29
+        public ApiResultDbo<List<SiteDbo>> GetHosts()
30
+        {
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);
45
+
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);
52
+        }
53
+
54
+        [HttpGet("/api/[controller]/dns")]
55
+        public ApiResultDbo<List<DnsZoneStatusDbo>> GetDns()
56
+        {
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();
63
+
64
+            return Handle(results);
65
+        }
66
+    }
67
+}

+ 13
- 0
backend/SiteStatus/SiteStatus/Dbo/ApiResultDbo.cs View File

@@ -0,0 +1,13 @@
1
+namespace SiteStatus.Dbo
2
+{
3
+    public class ApiResultDbo<TDbo>
4
+    {
5
+        public int Code { get; set; }
6
+
7
+        public string Message { get; set; }
8
+        
9
+        public TDbo Data { get; set; }
10
+
11
+        public string Version { get; set; }
12
+    }
13
+}

+ 9
- 0
backend/SiteStatus/SiteStatus/Dbo/DnsZoneServerStatusDbo.cs View File

@@ -0,0 +1,9 @@
1
+namespace SiteStatus.Dbo
2
+{
3
+    public class DnsZoneServerStatusDbo
4
+    {
5
+        public string Server { get; set; }
6
+
7
+        public string Soa { get; set; }
8
+    }
9
+}

+ 11
- 0
backend/SiteStatus/SiteStatus/Dbo/DnsZoneStatusDbo.cs View File

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

+ 9
- 0
backend/SiteStatus/SiteStatus/Dbo/HostDbo.cs View File

@@ -0,0 +1,9 @@
1
+namespace SiteStatus.Dbo
2
+{
3
+    public class HostDbo
4
+    {
5
+        public string Ip { get; set; }
6
+
7
+        public HostStatusDbo HostStatus { get; set; }
8
+    }
9
+}

+ 13
- 0
backend/SiteStatus/SiteStatus/Dbo/HostStatusDbo.cs View File

@@ -0,0 +1,13 @@
1
+using System.Collections.Generic;
2
+
3
+namespace SiteStatus.Dbo
4
+{
5
+    public class HostStatusDbo
6
+    {
7
+        public string Ip { get; set; }
8
+
9
+        public string Hostname { get; set; }
10
+
11
+        public IList<long?> Times { get; set; }
12
+    }
13
+}

+ 11
- 0
backend/SiteStatus/SiteStatus/Dbo/SettingsDbo.cs View File

@@ -0,0 +1,11 @@
1
+using System.Collections.Generic;
2
+
3
+namespace SiteStatus.Dbo
4
+{
5
+    public class SettingsDbo
6
+    {
7
+        public SettingsVpnS2sDbo VpnS2s { get; set; }
8
+
9
+        public SettingsDnsDbo Dns { get; set; }
10
+    }
11
+}

+ 11
- 0
backend/SiteStatus/SiteStatus/Dbo/SettingsDnsDbo.cs View File

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

+ 7
- 0
backend/SiteStatus/SiteStatus/Dbo/SettingsDnsZoneDbo.cs View File

@@ -0,0 +1,7 @@
1
+namespace SiteStatus.Dbo
2
+{
3
+    public class SettingsDnsZoneDbo
4
+    {
5
+        public string Name { get; set; }
6
+    }
7
+}

+ 7
- 0
backend/SiteStatus/SiteStatus/Dbo/SettingsHostDbo.cs View File

@@ -0,0 +1,7 @@
1
+namespace SiteStatus.Dbo
2
+{
3
+    public class SettingsHostDbo
4
+    {
5
+        public string Ip { get; set; }
6
+    }
7
+}

+ 9
- 0
backend/SiteStatus/SiteStatus/Dbo/SettingsPingDbo.cs View File

@@ -0,0 +1,9 @@
1
+namespace SiteStatus.Dbo
2
+{
3
+    public class SettingsPingDbo
4
+    {
5
+        public int Timeout { get; set; }
6
+
7
+        public int Count { get; set; }
8
+    }
9
+}

+ 11
- 0
backend/SiteStatus/SiteStatus/Dbo/SettingsSiteDbo.cs View File

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

+ 11
- 0
backend/SiteStatus/SiteStatus/Dbo/SettingsVpnS2sDbo.cs View File

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

+ 11
- 0
backend/SiteStatus/SiteStatus/Dbo/SiteDbo.cs View File

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

+ 24
- 0
backend/SiteStatus/SiteStatus/Program.cs View File

@@ -0,0 +1,24 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.IO;
4
+using System.Linq;
5
+using System.Threading.Tasks;
6
+using Microsoft.AspNetCore;
7
+using Microsoft.AspNetCore.Hosting;
8
+using Microsoft.Extensions.Configuration;
9
+using Microsoft.Extensions.Logging;
10
+
11
+namespace SiteStatus
12
+{
13
+    public class Program
14
+    {
15
+        public static void Main(string[] args)
16
+        {
17
+            CreateWebHostBuilder(args).Build().Run();
18
+        }
19
+
20
+        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
21
+            WebHost.CreateDefaultBuilder(args)
22
+                .UseStartup<Startup>();
23
+    }
24
+}

+ 30
- 0
backend/SiteStatus/SiteStatus/Properties/launchSettings.json View File

@@ -0,0 +1,30 @@
1
+{
2
+  "$schema": "http://json.schemastore.org/launchsettings.json",
3
+  "iisSettings": {
4
+    "windowsAuthentication": false,
5
+    "anonymousAuthentication": true,
6
+    "iisExpress": {
7
+      "applicationUrl": "http://localhost:64577",
8
+      "sslPort": 44397
9
+    }
10
+  },
11
+  "profiles": {
12
+    "IIS Express": {
13
+      "commandName": "IISExpress",
14
+      "launchBrowser": true,
15
+      "launchUrl": "api/values",
16
+      "environmentVariables": {
17
+        "ASPNETCORE_ENVIRONMENT": "Development"
18
+      }
19
+    },
20
+    "SiteStatus": {
21
+      "commandName": "Project",
22
+      "launchBrowser": true,
23
+      "launchUrl": "api/values",
24
+      "applicationUrl": "https://localhost:5001;http://localhost:5000",
25
+      "environmentVariables": {
26
+        "ASPNETCORE_ENVIRONMENT": "Development"
27
+      }
28
+    }
29
+  }
30
+}

+ 17
- 0
backend/SiteStatus/SiteStatus/SiteStatus.csproj View File

@@ -0,0 +1,17 @@
1
+<Project Sdk="Microsoft.NET.Sdk.Web">
2
+  <PropertyGroup>
3
+    <TargetFramework>netcoreapp2.1</TargetFramework>
4
+  </PropertyGroup>
5
+  <ItemGroup>
6
+    <Folder Include="wwwroot\" />
7
+  </ItemGroup>
8
+  <ItemGroup>
9
+    <PackageReference Include="ARSoft.Tools.Net">
10
+      <Version>2.2.9</Version>
11
+    </PackageReference>
12
+    <PackageReference Include="Microsoft.AspNetCore.App" />
13
+    <PackageReference Include="System.Net.Ping">
14
+      <Version>4.3.0</Version>
15
+    </PackageReference>
16
+  </ItemGroup>
17
+</Project>

+ 54
- 0
backend/SiteStatus/SiteStatus/Startup.cs View File

@@ -0,0 +1,54 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.Linq;
4
+using System.Threading.Tasks;
5
+using Microsoft.AspNetCore.Builder;
6
+using Microsoft.AspNetCore.Hosting;
7
+using Microsoft.AspNetCore.HttpsPolicy;
8
+using Microsoft.AspNetCore.Mvc;
9
+using Microsoft.Extensions.Configuration;
10
+using Microsoft.Extensions.DependencyInjection;
11
+using Microsoft.Extensions.Logging;
12
+using Microsoft.Extensions.Options;
13
+using SiteStatus.Business;
14
+using SiteStatus.Dbo;
15
+
16
+namespace SiteStatus
17
+{
18
+    public class Startup
19
+    {
20
+        public Startup(IConfiguration configuration)
21
+        {
22
+            Configuration = configuration;
23
+        }
24
+
25
+        public IConfiguration Configuration { get; }
26
+
27
+        // This method gets called by the runtime. Use this method to add services to the container.
28
+        public void ConfigureServices(IServiceCollection services)
29
+        {
30
+            services.Configure<SettingsDbo>(options => Configuration.GetSection("Settings").Bind(options));
31
+
32
+            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
33
+
34
+            services.AddSingleton<HostBusiness>();
35
+            services.AddSingleton<DnsBusiness>();
36
+        }
37
+
38
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
39
+        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
40
+        {
41
+            if (env.IsDevelopment())
42
+            {
43
+                app.UseDeveloperExceptionPage();
44
+            }
45
+            else
46
+            {
47
+//                app.UseHsts();
48
+            }
49
+
50
+//            app.UseHttpsRedirection();
51
+            app.UseMvc();
52
+        }
53
+    }
54
+}

+ 97
- 0
backend/SiteStatus/SiteStatus/appsettings.Development.json View File

@@ -0,0 +1,97 @@
1
+{
2
+  "Logging": {
3
+    "LogLevel": {
4
+      "Default": "Debug",
5
+      "System": "Information",
6
+      "Microsoft": "Information"
7
+    }
8
+  },
9
+  "Settings": {
10
+    "VpnS2S": {
11
+      "Ping":
12
+      {
13
+        "Timeout": 1000,
14
+        "Count": 1
15
+      },
16
+      "Sites": [
17
+        {
18
+          "Name": "ars",
19
+          "Hosts": [
20
+            { "Ip": "10.15.0.3" },
21
+            { "Ip": "10.15.0.20" }
22
+          ]
23
+        },
24
+        {
25
+          "Name": "lesse",
26
+          "Hosts": [
27
+            { "Ip": "10.15.8.3" },
28
+            { "Ip": "10.15.8.20" }
29
+          ]
30
+        },
31
+        {
32
+          "Name": "cambridge",
33
+          "Hosts": [
34
+            { "Ip": "10.15.16.3" }
35
+          ]
36
+        },
37
+        {
38
+          "Name": "aws-eu-central1",
39
+          "Hosts": [
40
+            { "Ip": "10.15.120.10" },
41
+            { "Ip": "10.15.120.11" },
42
+            { "Ip": "10.15.120.15" }
43
+          ]
44
+        },
45
+        {
46
+          "Name": "ovh-rbx5",
47
+          "Hosts": [
48
+            { "Ip": "10.15.128.10" },
49
+            { "Ip": "10.15.128.15" }
50
+          ]
51
+        },
52
+        {
53
+          "Name": null,
54
+          "Hosts": [
55
+            { "Ip": "8.8.8.8" }
56
+          ]
57
+        }
58
+      ]
59
+    },
60
+    "Dns":
61
+    {
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
+              ]
95
+    }
96
+  }
97
+}

+ 8
- 0
backend/SiteStatus/SiteStatus/appsettings.json View File

@@ -0,0 +1,8 @@
1
+{
2
+  "Logging": {
3
+    "LogLevel": {
4
+      "Default": "Warning"
5
+    }
6
+  },
7
+  "AllowedHosts": "*"
8
+}

+ 42
- 0
backend/run.sh View File

@@ -0,0 +1,42 @@
1
+#! /usr/bin/env bash
2
+
3
+export CONFIG_DIR="/etc/default/config-files/"
4
+
5
+replace_var()
6
+{
7
+  file="${1}"
8
+  var="${2}"
9
+  sed -e "s?${var}?${!var}?g" -i "${file}"
10
+}
11
+
12
+replace_vars()
13
+{
14
+  file="${1}"
15
+  for var in $(cat /etc/vars-vars)
16
+  do
17
+    replace_var "${file}" "${var}"
18
+  done
19
+}
20
+
21
+replace_files()
22
+{
23
+  cat /etc/vars-files | while read line
24
+  do
25
+    filesrc="${CONFIG_DIR}$(echo "${line}" | awk '{print $1}')"
26
+    filedst=$(echo "${line}" | awk '{print $2}')
27
+    if [ -f "${filesrc}" ]
28
+    then
29
+      echo "Expanding file ${filesrc} to ${filedst}"
30
+      cp "${filesrc}" "${filedst}"
31
+      replace_vars "${filedst}"
32
+    else
33
+      echo "File ${filesrc} does not exist. Skipping."
34
+    fi
35
+  done
36
+}
37
+
38
+replace_files
39
+
40
+cd /var/www &&
41
+dotnet __RUN_ME.DLL
42
+  sleep infinity

+ 0
- 0
backend/vars-files View File


+ 0
- 0
backend/vars-vars View File


+ 39
- 0
docker-compose.yml View File

@@ -0,0 +1,39 @@
1
+version: '2'
2
+
3
+services:
4
+    frontend:
5
+        build: ./frontend
6
+        image: docker-registry.rthoni.com/sites-status-frontend-image:dev
7
+        container_name: sites-status-frontend
8
+        networks:
9
+            sites-status.internal.docker:
10
+                aliases:
11
+                    - frontend.sites-status.internal.docker
12
+        ports:
13
+            - "127.0.0.1:34041:80"
14
+        env_file:
15
+            - env
16
+
17
+    backend:
18
+        build:
19
+            context: ./backend
20
+            args:
21
+                - WEBAPI_PROJECT=SiteStatus
22
+                - BUILD_CONFIG=Release
23
+        image: docker-registry.rthoni.com/sites-status-backend-image:dev
24
+        container_name: sites-status-backend
25
+        networks:
26
+            sites-status.internal.docker:
27
+                aliases:
28
+                    - backend.sites-status.internal.docker
29
+        volumes:
30
+            - ./data/backend/storage:/data/storage
31
+        env_file:
32
+            - env
33
+        environment:
34
+            - ASPNETCORE_ENVIRONMENT=Production
35
+            - ASPNETCORE_URLS=http://0.0.0.0:8080
36
+
37
+
38
+networks:
39
+    sites-status.internal.docker:

+ 2
- 0
env View File

@@ -0,0 +1,2 @@
1
+BACKEND_HOST=backend.sites-status.internal.docker
2
+BACKEND_PORT=8080

+ 58
- 0
frontend/Dockerfile View File

@@ -0,0 +1,58 @@
1
+FROM debian:jessie AS builder
2
+
3
+# FROM https://github.com/ZHAJOR/Docker-Apache-2.4-Php-5.6-for-Laravel
4
+MAINTAINER Robin Thoni <robin@rthoni.com>
5
+
6
+RUN apt-get update && apt-get -y install \
7
+        git \
8
+        curl && \
9
+        apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/ && \
10
+        curl https://deb.nodesource.com/setup_7.x | bash && \
11
+        ln -s /usr/bin/nodejs /usr/bin/node
12
+
13
+RUN apt-get update && apt-get -y install \
14
+        nodejs && \
15
+        apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/
16
+
17
+RUN npm install -g bower && \
18
+        npm install -g grunt
19
+
20
+COPY ./SiteStatus/ /tmp/frontend
21
+
22
+RUN cd /tmp/frontend && \
23
+    npm install && \
24
+    bower --allow-root install && \
25
+    for gruntfile in Gruntfile_*; do grunt --gruntfile "${gruntfile}"; done && \
26
+    mkdir -p /var/www/ && \
27
+    cp -r /tmp/frontend/build/release/ /var/www/html && \
28
+    rm -rf /tmp/frontend
29
+
30
+FROM debian:jessie
31
+
32
+RUN apt-get update && apt-get -y install \
33
+        apache2=2.4.* && \
34
+        apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/
35
+
36
+RUN rm -rf /var/log/* &&\
37
+    mkdir -p /var/log/apache2 &&\
38
+    ln -s /dev/stderr /var/log/apache2/error.log &&\
39
+    ln -s /dev/stdout /var/log/apache2/access.log &&\
40
+    ln -s /dev/stdout /var/log/apache2/other_vhosts_access.log &&\
41
+    /usr/sbin/a2enmod rewrite proxy_http proxy_wstunnel &&\
42
+    rm -rf /var/www/html &&\
43
+    mkdir -p /var/lock/apache2 /var/run/apache2 /var/log/apache2 /var/www/html &&\
44
+    chown -R www-data:www-data /var/lock/apache2 /var/run/apache2 /var/log/apache2 /var/www/html
45
+
46
+COPY apache2.conf /etc/apache2/apache2.conf
47
+
48
+COPY ./vars-vars /etc/vars-vars
49
+
50
+COPY ./vars-files /etc/vars-files
51
+
52
+COPY ./run.sh /run.sh
53
+
54
+COPY --from=builder /var/www/html /var/www/html
55
+
56
+EXPOSE 80
57
+
58
+CMD ["/run.sh"]

+ 13
- 0
frontend/SiteStatus/.editorconfig View File

@@ -0,0 +1,13 @@
1
+# Editor configuration, see http://editorconfig.org
2
+root = true
3
+
4
+[*]
5
+charset = utf-8
6
+indent_style = space
7
+indent_size = 2
8
+insert_final_newline = true
9
+trim_trailing_whitespace = true
10
+
11
+[*.md]
12
+max_line_length = off
13
+trim_trailing_whitespace = false

+ 39
- 0
frontend/SiteStatus/.gitignore View File

@@ -0,0 +1,39 @@
1
+# See http://help.github.com/ignore-files/ for more about ignoring files.
2
+
3
+# compiled output
4
+/dist
5
+/tmp
6
+/out-tsc
7
+
8
+# dependencies
9
+/node_modules
10
+
11
+# IDEs and editors
12
+/.idea
13
+.project
14
+.classpath
15
+.c9/
16
+*.launch
17
+.settings/
18
+*.sublime-workspace
19
+
20
+# IDE - VSCode
21
+.vscode/*
22
+!.vscode/settings.json
23
+!.vscode/tasks.json
24
+!.vscode/launch.json
25
+!.vscode/extensions.json
26
+
27
+# misc
28
+/.sass-cache
29
+/connect.lock
30
+/coverage
31
+/libpeerconnection.log
32
+npm-debug.log
33
+yarn-error.log
34
+testem.log
35
+/typings
36
+
37
+# System Files
38
+.DS_Store
39
+Thumbs.db

+ 27
- 0
frontend/SiteStatus/README.md View File

@@ -0,0 +1,27 @@
1
+# SiteStatus
2
+
3
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.1.4.
4
+
5
+## Development server
6
+
7
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8
+
9
+## Code scaffolding
10
+
11
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12
+
13
+## Build
14
+
15
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
16
+
17
+## Running unit tests
18
+
19
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20
+
21
+## Running end-to-end tests
22
+
23
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
24
+
25
+## Further help
26
+
27
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

+ 127
- 0
frontend/SiteStatus/angular.json View File

@@ -0,0 +1,127 @@
1
+{
2
+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3
+  "version": 1,
4
+  "newProjectRoot": "projects",
5
+  "projects": {
6
+    "SiteStatus": {
7
+      "root": "",
8
+      "sourceRoot": "src",
9
+      "projectType": "application",
10
+      "prefix": "app",
11
+      "schematics": {},
12
+      "architect": {
13
+        "build": {
14
+          "builder": "@angular-devkit/build-angular:browser",
15
+          "options": {
16
+            "outputPath": "dist/SiteStatus",
17
+            "index": "src/index.html",
18
+            "main": "src/main.ts",
19
+            "polyfills": "src/polyfills.ts",
20
+            "tsConfig": "src/tsconfig.app.json",
21
+            "assets": [
22
+              "src/favicon.ico",
23
+              "src/assets"
24
+            ],
25
+            "styles": [
26
+              "src/styles.scss"
27
+            ],
28
+            "scripts": []
29
+          },
30
+          "configurations": {
31
+            "production": {
32
+              "fileReplacements": [
33
+                {
34
+                  "replace": "src/environments/environment.ts",
35
+                  "with": "src/environments/environment.prod.ts"
36
+                }
37
+              ],
38
+              "optimization": true,
39
+              "outputHashing": "all",
40
+              "sourceMap": false,
41
+              "extractCss": true,
42
+              "namedChunks": false,
43
+              "aot": true,
44
+              "extractLicenses": true,
45
+              "vendorChunk": false,
46
+              "buildOptimizer": true
47
+            }
48
+          }
49
+        },
50
+        "serve": {
51
+          "builder": "@angular-devkit/build-angular:dev-server",
52
+          "options": {
53
+            "browserTarget": "SiteStatus:build"
54
+          },
55
+          "configurations": {
56
+            "production": {
57
+              "browserTarget": "SiteStatus:build:production"
58
+            }
59
+          }
60
+        },
61
+        "extract-i18n": {
62
+          "builder": "@angular-devkit/build-angular:extract-i18n",
63
+          "options": {
64
+            "browserTarget": "SiteStatus:build"
65
+          }
66
+        },
67
+        "test": {
68
+          "builder": "@angular-devkit/build-angular:karma",
69
+          "options": {
70
+            "main": "src/test.ts",
71
+            "polyfills": "src/polyfills.ts",
72
+            "tsConfig": "src/tsconfig.spec.json",
73
+            "karmaConfig": "src/karma.conf.js",
74
+            "styles": [
75
+              "src/styles.css"
76
+            ],
77
+            "scripts": [],
78
+            "assets": [
79
+              "src/favicon.ico",
80
+              "src/assets"
81
+            ]
82
+          }
83
+        },
84
+        "lint": {
85
+          "builder": "@angular-devkit/build-angular:tslint",
86
+          "options": {
87
+            "tsConfig": [
88
+              "src/tsconfig.app.json",
89
+              "src/tsconfig.spec.json"
90
+            ],
91
+            "exclude": [
92
+              "**/node_modules/**"
93
+            ]
94
+          }
95
+        }
96
+      }
97
+    },
98
+    "SiteStatus-e2e": {
99
+      "root": "e2e/",
100
+      "projectType": "application",
101
+      "architect": {
102
+        "e2e": {
103
+          "builder": "@angular-devkit/build-angular:protractor",
104
+          "options": {
105
+            "protractorConfig": "e2e/protractor.conf.js",
106
+            "devServerTarget": "SiteStatus:serve"
107
+          },
108
+          "configurations": {
109
+            "production": {
110
+              "devServerTarget": "SiteStatus:serve:production"
111
+            }
112
+          }
113
+        },
114
+        "lint": {
115
+          "builder": "@angular-devkit/build-angular:tslint",
116
+          "options": {
117
+            "tsConfig": "e2e/tsconfig.e2e.json",
118
+            "exclude": [
119
+              "**/node_modules/**"
120
+            ]
121
+          }
122
+        }
123
+      }
124
+    }
125
+  },
126
+  "defaultProject": "SiteStatus"
127
+}

+ 28
- 0
frontend/SiteStatus/e2e/protractor.conf.js View File

@@ -0,0 +1,28 @@
1
+// Protractor configuration file, see link for more information
2
+// https://github.com/angular/protractor/blob/master/lib/config.ts
3
+
4
+const { SpecReporter } = require('jasmine-spec-reporter');
5
+
6
+exports.config = {
7
+  allScriptsTimeout: 11000,
8
+  specs: [
9
+    './src/**/*.e2e-spec.ts'
10
+  ],
11
+  capabilities: {
12
+    'browserName': 'chrome'
13
+  },
14
+  directConnect: true,
15
+  baseUrl: 'http://localhost:4200/',
16
+  framework: 'jasmine',
17
+  jasmineNodeOpts: {
18
+    showColors: true,
19
+    defaultTimeoutInterval: 30000,
20
+    print: function() {}
21
+  },
22
+  onPrepare() {
23
+    require('ts-node').register({
24
+      project: require('path').join(__dirname, './tsconfig.e2e.json')
25
+    });
26
+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27
+  }
28
+};

+ 14
- 0
frontend/SiteStatus/e2e/src/app.e2e-spec.ts View File

@@ -0,0 +1,14 @@
1
+import { AppPage } from './app.po';
2
+
3
+describe('workspace-project App', () => {
4
+  let page: AppPage;
5
+
6
+  beforeEach(() => {
7
+    page = new AppPage();
8
+  });
9
+
10
+  it('should display welcome message', () => {
11
+    page.navigateTo();
12
+    expect(page.getParagraphText()).toEqual('Welcome to SiteStatus!');
13
+  });
14
+});

+ 11
- 0
frontend/SiteStatus/e2e/src/app.po.ts View File

@@ -0,0 +1,11 @@
1
+import { browser, by, element } from 'protractor';
2
+
3
+export class AppPage {
4
+  navigateTo() {
5
+    return browser.get('/');
6
+  }
7
+
8
+  getParagraphText() {
9
+    return element(by.css('app-root h1')).getText();
10
+  }
11
+}

+ 13
- 0
frontend/SiteStatus/e2e/tsconfig.e2e.json View File

@@ -0,0 +1,13 @@
1
+{
2
+  "extends": "../tsconfig.json",
3
+  "compilerOptions": {
4
+    "outDir": "../out-tsc/app",
5
+    "module": "commonjs",
6
+    "target": "es5",
7
+    "types": [
8
+      "jasmine",
9
+      "jasminewd2",
10
+      "node"
11
+    ]
12
+  }
13
+}

+ 10529
- 0
frontend/SiteStatus/package-lock.json
File diff suppressed because it is too large
View File


+ 55
- 0
frontend/SiteStatus/package.json View File

@@ -0,0 +1,55 @@
1
+{
2
+  "name": "site-status",
3
+  "version": "0.0.0",
4
+  "scripts": {
5
+    "ng": "ng",
6
+    "start": "ng serve --proxy-config proxy.conf.json --host=0.0.0.0 --disable-host-check",
7
+    "build": "ng build",
8
+    "test": "ng test",
9
+    "lint": "ng lint",
10
+    "e2e": "ng e2e"
11
+  },
12
+  "private": true,
13
+  "dependencies": {
14
+    "@angular/animations": "^6.1.3",
15
+    "@angular/cdk": "^6.4.5",
16
+    "@angular/common": "^6.1.0",
17
+    "@angular/compiler": "^6.1.0",
18
+    "@angular/core": "^6.1.0",
19
+    "@angular/flex-layout": "^6.0.0-beta.17",
20
+    "@angular/forms": "^6.1.0",
21
+    "@angular/http": "^6.1.0",
22
+    "@angular/material": "^6.4.5",
23
+    "@angular/platform-browser": "^6.1.0",
24
+    "@angular/platform-browser-dynamic": "^6.1.0",
25
+    "@angular/router": "^6.1.0",
26
+    "@ngrx/store": "^6.1.0",
27
+    "@ngx-translate/core": "^10.0.2",
28
+    "@ngx-translate/http-loader": "^3.0.1",
29
+    "angular-busy2": "^7.0.0",
30
+    "core-js": "^2.5.4",
31
+    "rxjs": "^6.0.0",
32
+    "zone.js": "~0.8.26"
33
+  },
34
+  "devDependencies": {
35
+    "@angular-devkit/build-angular": "~0.7.0",
36
+    "@angular/cli": "~6.1.4",
37
+    "@angular/compiler-cli": "^6.1.0",
38
+    "@angular/language-service": "^6.1.0",
39
+    "@types/jasmine": "~2.8.6",
40
+    "@types/jasminewd2": "~2.0.3",
41
+    "@types/node": "~8.9.4",
42
+    "codelyzer": "~4.2.1",
43
+    "jasmine-core": "~2.99.1",
44
+    "jasmine-spec-reporter": "~4.2.1",
45
+    "karma": "~1.7.1",
46
+    "karma-chrome-launcher": "~2.2.0",
47
+    "karma-coverage-istanbul-reporter": "~2.0.0",
48
+    "karma-jasmine": "~1.1.1",
49
+    "karma-jasmine-html-reporter": "^0.2.2",
50
+    "protractor": "~5.4.0",
51
+    "ts-node": "~5.0.1",
52
+    "tslint": "~5.9.1",
53
+    "typescript": "~2.7.2"
54
+  }
55
+}

+ 6
- 0
frontend/SiteStatus/proxy.conf.json View File

@@ -0,0 +1,6 @@
1
+{
2
+  "/api": {
3
+    "target": "http://localhost:5000",
4
+    "secure": false
5
+  }
6
+}

+ 14
- 0
frontend/SiteStatus/src/app/app-routing.module.ts View File

@@ -0,0 +1,14 @@
1
+import { NgModule } from '@angular/core';
2
+import { RouterModule, Routes } from '@angular/router';
3
+import {StatusComponent} from './views/status-component/status.component';
4
+
5
+const routes: Routes = [
6
+    { path: '', redirectTo: '/status', pathMatch: 'full' },
7
+    { path: 'status', component: StatusComponent },
8
+];
9
+
10
+@NgModule({
11
+    imports: [ RouterModule.forRoot(routes, { useHash: true }) ],
12
+    exports: [ RouterModule ]
13
+})
14
+export class AppRoutingModule {}

+ 27
- 0
frontend/SiteStatus/src/app/app.component.css View File

@@ -0,0 +1,27 @@
1
+@font-face {
2
+    font-family: 'Material Icons';
3
+    font-style: normal;
4
+    font-weight: 400;
5
+    src: url(../assets/material-icons.woff2) format('woff2');
6
+}
7
+
8
+.material-icons {
9
+    font-family: 'Material Icons';
10
+    font-weight: normal;
11
+    font-style: normal;
12
+    font-size: 24px;
13
+    line-height: 1;
14
+    letter-spacing: normal;
15
+    text-transform: none;
16
+    display: inline-block;
17
+    white-space: nowrap;
18
+    word-wrap: normal;
19
+    direction: ltr;
20
+    -webkit-font-feature-settings: 'liga';
21
+    -webkit-font-smoothing: antialiased;
22
+}
23
+
24
+footer {
25
+    /*background: #efefef;*/
26
+    padding: 15px;
27
+}

+ 53
- 0
frontend/SiteStatus/src/app/app.component.html View File

@@ -0,0 +1,53 @@
1
+<mat-sidenav-container class="all-wrap" fullscreen>
2
+    <mat-sidenav [mode]="uiStatus.sideMenu.mode" [opened]="uiStatus.sideMenu.opened" #sidenav>
3
+        <header>
4
+            <mat-toolbar color="primary">
5
+                {{ 'common.appName' | translate }}
6
+            </mat-toolbar>
7
+        </header>
8
+        <!--<h4>Test</h4>-->
9
+        <mat-list class="sidenav-menu">
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>
12
+            <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
+            <div style="height: auto; overflow: hidden">
14
+                <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-sub-item">SUB STUFF 1</a></mat-list-item>
15
+                <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-sub-item">SUB STUFF 2</a></mat-list-item>
16
+                <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-sub-item">SUB STUFF 3</a></mat-list-item>
17
+            </div>
18
+            <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-item">SOME OTHER STUFF  <mat-icon >keyboard_arrow_down</mat-icon></a></mat-list-item>
19
+            <div style="height: auto; overflow: hidden">
20
+                <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-sub-item">SUB STUFF 1</a></mat-list-item>
21
+                <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-sub-item">SUB STUFF 2</a></mat-list-item>
22
+                <mat-list-item><a [routerLink]="['/']" mat-button class="sidenav-menu-btn-sub-item">SUB STUFF 3</a></mat-list-item>
23
+            </div>
24
+        </mat-list>
25
+    </mat-sidenav>
26
+
27
+    <div class="page-wrap">
28
+        <header role="banner">
29
+            <mat-toolbar color="primary">
30
+                <button *ngIf="uiStatus.toolbar.displayMenuButton"
31
+                        type="button"
32
+                        mat-icon-button
33
+                        (click)="sidenav.open()"
34
+                        title="Open sidenav">
35
+                    <mat-icon>menu</mat-icon>
36
+                </button>
37
+                {{ 'common.appName' | translate }}
38
+            </mat-toolbar>
39
+        </header>
40
+        <main class="content">
41
+            <router-outlet></router-outlet>
42
+        </main>
43
+        <footer [innerHTML]="'common.footer' | translate">
44
+        </footer>
45
+    </div>
46
+</mat-sidenav-container>
47
+
48
+<ng-template #cgBusyTemplate let-options="options">
49
+    <div class="custom-template">
50
+        <mat-progress-bar mode="indeterminate">
51
+        </mat-progress-bar>
52
+    </div>
53
+</ng-template>

+ 27
- 0
frontend/SiteStatus/src/app/app.component.spec.ts View File

@@ -0,0 +1,27 @@
1
+import { TestBed, async } from '@angular/core/testing';
2
+import { AppComponent } from './app.component';
3
+describe('AppComponent', () => {
4
+  beforeEach(async(() => {
5
+    TestBed.configureTestingModule({
6
+      declarations: [
7
+        AppComponent
8
+      ],
9
+    }).compileComponents();
10
+  }));
11
+  it('should create the app', async(() => {
12
+    const fixture = TestBed.createComponent(AppComponent);
13
+    const app = fixture.debugElement.componentInstance;
14
+    expect(app).toBeTruthy();
15
+  }));
16
+  it(`should have as title 'SiteStatus'`, async(() => {
17
+    const fixture = TestBed.createComponent(AppComponent);
18
+    const app = fixture.debugElement.componentInstance;
19
+    expect(app.title).toEqual('SiteStatus');
20
+  }));
21
+  it('should render title in a h1 tag', async(() => {
22
+    const fixture = TestBed.createComponent(AppComponent);
23
+    fixture.detectChanges();
24
+    const compiled = fixture.debugElement.nativeElement;
25
+    expect(compiled.querySelector('h1').textContent).toContain('Welcome to SiteStatus!');
26
+  }));
27
+});

+ 43
- 0
frontend/SiteStatus/src/app/app.component.ts View File

@@ -0,0 +1,43 @@
1
+import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core';
2
+import {MediaChange, ObservableMedia} from '@angular/flex-layout';
3
+import {CgBusyDefaults} from 'angular-busy2';
4
+
5
+@Component({
6
+  selector: 'app-root',
7
+  templateUrl: './app.component.html',
8
+  styleUrls: ['./app.component.css']
9
+})
10
+export class AppComponent implements OnInit {
11
+    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
+
23
+    @ViewChild('cgBusyTemplate')
24
+    private cgBusyTemplate: TemplateRef<any>;
25
+
26
+    constructor(media: ObservableMedia, private busyDefaults: CgBusyDefaults) {
27
+        this.mediaWatcher = media.subscribe((change: MediaChange) => {
28
+            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;
32
+            } else {
33
+                this.uiStatus.sideMenu.opened = true;
34
+                this.uiStatus.sideMenu.mode = 'side';
35
+                this.uiStatus.toolbar.displayMenuButton = false;
36
+            }
37
+        });
38
+    }
39
+
40
+    ngOnInit() {
41
+        this.busyDefaults.templateRef = this.cgBusyTemplate;
42
+    }
43
+}

+ 72
- 0
frontend/SiteStatus/src/app/app.module.ts View File

@@ -0,0 +1,72 @@
1
+import { BrowserModule } from '@angular/platform-browser';
2
+import { NgModule } from '@angular/core';
3
+
4
+import { AppComponent } from './app.component';
5
+import {AppRoutingModule} from './app-routing.module';
6
+import {HttpClient, HttpClientModule} from '@angular/common/http';
7
+import {StatusComponent} from './views/status-component/status.component';
8
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
9
+import {
10
+    MatButtonModule,
11
+    MatCheckboxModule,
12
+    MatIconModule,
13
+    MatListModule,
14
+    MatSidenavModule,
15
+    MatToolbarModule
16
+} from '@angular/material';
17
+import {MatProgressBarModule} from '@angular/material/progress-bar';
18
+import {MatExpansionModule} from '@angular/material/expansion';
19
+import {MatTableModule} from '@angular/material/table';
20
+import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
21
+import {TranslateHttpLoader} from '@ngx-translate/http-loader';
22
+import {FlexLayoutModule} from '@angular/flex-layout';
23
+import {CgBusyModule} from 'angular-busy2';
24
+import {StatusLedDirective} from './directives/status-led.directive';
25
+
26
+export function HttpLoaderFactory(http: HttpClient) {
27
+    return new TranslateHttpLoader(http, './assets/languages/');
28
+}
29
+
30
+@NgModule({
31
+  declarations: [
32
+    AppComponent,
33
+    StatusComponent,
34
+    StatusLedDirective
35
+  ],
36
+  imports: [
37
+    BrowserModule,
38
+    AppRoutingModule,
39
+    HttpClientModule,
40
+    BrowserAnimationsModule,
41
+    MatButtonModule,
42
+    MatCheckboxModule,
43
+    MatToolbarModule,
44
+    MatSidenavModule,
45
+    MatIconModule,
46
+    MatListModule,
47
+    MatProgressBarModule,
48
+    MatExpansionModule,
49
+    MatTableModule,
50
+    TranslateModule.forRoot({
51
+        loader: {
52
+            provide: TranslateLoader,
53
+                useFactory: HttpLoaderFactory,
54
+                deps: [HttpClient]
55
+        }
56
+    }),
57
+    FlexLayoutModule,
58
+    CgBusyModule
59
+  ],
60
+  providers: [],
61
+  bootstrap: [AppComponent]
62
+})
63
+export class AppModule {
64
+
65
+    constructor(translate: TranslateService) {
66
+        // this language will be used as a fallback when a translation isn't found in the current language
67
+        translate.setDefaultLang('en');
68
+
69
+        // the lang to use, if the lang isn't available, it will use the current loader to get them
70
+        translate.use('en');
71
+    }
72
+}

+ 35
- 0
frontend/SiteStatus/src/app/directives/status-led.directive.ts View File

@@ -0,0 +1,35 @@
1
+import {Directive, ElementRef, Input, OnChanges, SimpleChanges} from '@angular/core';
2
+import {StatusEnumViewModel} from '../view-models/status-enum.view-model';
3
+
4
+@Directive({
5
+    selector: '[appStatusLed]'
6
+})
7
+export class StatusLedDirective implements OnChanges {
8
+    @Input()
9
+    appStatusLed: string;
10
+
11
+    constructor(private el: ElementRef) {
12
+        const size = 15;
13
+        this.el.nativeElement.style.width = `${size}px`;
14
+        this.el.nativeElement.style.height = `${size}px`;
15
+        this.el.nativeElement.style.borderRadius = `${size / 2}px`;
16
+        this.el.nativeElement.style.display = 'inline-block';
17
+    }
18
+
19
+    static colorFromValue(value: StatusEnumViewModel): string {
20
+        if (value === StatusEnumViewModel.Ok) {
21
+            return '#01ff01';
22
+        } else if (value === StatusEnumViewModel.Warning) {
23
+            return '#ffa500';
24
+        } else if (value === StatusEnumViewModel.Error) {
25
+            return '#ff0000';
26
+        }
27
+        return null;
28
+    }
29
+
30
+    ngOnChanges(changes: SimpleChanges): void {
31
+        if (changes.appStatusLed) {
32
+            this.el.nativeElement.style.backgroundColor = StatusLedDirective.colorFromValue(changes.appStatusLed.currentValue);
33
+        }
34
+    }
35
+}

+ 6
- 0
frontend/SiteStatus/src/app/models/api-result.model.ts View File

@@ -0,0 +1,6 @@
1
+export class ApiResultModel<T> {
2
+    code: number;
3
+    message: string;
4
+    data: T;
5
+    version: string;
6
+}

+ 5
- 0
frontend/SiteStatus/src/app/models/host-status.model.ts View File

@@ -0,0 +1,5 @@
1
+export class HostStatusModel {
2
+    ip: string;
3
+    hostname: string;
4
+    times: number[];
5
+}

+ 6
- 0
frontend/SiteStatus/src/app/models/host.model.ts View File

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

+ 6
- 0
frontend/SiteStatus/src/app/models/site.model.ts View File

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

+ 21
- 0
frontend/SiteStatus/src/app/services/status.service.ts View File

@@ -0,0 +1,21 @@
1
+import {HttpClient, HttpHeaders} from '@angular/common/http';
2
+import {Injectable} from '@angular/core';
3
+import {Observable} from 'rxjs';
4
+import {SiteModel} from '../models/site.model';
5
+import {ApiResultModel} from '../models/api-result.model';
6
+
7
+const httpPostOptions = {
8
+    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
9
+};
10
+
11
+@Injectable({ providedIn: 'root' })
12
+export class StatusService {
13
+    private urlBase = 'api/status';
14
+
15
+    constructor(
16
+        private http: HttpClient) { }
17
+
18
+        getHosts(): Observable<ApiResultModel<SiteModel[]>> {
19
+            return this.http.get<ApiResultModel<SiteModel[]>>(`${this.urlBase}/hosts`);
20
+        }
21
+}

+ 12
- 0
frontend/SiteStatus/src/app/view-models/host-status.view-model.ts View File

@@ -0,0 +1,12 @@
1
+import {StatusEnumViewModel} from './status-enum.view-model';
2
+
3
+export class HostStatusViewModel {
4
+    ip: string;
5
+    hostname: string;
6
+    times: number[];
7
+    timesMin: number;
8
+    timesAvg: number;
9
+    timesMax: number;
10
+    timesLost: number;
11
+    status: StatusEnumViewModel;
12
+}

+ 8
- 0
frontend/SiteStatus/src/app/view-models/site.view-model.ts View File

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

+ 5
- 0
frontend/SiteStatus/src/app/view-models/status-enum.view-model.ts View File

@@ -0,0 +1,5 @@
1
+export enum StatusEnumViewModel {
2
+    Ok,
3
+    Warning,
4
+    Error
5
+}

+ 0
- 0
frontend/SiteStatus/src/app/views/status-component/status.component.css View File


+ 49
- 0
frontend/SiteStatus/src/app/views/status-component/status.component.html View File

@@ -0,0 +1,49 @@
1
+<div [cgBusy]="promise">
2
+
3
+    <mat-expansion-panel *ngFor="let hostStatus of hostsStatus">
4
+        <mat-expansion-panel-header>
5
+            <mat-panel-title>
6
+                <span [appStatusLed]="hostStatus.status"></span>&nbsp;<strong>{{ hostStatus.name == null ? "Internet" : hostStatus.name }}</strong>
7
+            </mat-panel-title>
8
+            <mat-panel-description>
9
+            </mat-panel-description>
10
+        </mat-expansion-panel-header>
11
+
12
+        <table mat-table [dataSource]="hostStatus.hosts" class="mat-elevation-z0">
13
+            <ng-container matColumnDef="status">
14
+                <th mat-header-cell *matHeaderCellDef> Status </th>
15
+                <td mat-cell *matCellDef="let element">  <span [appStatusLed]="element.status"></span> </td>
16
+            </ng-container>
17
+            <ng-container matColumnDef="ip">
18
+                <th mat-header-cell *matHeaderCellDef> IP </th>
19
+                <td mat-cell *matCellDef="let element"> {{element.ip}} </td>
20
+            </ng-container>
21
+            <ng-container matColumnDef="hostname">
22
+                <th mat-header-cell *matHeaderCellDef> Hostname </th>
23
+                <td mat-cell *matCellDef="let element"> {{element.hostname}} </td>
24
+            </ng-container>
25
+            <ng-container matColumnDef="timesMin">
26
+                <th mat-header-cell *matHeaderCellDef> Min </th>
27
+                <td mat-cell *matCellDef="let element"> {{element.timesMin}} </td>
28
+            </ng-container>
29
+            <ng-container matColumnDef="timesAvg">
30
+                <th mat-header-cell *matHeaderCellDef> Avg </th>
31
+                <td mat-cell *matCellDef="let element"> {{element.timesAvg}} </td>
32
+            </ng-container>
33
+            <ng-container matColumnDef="timesMax">
34
+                <th mat-header-cell *matHeaderCellDef> Max </th>
35
+                <td mat-cell *matCellDef="let element"> {{element.timesMax}} </td>
36
+            </ng-container>
37
+            <ng-container matColumnDef="timesLost">
38
+                <th mat-header-cell *matHeaderCellDef> Lost </th>
39
+                <td mat-cell *matCellDef="let element"> {{element.timesLost}} </td>
40
+            </ng-container>
41
+
42
+            <tr mat-header-row *matHeaderRowDef="['status', 'ip', 'hostname', 'timesMin', 'timesAvg', 'timesMax', 'timesLost']"></tr>
43
+            <tr mat-row *matRowDef="let row; columns: ['status', 'ip', 'hostname', 'timesMin', 'timesAvg', 'timesMax', 'timesLost'];"></tr>
44
+        </table>
45
+
46
+    </mat-expansion-panel>
47
+
48
+    <button (click)="onclick()" mat-button mat-raised-button>Reload</button>
49
+</div>

+ 99
- 0
frontend/SiteStatus/src/app/views/status-component/status.component.ts View File

@@ -0,0 +1,99 @@
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/assets/.gitkeep View File


+ 6
- 0
frontend/SiteStatus/src/assets/languages/en.json View File

@@ -0,0 +1,6 @@
1
+{
2
+  "common": {
3
+    "appName": "Site Status",
4
+    "footer": "Developed by <a href=\"https://www.rthoni.com\">Robin Thoni</a>"
5
+  }
6
+}

BIN
frontend/SiteStatus/src/assets/material-icons.woff2 View File


+ 9
- 0
frontend/SiteStatus/src/browserslist View File

@@ -0,0 +1,9 @@
1
+# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2
+# For additional information regarding the format and rule options, please see:
3
+# https://github.com/browserslist/browserslist#queries
4
+# For IE 9-11 support, please uncomment the last line of the file and adjust as needed
5
+> 0.5%
6
+last 2 versions
7
+Firefox ESR
8
+not dead
9
+# IE 9-11

+ 3
- 0
frontend/SiteStatus/src/environments/environment.prod.ts View File

@@ -0,0 +1,3 @@
1
+export const environment = {
2
+  production: true
3
+};

+ 15
- 0
frontend/SiteStatus/src/environments/environment.ts View File

@@ -0,0 +1,15 @@
1
+// This file can be replaced during build by using the `fileReplacements` array.
2
+// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
3
+// The list of file replacements can be found in `angular.json`.
4
+
5
+export const environment = {
6
+  production: false
7
+};
8
+
9
+/*
10
+ * In development mode, for easier debugging, you can ignore zone related error
11
+ * stack frames such as `zone.run`/`zoneDelegate.invokeTask` by importing the
12
+ * below file. Don't forget to comment it out in production mode
13
+ * because it will have a performance impact when errors are thrown
14
+ */
15
+// import 'zone.js/dist/zone-error';  // Included with Angular CLI.

BIN
frontend/SiteStatus/src/favicon.ico View File


+ 16
- 0
frontend/SiteStatus/src/index.html View File

@@ -0,0 +1,16 @@
1
+<!doctype html>
2
+<html lang="en">
3
+<head>
4
+  <meta charset="utf-8">
5
+  <title>Site Status</title>
6
+  <base href="/">
7
+
8
+  <meta name="viewport" content="width=device-width, initial-scale=1">
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
+</head>
13
+<body class="app-theme">
14
+  <app-root></app-root>
15
+</body>
16
+</html>

+ 31
- 0
frontend/SiteStatus/src/karma.conf.js View File

@@ -0,0 +1,31 @@
1
+// Karma configuration file, see link for more information
2
+// https://karma-runner.github.io/1.0/config/configuration-file.html
3
+
4
+module.exports = function (config) {
5
+  config.set({
6
+    basePath: '',
7
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],
8
+    plugins: [
9
+      require('karma-jasmine'),
10
+      require('karma-chrome-launcher'),
11
+      require('karma-jasmine-html-reporter'),
12
+      require('karma-coverage-istanbul-reporter'),
13
+      require('@angular-devkit/build-angular/plugins/karma')
14
+    ],
15
+    client: {
16
+      clearContext: false // leave Jasmine Spec Runner output visible in browser
17
+    },
18
+    coverageIstanbulReporter: {
19
+      dir: require('path').join(__dirname, '../coverage'),
20
+      reports: ['html', 'lcovonly'],
21
+      fixWebpackSourcePaths: true
22
+    },
23
+    reporters: ['progress', 'kjhtml'],
24
+    port: 9876,
25
+    colors: true,
26
+    logLevel: config.LOG_INFO,
27
+    autoWatch: true,
28
+    browsers: ['Chrome'],
29
+    singleRun: false
30
+  });
31
+};

+ 12
- 0
frontend/SiteStatus/src/main.ts View File

@@ -0,0 +1,12 @@
1
+import { enableProdMode } from '@angular/core';
2
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3
+
4
+import { AppModule } from './app/app.module';
5
+import { environment } from './environments/environment';
6
+
7
+if (environment.production) {
8
+  enableProdMode();
9
+}
10
+
11
+platformBrowserDynamic().bootstrapModule(AppModule)
12
+  .catch(err => console.log(err));

+ 80
- 0
frontend/SiteStatus/src/polyfills.ts View File

@@ -0,0 +1,80 @@
1
+/**
2
+ * This file includes polyfills needed by Angular and is loaded before the app.
3
+ * You can add your own extra polyfills to this file.
4
+ *
5
+ * This file is divided into 2 sections:
6
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
8
+ *      file.
9
+ *
10
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13
+ *
14
+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15
+ */
16
+
17
+/***************************************************************************************************
18
+ * BROWSER POLYFILLS
19
+ */
20
+
21
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/
22
+// import 'core-js/es6/symbol';
23
+// import 'core-js/es6/object';
24
+// import 'core-js/es6/function';
25
+// import 'core-js/es6/parse-int';
26
+// import 'core-js/es6/parse-float';
27
+// import 'core-js/es6/number';
28
+// import 'core-js/es6/math';
29
+// import 'core-js/es6/string';
30
+// import 'core-js/es6/date';
31
+// import 'core-js/es6/array';
32
+// import 'core-js/es6/regexp';
33
+// import 'core-js/es6/map';
34
+// import 'core-js/es6/weak-map';
35
+// import 'core-js/es6/set';
36
+
37
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
38
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
39
+
40
+/** IE10 and IE11 requires the following for the Reflect API. */
41
+// import 'core-js/es6/reflect';
42
+
43
+
44
+/** Evergreen browsers require these. **/
45
+// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46
+import 'core-js/es7/reflect';
47
+
48
+
49
+/**
50
+ * Web Animations `@angular/platform-browser/animations`
51
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
52
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
53
+ **/
54
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
55
+
56
+/**
57
+ * By default, zone.js will patch all possible macroTask and DomEvents
58
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
59
+ */
60
+
61
+ // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
62
+ // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
63
+ // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
64
+
65
+ /*
66
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
67
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
68
+ */
69
+// (window as any).__Zone_enable_cross_context_check = true;
70
+
71
+/***************************************************************************************************
72
+ * Zone JS is required by default for Angular itself.
73
+ */
74
+import 'zone.js/dist/zone';  // Included with Angular CLI.
75
+
76
+
77
+
78
+/***************************************************************************************************
79
+ * APPLICATION IMPORTS
80
+ */

+ 73
- 0
frontend/SiteStatus/src/styles.scss View File

@@ -0,0 +1,73 @@
1
+@import '~@angular/material/theming';
2
+@include mat-core();
3
+
4
+@import 'theme.scss';
5
+
6
+.app-theme {
7
+    @include angular-material-theme($app-theme);
8
+}
9
+
10
+/*
11
+ * Actual Sticky Footer Styles
12
+ */
13
+.all-wrap {
14
+    min-height: 100vh;
15
+}
16
+
17
+.page-wrap {
18
+    display: flex;
19
+    flex-direction: column;
20
+    min-height: 100vh;
21
+    margin-left: 10px;
22
+}
23
+
24
+.content {
25
+    margin-top: 10px;
26
+    flex: 1;
27
+}
28
+
29
+.sidenav-menu-title {
30
+    padding-left: 16px !important;
31
+    color: rgb(140, 158, 255) !important;
32
+}
33
+
34
+.sidenav-menu-btn-item {
35
+    width: 100%;
36
+    text-align: start !important;
37
+    color: white !important;
38
+}
39
+
40
+.sidenav-menu-btn-sub-item {
41
+    width: 100%;
42
+    padding-left: 32px !important;
43
+    text-align: start !important;
44
+    color: white !important;
45
+}
46
+
47
+.sidenav-menu .mat-list-item-content {
48
+    padding: 0 !important;
49
+}
50
+
51
+.sidenav-menu .mat-list-item {
52
+    height: 36px !important;
53
+}
54
+
55
+/*
56
+ * Make the Component injected by Router Outlet full height:
57
+ */
58
+main {
59
+    display: flex;
60
+    flex-direction: column;
61
+    > *:not(router-outlet) {
62
+        flex: 1;
63
+        display: block;
64
+    }
65
+}
66
+
67
+footer {
68
+    text-align: center;
69
+}
70
+
71
+table {
72
+    width: 100%;
73
+}

+ 20
- 0
frontend/SiteStatus/src/test.ts View File

@@ -0,0 +1,20 @@
1
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
2
+
3
+import 'zone.js/dist/zone-testing';
4
+import { getTestBed } from '@angular/core/testing';
5
+import {
6
+  BrowserDynamicTestingModule,
7
+  platformBrowserDynamicTesting
8
+} from '@angular/platform-browser-dynamic/testing';
9
+
10
+declare const require: any;
11
+
12
+// First, initialize the Angular testing environment.
13
+getTestBed().initTestEnvironment(
14
+  BrowserDynamicTestingModule,
15
+  platformBrowserDynamicTesting()
16
+);
17
+// Then we find all the tests.
18
+const context = require.context('./', true, /\.spec\.ts$/);
19
+// And load the modules.
20
+context.keys().map(context);

+ 9
- 0
frontend/SiteStatus/src/theme.scss View File

@@ -0,0 +1,9 @@
1
+$primaryPalette: mat-palette($mat-grey, 700, 300, 900);
2
+$accentPalette: mat-palette($mat-blue-grey, 400);
3
+$warnPalette: mat-palette($mat-red, 500);
4
+
5
+$app-theme: mat-dark-theme(
6
+                $primaryPalette,
7
+                $accentPalette,
8
+                $warnPalette
9
+);

+ 11
- 0
frontend/SiteStatus/src/tsconfig.app.json View File

@@ -0,0 +1,11 @@
1
+{
2
+  "extends": "../tsconfig.json",
3
+  "compilerOptions": {
4
+    "outDir": "../out-tsc/app",
5
+    "types": []
6
+  },
7
+  "exclude": [
8
+    "test.ts",
9
+    "**/*.spec.ts"
10
+  ]
11
+}

+ 18
- 0
frontend/SiteStatus/src/tsconfig.spec.json View File

@@ -0,0 +1,18 @@
1
+{
2
+  "extends": "../tsconfig.json",
3
+  "compilerOptions": {
4
+    "outDir": "../out-tsc/spec",
5
+    "types": [
6
+      "jasmine",
7
+      "node"
8
+    ]
9
+  },
10
+  "files": [
11
+    "test.ts",
12
+    "polyfills.ts"
13
+  ],
14
+  "include": [
15
+    "**/*.spec.ts",
16
+    "**/*.d.ts"
17
+  ]
18
+}

+ 17
- 0
frontend/SiteStatus/src/tslint.json View File

@@ -0,0 +1,17 @@
1
+{
2
+    "extends": "../tslint.json",
3
+    "rules": {
4
+        "directive-selector": [
5
+            true,
6
+            "attribute",
7
+            "app",
8
+            "camelCase"
9
+        ],
10
+        "component-selector": [
11
+            true,
12
+            "element",
13
+            "app",
14
+            "kebab-case"
15
+        ]
16
+    }
17
+}

+ 21
- 0
frontend/SiteStatus/tsconfig.json View File

@@ -0,0 +1,21 @@
1
+{
2
+  "compileOnSave": false,
3
+  "compilerOptions": {
4
+    "baseUrl": "./",
5
+    "outDir": "./dist/out-tsc",
6
+    "sourceMap": true,
7
+    "declaration": false,
8
+    "module": "es2015",
9
+    "moduleResolution": "node",
10
+    "emitDecoratorMetadata": true,
11
+    "experimentalDecorators": true,
12
+    "target": "es5",
13
+    "typeRoots": [
14
+      "node_modules/@types"
15
+    ],
16
+    "lib": [
17
+      "es2017",
18
+      "dom"
19
+    ]
20
+  }
21
+}

+ 130
- 0
frontend/SiteStatus/tslint.json View File

@@ -0,0 +1,130 @@
1
+{
2
+  "rulesDirectory": [
3
+    "node_modules/codelyzer"
4
+  ],
5
+  "rules": {
6
+    "arrow-return-shorthand": true,
7
+    "callable-types": true,
8
+    "class-name": true,
9
+    "comment-format": [
10
+      true,
11
+      "check-space"
12
+    ],
13
+    "curly": true,
14
+    "deprecation": {
15
+      "severity": "warn"
16
+    },
17
+    "eofline": true,
18
+    "forin": true,
19
+    "import-blacklist": [
20
+      true,
21
+      "rxjs/Rx"
22
+    ],
23
+    "import-spacing": true,
24
+    "indent": [
25
+      true,
26
+      "spaces"
27
+    ],
28
+    "interface-over-type-literal": true,
29
+    "label-position": true,
30
+    "max-line-length": [
31
+      true,
32
+      140
33
+    ],
34
+    "member-access": false,
35
+    "member-ordering": [
36
+      true,
37
+      {
38
+        "order": [
39
+          "static-field",
40
+          "instance-field",
41
+          "static-method",
42
+          "instance-method"
43
+        ]
44
+      }
45
+    ],
46
+    "no-arg": true,
47
+    "no-bitwise": true,
48
+    "no-console": [
49
+      true,
50
+      "debug",
51
+      "info",
52
+      "time",
53
+      "timeEnd",
54
+      "trace"
55
+    ],
56
+    "no-construct": true,
57
+    "no-debugger": true,
58
+    "no-duplicate-super": true,
59
+    "no-empty": false,
60
+    "no-empty-interface": true,
61
+    "no-eval": true,
62
+    "no-inferrable-types": [
63
+      true,
64
+      "ignore-params"
65
+    ],
66
+    "no-misused-new": true,
67
+    "no-non-null-assertion": true,
68
+    "no-shadowed-variable": true,
69
+    "no-string-literal": false,
70
+    "no-string-throw": true,
71
+    "no-switch-case-fall-through": true,
72
+    "no-trailing-whitespace": true,
73
+    "no-unnecessary-initializer": true,
74
+    "no-unused-expression": true,
75
+    "no-use-before-declare": true,
76
+    "no-var-keyword": true,
77
+    "object-literal-sort-keys": false,
78
+    "one-line": [
79
+      true,
80
+      "check-open-brace",
81
+      "check-catch",
82
+      "check-else",
83
+      "check-whitespace"
84
+    ],
85
+    "prefer-const": true,
86
+    "quotemark": [
87
+      true,
88
+      "single"
89
+    ],
90
+    "radix": true,
91
+    "semicolon": [
92
+      true,
93
+      "always"
94
+    ],
95
+    "triple-equals": [
96
+      true,
97
+      "allow-null-check"
98
+    ],
99
+    "typedef-whitespace": [
100
+      true,
101
+      {
102
+        "call-signature": "nospace",
103
+        "index-signature": "nospace",
104
+        "parameter": "nospace",
105
+        "property-declaration": "nospace",
106
+        "variable-declaration": "nospace"
107
+      }
108
+    ],
109
+    "unified-signatures": true,
110
+    "variable-name": false,
111
+    "whitespace": [
112
+      true,
113
+      "check-branch",
114
+      "check-decl",
115
+      "check-operator",
116
+      "check-separator",
117
+      "check-type"
118
+    ],
119
+    "no-output-on-prefix": true,
120
+    "use-input-property-decorator": true,
121
+    "use-output-property-decorator": true,
122
+    "use-host-property-decorator": true,
123
+    "no-input-rename": true,
124
+    "no-output-rename": true,
125
+    "use-life-cycle-interface": true,
126
+    "use-pipe-transform-interface": true,
127
+    "component-class-suffix": true,
128
+    "directive-class-suffix": true
129
+  }
130
+}

+ 73
- 0
frontend/apache2.conf View File

@@ -0,0 +1,73 @@
1
+# see http://sources.debian.net/src/apache2/2.4.10-1/debian/config-dir/apache2.conf
2
+
3
+Mutex file:/var/lock/apache2 default
4
+PidFile /var/run/apache2/apache2.pid
5
+Timeout 300
6
+KeepAlive On
7
+MaxKeepAliveRequests 100
8
+KeepAliveTimeout 5
9
+User www-data
10
+Group www-data
11
+HostnameLookups Off
12
+ErrorLog /var/log/apache2/error.log
13
+LogLevel warn
14
+
15
+IncludeOptional mods-enabled/*.load
16
+IncludeOptional mods-enabled/*.conf
17
+
18
+# ports.conf
19
+Listen 80
20
+<IfModule ssl_module>
21
+    Listen 443
22
+</IfModule>
23
+<IfModule mod_gnutls.c>
24
+    Listen 443
25
+</IfModule>
26
+
27
+DocumentRoot "/var/www/html/"
28
+
29
+<Directory />
30
+    Options FollowSymLinks
31
+    AllowOverride None
32
+    Require all denied
33
+</Directory>
34
+
35
+<Directory /var/www/html/>
36
+    Options FollowSymLinks
37
+    AllowOverride All
38
+    Require all granted
39
+</Directory>
40
+
41
+<Location "/api/">
42
+  ProxyPass http://BACKEND_HOST:BACKEND_PORT/ retry=0
43
+  ProxyPassReverse http://BACKEND_HOST:BACKEND_PORT/
44
+</Location>
45
+<Location "/signalr/">
46
+  ProxyPass http://BACKEND_HOST:BACKEND_PORT/signalr/ retry=0
47
+  ProxyPassReverse http://BACKEND_HOST:BACKEND_PORT/signalr/
48
+</Location>
49
+<Location "/signalr/connect">
50
+  ProxyPass ws://BACKEND_HOST:BACKEND_PORT/signalr/connect retry=0
51
+</Location>
52
+<Location "/signalr/reconnect">
53
+  ProxyPass ws://BACKEND_HOST:BACKEND_PORT/signalr/reconnect retry=0
54
+</Location>
55
+
56
+
57
+LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
58
+LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
59
+LogFormat "%h %l %u %t \"%r\" %>s %O" common
60
+LogFormat "%{Referer}i -> %U" referer
61
+LogFormat "%{User-agent}i" agent
62
+
63
+CustomLog /var/log/apache2/access.log combined
64
+
65
+<FilesMatch \.php$>
66
+    SetHandler application/x-httpd-php
67
+</FilesMatch>
68
+
69
+# Multiple DirectoryIndex directives within the same context will add
70
+# to the list of resources to look for rather than replace
71
+# https://httpd.apache.org/docs/current/mod/mod_dir.html#directoryindex
72
+DirectoryIndex disabled
73
+DirectoryIndex index.php index.html

+ 30
- 0
frontend/run.sh View File

@@ -0,0 +1,30 @@
1
+#! /usr/bin/env bash
2
+
3
+replace_var()
4
+{
5
+  file="${1}"
6
+  var="${2}"
7
+  sed -e "s?${var}?${!var}?g" -i "${file}"
8
+}
9
+
10
+replace_vars()
11
+{
12
+  file="${1}"
13
+  for var in $(cat /etc/vars-vars)
14
+  do
15
+    replace_var "${file}" "${var}"
16
+  done
17
+}
18
+
19
+replace_files()
20
+{
21
+  for file in $(cat /etc/vars-files)
22
+  do
23
+    replace_vars "${file}"
24
+  done
25
+}
26
+
27
+replace_files
28
+
29
+rm -f /run/apache2/apache2.pid
30
+exec /usr/sbin/apache2ctl -D FOREGROUND

+ 1
- 0
frontend/vars-files View File

@@ -0,0 +1 @@
1
+/etc/apache2/apache2.conf

+ 7
- 0
frontend/vars-vars View File

@@ -0,0 +1,7 @@
1
+POSTGRES_HOST
2
+POSTGRES_USER
3
+POSTGRES_PASSWORD
4
+POSTGRES_DB
5
+
6
+BACKEND_HOST
7
+BACKEND_PORT

Loading…
Cancel
Save