├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── bicep ├── firewall-mon-azure-stuff.bicep └── firewall-mon-azure-stuff.json ├── firewall-mon-api ├── .gitignore ├── Program.cs ├── Properties │ └── launchSettings.json ├── api.csproj ├── azure-api-response.cs ├── backend.cs ├── host.json └── local.settings.json.sample ├── firewall-mon-app ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── extra-webpack.config.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── directives │ │ │ └── resize-column.directive.ts │ │ ├── login │ │ │ ├── login.component.html │ │ │ ├── login.component.scss │ │ │ └── login.component.ts │ │ ├── main-page │ │ │ ├── main-page.component.html │ │ │ ├── main-page.component.scss │ │ │ ├── main-page.component.spec.ts │ │ │ └── main-page.component.ts │ │ ├── services │ │ │ ├── demo-source.service.ts │ │ │ ├── encryption.service.ts │ │ │ ├── event-hub-source.service.ts │ │ │ ├── flags-all-countries.json │ │ │ ├── flags.service.ts │ │ │ ├── logging.service.ts │ │ │ ├── model.service.ts │ │ │ └── search-field.service.ts │ │ ├── shared.scss │ │ └── yesno-dialog │ │ │ ├── yesno-dialog.component.html │ │ │ ├── yesno-dialog.component.scss │ │ │ ├── yesno-dialog.component.spec.ts │ │ │ └── yesno-dialog.component.ts │ ├── assets │ │ ├── logo.png │ │ ├── lottie │ │ │ ├── 90342-security.json │ │ │ ├── lf20_giodppcr.json │ │ │ └── rubik.json │ │ └── staticwebapp.config.json │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json └── images ├── 01-text-filtering.png ├── 02-time-filtering.png ├── 03-chatgpt.gif ├── architecture.drawio ├── architecture.png ├── checkpoint-smartview.png ├── firewall-mon-app.png ├── logo.png ├── logo.psd └── sysinternals-process-monitor.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolgit/azure-firewall-mon/09edef871f293c0343ae6d0e804a26b91cf7d7f6/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dtmp 2 | *.bkp 3 | *.tmp 4 | *.sln -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 NicolD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | logo 3 |
4 |

az-firewall-mon🧑‍🚒

5 | 6 |
7 | an alternative and opinionable way to access and inspect Azure Firewall logs 8 |
9 | 10 |
11 | 12 |
13 | built by 14 | nicolgit and 15 | 16 | contributors 17 | 18 |
19 | 20 |
21 | 22 | ![azure-firewall-mon-app](images/firewall-mon-app.png) 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 | ![text filter](images/01-text-filtering.png) 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 | ![text filter](images/02-time-filtering.png) 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 | ![chatgpt](images/03-chatgpt.gif) 52 | 53 | # Setup a connection with your Azure Firewall 54 | 55 | ![architecture](images/architecture.png) 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 [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](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 | ![smart view](images/checkpoint-smartview.png) 116 | 117 | ## SysInternals [process monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon) 118 | ![process monitor](images/sysinternals-process-monitor.png) 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 |

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

18 | demo mode 19 |
20 | 21 | 26 |
27 | 32 |
33 | 37 | 38 |

The following optional parameters are needed if you want to use natural language to filter the log stream. 39 |

40 | 41 |
45 |
49 |
53 | 54 | 56 |
57 |
58 |
59 |
60 |
61 | 62 | 63 | 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.

71 |
-------------------------------------------------------------------------------- /firewall-mon-app/src/app/login/login.component.scss: -------------------------------------------------------------------------------- 1 | lottie-player { 2 | width: 250px; 3 | height: 250px; 4 | } 5 | 6 | $spacing: 12px; 7 | 8 | .centered-container > * { 9 | width: 100%; 10 | margin: $spacing; 11 | } 12 | 13 | .login-field { 14 | width: 100%; 15 | } 16 | 17 | .container-lottie-row { 18 | margin: 0 auto !important; 19 | } 20 | 21 | .logon-footer { 22 | text-align: center; 23 | } -------------------------------------------------------------------------------- /firewall-mon-app/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { environment } from 'src/environments/environment'; 4 | 5 | import { ModelService } from '../services/model.service'; 6 | 7 | @Component({ 8 | standalone: false, 9 | selector: 'app-login', 10 | templateUrl: './login.component.html', 11 | styleUrls: ['../shared.scss', './login.component.scss'] 12 | }) 13 | export class LoginComponent implements OnInit { 14 | 15 | constructor( 16 | private model: ModelService, 17 | private router: Router 18 | ) { 19 | this.setStart(); 20 | } 21 | 22 | ngOnInit(): void { 23 | } 24 | 25 | buildDate: string = environment.BuildDate; 26 | eventHubConnectionString: string = this.model.eventHubConnection; 27 | eventHubConsumerGroup: string = this.model.eventHubConsumerGroup; 28 | azureMapsSharedKey: string = this.model.azureMapsSharedKey; 29 | aoaiEndpoint: string = this.model.aoaiEndpoint; 30 | aoaiDeploymentId: string = this.model.aoaiDeploymentId; 31 | aoaiAccessKey: string = this.model.aoaiAccessKey; 32 | isDemoMode: boolean = this.model.demoMode; 33 | isStartDisabled: boolean = false; 34 | 35 | setDemo(completed: boolean) { 36 | this.isDemoMode = completed; 37 | 38 | this.setStart(); 39 | } 40 | 41 | setStart(): void { 42 | this.isStartDisabled = true; 43 | 44 | if (this.isDemoMode) { 45 | this.isStartDisabled = false; 46 | } 47 | 48 | if (this.eventHubConnectionString != null && this.eventHubConnectionString.length > 0 && this.eventHubConsumerGroup != null && this.eventHubConsumerGroup.length > 0) { 49 | this.isStartDisabled = false; 50 | } 51 | 52 | } 53 | 54 | navigateNext(): void { 55 | if (!this.isStartDisabled) { 56 | this.model.eventHubConnection = this.eventHubConnectionString; 57 | this.model.eventHubConsumerGroup = this.eventHubConsumerGroup; 58 | this.model.azureMapsSharedKey = this.azureMapsSharedKey; 59 | this.model.aoaiEndpoint = this.aoaiEndpoint; 60 | this.model.aoaiDeploymentId = this.aoaiDeploymentId; 61 | this.model.aoaiAccessKey = this.aoaiAccessKey; 62 | this.model.demoMode = this.isDemoMode; 63 | this.model.save(); 64 | 65 | this.router.navigateByUrl('/live') 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /firewall-mon-app/src/app/main-page/main-page.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | logo az-firewall-mon 6 | 7 | 8 | search 9 | filter 10 | 15 | 18 | 19 | 20 |
21 |   22 | 23 | 24 |   25 | 26 |   27 |
28 |
29 |
30 | 31 |
32 | 38 | 39 |
{{message}}
40 |
41 | 42 |
43 | 44 | 45 | query mode 46 | 47 | text filter 48 | chatGPT (experimental) 49 | 50 | 51 | 52 | GMT 53 | local time 54 | 55 | 56 |
57 | filter 58 | 59 | all 60 | last 5 minutes 61 | last 10 minutes 62 | last 20 minutes 63 | last 30 minutes 64 | last hour 65 | custom 66 | 67 |
68 | 69 | start (local time) 70 | 71 |   72 | 73 | end (local time) 74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 | 87 |
88 | 89 | 90 |
91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 103 | 104 | 105 | 106 | 107 | 110 | 111 | 112 | 113 | 114 | 117 | 118 | 119 | 120 | 121 | 127 | 128 | 129 | 130 | 131 | 137 | 138 | 139 | 140 | 141 | 150 | 151 | 152 | 153 | 154 | 157 | 158 | 159 | 160 | 161 | 164 | 165 |
Timestamp 101 | 102 | Category 108 | 109 | Protocol 115 | 116 | Source 122 | 123 | lan 124 | public 125 | 126 | Target 132 | 133 | lan 134 | public 135 | 136 | Action 142 | task_alt 143 | cancel 144 | cancel 145 | psychology_alt 146 | warning_amber 147 |   148 | 149 | Policy 155 | 156 | More info 162 | 163 |
166 |
167 | 168 |
169 | 170 | 171 | 172 | {{message}} 173 | 174 | rows: {{totalRows}} availables, {{visibleRows}} visibles, {{skippedRows}} skipped 175 | 176 | 177 |
178 |
179 | 182 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | -------------------------------------------------------------------------------- /firewall-mon-app/src/app/main-page/main-page.component.scss: -------------------------------------------------------------------------------- 1 | $bottom-bar-size: 30px; 2 | 3 | .lottie-player { 4 | width: 250px; 5 | height: 250px; 6 | 7 | display: block; 8 | margin-left: auto; 9 | margin-right: auto; 10 | } 11 | 12 | .lottie-text { 13 | width: 100%; 14 | text-align: center; 15 | } 16 | 17 | 18 | .search-field { 19 | font-size: 14px; 20 | position: absolute; 21 | left: 50%; 22 | width: 400px; 23 | -webkit-transform: translate(-50%,0); 24 | transform: translate(-50%, 0); 25 | } 26 | 27 | .main-toolbar-buttons { 28 | margin-left: auto; 29 | margin-right: 0; 30 | } 31 | 32 | // icons inside top right buttons 33 | .main-toolbar-buttons button span{ 34 | vertical-align: middle; 35 | margin-right: 3px; 36 | } 37 | 38 | 39 | .parent { 40 | display: flex; 41 | flex-flow: column; 42 | align-content: stretch; 43 | width: 100%; 44 | height: 100%; 45 | justify-content: space-around; 46 | } 47 | 48 | .main-toolbar { 49 | flex-grow: 0; 50 | } 51 | 52 | .main{ 53 | flex-grow: 1; 54 | margin: 6px; 55 | } 56 | 57 | .main-accordionbar { 58 | flex-grow: 0; 59 | padding: 6px; 60 | } 61 | 62 | .main-bottombar div { 63 | margin-top:4px; 64 | display: inline-block; 65 | width: 100%; 66 | text-align: center; 67 | } 68 | 69 | table { 70 | width: 100%; 71 | table-layout: fixed; 72 | } 73 | 74 | .column01 { 75 | width: 255px; 76 | border-right: 1px solid currentColor; 77 | padding-right: 6px; 78 | } 79 | 80 | .column02 { 81 | padding-left: 6px; 82 | width: 132px; 83 | } 84 | 85 | .column03 { 86 | width: 77px; 87 | } 88 | 89 | .column04 { 90 | width: 195px; 91 | } 92 | 93 | .column05 { 94 | width: 195px; 95 | } 96 | 97 | .column06 { 98 | width: 122px; 99 | } 100 | 101 | .column07 { 102 | width: 300px; 103 | } 104 | 105 | .column08 { 106 | } 107 | 108 | .col { 109 | background-color: white; 110 | } 111 | 112 | .error-text { 113 | color: red; 114 | } 115 | 116 | .mat-mdc-table { 117 | width: 100%; 118 | &.resizing { 119 | -moz-user-select: none; 120 | -ms-user-select: none; 121 | -webkit-user-select: none; 122 | user-select: none; 123 | cursor: col-resize; 124 | } 125 | .mat-mdc-cell { 126 | overflow:hidden; 127 | white-space: nowrap; 128 | span { 129 | display: inline-block; 130 | overflow: hidden; 131 | white-space: nowrap; 132 | } 133 | } 134 | .mat-mdc-header-cell { 135 | position: relative; 136 | &:not(:last-child) { 137 | .resize-holder { 138 | cursor: col-resize; 139 | width: 20px; 140 | height: 100%; 141 | position: absolute; 142 | right: -10px; 143 | top: 0; 144 | z-index: 1; 145 | } 146 | } 147 | } 148 | .mat-mdc-cell, 149 | .mat-mdc-header-cell { 150 | border-right: 1px solid rgba(0, 0, 0, 0.12); 151 | &:not(:nth-child(1)) { 152 | padding: 0 10px; 153 | } 154 | } 155 | } 156 | 157 | .mat-mdc-row:hover { 158 | background-color: rgb(240, 240, 240); 159 | } 160 | 161 | 162 | .highlight-flag { 163 | color: red; 164 | font-variation-settings: 165 | 'FILL' 0, 166 | 'wght' 700, 167 | 'GRAD' 0, 168 | 'opsz' 20 169 | } 170 | 171 | .flag-icon { 172 | margin: 3px; 173 | } 174 | 175 | pre { 176 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ 177 | white-space: -pre-wrap; /* Opera 4-6 */ 178 | white-space: -o-pre-wrap; /* Opera 7 */ 179 | white-space: pre-wrap; /* css-3 */ 180 | word-wrap: break-word; /* Internet Explorer 5.5+ */ 181 | 182 | outline: 1px solid #ccc; padding: 5px; margin: 5px; 183 | } 184 | 185 | .jsontexttoolbar 186 | { 187 | position: absolute; 188 | top: 120px; 189 | right: 60px; 190 | } 191 | 192 | .jsontext 193 | { 194 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ 195 | white-space: -pre-wrap; /* Opera 4-6 */ 196 | white-space: -o-pre-wrap; /* Opera 7 */ 197 | white-space: pre-wrap; /* css-3 */ 198 | word-wrap: break-word; /* Internet Explorer 5.5+ */ 199 | 200 | background-color: #faf5c8; 201 | outline: 1px solid #ccc; padding: 5px; margin: 5px; 202 | //height: 180px; 203 | overflow-y: scroll; 204 | } 205 | :host ::ng-deep .jsontext > span.string { font-weight: bold; } 206 | :host ::ng-deep .jsontext > span.number { font-weight: bold; } 207 | :host ::ng-deep .jsontext > span.boolean { font-style: italic; } 208 | :host ::ng-deep .jsontext > span.null { color: red; } 209 | :host ::ng-deep .jsontext > span.key { font-style: italic; } 210 | 211 | 212 | .centered-container-lottie > * { 213 | width: 64px; 214 | float: left; 215 | } 216 | 217 | .container-lottie { 218 | margin: 0 auto !important; 219 | } -------------------------------------------------------------------------------- /firewall-mon-app/src/app/main-page/main-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MainPageComponent } from './main-page.component'; 4 | 5 | describe('MainPageComponent', () => { 6 | let component: MainPageComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ MainPageComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MainPageComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /firewall-mon-app/src/app/main-page/main-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, HostListener, ElementRef, AfterViewInit, ViewChild } from '@angular/core'; 2 | 3 | import { IFirewallSource, FirewallDataRow, ModelService } from '../services/model.service'; 4 | import { DemoSourceService } from '../services/demo-source.service'; 5 | import { EventHubSourceService } from '../services/event-hub-source.service'; 6 | import { FlagData, FlagsService } from '../services/flags.service'; 7 | 8 | import { MatDialog } from '@angular/material/dialog'; 9 | import { MatSnackBar } from '@angular/material/snack-bar'; 10 | 11 | import { TableVirtualScrollDataSource } from 'ng-table-virtual-scroll'; 12 | import { Router } from '@angular/router'; 13 | import { YesnoDialogComponent } from '../yesno-dialog/yesno-dialog.component'; 14 | import { LoggingService } from '../services/logging.service'; 15 | import { time } from 'console'; 16 | import { formatDate } from '@angular/common'; 17 | import { PromptType, SearchFieldService } from '../services/search-field.service'; 18 | import { debounceTime, elementAt, filter, Subject } from 'rxjs'; 19 | 20 | 21 | enum TimestampFormat { GMT, local }; 22 | 23 | @Component({ 24 | standalone: false, 25 | selector: 'app-main-page', 26 | templateUrl: './main-page.component.html', 27 | styleUrls: ['./main-page.component.scss'], 28 | }) 29 | export class MainPageComponent implements AfterViewInit, OnInit { 30 | @ViewChild('searchInput') searchInput!: ElementRef; 31 | private firewallSource: IFirewallSource; 32 | 33 | ngAfterViewInit() { 34 | this.searchInput.nativeElement.focus(); 35 | } 36 | 37 | constructor( 38 | private model: ModelService, 39 | private demoSource: DemoSourceService, 40 | private eventHubService: EventHubSourceService, 41 | private flagService: FlagsService, 42 | private router: Router, 43 | private logging: LoggingService, 44 | private snackBar: MatSnackBar, 45 | public searchFieldService: SearchFieldService, 46 | public dialog: MatDialog, 47 | ) { 48 | this.firewallSource = this.model.demoMode ? this.demoSource : this.eventHubService; 49 | this.firewallSource.onDataArrived = (data) => this.onDataSourceChanged(data); 50 | this.firewallSource.onRowSkipped = (skipped) => this.onRowSkipped(skipped); 51 | this.firewallSource.onMessageArrived = (message) => this.onMessageArrived(message); 52 | 53 | this.toggleExpandJsonSpace(); 54 | } 55 | 56 | @HostListener('document:keydown.escape', ['$event']) onKeydownHandler(event: KeyboardEvent) { 57 | console.log(event); 58 | 59 | this.advSearchVisibility = !this.advSearchVisibility; 60 | 61 | if (this.advSearchVisibility) { 62 | this.searchInput.nativeElement.focus(); 63 | } 64 | } 65 | @HostListener('document:keypress', ['$event']) 66 | handleKeyboardEvent(event: KeyboardEvent) { 67 | // avoid handling keypress events when typing in input fields 68 | if (event.target instanceof HTMLInputElement || 69 | event.target instanceof HTMLTextAreaElement || 70 | event.target instanceof HTMLSelectElement) { 71 | return; 72 | } 73 | 74 | this.advSearchVisibility = true; 75 | this.searchInput.nativeElement.focus(); 76 | 77 | } 78 | 79 | private onDataSourceChanged(data: Array) { 80 | this.dataSource = new TableVirtualScrollDataSource(data); 81 | this.dataSource.filterPredicate = (data: FirewallDataRow, filter_in: string) => { 82 | try { 83 | 84 | if (this.searchFieldService.isTimestampWithinFilter(data.time) == false) 85 | return false; 86 | 87 | var filters = 0; 88 | filters += this.searchFieldService.searchParams.fulltext.length; 89 | 90 | filters += this.searchFieldService.searchParams.category.length; 91 | filters += this.searchFieldService.searchParams.protocol.length 92 | filters += this.searchFieldService.searchParams.source.length; 93 | filters += this.searchFieldService.searchParams.target.length; 94 | filters += this.searchFieldService.searchParams.action.length; 95 | filters += this.searchFieldService.searchParams.policy.length; 96 | filters += this.searchFieldService.searchParams.moreinfo.length; 97 | 98 | if (filters == 0) 99 | return true; 100 | 101 | var filterHits = 0; 102 | for (let word of this.searchFieldService.searchParams.fulltext) { 103 | if (word.length > 0 && 104 | data.category?.toLowerCase().includes(word) || 105 | data.protocol?.toLowerCase().includes(word) || 106 | data.sourceip?.toLowerCase().includes(word) || 107 | data.srcport?.toLowerCase().includes(word) || 108 | data.targetip?.toLowerCase().includes(word) || 109 | data.targetport?.toLowerCase().includes(word) || 110 | data.policy?.toLowerCase().includes(word) || 111 | data.moreInfo?.toLowerCase().includes(word) || 112 | data.action?.toLowerCase().includes(word) 113 | ) { 114 | filterHits++; 115 | } 116 | } 117 | if (filters == filterHits) return true; 118 | 119 | for (let word of this.searchFieldService.searchParams.category) { 120 | if (word.length > 0 && data.category?.toLowerCase().includes(word)) { 121 | filterHits++; 122 | } 123 | } 124 | 125 | for (let word of this.searchFieldService.searchParams.protocol) { 126 | if (word.length > 0 && data.protocol?.toLowerCase().includes(word)) { 127 | filterHits++; 128 | } 129 | } 130 | 131 | for (let word of this.searchFieldService.searchParams.source) { 132 | if (word.length > 0 && data.sourceip?.toLowerCase().includes(word)) { 133 | filterHits++; 134 | } 135 | } 136 | 137 | for (let word of this.searchFieldService.searchParams.target) { 138 | if (word.length > 0 && data.targetip?.toLowerCase().includes(word)) { 139 | filterHits++; 140 | } 141 | } 142 | 143 | for (let word of this.searchFieldService.searchParams.action) { 144 | if (word.length > 0 && data.action?.toLowerCase().includes(word)) { 145 | filterHits++; 146 | } 147 | } 148 | 149 | for (let word of this.searchFieldService.searchParams.policy) { 150 | if (word.length > 0 && data.policy?.toLowerCase().includes(word)) { 151 | filterHits++; 152 | } 153 | } 154 | 155 | for (let word of this.searchFieldService.searchParams.moreinfo) { 156 | if (word.length > 0 && data.moreInfo?.toLowerCase().includes(word)) { 157 | filterHits++; 158 | } 159 | } 160 | 161 | return filters == filterHits; 162 | } catch (error) { 163 | //console.log ("Error [" + error + "] in filterPredicate working on: " + data); 164 | return true; 165 | } 166 | }; 167 | this.dataSource.filter = " " + this.filterText;; // not empty filter string forces filterPredicate to be called 168 | this.totalRows = data.length; 169 | this.visibleRows = this.dataSource.filteredData.length; 170 | } 171 | 172 | private onRowSkipped(skipped: number) { 173 | this.skippedRows = skipped; 174 | } 175 | 176 | private onMessageArrived(message: string) { 177 | this.message = message; 178 | } 179 | 180 | public onRowClicked(row: FirewallDataRow) { 181 | if (row == this.selectedRow) { 182 | this.selectedRow = null; 183 | this.selectedRowJson = null; 184 | this.panelOpenState = false; 185 | return; 186 | } 187 | this.selectedRow = row; 188 | this.panelOpenState = true; 189 | this.selectedRowJson = this.syntaxHighlight(JSON.stringify(row.dataRow, null, 2)); 190 | return; 191 | } 192 | 193 | // format a json string to be more readable with bold and colors 194 | prettyPrintJson(json: string): string { 195 | if (json == null || json.length == 0) 196 | return ""; 197 | 198 | 199 | var result = json.replace(/{/g, '{').replace(/}/g, '}').replace(/:/g, ':').replace(/,/g, ',').replace(/"/g, '"'); 200 | return result; 201 | } 202 | 203 | // https://stackoverflow.com/questions/4810841/pretty-print-json-using-javascript 204 | syntaxHighlight(json: string): string { 205 | json = json.replace(/&/g, '&').replace(//g, '>'); 206 | return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match: string) { 207 | var cls = 'number'; 208 | if (/^"/.test(match)) { 209 | if (/:$/.test(match)) { 210 | cls = 'key'; 211 | } else { 212 | cls = 'string'; 213 | } 214 | } else if (/true|false/.test(match)) { 215 | cls = 'boolean'; 216 | } else if (/null/.test(match)) { 217 | cls = 'null'; 218 | } 219 | return '' + match + ''; 220 | }); 221 | } 222 | 223 | private searchFieldSubject = new Subject(); 224 | private readonly debounceTimeMs = 2000; 225 | filterTextChanged(): void { 226 | this.searchFieldSubject.next(this.filterText); 227 | } 228 | 229 | private filterTextChangedDebounced(): void { 230 | this.searchFieldSubject.next(this.filterText); 231 | if (this.searchFieldService.promptType == PromptType.Classic) { 232 | this.searchFieldService.setPrompt(this.filterText); 233 | this.refreshList(); 234 | } 235 | } 236 | 237 | async filterTextEnter() { 238 | if (this.searchFieldService.promptType == PromptType.Chatgpt) { 239 | this.searchFieldService.setPrompt(this.filterText); 240 | await this.searchFieldService.parsePrompt(); 241 | 242 | this.filterText = ""; 243 | 244 | this.refreshList(); 245 | } 246 | } 247 | 248 | public displayedColumns = ['time', 'category', 'protocol', 'source', 'target', 'action', 'policy', 'targetUrl']; 249 | public dataSource: TableVirtualScrollDataSource = new TableVirtualScrollDataSource(new Array()); 250 | public skippedRows: number = 0; 251 | public totalRows: number = 0; 252 | public visibleRows: number = 0; 253 | public advSearchVisibility = false; 254 | public message: string = ""; 255 | public selectedRow: FirewallDataRow | null = null; 256 | public selectedRowJson: string | null = null; 257 | public isPaused: boolean = false; 258 | public jsontextHeight: string = ""; 259 | 260 | public panelOpenState = false; 261 | public timestampFormat: TimestampFormat = TimestampFormat.GMT; 262 | public filterText: string = ""; 263 | public timestampFilterMinutes: number = 0; 264 | 265 | setTimestampFilterMinutes(newValue: number) { 266 | if (newValue > 0) { 267 | this.timestampFilterMinutes = newValue; 268 | this.searchFieldService.setLastMinutes(newValue); 269 | } 270 | else if (newValue == -1) { 271 | this.timestampFilterMinutes = -1; 272 | 273 | this.searchFieldService.setLastMinutes(0); 274 | var now = new Date(); 275 | this.searchFieldService.searchParams.startdate = this.searchFieldService.searchParams.enddate = formatDate(now.getTime(), 'yyyy-MM-ddTHH:mm', 'en_US'); 276 | } 277 | else { 278 | this.timestampFilterMinutes = 0; 279 | this.searchFieldService.setLastMinutes(newValue); 280 | this.searchFieldService.searchParams.startdate = this.searchFieldService.searchParams.enddate = ""; 281 | } 282 | 283 | this.refreshList(); 284 | } 285 | 286 | public setActionBackground(action: string): string { 287 | 288 | if (this.safeCheckString(action, "Deny") || this.safeCheckString(action, "drop")) 289 | return '#ffe6f0'; 290 | else if (this.safeCheckString(action, "Allow")) 291 | return '#e6fff7'; 292 | else if (this.safeCheckString(action, "Request") || this.safeCheckString(action, "alert")) 293 | return '#e6faff'; 294 | else 295 | return ''; 296 | } 297 | 298 | public highlightSelection(columnName: string, textContent: string) { 299 | if (textContent == null || textContent.length == 0) 300 | return ""; 301 | 302 | this.searchFieldService.searchParams.fulltext.forEach(word => { 303 | const position = textContent.toLowerCase().indexOf(word.toLowerCase()); 304 | if (position >= 0 && word.length > 0) { 305 | textContent = textContent.substring(0, position) + "" + textContent.substring(position, position + word.length) + "" + textContent.substring(position + word.length); 306 | } 307 | }); 308 | 309 | switch (columnName) { 310 | case "category": 311 | this.searchFieldService.searchParams.category.forEach(word => { 312 | const position = textContent.toLowerCase().indexOf(word.toLowerCase()); 313 | if (position >= 0 && word.length > 0) { 314 | textContent = textContent.substring(0, position) + "" + textContent.substring(position, position + word.length) + "" + textContent.substring(position + word.length); 315 | } 316 | }); 317 | break; 318 | case "protocol": 319 | this.searchFieldService.searchParams.protocol.forEach(word => { 320 | const position = textContent.toLowerCase().indexOf(word.toLowerCase()); 321 | if (position >= 0 && word.length > 0) { 322 | textContent = textContent.substring(0, position) + "" + textContent.substring(position, position + word.length) + "" + textContent.substring(position + word.length); 323 | } 324 | }); 325 | break; 326 | case "source": 327 | this.searchFieldService.searchParams.source.forEach(word => { 328 | const position = textContent.toLowerCase().indexOf(word.toLowerCase()); 329 | if (position >= 0 && word.length > 0) { 330 | textContent = textContent.substring(0, position) + "" + textContent.substring(position, position + word.length) + "" + textContent.substring(position + word.length); 331 | } 332 | }); 333 | break; 334 | case "target": 335 | this.searchFieldService.searchParams.target.forEach(word => { 336 | const position = textContent.toLowerCase().indexOf(word.toLowerCase()); 337 | if (position >= 0 && word.length > 0) { 338 | textContent = textContent.substring(0, position) + "" + textContent.substring(position, position + word.length) + "" + textContent.substring(position + word.length); 339 | } 340 | }); 341 | break; 342 | case "action": 343 | this.searchFieldService.searchParams.action.forEach(word => { 344 | const position = textContent.toLowerCase().indexOf(word.toLowerCase()); 345 | if (position >= 0 && word.length > 0) { 346 | textContent = textContent.substring(0, position) + "" + textContent.substring(position, position + word.length) + "" + textContent.substring(position + word.length); 347 | } 348 | }); 349 | break; 350 | case "policy": 351 | this.searchFieldService.searchParams.policy.forEach(word => { 352 | const position = textContent.toLowerCase().indexOf(word.toLowerCase()); 353 | if (position >= 0 && word.length > 0) { 354 | textContent = textContent.substring(0, position) + "" + textContent.substring(position, position + word.length) + "" + textContent.substring(position + word.length); 355 | } 356 | }); 357 | break; 358 | case "moreinfo": 359 | this.searchFieldService.searchParams.moreinfo.forEach(word => { 360 | const position = textContent.toLowerCase().indexOf(word.toLowerCase()); 361 | if (position >= 0 && word.length > 0) { 362 | textContent = textContent.substring(0, position) + "" + textContent.substring(position, position + word.length) + "" + textContent.substring(position + word.length); 363 | } 364 | }); 365 | break; 366 | } 367 | 368 | return textContent; 369 | } 370 | 371 | public hasHighlightColorTimestamp(rowid: string): string { 372 | if (rowid == this.selectedRow?.rowid) 373 | return "#faf5c8"; 374 | 375 | if (this.searchFieldService.searchParams.lastminutes != 0) 376 | return "SeaShell"; 377 | 378 | if (this.searchFieldService.searchParams.startdate != "" && 379 | this.searchFieldService.searchParams.enddate != "") 380 | return "SeaShell"; 381 | 382 | return ""; 383 | } 384 | 385 | public hasHighlightColor(columnName: string, text: string, rowid: string): string { 386 | var result = false; 387 | 388 | if (text != null && text.length > 0) { 389 | result = text.length != this.highlightSelection(columnName, text).length; 390 | } 391 | 392 | if (rowid != null && rowid.length > 0) { 393 | if (rowid == this.selectedRow?.rowid) 394 | return "#faf5c8"; 395 | } 396 | 397 | return result ? "SeaShell" : ""; 398 | } 399 | 400 | // check if an IP is internal or external 401 | public isInternalIP(ip: string): boolean { 402 | if (ip == null || ip.length == 0) 403 | return false; 404 | 405 | var octets = ip.split("."); 406 | if (octets.length != 4) 407 | return false; 408 | 409 | var firstOctet = parseInt(octets[0]); 410 | if (firstOctet == 10) 411 | return true; 412 | else if (firstOctet == 172 && parseInt(octets[1]) >= 16 && parseInt(octets[1]) <= 31) 413 | return true; 414 | else if (firstOctet == 192 && parseInt(octets[1]) == 168) 415 | return true; 416 | else 417 | return false; 418 | } 419 | 420 | public isIP(ip: string): boolean { 421 | if (ip == null || ip.length == 0) 422 | return false; 423 | 424 | var octets = ip.split("."); 425 | if (octets.length != 4) 426 | return false; 427 | 428 | // check if all octets are numbers 429 | for (var i = 0; i < octets.length; i++) { 430 | var octet = parseInt(octets[i]); 431 | if (isNaN(octet)) 432 | return false; 433 | } 434 | 435 | return true; 436 | } 437 | 438 | public isExternalIP(ip: string): boolean { 439 | if (ip == null || ip.length == 0) 440 | return false; 441 | 442 | var octets = ip.split("."); 443 | if (octets.length != 4) 444 | return false; 445 | 446 | return !this.isInternalIP(ip); 447 | } 448 | 449 | public isTimestampLocal() { 450 | return this.timestampFormat == TimestampFormat.local; 451 | } 452 | 453 | public isTimestampGMT() { 454 | return this.timestampFormat == TimestampFormat.GMT; 455 | } 456 | 457 | public setTimestampLocal() { 458 | this.timestampFormat = TimestampFormat.local; 459 | } 460 | 461 | public setTimeStampGMT() { 462 | this.timestampFormat = TimestampFormat.GMT; 463 | } 464 | 465 | public isPromptTypeClassic() { 466 | return this.searchFieldService.promptType == PromptType.Classic; 467 | } 468 | 469 | public isPromptTypeChat() { 470 | return this.searchFieldService.promptType == PromptType.Chatgpt; 471 | } 472 | 473 | public setPromptTypeClassic() { 474 | this.filterText = ""; 475 | this.timestampFilterMinutes = 0; 476 | this.searchFieldService.resetParams({ includeTimeFilter: true }); 477 | this.searchFieldService.promptType = PromptType.Classic; 478 | this.searchInput.nativeElement.focus(); 479 | 480 | this.refreshList(); 481 | } 482 | 483 | public setPromptTypeChat() { 484 | this.filterText = ""; 485 | this.timestampFilterMinutes = 0; 486 | this.searchFieldService.resetParams({ includeTimeFilter: true }); 487 | this.searchFieldService.promptType = PromptType.Chatgpt; 488 | this.searchInput.nativeElement.focus(); 489 | 490 | this.refreshList(); 491 | } 492 | 493 | PromptAnswer() { 494 | if (this.searchFieldService.getPromptAnswer() == null || this.searchFieldService.getPromptAnswer().length == 0) 495 | return ""; 496 | 497 | return "az-firewallnmon > " + this.searchFieldService.getPromptAnswer() + ""; 498 | } 499 | 500 | isThinking(): boolean { 501 | return this.searchFieldService.isThinking; 502 | } 503 | public JSONfySearchParams() { 504 | return this.syntaxHighlight(JSON.stringify(this.searchFieldService.searchParams)); 505 | //return JSON.stringify(this.searchFieldService.searchParams); 506 | } 507 | 508 | public getFlagFromIP(ip: string): FlagData | undefined { 509 | if (!this.isIP(ip)) 510 | return undefined; 511 | 512 | if (this.isInternalIP(ip)) 513 | return undefined; 514 | 515 | return this.flagService.getFlagFromIP(ip); 516 | } 517 | 518 | ngOnInit(): void { 519 | this.firewallSource.start(); 520 | 521 | this.searchFieldSubject.pipe(debounceTime(this.debounceTimeMs)).subscribe((searchValue) => { 522 | this.filterTextChangedDebounced(); 523 | }); 524 | } 525 | 526 | /// check if a string is equal to another string, ignoring case 527 | public safeCheckString(text: string, content: string): boolean { 528 | if (text == null || text.length == 0) 529 | return false; 530 | 531 | if (content == null || content.length == 0) 532 | return false; 533 | 534 | return content.toLowerCase() == text.toLowerCase(); 535 | } 536 | 537 | public logout() { 538 | var dialogRef = this.dialog.open(YesnoDialogComponent, { 539 | data: { 540 | title: "Exit", 541 | description: "Are you sure you want to exit?" 542 | } 543 | }); 544 | 545 | dialogRef.afterClosed().subscribe(result => { 546 | if (result == true) { 547 | this.firewallSource.stop(); 548 | this.router.navigate(['/']); 549 | } 550 | }); 551 | } 552 | 553 | public clear() { 554 | var dialogRef = this.dialog.open(YesnoDialogComponent, { 555 | data: { 556 | title: "Clear all", 557 | description: "Are you sure you want to delete all firewall logs?" 558 | } 559 | }); 560 | 561 | dialogRef.afterClosed().subscribe(result => { 562 | if (result == true) { 563 | this.firewallSource.clear(); 564 | } 565 | }); 566 | } 567 | 568 | public pause() { 569 | this.isPaused = true; 570 | this.firewallSource.pause(); 571 | } 572 | 573 | public resume() { 574 | this.isPaused = false; 575 | this.firewallSource.start(); 576 | } 577 | 578 | public copyJson() { 579 | navigator.clipboard.writeText(JSON.stringify(this.selectedRow, null, 2)); 580 | this.snackBar.open("JSON copied successfully!", "", { duration: 2000 }); 581 | } 582 | 583 | public toggleExpandJsonSpace() { 584 | const values = ["120px", "400px"]; 585 | 586 | if (this.jsontextHeight == values[0]) 587 | this.jsontextHeight = values[1]; 588 | else 589 | this.jsontextHeight = values[0]; 590 | } 591 | 592 | public showTimestamp(timestamp: string): string { 593 | if (timestamp == null || timestamp.length == 0) 594 | return ""; 595 | 596 | var returnString = ""; 597 | var date = new Date(timestamp); 598 | if (this.timestampFormat == TimestampFormat.GMT) 599 | returnString = timestamp; 600 | else 601 | returnString = date.toLocaleString() + " (Local)"; 602 | 603 | if (this.searchFieldService.searchParams.lastminutes != 0 || 604 | (this.searchFieldService.searchParams.startdate != "" && this.searchFieldService.searchParams.enddate != "") 605 | ) { 606 | returnString = "" + returnString + ""; 607 | } 608 | return returnString; 609 | } 610 | 611 | private refreshList() { 612 | this.dataSource.filter = " "; // not empty filter string forces filterPredicate to be called 613 | this.dataSource.filteredData.length; 614 | this.visibleRows = this.dataSource.filteredData.length; 615 | } 616 | } 617 | -------------------------------------------------------------------------------- /firewall-mon-app/src/app/services/demo-source.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { LoggingService } from './logging.service'; 4 | 5 | import { IFirewallSource, FirewallDataRow, ModelService } from '../services/model.service'; 6 | import { environment } from 'src/environments/environment'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class DemoSourceService implements IFirewallSource { 12 | 13 | private startingRows: number = 50000; 14 | private moreRows: number = 2000; 15 | private intervalBetweenMoreRows: number = 5000; // millisenconds 16 | 17 | constructor( private model:ModelService, 18 | private logginService: LoggingService, 19 | ) { 20 | 21 | this.DATA = []; 22 | 23 | for (let i = 0; i < this.startingRows; i++) { 24 | 25 | var currentTime = new Date(); 26 | currentTime.setSeconds(currentTime.getSeconds() - i); 27 | 28 | var time = JSON.stringify({'now': currentTime}).replace("{\"now\":\"","").replace("\"}",""); //2022-10-18T10:19:05.9886250Z 29 | //var time = JSON.stringify({'now': new Date()}).replace("{\"now\":\"","").replace("\"}",""); //2022-10-18T10:19:05.9886250Z 30 | 31 | var row = { 32 | rowid: this.getRowID(), 33 | 34 | time: time, 35 | category: this.categories[Math.floor(Math.random() * this.categories.length)], 36 | protocol: this.protocolsArray[Math.floor(Math.random() * this.protocolsArray.length)], 37 | sourceip: (Math.floor(Math.random() * 255) + 1)+"."+(Math.floor(Math.random() * 255))+"."+(Math.floor(Math.random() * 255))+"."+(Math.floor(Math.random() * 255)), 38 | srcport: this.portsArray[Math.floor(Math.random() * this.portsArray.length)], 39 | targetip: (Math.floor(Math.random() * 255) + 1)+"."+(Math.floor(Math.random() * 255))+"."+(Math.floor(Math.random() * 255))+"."+(Math.floor(Math.random() * 255)), 40 | targetport: this.portsArray[Math.floor(Math.random() * this.portsArray.length)], 41 | action: this.actionsArray[Math.floor(Math.random() * this.actionsArray.length)], 42 | policy: this.policies[Math.floor(Math.random() * this.policies.length)], 43 | dataRow: this.buildDatarow(time) 44 | } as FirewallDataRow; 45 | 46 | this.DATA.push(row); 47 | } 48 | } 49 | 50 | private intervalId: any=null; 51 | private protocolsArray: Array = ["TCP", "UDP"]; 52 | private actionsArray: Array = ["Allow", "Deny", "Request", "Alert", "Drop"]; 53 | private portsArray: Array = ["80", "443", "8080", "8443","22","21","23","25","53","110","143","389","443","445","993","995","1723","3306","3389","5900","8080","8443"]; 54 | private categories: Array = ["NetworkRule", "ApplicationRule", "NatRule"]; 55 | private policies: Array = ["Category01>Group01>Policy01", "Category02>Group02>Policy02", "Category03>Group03>Policy03", "Category04>Group04>Policy04" ]; 56 | private DATA: Array = []; 57 | 58 | public skippedRows: number = 0; 59 | public onDataArrived?: (data: Array) => void; 60 | public onRowSkipped?: (skipped: number) => void; 61 | public onMessageArrived?: ((message: string) => void); 62 | 63 | public async start() { 64 | await this.randomQuote(); 65 | await this.randomQuote(); 66 | await this.randomQuote(); 67 | 68 | this.outputMessage(""); 69 | 70 | this.onDataArrived?.(this.DATA); 71 | 72 | this.intervalId = setInterval(() => { 73 | const moreRows: number = Math.floor(Math.random() * this.moreRows); 74 | for (let i = 0; i < moreRows; i++) { 75 | if (Math.random() > 0.2) { 76 | var time = JSON.stringify({'now': new Date()}).replace("{\"now\":\"","").replace("\"}",""); //2022-10-18T10:19:05.9886250Z 77 | var row = { 78 | rowid: this.getRowID(), 79 | 80 | time: time, 81 | category: this.categories[Math.floor(Math.random() * this.categories.length)], 82 | protocol: this.protocolsArray[Math.floor(Math.random() * this.protocolsArray.length)], 83 | sourceip: (Math.floor(Math.random() * 255) + 1)+"."+(Math.floor(Math.random() * 255))+"."+(Math.floor(Math.random() * 255))+"."+(Math.floor(Math.random() * 255)), 84 | srcport: this.portsArray[Math.floor(Math.random() * this.portsArray.length)], 85 | targetip: (Math.floor(Math.random() * 255) + 1)+"."+(Math.floor(Math.random() * 255))+"."+(Math.floor(Math.random() * 255))+"."+(Math.floor(Math.random() * 255)), 86 | targetport: this.portsArray[Math.floor(Math.random() * this.portsArray.length)], 87 | action: this.actionsArray[Math.floor(Math.random() * this.actionsArray.length)], 88 | policy: this.policies[Math.floor(Math.random() * this.policies.length)], 89 | dataRow: this.buildDatarow(time) 90 | } as FirewallDataRow; 91 | 92 | if (Math.random() > 0.8) { 93 | row.moreInfo = "https://www." + this.randomQuotes[Math.floor(Math.random() * this.randomQuotes.length)].replace(/ /g,".") + ".com"; 94 | } 95 | } 96 | else { 97 | var time = JSON.stringify({'now': new Date()}).replace("{\"now\":\"","").replace("\"}",""); //2022-10-18T10:19:05.9886250Z 98 | row = { 99 | rowid: this.getRowID(), 100 | 101 | time: time, 102 | category: "SKIPPED", 103 | action: "unmanaged row type", 104 | dataRow: this.buildDatarow(time) 105 | } as FirewallDataRow; 106 | 107 | this.skippedRows++; 108 | this.onRowSkipped?.(this.skippedRows); 109 | } 110 | this.DATA.unshift(row); 111 | 112 | while (this.DATA.length > environment.EventsQueueLength) { 113 | this.DATA.pop(); 114 | } 115 | 116 | } 117 | 118 | this.onDataArrived?.(this.DATA); 119 | this.outputMessage( moreRows + " more events received as of " + new Date().toLocaleString()); 120 | 121 | this.logginService.logEvent ("DEMO Source heartbit"); 122 | }, this.intervalBetweenMoreRows); 123 | } 124 | 125 | public async pause() { 126 | clearInterval(this.intervalId); 127 | this.logginService.logEvent("demo source paused"); 128 | } 129 | 130 | public async stop() { 131 | clearInterval(this.intervalId); 132 | this.logginService.logEvent("demo source stopped"); 133 | } 134 | 135 | public async clear() { 136 | this.DATA = []; 137 | this.onDataArrived?.(this.DATA); 138 | this.outputMessage("logs successfully deleted!"); 139 | } 140 | 141 | private outputMessage (text:string): void { 142 | this.onMessageArrived?.(text); 143 | this.logginService.logTrace(text); 144 | } 145 | 146 | private buildDatarow(time:string):any { 147 | const datarow:string = `{ 148 | "category": "AzureFirewallNetworkRule", 149 | "time": "` + time + `", 150 | "resourceId": "/SUBSCRIPTIONS/` + crypto.randomUUID() + `", 151 | "operationName": "AzureFirewallNatRuleLog", 152 | "properties": { 153 | "Protocol": "HTTP", 154 | "SourceIp": "10.13.1.4", 155 | "SourcePort": 51674, 156 | "DestinationIp": "", 157 | "DestinationPort": 80, 158 | "Fqdn": "testmaliciousdomain.eastus.cloudapp.azure.com", 159 | "TargetUrl": "", 160 | "Action": "Alert", 161 | "ThreatDescription": "This is a test indicator for a Microsoft owned domain.", 162 | "IsTlsInspected": "false", 163 | "msg": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 164 | }}` 165 | return JSON.parse(datarow); 166 | } 167 | 168 | private async randomQuote() { 169 | this.outputMessage(this.randomQuotes[Math.floor(Math.random() * this.randomQuotes.length)]); 170 | await new Promise(resolve => setTimeout(resolve, 500)); 171 | } 172 | 173 | private lastRowID: number = 0; 174 | private getRowID(): string { 175 | this.lastRowID++; 176 | return this.lastRowID.toString(); 177 | } 178 | 179 | private randomQuotes:Array = [ 180 | "Reticulating splines...", 181 | "Generating witty dialog...", 182 | "Swapping time and space...", 183 | "Spinning violently around the y-axis...", 184 | "Tokenizing real life...", 185 | "Bending the spoon...", 186 | "Filtering morale...", 187 | "Don't think of purple hippos...", 188 | "We need a new fuse...", 189 | "Have a good day.", 190 | "Upgrading Windows, your PC will restart several times. Sit back and relax.", 191 | "640K ought to be enough for anybody", 192 | "The architects are still drafting", 193 | "The bits are breeding", 194 | "We're building the buildings as fast as we can", 195 | "Would you prefer chicken, steak, or tofu?", 196 | "(Pay no attention to the man behind the curtain)", 197 | "...and enjoy the elevator music...", 198 | "Please wait while the little elves draw your map", 199 | "Don't worry - a few bits tried to escape, but we caught them", 200 | "Would you like fries with that?", 201 | "Checking the gravitational constant in your locale...", 202 | "Go ahead -- hold your breath!", 203 | "...at least you're not on hold...", 204 | "Hum something loud while others stare", 205 | "You're not in Kansas any more", 206 | "The server is powered by a lemon and two electrodes.", 207 | "Please wait while a larger software vendor in Seattle takes over the world", 208 | "We're testing your patience", 209 | "As if you had any other choice", 210 | "Follow the white rabbit", 211 | "Why don't you order a sandwich?", 212 | "While the satellite moves into position", 213 | "keep calm and npm install", 214 | "The bits are flowing slowly today", 215 | "Dig on the 'X' for buried treasure... ARRR!", 216 | "It's still faster than you could draw it", 217 | "The last time I tried this the monkey didn't survive. Let's hope it works better this time.", 218 | "I should have had a V8 this morning.", 219 | "My other loading screen is much faster.", 220 | "Testing on Timmy... We're going to need another Timmy.", 221 | "Reconfoobling energymotron...", 222 | "(Insert quarter)", 223 | "Are we there yet?", 224 | "Have you lost weight?", 225 | "Just count to 10", 226 | "Why so serious?", 227 | "It's not you. It's me.", 228 | "Counting backwards from Infinity", 229 | "Don't panic...", 230 | "Embiggening Prototypes", 231 | "Do not run! We are your friends!", 232 | "Do you come here often?", 233 | "Warning: Don't set yourself on fire.", 234 | "We're making you a cookie.", 235 | "Creating time-loop inversion field", 236 | "Spinning the wheel of fortune...", 237 | "Loading the enchanted bunny...", 238 | "Computing chance of success", 239 | "I'm sorry Dave, I can't do that.", 240 | "Looking for exact change", 241 | "All your web browser are belong to us", 242 | "All I really need is a kilobit.", 243 | "I feel like im supposed to be loading something. . .", 244 | "What do you call 8 Hobbits? A Hobbyte.", 245 | "Should have used a compiled language...", 246 | "Is this Windows?", 247 | "Adjusting flux capacitor...", 248 | "Please wait until the sloth starts moving.", 249 | "Don't break your screen yet!", 250 | "I swear it's almost done.", 251 | "Let's take a mindfulness minute...", 252 | "Unicorns are at the end of this road, I promise.", 253 | "Listening for the sound of one hand clapping...", 254 | "Keeping all the 1's and removing all the 0's...", 255 | "Putting the icing on the cake. The cake is not a lie...", 256 | "Cleaning off the cobwebs...", 257 | "Making sure all the i's have dots...", 258 | "We need more dilithium crystals", 259 | "Where did all the internets go", 260 | "Connecting Neurotoxin Storage Tank...", 261 | "Granting wishes...", 262 | "Time flies when you’re having fun.", 263 | "Get some coffee and come back in ten minutes..", 264 | "Spinning the hamster…", 265 | "99 bottles of beer on the wall..", 266 | "Stay awhile and listen..", 267 | "Be careful not to step in the git-gui", 268 | "You edhall not pass! yet..", 269 | "Load it and they will come", 270 | "Convincing AI not to turn evil..", 271 | "There is no spoon. Because we are not done loading it", 272 | "Your left thumb points to the right and your right thumb points to the left.", 273 | "How did you get here?", 274 | "Wait, do you smell something burning?", 275 | "Computing the secret to life, the universe, and everything.", 276 | "When nothing is going right, go left!!...", 277 | "I love my job only when I'm on vacation...", 278 | "i'm not lazy, I'm just relaxed!!", 279 | "Never steal. The government hates competition....", 280 | "Why are they called apartments if they are all stuck together?", 281 | "Life is Short – Talk Fast!!!!", 282 | "Optimism – is a lack of information.....", 283 | "Save water and shower together", 284 | "Whenever I find the key to success, someone changes the lock.", 285 | "Sometimes I think war is God’s way of teaching us geography.", 286 | "I’ve got problem for your solution…..", 287 | "Where there’s a will, there’s a relative.", 288 | "User: the word computer professionals use when they mean !!idiot!!", 289 | "Adults are just kids with money.", 290 | "I think I am, therefore, I am. I think.", 291 | "A kiss is like a fight, with mouths.", 292 | "You don’t pay taxes—they take taxes.", 293 | "Coffee, Chocolate, Men. The richer the better!", 294 | "I am free of all prejudices. I hate everyone equally.", 295 | "git happens", 296 | "May the forks be with you", 297 | "A commit a day keeps the mobs away", 298 | "This is not a joke, it's a commit.", 299 | "Constructing additional pylons...", 300 | "Roping some seaturtles...", 301 | "Locating Jebediah Kerman...", 302 | "We are not liable for any broken screens as a result of waiting.", 303 | "Hello IT, have you tried turning it off and on again?", 304 | "If you type Google into Google you can break the internet", 305 | "Well, this is embarrassing.", 306 | "What is the airspeed velocity of an unladen swallow?", 307 | "Hello, IT... Have you tried forcing an unexpected reboot?", 308 | "They just toss us away like yesterday's jam.", 309 | "They're fairly regular, the beatings, yes. I'd say we're on a bi-weekly beating.", 310 | "The Elders of the Internet would never stand for it.", 311 | "Space is invisible mind dust, and stars are but wishes.", 312 | "Didn't know paint dried so quickly.", 313 | "Everything sounds the same", 314 | "I'm going to walk the dog", 315 | "I didn't choose the engineering life. The engineering life chose me.", 316 | "Dividing by zero...", 317 | "Spawn more Overlord!", 318 | "If I’m not back in five minutes, just wait longer.", 319 | "Some days, you just can’t get rid of a bug!", 320 | "We’re going to need a bigger boat.", 321 | "Chuck Norris never git push. The repo pulls before.", 322 | "Web developers do it with