Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions roles/database/files/sql/idempotent/fworch-texts.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2666,6 +2666,16 @@ INSERT INTO txt VALUES ('edit_notification', 'German', 'Benachrichtigung bea
INSERT INTO txt VALUES ('edit_notification', 'English', 'Edit Notification');
INSERT INTO txt VALUES ('delete_notification', 'German', 'Benachrichtigung löschen');
INSERT INTO txt VALUES ('delete_notification', 'English', 'Delete Notification');
INSERT INTO txt VALUES ('color_scheme', 'German', 'Farbschema');
INSERT INTO txt VALUES ('color_scheme', 'English', 'Color Scheme');
INSERT INTO txt VALUES ('color_scheme_blue', 'German', 'Blaues Farbschema');
INSERT INTO txt VALUES ('color_scheme_blue', 'English', 'Blue Color Scheme');
INSERT INTO txt VALUES ('color_scheme_green', 'German', 'Grünes Farbschema');
INSERT INTO txt VALUES ('color_scheme_green', 'English', 'Green Color Scheme');
INSERT INTO txt VALUES ('color_scheme_red', 'German', 'Rotes Farbschema');
INSERT INTO txt VALUES ('color_scheme_red', 'English', 'Red Color Scheme');
INSERT INTO txt VALUES ('color_scheme_purple', 'German', 'Violettes Farbschema');
INSERT INTO txt VALUES ('color_scheme_purple', 'English', 'Purple Color Scheme');

-- monitoring
INSERT INTO txt VALUES ('open_alerts', 'German', 'Offene Alarme');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import traceback
from difflib import ndiff
import json
from typing import Any
from typing_extensions import Literal

import fwo_globals
import fwo_const
Expand Down Expand Up @@ -36,7 +38,7 @@ class RefType(Enum):
# this class is used for importing rules and rule refs into the FWO API
class FwConfigImportRule():

_changed_rule_id_map: dict
_changed_rule_id_map: dict[str, list[int]]
uid2id_mapper: Uid2IdMapper
group_flats_mapper: GroupFlatsMapper
prev_group_flats_mapper: GroupFlatsMapper
Expand All @@ -55,16 +57,16 @@ def __init__(self):
self.prev_group_flats_mapper = service_provider.get_service(Services.PREV_GROUP_FLATS_MAPPER, self.import_details.ImportId)
self.rule_order_service = service_provider.get_service(Services.RULE_ORDER_SERVICE, self.import_details.ImportId)

def updateRulebaseDiffs(self, prevConfig: FwConfigNormalized):
def updateRulebaseDiffs(self, prevConfig: FwConfigNormalized) -> list[int]:

logger = getFwoLogger(debug_level=self.import_details.DebugLevel)

# calculate rule diffs
changedRuleUids = {}
ruleUidsInBoth = {}
previousRulebaseUids = []
currentRulebaseUids = []
new_hit_information = []
changedRuleUids: dict[str, list[str]] = {}
ruleUidsInBoth: dict[str, list[str]] = {}
previousRulebaseUids: list[str] = []
currentRulebaseUids: list[str] = []
new_hit_information: list[dict[str, Any]] = []

rule_order_diffs: dict[str, dict[str, list[str]]] = self.rule_order_service.update_rule_order_diffs(self.import_details.DebugLevel)

Expand Down Expand Up @@ -103,7 +105,7 @@ def updateRulebaseDiffs(self, prevConfig: FwConfigNormalized):
newRulebases = self.getRules(rule_order_diffs["new_rule_uids"])

# update rule_metadata before adding rules
num_added_metadata_rules, new_rule_metadata_ids = self.addNewRuleMetadata(newRulebases)
_, _ = self.addNewRuleMetadata(newRulebases)
_, _ = self.update_rule_metadata_last_hit(new_hit_information)

# # now update the database with all rule diffs
Expand All @@ -113,10 +115,10 @@ def updateRulebaseDiffs(self, prevConfig: FwConfigNormalized):
num_changed_rules, old_rule_ids, updated_rule_ids = self.create_new_rule_version(changedRuleUids)

self.uid2id_mapper.add_rule_mappings(new_rule_ids + updated_rule_ids)
num_new_refs = self.add_new_refs(prevConfig)
_ = self.add_new_refs(prevConfig)

num_deleted_rules, removed_rule_ids = self.markRulesRemoved(rule_order_diffs["deleted_rule_uids"], changedRuleUids)
num_removed_refs = self.remove_outdated_refs(prevConfig)
_ = self.remove_outdated_refs(prevConfig)

_, num_moved_rules, _ = self.verify_rules_moved(changedRuleUids)

Expand Down Expand Up @@ -147,27 +149,27 @@ def _create_removed_rules_map(self, removed_rule_ids: list[int]):



def _collect_uncaught_moves(self, movedRuleUids, changedRuleUids):
def _collect_uncaught_moves(self, movedRuleUids: dict[str, list[str]], changedRuleUids: dict[str, list[str]]):
for rulebaseId in movedRuleUids:
for ruleUid in movedRuleUids[rulebaseId]:
if ruleUid not in changedRuleUids.get(rulebaseId, []):
if rulebaseId not in changedRuleUids:
changedRuleUids[rulebaseId] = []
changedRuleUids[rulebaseId].append(ruleUid)

def collect_last_hit_changes(self, rule_uid, current_rulebase, previous_rulebase, new_hit_information):
def collect_last_hit_changes(self, rule_uid: str, current_rulebase: Rulebase, previous_rulebase: Rulebase, new_hit_information: list[dict[str, Any]]):
if self.last_hit_changed(current_rulebase.Rules[rule_uid], previous_rulebase.Rules[rule_uid]):
self.append_rule_metadata_last_hit(new_hit_information, current_rulebase.Rules[rule_uid], self.import_details.MgmDetails.CurrentMgmId)


@staticmethod
def collect_changed_rules(rule_uid, current_rulebase, previous_rulebase, rulebase_id, changed_rule_uids):
def collect_changed_rules(rule_uid: str, current_rulebase: Rulebase, previous_rulebase: Rulebase, rulebase_id: str, changed_rule_uids: dict[str, list[str]]):
if current_rulebase.Rules[rule_uid] != previous_rulebase.Rules[rule_uid]:
changed_rule_uids[rulebase_id].append(rule_uid)


@staticmethod
def preserve_rule_num_numeric(current_rulebase, previous_rulebase, rule_uid):
def preserve_rule_num_numeric(current_rulebase: Rulebase, previous_rulebase: Rulebase, rule_uid: str):
if current_rulebase.Rules[rule_uid].rule_num_numeric == 0:
current_rulebase.Rules[rule_uid].rule_num_numeric = previous_rulebase.Rules[rule_uid].rule_num_numeric

Expand Down Expand Up @@ -459,8 +461,8 @@ def getRulesByIdWithRefUids(self, ruleIds: list[int]) -> tuple[int, int, list[Ru
raise


def getRules(self, ruleUids) -> list[Rulebase]:
rulebases = []
def getRules(self, ruleUids: dict[str, list[str]]) -> list[Rulebase]:
rulebases: list[Rulebase] = []
for rb in self.normalized_config.rulebases:
if rb.uid in ruleUids:
filtered_rules = {uid: rule for uid, rule in rb.Rules.items() if uid in ruleUids[rb.uid]}
Expand Down Expand Up @@ -530,7 +532,7 @@ def addNewRuleMetadata(self, newRules: list[Rulebase]):
}
"""

addNewRuleMetadata: list[dict] = self.PrepareNewRuleMetadata(newRules)
addNewRuleMetadata: list[dict[str, Any]] = self.PrepareNewRuleMetadata(newRules)
query_variables = { 'ruleMetadata': addNewRuleMetadata }

if fwo_globals.debug_level>9:
Expand All @@ -554,7 +556,7 @@ def addNewRuleMetadata(self, newRules: list[Rulebase]):

# collect new last hit information
@staticmethod
def append_rule_metadata_last_hit (new_hit_information: list[dict], rule: RuleNormalized, mgm_id: int):
def append_rule_metadata_last_hit (new_hit_information: list[dict[str, Any]], rule: RuleNormalized, mgm_id: int):
if new_hit_information is None:
new_hit_information = []
new_hit_information.append({
Expand All @@ -564,7 +566,7 @@ def append_rule_metadata_last_hit (new_hit_information: list[dict], rule: RuleNo


# adds new rule_metadatum to the database
def update_rule_metadata_last_hit (self, new_hit_information: list[dict]):
def update_rule_metadata_last_hit (self, new_hit_information: list[dict[str, Any]]):
logger = getFwoLogger()
errors = 0
changes = 0
Expand All @@ -586,11 +588,10 @@ def update_rule_metadata_last_hit (self, new_hit_information: list[dict]):
return errors, changes


def addRulebasesWithoutRules(self, newRules: list[Rulebase]):
def addRulebasesWithoutRules(self, newRules: list[Rulebase]) -> tuple[int, list[str]]:
logger = getFwoLogger()
changes = 0
newRulebaseIds = []
newRuleIds = []
newRulebaseIds: list[str] = []

addRulebasesWithoutRulesMutation = """mutation upsertRulebaseWithoutRules($rulebases: [rulebase_insert_input!]!) {
insert_rulebase(
Expand Down Expand Up @@ -632,10 +633,10 @@ def addRulebasesWithoutRules(self, newRules: list[Rulebase]):

# as we cannot add the rules for all rulebases in one go (using a constraint from the rule table),
# we need to add them per rulebase separately
def addRulesWithinRulebases(self, newRules: list[Rulebase]):
def addRulesWithinRulebases(self, newRules: list[Rulebase]) -> tuple[Any | Literal[0], list[str]]:
logger = getFwoLogger()
changes = 0
newRuleIds = []
newRuleIds: list[str] = []
# TODO: need to update the RulebaseMap here?!

newRulesForImport: list[RulebaseForImport] = self.PrepareNewRulebases(newRules)
Expand Down Expand Up @@ -666,15 +667,15 @@ def addRulesWithinRulebases(self, newRules: list[Rulebase]):

# adds only new rules to the database
# unchanged or deleted rules are not touched here
def addNewRules(self, newRulebases: list[Rulebase]):
changes1, newRulebaseIds = self.addRulebasesWithoutRules(newRulebases)
def addNewRules(self, newRulebases: list[Rulebase]) -> tuple[int, list[dict[str, Any]]]:
changes1, _ = self.addRulebasesWithoutRules(newRulebases)
changes2, newRuleIds = self.addRulesWithinRulebases(newRulebases)

return changes1+changes2, newRuleIds


def PrepareNewRuleMetadata(self, newRules: list[Rulebase]) -> list[dict]:
newRuleMetadata: list[dict] = []
def PrepareNewRuleMetadata(self, newRules: list[Rulebase]) -> list[dict[str, Any]]:
newRuleMetadata: list[dict[str, Any]] = []

now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
for rulebase in newRules:
Expand Down Expand Up @@ -710,10 +711,10 @@ def PrepareNewRulebases(self, newRules: list[Rulebase], includeRules: bool = Tru
# add rules for each rulebase
return newRulesForImport

def markRulesRemoved(self, removedRuleUids, changedRuleUids):
def markRulesRemoved(self, removedRuleUids: dict[str, list[str]], changedRuleUids: dict[str, list[str]]) -> tuple[int, list[int]]:
logger = getFwoLogger()
changes = 0
collectedRemovedRuleIds = []
collectedRemovedRuleIds: list[str] = []

# TODO: make sure not to mark new (changed) rules as removed (order of calls!)

Expand All @@ -728,7 +729,7 @@ def markRulesRemoved(self, removedRuleUids, changedRuleUids):
}
}
"""
query_variables = { 'importId': self.import_details.ImportId,
query_variables: dict[str, Any] = { 'importId': self.import_details.ImportId,
'mgmId': self.import_details.MgmDetails.CurrentMgmId,
'uids': list(removedRuleUids[rbName]) }

Expand All @@ -746,7 +747,7 @@ def markRulesRemoved(self, removedRuleUids, changedRuleUids):
return changes, collectedRemovedRuleIds


def create_new_rule_version(self, rule_uids):
def create_new_rule_version(self, rule_uids: dict[str, list[str]]) -> tuple[int, list[int], list[dict[str, Any]]]:
logger = getFwoLogger()
self._changed_rule_id_map = {}

Expand Down Expand Up @@ -793,7 +794,7 @@ def create_new_rule_version(self, rule_uids):
}
"""

import_rules: list = []
import_rules: list[dict[str, Any]] = []

for rulebase_uid in list(rule_uids.keys()):

Expand Down Expand Up @@ -924,7 +925,7 @@ def update_rule_enforced_on_gateway_after_move(self, insert_rules_return, update
logger.exception(f"failed to move rules: {str(traceback.format_exc())}")
return 1, 0, []

def verify_rules_moved(self, changed_rule_uids):
def verify_rules_moved(self, changed_rule_uids: dict[str, list[str]]) -> tuple[int, int, list[str]]:
error_count_move = 0
number_of_moved_rules = 0

Expand Down Expand Up @@ -1172,7 +1173,7 @@ def prepare_single_rule_for_import(self, rule: RuleNormalized, importDetails: Im

return rule_for_import

def write_changelog_rules(self, added_rules_ids, removed_rules_ids):
def write_changelog_rules(self, added_rules_ids: list[str], removed_rules_ids: list[str]) -> int:
logger = getFwoLogger()
errors = 0

Expand All @@ -1197,7 +1198,7 @@ def write_changelog_rules(self, added_rules_ids, removed_rules_ids):
return errors


def prepare_changelog_rules_insert_objects(self, added_rules_ids, removed_rules_ids):
def prepare_changelog_rules_insert_objects(self, added_rules_ids: list[str], removed_rules_ids: dict[str, list[str]]) -> list[dict[str, Any]]:
"""
Creates two lists of insert arguments for the changelog_rules db table, one for new rules, one for deleted.
"""
Expand Down
44 changes: 44 additions & 0 deletions roles/lib/files/FWO.Config.Api/Data/ColorScheme.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;

namespace FWO.Config.Api.Data
{
/// <summary>
/// a list of all available ColorSchemes
/// </summary>
public class ColorScheme
{
[JsonProperty("name"), JsonPropertyName("name")]
public string Name { get; set; } = "";

[JsonProperty("isDefault"), JsonPropertyName("isDefault")]
public bool IsDefault { get; set; } = false;

[JsonProperty("hex"), JsonPropertyName("hex")]
public string Hex { get; set; } = "";

[JsonProperty("hex2"), JsonPropertyName("hex2")]
public string Hex2 { get; set; } = "";

[JsonProperty("textHex"), JsonPropertyName("textHex")]
public string TextHex { get; set; } = "";


public static List<ColorScheme> AvailableSchemes { get; } = new List<ColorScheme>
{
new ColorScheme { Name = "color_scheme_blue", IsDefault = true, Hex = "#054B8C", Hex2 = "#03335E", TextHex = "#2FA5ED" },
new ColorScheme { Name = "color_scheme_green", Hex = "#1c8c05ff" , Hex2 = "#155e03ff", TextHex = "#b4ed2fff" },
new ColorScheme { Name = "color_scheme_red", Hex = "#8c0505ff", Hex2 = "#5e0303ff", TextHex = "#ed2f2fff" },
new ColorScheme { Name = "color_scheme_purple", Hex = "#5b058cff", Hex2 = "#3a035eff", TextHex = "#a12fedff" }
// Add more schemes as needed
};

public static ColorScheme GetSchemeByName(string name)
{
return AvailableSchemes.FirstOrDefault(s => s.Name == name) ?? AvailableSchemes.First(s => s.IsDefault);
}
}

}


3 changes: 3 additions & 0 deletions roles/lib/files/FWO.Config.Api/Data/ConfigData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,9 @@ public class ConfigData : ICloneable
[JsonProperty("debugConfig"), JsonPropertyName("debugConfig")]
public string DebugConfig { get; set; } = "";

[JsonProperty("colorScheme"), JsonPropertyName("colorScheme"), UserConfigData]
public string ColorScheme { get; set; } = "color_scheme_blue";

[JsonProperty("autoCalculateInternetZone"), JsonPropertyName("autoCalculateInternetZone")]
public bool AutoCalculateInternetZone { get; set; } = true;

Expand Down
14 changes: 14 additions & 0 deletions roles/ui/files/FWO.UI/Pages/Settings/SettingsDefaults.razor
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@
<CustomLogoUpload AuthorizedRoles="@Roles.Admin" SupportedFileFormats=".png,.jpg,.jpeg"></CustomLogoUpload>
</div>
</div>
<div class="form-group row" data-toggle="tooltip" title="@(userConfig.PureLine("H5411"))">
<label class="col-form-label col-sm-4">@(userConfig.GetText("color_scheme")):</label>
<div class="col-sm-2">
<Dropdown ElementType="ColorScheme" ElementToString="@(c => userConfig.GetText(c.Name ))" @bind-SelectedElement="selectedColorScheme" Elements="ColorScheme.AvailableSchemes">
<ElementTemplate Context="colorScheme">
@(userConfig.GetText(colorScheme.Name))
</ElementTemplate>
</Dropdown>
</div>
</div>
<hr />
<div class="form-group row" data-toggle="tooltip" title="@(userConfig.PureLine("H5412"))">
<label class="col-form-label col-sm-4">@(userConfig.GetText("elementsPerFetch"))*:</label>
Expand Down Expand Up @@ -223,6 +233,7 @@ else

private ConfigData? configData;
private Language selectedLanguage = new Language();
private ColorScheme selectedColorScheme = new ColorScheme();
private DateTime autoDiscStartDate = DateTime.Today;
private DateTime autoDiscStartTime = DateTime.Now.AddSeconds(-DateTime.Now.Second);
private List<Module> availableModules { get; set; } = [];
Expand All @@ -239,6 +250,7 @@ else

configData = await globalConfig.GetEditableConfig();
selectedLanguage = globalConfig.UiLanguages.FirstOrDefault(l => l.Name == configData.DefaultLanguage) ?? new Language();
selectedColorScheme = ColorScheme.GetSchemeByName(configData.ColorScheme);
autoDiscStartDate = autoDiscStartTime = configData.AutoDiscoverStartAt;
availableModules = JsonSerializer.Deserialize<List<Module>>(string.IsNullOrEmpty(configData.AvailableModules) ? ModuleGroups.AllModulesNumList() : configData.AvailableModules) ?? throw new JsonException("Config data could not be parsed.");
modulesVisibleDict = [];
Expand All @@ -261,6 +273,8 @@ else
if(configData != null)
{
configData.DefaultLanguage = selectedLanguage.Name;
configData.ColorScheme = selectedColorScheme.Name;
StateHasChanged();
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling StateHasChanged() in the middle of the Save() method is unnecessary and could cause premature re-rendering before the save operation completes. If a state refresh is needed, it should be called after all data modifications are complete, or removed entirely if the framework handles it automatically after the async operation completes.

Suggested change
StateHasChanged();

Copilot uses AI. Check for mistakes.
configData.AutoDiscoverStartAt = autoDiscStartDate.Date.Add(autoDiscStartTime.TimeOfDay);
availableModules = [];
foreach(Module module in modulesVisibleDict.Keys)
Expand Down
4 changes: 3 additions & 1 deletion roles/ui/files/FWO.UI/Shared/NavigationMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
@if (InitComplete)
{
<div class="position-sticky" style="z-index:15; top:0px;">
<nav id="navbar" class="navbar navbar-expand-xl navbar-dark bg-blue shadow w-100">
<nav id="navbar" class="navbar navbar-expand-xl navbar-dark bg-blue shadow w-100"
style="--bg-color:@(ColorScheme.GetSchemeByName(userConfig.ColorScheme).Hex);
--bg-color-2:@(ColorScheme.GetSchemeByName(userConfig.ColorScheme).Hex2);">
<a class="navbar-brand pad-10" href="#">
@if (globalConfig.UseCustomLogo)
{
Expand Down
5 changes: 2 additions & 3 deletions roles/ui/files/FWO.UI/wwwroot/css/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ header {

.bg-blue {
z-index: 10;
background-image: linear-gradient(#054B8C, #03335E);
background-image: linear-gradient(var(--bg-color, #054B8C), var(--bg-color-2, #03335E));
}

.bg-gray {
Expand Down Expand Up @@ -173,10 +173,9 @@ h2,
h3,
h4,
h5 {
color: #2FA5ED;
color: var(--heading-color, #2FA5ED); /* fallback if not set */
font-weight: bold;
}

.popup-body {
height: fit-content;
/* Increase/decrease this value for cross-browser compatibility */
Expand Down