19 |
20 |
21 |
22 | 
23 |
24 | We all know that Microsoft's recommended approach for analysing Azure Firewall logs is to set up a Log Analytics Workspace to collect all the data and use Kusto (KQL) queries to check the results.
25 |
26 | Azure-Firewall-mon focuses more on providing a tool that can answer the simple question "_what is happening right now?_" in an alternative and hopefully practical way: the idea is to provide an approach much more like [Sysinternals Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon) or [Check Point's SmartView/SmartLog](https://sc1.checkpoint.com/documents/R80.40/WebAdminGuides/EN/CP_R80.40_LoggingAndMonitoring_AdminGuide/Topics-LMG/Using-log-view.htm?tocpath=Logging%7C_____2), where there is no KUSTO queries or dashboards that you need to implement first to get working. Still, all events are available as a _log-stream_.
27 |
28 | The real strength of the tool is the search field available in the top toolbar. To search for an event, simply start typing and the log flow will be automatically filtered according to those parameters.
29 |
30 | 
31 |
32 | The timestamp field displays the event date in UTC or local format. You can filter the view for the last few minutes or for a specific time range.
33 |
34 | 
35 |
36 | Within this tool, only events from the last 24 hours will appear because this is the duration set on the Event Hub Namespace. A longer duration would slow down the tool and not help answer the question "_what is happening right now_" that az-firewall-mon aims to address.
37 |
38 | As an alternative to full-text search, you can use the **chatGPT mode**: in the top search field, you can enter a request in natural language, and the system will filter the content accordingly.
39 |
40 | Some examples of queries are as follows:
41 |
42 | * "_Show me events from the last 15 minutes_"
43 | * "_Search project alpha_"
44 | * "_Filter rows with category containing "NetworkRule"_"
45 | * "_Filter events between 12:00 and 13:00_"
46 | * "_Filter for target containing 192.168.1.1_"
47 | * "_Include only logs with protocol TCP_"
48 | * "_Show me only the deny actions_"
49 | * "_More information on source 192.168.1.1_"
50 |
51 | 
52 |
53 | # Setup a connection with your Azure Firewall
54 |
55 | 
56 |
57 | Azure-Firewall-mon is an open source, [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application), written in [Angular](https://angular.io/).
58 |
59 | To use this app with **YOUR data**, you must perform the following steps on your Azure Subscription:
60 |
61 | 1. Create an Azure Event Hub Namespace
62 | 2. Create an Azure Event Hub inside the namespace, with a `1-day retention` and `1 partition`
63 | 3. Create a Shared Access Policy, with _Listen_ claim
64 | 4. Create an Azure Map Account
65 | 5. Create an Azure OpenAI Service
66 | 6. Go to OpenAI Studio > Deployments > Create a new deployment using as model `gpt-4o version 2024-05-13`
67 | 7. Open the Azure Firewall instance you want to monitor, go to Monitoring > Diagnostic Settings > Add Diagnostic Settings:
68 |
69 | - Select _all_ _logs_ and "Stream to Event Hub"
70 | - Select the Event Hub Namespace and Hub created above
71 | - click `SAVE`
72 |
73 | Lazy engineers can performs steps from 1 to 6 by clicking the following button [](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fnicolgit%2Fazure-firewall-mon%2Fmain%2Fbicep%2Ffirewall-mon-azure-stuff.json) :-)
74 |
75 | Now, open and do the following:
76 |
77 | 1. copy in the `Event Hub Connection String` field the connection string of the Shared Access Policy created above
78 | 2. copy the corresponding `Event Hub Consumer Group` Name
79 | 3. copy in the `Azure Map Account Shared Key` field the primary or secondary Shared Key of the Azure Map Account created above
80 | 4. copy in the `Azure OpenAi Endpoint` field the enpoint URI for the OpenAI resouce created above
81 | 5. copy in the `Azure OpenAI deployment` field tne name of the deployment created above
82 | 6. copy in the `Azure OpenAI access key` field the primary or secondary Shared Key of the Azure OpenAI account created above
83 | 7. click on `Let's begin`.
84 |
85 | # Install Azure-firewall-mon in your environment
86 |
87 | [@lukemurraynz](https://github.com/lukemurraynz) has written a very detailed blog post on how deploy Azure-Firewall-mon in an Azure Static Web App. If you prefer this approach, have a look at his blog post
88 |
89 | > NOTE: `environment.prod.ts` must be updated with your environment information. az-firewall-mon requires an [Application Insights](https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview) instance to work properly.
90 |
91 | # More Information
92 |
93 | [Azure Firewall](https://learn.microsoft.com/en-us/azure/firewall/overview) (AF) is a cloud-native and intelligent network firewall security service that provides the best of breed threat protection for your cloud workloads running in Azure. It's a fully stateful, firewall as a service with built-in high availability and unrestricted cloud scalability. It provides both east-west and north-south traffic inspection.
94 |
95 | [Azure Monitor](https://learn.microsoft.com/en-us/azure/azure-monitor/overview) helps you maximize the availability and performance of your applications and services. It delivers a comprehensive solution for collecting, analyzing, and acting on telemetry from your cloud and on-premises environments.
96 |
97 | AF (Azure-Firewall-Mon) is integrated with Azure Monitor. This means you can forward AF metrics and logs to:
98 |
99 | * Log Analytics Workspace
100 | * Azure Storage
101 | * Event hub
102 |
103 | A [Log Analytics workspace](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-workspace-overview) is a unique environment for log data from Azure Monitor and other Azure services. Each workspace has its own data repository and configuration but might combine data from multiple services.
104 |
105 | Be mindful, that the ingest of logs into a Log Analytics workspace has some Latency, so you may see a delay with the logs displaying.
106 |
107 | Latency refers to the time that data is created on the monitored system and the time that it comes available for analysis in Azure Monitor.
108 |
109 | The [Kusto](https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/) Query Language is a tool to explore your data in a Log Analytics Workspace. The query uses schema entities that are organized in a hierarchy similar to SQL's: databases, tables, and columns.
110 |
111 | # UIs and tools that inspired Az-Firewall-mon
112 |
113 | ## [Check Point's SmartView](https://community.checkpoint.com/t5/Management/SmartView-Accessing-Check-Point-Logs-from-Web/td-p/3710) web log access
114 |
115 | 
116 |
117 | ## SysInternals [process monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon)
118 | 
119 |
120 | # Credits
121 |
122 | * [90342-security.json](https://lottiefiles.com/90342-security) lottie animation by Kawsar Mahmud
123 | * [lf20_giodppcr.json](https://lottiefiles.com/95739-no-connection-to-internet) lottie animation by hdev coder
124 | * [rubik.json](https://lottiefiles.com/animations/abstract-modular-cube-1-INITf22TH2) lottie animation by Ision Industries
125 | * Logo built with the [new Bing](https://www.bing.com/new)
126 |
127 |
--------------------------------------------------------------------------------
/bicep/firewall-mon-azure-stuff.bicep:
--------------------------------------------------------------------------------
1 | param namespace string = 'fwmonns354526'
2 | param hubname string = 'fwmonhub'
3 | param sharedkey string = 'fwmonkey'
4 | param mapAccountName string = 'fwmonflags'
5 | param openAiAccountName string = 'fwmonaoai'
6 | param location string = resourceGroup().location
7 | param locationaoai string = 'swedencentral'
8 | param fwmonappinsights string = 'fwmonappinsights'
9 |
10 | resource eventHubNamespace 'Microsoft.EventHub/namespaces@2017-04-01' = {
11 | name: namespace
12 | location: location
13 | sku: {
14 | name: 'Standard'
15 | tier: 'Standard'
16 | }
17 | }
18 |
19 | resource eventhub 'Microsoft.EventHub/namespaces/eventhubs@2017-04-01' = {
20 | parent: eventHubNamespace
21 | name: hubname
22 | properties: {
23 | messageRetentionInDays: 1
24 | partitionCount: 1
25 | }
26 | }
27 |
28 | resource firewallMonHub 'Microsoft.EventHub/namespaces/eventhubs/authorizationRules@2017-04-01' = {
29 | name: '${eventHubNamespace.name}/${hubname}/${sharedkey}'
30 | dependsOn: [
31 | eventhub
32 | ]
33 | properties: {
34 | rights: [
35 | 'Listen'
36 | ]
37 | }
38 | }
39 |
40 | resource mapsAccount 'Microsoft.Maps/accounts@2023-06-01' = {
41 | name: mapAccountName
42 | location: location
43 | sku: {
44 | name: 'G2'
45 | }
46 | kind: 'Gen2'
47 | }
48 |
49 | resource openAiService 'Microsoft.CognitiveServices/accounts@2022-03-01' = {
50 | name: openAiAccountName
51 | location: locationaoai
52 | sku: {
53 | name: 'S0'
54 | }
55 | kind: 'OpenAI'
56 | properties: {
57 | customSubDomainName: openAiAccountName
58 | networkAcls: {
59 | defaultAction: 'Allow'
60 | virtualNetworkRules: []
61 | ipRules: []
62 | }
63 | publicNetworkAccess: 'Enabled'
64 | }
65 | }
66 |
67 | resource cognitiveServicesDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-04-01-preview' = {
68 | parent: openAiService
69 | name: 'mygpt4'
70 | sku: {
71 | name: 'Standard'
72 | capacity: 2
73 | }
74 | properties: {
75 | model: {
76 | format: 'OpenAI'
77 | name: 'gpt-4o'
78 | version: '2024-05-13'
79 | }
80 | versionUpgradeOption: 'OnceNewDefaultVersionAvailable'
81 | currentCapacity: 2
82 | raiPolicyName: 'Microsoft.Default'
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/bicep/firewall-mon-azure-stuff.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "metadata": {
5 | "_generator": {
6 | "name": "bicep",
7 | "version": "0.33.93.31351",
8 | "templateHash": "8760060908616098772"
9 | }
10 | },
11 | "parameters": {
12 | "namespace": {
13 | "type": "string",
14 | "defaultValue": "fwmonns354526"
15 | },
16 | "hubname": {
17 | "type": "string",
18 | "defaultValue": "fwmonhub"
19 | },
20 | "sharedkey": {
21 | "type": "string",
22 | "defaultValue": "fwmonkey"
23 | },
24 | "mapAccountName": {
25 | "type": "string",
26 | "defaultValue": "fwmonflags"
27 | },
28 | "openAiAccountName": {
29 | "type": "string",
30 | "defaultValue": "fwmonaoai"
31 | },
32 | "location": {
33 | "type": "string",
34 | "defaultValue": "[resourceGroup().location]"
35 | },
36 | "locationaoai": {
37 | "type": "string",
38 | "defaultValue": "swedencentral"
39 | },
40 | "fwmonappinsights": {
41 | "type": "string",
42 | "defaultValue": "fwmonappinsights"
43 | }
44 | },
45 | "resources": [
46 | {
47 | "type": "Microsoft.EventHub/namespaces",
48 | "apiVersion": "2017-04-01",
49 | "name": "[parameters('namespace')]",
50 | "location": "[parameters('location')]",
51 | "sku": {
52 | "name": "Standard",
53 | "tier": "Standard"
54 | }
55 | },
56 | {
57 | "type": "Microsoft.EventHub/namespaces/eventhubs",
58 | "apiVersion": "2017-04-01",
59 | "name": "[format('{0}/{1}', parameters('namespace'), parameters('hubname'))]",
60 | "properties": {
61 | "messageRetentionInDays": 1,
62 | "partitionCount": 1
63 | },
64 | "dependsOn": [
65 | "[resourceId('Microsoft.EventHub/namespaces', parameters('namespace'))]"
66 | ]
67 | },
68 | {
69 | "type": "Microsoft.EventHub/namespaces/eventhubs/authorizationRules",
70 | "apiVersion": "2017-04-01",
71 | "name": "[format('{0}/{1}/{2}', parameters('namespace'), parameters('hubname'), parameters('sharedkey'))]",
72 | "properties": {
73 | "rights": [
74 | "Listen"
75 | ]
76 | },
77 | "dependsOn": [
78 | "[resourceId('Microsoft.EventHub/namespaces/eventhubs', parameters('namespace'), parameters('hubname'))]",
79 | "[resourceId('Microsoft.EventHub/namespaces', parameters('namespace'))]"
80 | ]
81 | },
82 | {
83 | "type": "Microsoft.Maps/accounts",
84 | "apiVersion": "2023-06-01",
85 | "name": "[parameters('mapAccountName')]",
86 | "location": "[parameters('location')]",
87 | "sku": {
88 | "name": "G2"
89 | },
90 | "kind": "Gen2"
91 | },
92 | {
93 | "type": "Microsoft.CognitiveServices/accounts",
94 | "apiVersion": "2022-03-01",
95 | "name": "[parameters('openAiAccountName')]",
96 | "location": "[parameters('locationaoai')]",
97 | "sku": {
98 | "name": "S0"
99 | },
100 | "kind": "OpenAI",
101 | "properties": {
102 | "customSubDomainName": "[parameters('openAiAccountName')]",
103 | "networkAcls": {
104 | "defaultAction": "Allow",
105 | "virtualNetworkRules": [],
106 | "ipRules": []
107 | },
108 | "publicNetworkAccess": "Enabled"
109 | }
110 | },
111 | {
112 | "type": "Microsoft.CognitiveServices/accounts/deployments",
113 | "apiVersion": "2024-04-01-preview",
114 | "name": "[format('{0}/{1}', parameters('openAiAccountName'), 'mygpt4')]",
115 | "sku": {
116 | "name": "Standard",
117 | "capacity": 2
118 | },
119 | "properties": {
120 | "model": {
121 | "format": "OpenAI",
122 | "name": "gpt-4o",
123 | "version": "2024-05-13"
124 | },
125 | "versionUpgradeOption": "OnceNewDefaultVersionAvailable",
126 | "currentCapacity": 2,
127 | "raiPolicyName": "Microsoft.Default"
128 | },
129 | "dependsOn": [
130 | "[resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiAccountName'))]"
131 | ]
132 | }
133 | ]
134 | }
--------------------------------------------------------------------------------
/firewall-mon-api/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Azure Functions localsettings file
5 | local.settings.json
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # NCrunch
117 | _NCrunch_*
118 | .*crunch*.local.xml
119 | nCrunchTemp_*
120 |
121 | # MightyMoose
122 | *.mm.*
123 | AutoTest.Net/
124 |
125 | # Web workbench (sass)
126 | .sass-cache/
127 |
128 | # Installshield output folder
129 | [Ee]xpress/
130 |
131 | # DocProject is a documentation generator add-in
132 | DocProject/buildhelp/
133 | DocProject/Help/*.HxT
134 | DocProject/Help/*.HxC
135 | DocProject/Help/*.hhc
136 | DocProject/Help/*.hhk
137 | DocProject/Help/*.hhp
138 | DocProject/Help/Html2
139 | DocProject/Help/html
140 |
141 | # Click-Once directory
142 | publish/
143 |
144 | # Publish Web Output
145 | *.[Pp]ublish.xml
146 | *.azurePubxml
147 | # TODO: Comment the next line if you want to checkin your web deploy settings
148 | # but database connection strings (with potential passwords) will be unencrypted
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
153 | # checkin your Azure Web App publish settings, but sensitive information contained
154 | # in these scripts will be unencrypted
155 | PublishScripts/
156 |
157 | # NuGet Packages
158 | *.nupkg
159 | # The packages folder can be ignored because of Package Restore
160 | **/packages/*
161 | # except build/, which is used as an MSBuild target.
162 | !**/packages/build/
163 | # Uncomment if necessary however generally it will be regenerated when needed
164 | #!**/packages/repositories.config
165 | # NuGet v3's project.json files produces more ignoreable files
166 | *.nuget.props
167 | *.nuget.targets
168 |
169 | # Microsoft Azure Build Output
170 | csx/
171 | *.build.csdef
172 |
173 | # Microsoft Azure Emulator
174 | ecf/
175 | rcf/
176 |
177 | # Windows Store app package directories and files
178 | AppPackages/
179 | BundleArtifacts/
180 | Package.StoreAssociation.xml
181 | _pkginfo.txt
182 |
183 | # Visual Studio cache files
184 | # files ending in .cache can be ignored
185 | *.[Cc]ache
186 | # but keep track of directories ending in .cache
187 | !*.[Cc]ache/
188 |
189 | # Others
190 | ClientBin/
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.jfm
196 | *.pfx
197 | *.publishsettings
198 | node_modules/
199 | orleans.codegen.cs
200 |
201 | # Since there are multiple workflows, uncomment next line to ignore bower_components
202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
203 | #bower_components/
204 |
205 | # RIA/Silverlight projects
206 | Generated_Code/
207 |
208 | # Backup & report files from converting an old project file
209 | # to a newer Visual Studio version. Backup files are not needed,
210 | # because we have git ;-)
211 | _UpgradeReport_Files/
212 | Backup*/
213 | UpgradeLog*.XML
214 | UpgradeLog*.htm
215 |
216 | # SQL Server files
217 | *.mdf
218 | *.ldf
219 |
220 | # Business Intelligence projects
221 | *.rdl.data
222 | *.bim.layout
223 | *.bim_*.settings
224 |
225 | # Microsoft Fakes
226 | FakesAssemblies/
227 |
228 | # GhostDoc plugin setting file
229 | *.GhostDoc.xml
230 |
231 | # Node.js Tools for Visual Studio
232 | .ntvs_analysis.dat
233 |
234 | # Visual Studio 6 build log
235 | *.plg
236 |
237 | # Visual Studio 6 workspace options file
238 | *.opt
239 |
240 | # Visual Studio LightSwitch build output
241 | **/*.HTMLClient/GeneratedArtifacts
242 | **/*.DesktopClient/GeneratedArtifacts
243 | **/*.DesktopClient/ModelManifest.xml
244 | **/*.Server/GeneratedArtifacts
245 | **/*.Server/ModelManifest.xml
246 | _Pvt_Extensions
247 |
248 | # Paket dependency manager
249 | .paket/paket.exe
250 | paket-files/
251 |
252 | # FAKE - F# Make
253 | .fake/
254 |
255 | # JetBrains Rider
256 | .idea/
257 | *.sln.iml
258 |
259 | # CodeRush
260 | .cr/
261 |
262 | # Python Tools for Visual Studio (PTVS)
263 | __pycache__/
264 | *.pyc
--------------------------------------------------------------------------------
/firewall-mon-api/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.Functions.Worker;
2 | using Microsoft.Azure.Functions.Worker.Builder;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.Hosting;
5 |
6 | var builder = FunctionsApplication.CreateBuilder(args);
7 |
8 | builder.ConfigureFunctionsWebApplication();
9 |
10 | builder.Services
11 | .AddApplicationInsightsTelemetryWorkerService()
12 | .ConfigureFunctionsApplicationInsights();
13 |
14 | builder.Build().Run();
15 |
--------------------------------------------------------------------------------
/firewall-mon-api/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "api": {
4 | "commandName": "Project",
5 | "commandLineArgs": "--port 7263",
6 | "launchBrowser": false
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/firewall-mon-api/api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | v4
5 | Exe
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/firewall-mon-api/azure-api-response.cs:
--------------------------------------------------------------------------------
1 | namespace firewallmon.response {
2 |
3 | public class AzureAPIResponse
4 | {
5 | public CountryRegion countryRegion { get; set; }
6 | }
7 |
8 | public class CountryRegion
9 | {
10 | public string isoCode { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/firewall-mon-api/backend.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.Net;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Azure.Functions.Worker;
6 | using Microsoft.Extensions.Logging;
7 |
8 | using firewallmon.response;
9 | using System.Net.Http.Json;
10 | namespace firewallmon.function;
11 |
12 | class RequestLog
13 | {
14 | public List Requests { get; } = new();
15 | }
16 |
17 |
18 | public class Backend
19 | {
20 | private static readonly ConcurrentDictionary IpLogs = new();
21 |
22 | private readonly ILogger _logger;
23 |
24 | private int ThrottlingInterval = int.TryParse(Environment.GetEnvironmentVariable("aoai_throttling_window"), out var interval) ? interval : 0; // minutes
25 | private int ThrottlingRequests = int.TryParse(Environment.GetEnvironmentVariable("aoai_throttling_calls"), out var requests) ? requests : 0; // max requests in the interval
26 | private bool IsThrottlingEnabled => ThrottlingInterval > 0 && ThrottlingRequests > 0;
27 | private string IpApiKey = Environment.GetEnvironmentVariable("ip_api_key") ?? "unknown";
28 |
29 | private bool ImplementThrottling(HttpRequest req)
30 | {
31 | if (!IsThrottlingEnabled)
32 | {
33 | return false;
34 | }
35 |
36 | string ip = req.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
37 |
38 | var now = DateTime.UtcNow;
39 | var logEntry = IpLogs.GetOrAdd(ip, _ => new RequestLog());
40 |
41 | lock (logEntry)
42 | {
43 | logEntry.Requests.RemoveAll(t => (now - t).TotalMinutes > ThrottlingInterval);
44 |
45 | int count = logEntry.Requests.Count;
46 | if (count >= ThrottlingRequests)
47 | {
48 | // Log the throttling event
49 | _logger.LogError($"Throttling request from IP: {ip}");
50 | return true;
51 | }
52 | else
53 | {
54 | _logger.LogInformation($"Request {count} from IP {ip}.");
55 | }
56 |
57 | logEntry.Requests.Add(now);
58 | }
59 |
60 | return false;
61 | }
62 |
63 | public Backend(ILogger logger)
64 | {
65 | _logger = logger;
66 | }
67 |
68 | [Function("helloWorld")]
69 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
70 | {
71 | if (ImplementThrottling(req))
72 | {
73 | return new ContentResult
74 | {
75 | StatusCode = StatusCodes.Status429TooManyRequests,
76 | Content = "Too many requests. Please try again later."
77 | };
78 | }
79 |
80 | string author = Environment.GetEnvironmentVariable("author") ?? "unknown";
81 | return new OkObjectResult($"Hello from the other side... of the endpoint.\r\nbackend owned by {author}.\r\n");
82 | }
83 |
84 | [Function("ip")]
85 | public async Task RunIpAsync([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ip/{ipAddress}")] HttpRequest req, string ipAddress)
86 | {
87 | var callRequest = $"https://atlas.microsoft.com/geolocation/ip/json?api-version=1.0&ip={ipAddress}&subscription-key={IpApiKey}";
88 |
89 | using (var httpClient = new HttpClient())
90 | {
91 | httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", IpApiKey);
92 | var response = await httpClient.GetAsync(callRequest);
93 | response.EnsureSuccessStatusCode();
94 |
95 | var json = await response.Content.ReadAsStringAsync();
96 | var apiResponse = System.Text.Json.JsonSerializer.Deserialize(json);
97 |
98 | return new OkObjectResult(apiResponse!.countryRegion.isoCode.ToLower());
99 | }
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/firewall-mon-api/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | },
9 | "enableLiveMetricsFilters": true
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/firewall-mon-api/local.settings.json.sample:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
6 | "author" : "john john",
7 | "aoai_throttling_window" : "10",
8 | "aoai_throttling_calls" : "3",
9 | "ip_api_key":""
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/firewall-mon-app/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://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 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/firewall-mon-app/.gitignore:
--------------------------------------------------------------------------------
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 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 |
16 | # IDEs and editors
17 | /.idea
18 | .project
19 | .classpath
20 | .c9/
21 | *.launch
22 | .settings/
23 | *.sublime-workspace
24 |
25 | # IDE - VSCode
26 | .vscode/*
27 | #!.vscode/settings.json
28 | #!.vscode/tasks.json
29 | #!.vscode/launch.json
30 | #!.vscode/extensions.json
31 | .history/*
32 |
33 | # misc
34 | /.sass-cache
35 | /connect.lock
36 | /coverage
37 | /libpeerconnection.log
38 | npm-debug.log
39 | yarn-error.log
40 | testem.log
41 | /typings
42 |
43 | # System Files
44 | .DS_Store
45 | Thumbs.db
46 |
47 | /.angular
48 |
49 | # local env files
50 | environment.dev.ts
--------------------------------------------------------------------------------
/firewall-mon-app/README.md:
--------------------------------------------------------------------------------
1 | # FirewallMonApp
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.5.
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.
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 a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/firewall-mon-app/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "cli": {
4 | "analytics": "ef9d9eac-146e-47cf-a426-79727ad2717f"
5 | },
6 | "version": 1,
7 | "newProjectRoot": "projects",
8 | "projects": {
9 | "firewall-mon-app": {
10 | "projectType": "application",
11 | "schematics": {
12 | "@schematics/angular:component": {
13 | "style": "scss"
14 | },
15 | "@schematics/angular:application": {
16 | "strict": true
17 | }
18 | },
19 | "root": "",
20 | "sourceRoot": "src",
21 | "prefix": "app",
22 | "architect": {
23 | "build": {
24 | "builder": "@angular-builders/custom-webpack:browser",
25 | "options": {
26 | "outputPath": "dist/firewall-mon-app",
27 | "index": "src/index.html",
28 | "main": "src/main.ts",
29 | "polyfills": "src/polyfills.ts",
30 | "tsConfig": "tsconfig.app.json",
31 | "inlineStyleLanguage": "scss",
32 | "assets": [
33 | "src/favicon.ico",
34 | "src/assets"
35 | ],
36 | "styles": [
37 | "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
38 | "src/styles.scss"
39 | ],
40 | "scripts": [
41 | "./node_modules/@lottiefiles/lottie-player/dist/lottie-player.js"
42 | ]
43 | },
44 | "configurations": {
45 | "production": {
46 | "customWebpackConfig": {
47 | "path": "./extra-webpack.config.js",
48 | "replaceDuplicatePlugins": true
49 | },
50 | "budgets": [
51 | {
52 | "type": "initial",
53 | "maximumWarning": "2mb",
54 | "maximumError": "5mb"
55 | },
56 | {
57 | "type": "anyComponentStyle",
58 | "maximumWarning": "2kb",
59 | "maximumError": "4kb"
60 | }
61 | ],
62 | "fileReplacements": [
63 | {
64 | "replace": "src/environments/environment.ts",
65 | "with": "src/environments/environment.prod.ts"
66 | }
67 | ],
68 | "outputHashing": "all"
69 | },
70 | "development": {
71 | "customWebpackConfig": {
72 | "path": "./extra-webpack.config.js",
73 | "replaceDuplicatePlugins": true
74 | },
75 | "buildOptimizer": false,
76 | "optimization": false,
77 | "vendorChunk": true,
78 | "extractLicenses": false,
79 | "sourceMap": true,
80 | "namedChunks": true,
81 | "fileReplacements": [
82 | {
83 | "replace": "src/environments/environment.ts",
84 | "with": "src/environments/environment.dev.ts"
85 | }
86 | ]
87 | }
88 | },
89 | "defaultConfiguration": "production"
90 | },
91 | "serve": {
92 | "builder": "@angular-builders/custom-webpack:dev-server",
93 | "configurations": {
94 | "production": {
95 | "buildTarget": "firewall-mon-app:build:production"
96 | },
97 | "development": {
98 | "buildTarget": "firewall-mon-app:build:development"
99 | }
100 | },
101 | "defaultConfiguration": "development"
102 | },
103 | "extract-i18n": {
104 | "builder": "@angular-devkit/build-angular:extract-i18n",
105 | "options": {
106 | "buildTarget": "firewall-mon-app:build"
107 | }
108 | },
109 | "test": {
110 | "builder": "@angular-devkit/build-angular:karma",
111 | "options": {
112 | "main": "src/test.ts",
113 | "polyfills": "src/polyfills.ts",
114 | "tsConfig": "tsconfig.spec.json",
115 | "karmaConfig": "karma.conf.js",
116 | "inlineStyleLanguage": "scss",
117 | "assets": [
118 | "src/favicon.ico",
119 | "src/assets"
120 | ],
121 | "styles": [
122 | "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
123 | "src/styles.scss"
124 | ],
125 | "scripts": [
126 | "./node_modules/@lottiefiles/lottie-player/dist/lottie-player.js"
127 | ]
128 | }
129 | }
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/firewall-mon-app/extra-webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 |
3 | module.exports = {
4 | plugins: [
5 | new webpack.ProvidePlugin({
6 | process: "process/browser",
7 | }),
8 | new webpack.ProvidePlugin({
9 | Buffer: ["buffer", "Buffer"],
10 | }),
11 | ],
12 | resolve: {
13 | fallback: {
14 | buffer: require.resolve("buffer/"),
15 | os: require.resolve("os-browserify"),
16 | path: require.resolve("path-browserify"),
17 | }
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/firewall-mon-app/karma.conf.js:
--------------------------------------------------------------------------------
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'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, './coverage/firewall-mon-app'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/firewall-mon-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "firewall-mon-app",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "engines": {
13 | "node": ">=18.0.0"
14 | },
15 | "dependencies": {
16 | "@angular/animations": "~19.2.10",
17 | "@angular/cdk": "^19.2.15",
18 | "@angular/common": "~19.2.10",
19 | "@angular/compiler": "~19.2.10",
20 | "@angular/core": "~19.2.10",
21 | "@angular/forms": "~19.2.10",
22 | "@angular/material": "^19.2.15",
23 | "@angular/platform-browser": "~19.2.10",
24 | "@angular/platform-browser-dynamic": "~19.2.10",
25 | "@angular/router": "~19.2.10",
26 | "@azure/event-hubs": "^6.0.0",
27 | "@azure/identity": "^4.9.1",
28 | "@azure/openai": "^2.0.0",
29 | "@lottiefiles/lottie-player": "^2.0.12",
30 | "@microsoft/applicationinsights-angularplugin-js": "^15.3.6",
31 | "@microsoft/applicationinsights-web": "^3.3.7",
32 | "axios": "^1.9.0",
33 | "crypto-js": "^4.2.0",
34 | "crypto.js": "^3.3.4",
35 | "ng-table-virtual-scroll": "^1.6.1",
36 | "openai": "^4.98.0",
37 | "rhea-promise": "^3.0.3",
38 | "rxjs": "^7.8.2",
39 | "secure-web-storage": "^1.0.2",
40 | "tslib": "^2.8.1",
41 | "util": "^0.12.5",
42 | "zone.js": "~0.15.0"
43 | },
44 | "devDependencies": {
45 | "@angular-builders/custom-webpack": "^19.0.1",
46 | "@angular-devkit/build-angular": "~19.2.11",
47 | "@angular/cli": "^19.2.11",
48 | "@angular/compiler-cli": "~19.2.10",
49 | "@types/crypto-js": "^4.2.2",
50 | "@types/jasmine": "~5.1.8",
51 | "@types/node": "^22.15.18",
52 | "buffer": "^6.0.3",
53 | "jasmine-core": "^5.7.1",
54 | "karma": "^6.4.4",
55 | "karma-chrome-launcher": "^3.2.0",
56 | "karma-coverage": "^2.2.1",
57 | "karma-jasmine": "^5.1.0",
58 | "karma-jasmine-html-reporter": "^2.1.0",
59 | "os-browserify": "^0.3.0",
60 | "path-browserify": "^1.0.1",
61 | "process": "^0.11.10",
62 | "typescript": "^5.8.3"
63 | }
64 | }
--------------------------------------------------------------------------------
/firewall-mon-app/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { LoginComponent } from './login/login.component';
4 | import { MainPageComponent } from './main-page/main-page.component';
5 |
6 | const routes: Routes = [
7 | { path: '', redirectTo: 'login', pathMatch: 'full'},
8 | { path: 'login', component: LoginComponent },
9 | { path: 'live', component: MainPageComponent }
10 | ];
11 |
12 | @NgModule({
13 | imports: [RouterModule.forRoot(routes)],
14 | exports: [RouterModule]
15 | })
16 | export class AppRoutingModule { }
17 |
--------------------------------------------------------------------------------
/firewall-mon-app/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/firewall-mon-app/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/firewall-mon-app/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async () => {
7 | await TestBed.configureTestingModule({
8 | imports: [
9 | RouterTestingModule
10 | ],
11 | declarations: [
12 | AppComponent
13 | ],
14 | }).compileComponents();
15 | });
16 |
17 | it('should create the app', () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.componentInstance;
20 | expect(app).toBeTruthy();
21 | });
22 |
23 | it(`should have as title 'firewall-mon-app'`, () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | const app = fixture.componentInstance;
26 | expect(app.title).toEqual('firewall-mon-app');
27 | });
28 |
29 | it('should render title', () => {
30 | const fixture = TestBed.createComponent(AppComponent);
31 | fixture.detectChanges();
32 | const compiled = fixture.nativeElement as HTMLElement;
33 | expect(compiled.querySelector('.content span')?.textContent).toContain('firewall-mon-app app is running!');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/firewall-mon-app/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { LoggingService } from './services/logging.service';
4 |
5 | @Component({
6 | standalone: false,
7 | selector: 'app-root',
8 | templateUrl: './app.component.html',
9 | styleUrls: ['./app.component.scss']
10 | })
11 | export class AppComponent {
12 | title = 'az-firewall-mon';
13 |
14 | constructor(private router: Router,
15 | private logingService: LoggingService) {
16 |
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/firewall-mon-app/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA, ErrorHandler } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { AppRoutingModule } from './app-routing.module';
6 | import { AppComponent } from './app.component';
7 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
8 | import { LoginComponent } from './login/login.component';
9 | import { MainPageComponent } from './main-page/main-page.component';
10 | import { DatePipe } from '@angular/common';
11 |
12 | import { MatButtonModule } from '@angular/material/button';
13 | import { MatButtonToggleModule } from '@angular/material/button-toggle';
14 | import { MatDialogModule } from '@angular/material/dialog';
15 | import { MatFormFieldModule} from '@angular/material/form-field';
16 | import { MatExpansionModule} from '@angular/material/expansion';
17 | import { MatInputModule} from '@angular/material/input';
18 | import { MatCardModule } from '@angular/material/card';
19 | import { MatCheckboxModule} from '@angular/material/checkbox';
20 | import { MatChipsModule } from '@angular/material/chips';
21 | import { MatSnackBarModule} from '@angular/material/snack-bar';
22 | import { MatTableModule } from '@angular/material/table'
23 | import { MatTooltipModule } from '@angular/material/tooltip';
24 | import { ScrollingModule } from '@angular/cdk/scrolling';
25 | import { MatToolbarModule } from '@angular/material/toolbar';
26 | import { MatBadgeModule} from '@angular/material/badge';
27 |
28 |
29 | import { TableVirtualScrollModule } from 'ng-table-virtual-scroll';
30 | import { ResizeColumnDirective } from './directives/resize-column.directive';
31 | import { YesnoDialogComponent } from './yesno-dialog/yesno-dialog.component';
32 |
33 | import { ApplicationinsightsAngularpluginErrorService } from '@microsoft/applicationinsights-angularplugin-js';
34 |
35 | @NgModule({
36 | schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
37 | declarations: [
38 | AppComponent,
39 | LoginComponent,
40 | MainPageComponent,
41 | ResizeColumnDirective,
42 | YesnoDialogComponent
43 | ],
44 | imports: [
45 | BrowserModule,
46 | FormsModule,
47 | AppRoutingModule,
48 | BrowserAnimationsModule,
49 | MatButtonModule,
50 | MatButtonToggleModule,
51 | MatBadgeModule,
52 | MatCardModule,
53 | MatCheckboxModule,
54 | MatChipsModule,
55 | MatDialogModule,
56 | MatFormFieldModule,
57 | MatExpansionModule,
58 | MatInputModule,
59 | MatSnackBarModule,
60 | MatTableModule,
61 | MatTooltipModule,
62 | ScrollingModule,
63 | TableVirtualScrollModule,
64 | MatToolbarModule
65 | ],
66 | providers: [
67 | DatePipe,
68 | {
69 | provide: ErrorHandler,
70 | useClass: ApplicationinsightsAngularpluginErrorService
71 | }
72 | ],
73 | bootstrap: [AppComponent]
74 | })
75 | export class AppModule { }
76 |
--------------------------------------------------------------------------------
/firewall-mon-app/src/app/directives/resize-column.directive.ts:
--------------------------------------------------------------------------------
1 | /// more information:
2 | /// https://medium.com/@imdebasispanda/resize-table-column-angular-5cb58b67367
3 | /// https://stackblitz.com/edit/angular-table-resize?file=src%2Fapp%2Fresize-column.directive.ts
4 |
5 | import { Directive, OnInit, Renderer2, Input, ElementRef } from "@angular/core";
6 |
7 | @Directive({
8 | selector: "[resizeColumn]",
9 | standalone: false
10 | })
11 | export class ResizeColumnDirective implements OnInit {
12 | @Input("resizeColumn") resizable!: boolean;
13 | @Input() index!: number;
14 | private startX!: number;
15 | private startWidth!: number;
16 | private column: HTMLElement;
17 | private table!: HTMLElement;
18 | private pressed!: boolean;
19 |
20 | constructor(private renderer: Renderer2, private el: ElementRef) {
21 | this.column = this.el.nativeElement;
22 | }
23 |
24 | ngOnInit() {
25 | if (this.resizable) {
26 | const row = this.renderer.parentNode(this.column);
27 | const thead = this.renderer.parentNode(row);
28 | this.table = this.renderer.parentNode(thead);
29 |
30 | const resizer = this.renderer.createElement("span");
31 | this.renderer.addClass(resizer, "resize-holder");
32 | this.renderer.appendChild(this.column, resizer);
33 | this.renderer.listen(resizer, "mousedown", this.onMouseDown);
34 | this.renderer.listen(this.table, "mousemove", this.onMouseMove);
35 | this.renderer.listen("document", "mouseup", this.onMouseUp);
36 | }
37 | }
38 |
39 | onMouseDown = (event: MouseEvent) => {
40 | this.pressed = true;
41 | this.startX = event.pageX;
42 | this.startWidth = this.column.offsetWidth;
43 | };
44 |
45 | onMouseMove = (event: MouseEvent) => {
46 | const offset = 35;
47 | if (this.pressed && event.buttons) {
48 | this.renderer.addClass(this.table, "resizing");
49 |
50 | // Calculate width of column
51 | let width =
52 | this.startWidth + (event.pageX - this.startX - offset);
53 |
54 | const tableCells = Array.from(this.table.querySelectorAll(".mat-row")).map(
55 | (row: any) => row.querySelectorAll(".mat-cell").item(this.index)
56 | );
57 |
58 | // Set table header width
59 | this.renderer.setStyle(this.column, "width", `${width}px`);
60 |
61 | // Set table cells width
62 | for (const cell of tableCells) {
63 | this.renderer.setStyle(cell, "width", `${width}px`);
64 | }
65 | }
66 | };
67 |
68 | onMouseUp = (event: MouseEvent) => {
69 | if (this.pressed) {
70 | this.pressed = false;
71 | this.renderer.removeClass(this.table, "resizing");
72 | }
73 | };
74 | }
75 |
--------------------------------------------------------------------------------
/firewall-mon-app/src/app/login/login.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
az-firewall-mon
11 |
12 |
Welcome to Az-firewall-mon: a tool that allows to view, search, filter and inspect the log stream of your
13 | Azure Firewall.
14 | Connect your Azure Firewall to this tool and start monitoring your network traffic
15 | (how?)
17 |
This is an open-source project available on GitHub, under MIT license.
66 |
67 |
Please note that while being developed by a Microsoft employee, this is not a Microsoft service or product.
68 | az-firewall-mon is a personal driven project supported by the community, there are none implicit or explicit
69 | obligations related to this project,
70 | it is provided 'as is' with no warranties and confer no rights.