Skip to content

Commit 87e2696

Browse files
authored
Merge pull request #3783 from Elutrixx/Statistic-Report-Improvements-2254
Implemented new fields for the statistic report that shows how many unused rules there are Globally per Management per Device
2 parents f82d3ca + f87fdb8 commit 87e2696

File tree

8 files changed

+76
-29
lines changed

8 files changed

+76
-29
lines changed

roles/database/files/sql/idempotent/fworch-texts.sql

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
-- text codes (roughly) categorized:
2+
-- U: user texts (explanation or confirmation texts)
3+
-- E: error texts
4+
-- A: Api errors
5+
-- T: texts from external sources (Ldap, other database tables)
6+
-- C: Contextual Info (Tooltips)
7+
-- H: help pages
8+
-- 0000-0999: General
9+
-- 1000-1999: Reporting
10+
-- 2000-2999: Scheduling
11+
-- 3000-3999: Archive
12+
-- 4000-4999: Recertification
13+
-- 5000-5999: Settings
14+
-- 5000-5099: general
15+
-- 5100-5199: devices
16+
-- 5200-5299: authorization
17+
-- 5300-5399: defaults
18+
-- 5400-5499: personal settings
19+
-- 5500-5599: workflow module
20+
-- 5600-5699: workflow module
21+
-- 6000-6999: API
22+
-- 7000-7999: Monitoring
23+
-- 8000-8999: Workflow
24+
-- 9000-9999: Modelling
125

226
-- cleanup
327
DELETE FROM txt;
@@ -644,6 +668,8 @@ INSERT INTO txt VALUES ('user_objects', 'German', 'Nutzerobjekte');
644668
INSERT INTO txt VALUES ('user_objects', 'English', 'User objects');
645669
INSERT INTO txt VALUES ('rules', 'German', 'Regeln');
646670
INSERT INTO txt VALUES ('rules', 'English', 'Rules');
671+
INSERT INTO txt VALUES ('unused_rules', 'German', 'Unbenutze Regeln');
672+
INSERT INTO txt VALUES ('unused_rules', 'English', 'Unused Rules');
647673
INSERT INTO txt VALUES ('changes', 'German', 'Änderungen');
648674
INSERT INTO txt VALUES ('changes', 'English', 'Changes');
649675
INSERT INTO txt VALUES ('used_objects', 'German', 'Benutzte Objekte');
@@ -2825,31 +2851,6 @@ INSERT INTO txt VALUES ('naming_convention', 'English', 'Naming Convention')
28252851
INSERT INTO txt VALUES ('import_app_server', 'German', 'App Server importieren');
28262852
INSERT INTO txt VALUES ('import_app_server', 'English', 'Import app servers');
28272853

2828-
-- text codes (roughly) categorized:
2829-
-- U: user texts (explanation or confirmation texts)
2830-
-- E: error texts
2831-
-- A: Api errors
2832-
-- T: texts from external sources (Ldap, other database tables)
2833-
-- C: Contextual Info (Tooltips)
2834-
-- H: help pages
2835-
-- 0000-0999: General
2836-
-- 1000-1999: Reporting
2837-
-- 2000-2999: Scheduling
2838-
-- 3000-3999: Archive
2839-
-- 4000-4999: Recertification
2840-
-- 5000-5999: Settings
2841-
-- 5000-5099: general
2842-
-- 5100-5199: devices
2843-
-- 5200-5299: authorization
2844-
-- 5300-5399: defaults
2845-
-- 5400-5499: personal settings
2846-
-- 5500-5599: workflow module
2847-
-- 5600-5699: workflow module
2848-
-- 6000-6999: API
2849-
-- 7000-7999: Monitoring
2850-
-- 8000-8999: Workflow
2851-
-- 9000-9999: Modelling
2852-
28532854
-- user messages
28542855
INSERT INTO txt VALUES ('U0001', 'German', 'Eingabetext wurde um nicht erlaubte Zeichen gekürzt');
28552856
INSERT INTO txt VALUES ('U0001', 'English', 'Input text has been shortened by not allowed characters');

roles/lib/files/FWO.Data/Report/DeviceReport.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public class DeviceReport
2020
[JsonProperty("rules_aggregate"), JsonPropertyName("rules_aggregate")]
2121
public ObjectStatistics RuleStatistics { get; set; } = new ObjectStatistics();
2222

23+
[JsonProperty("unusedRules_Count"), JsonPropertyName("unusedRules_Count")]
24+
public ObjectStatistics UnusedRulesStatistics { get; set; } = new();
25+
2326

2427
public DeviceReport()
2528
{ }
@@ -53,6 +56,17 @@ public bool ContainsRules()
5356
{
5457
return Rules != null && Rules.Count() >0 ;
5558
}
59+
60+
/// <summary>
61+
/// Conforms <see cref="DeviceReport"/> internal data to be valid for further usage.
62+
/// </summary>
63+
public void EnforceValidity()
64+
{
65+
if (UnusedRulesStatistics.ObjectAggregate.ObjectCount >= RuleStatistics.ObjectAggregate.ObjectCount)
66+
{
67+
UnusedRulesStatistics.ObjectAggregate.ObjectCount = 0;
68+
}
69+
}
5670
}
5771

5872

roles/lib/files/FWO.Data/Report/ManagementReport.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public class ManagementReport
5353

5454
[JsonProperty("rules_aggregate"), JsonPropertyName("rules_aggregate")]
5555
public ObjectStatistics RuleStatistics { get; set; } = new ();
56+
57+
[JsonProperty("unusedRules_Count"), JsonPropertyName("unusedRules_Count")]
58+
public ObjectStatistics UnusedRulesStatistics { get; set; } = new ();
5659

5760
public bool Ignore { get; set; }
5861
public List<long> RelevantObjectIds = [];
@@ -104,6 +107,22 @@ public string NameAndDeviceNames(string separator = ", ")
104107
{
105108
return $"{Name} [{string.Join(separator, Array.ConvertAll(Devices, device => device.Name))}]";
106109
}
110+
111+
/// <summary>
112+
/// Conforms <see cref="ManagementReport"/> internal data to be valid for further usage.
113+
/// </summary>
114+
public void EnforceValidity()
115+
{
116+
if (UnusedRulesStatistics.ObjectAggregate.ObjectCount >= RuleStatistics.ObjectAggregate.ObjectCount)
117+
{
118+
UnusedRulesStatistics.ObjectAggregate.ObjectCount = 0;
119+
}
120+
121+
foreach (var device in Devices)
122+
{
123+
device.EnforceValidity();
124+
}
125+
}
107126
}
108127

109128
public static class ManagementUtility

roles/lib/files/FWO.Report.Filter/DynGraphqlQuery.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ private static void ConstructWhereStatements(DynGraphqlQuery query, ReportTempla
9797

9898
private static string ConstructStatisticsQuery(DynGraphqlQuery query, string paramString)
9999
{
100+
var insertIndex = query.RuleWhereStatement.LastIndexOf(']');
101+
var unusedRulesWhereStatement = insertIndex >= 0
102+
? query.RuleWhereStatement.Insert(insertIndex, ", {rule_metadatum: { rule_last_hit: { _is_null: true }}}")
103+
: query.RuleWhereStatement;
100104
return $@"
101105
query statisticsReport ({paramString})
102106
{{
@@ -108,11 +112,13 @@ query statisticsReport ({paramString})
108112
services_aggregate(where: {{ {query.SvcObjWhereStatement} }}) {{ aggregate {{ count }} }}
109113
usrs_aggregate(where: {{ {query.UserObjWhereStatement} }}) {{ aggregate {{ count }} }}
110114
rules_aggregate(where: {{ {query.RuleWhereStatement} }}) {{ aggregate {{ count }} }}
115+
unusedRules_Count: rules_aggregate(where: {{ {unusedRulesWhereStatement}}}) {{ aggregate {{ count }} }}
111116
devices({devWhereString})
112117
{{
113118
name: dev_name
114119
id: dev_id
115120
rules_aggregate(where: {{ {query.RuleWhereStatement} }}) {{ aggregate {{ count }} }}
121+
unusedRules_Count: rules_aggregate(where: {{ {unusedRulesWhereStatement}}}) {{ aggregate {{ count }} }}
116122
}}
117123
}}
118124
}}";

roles/lib/files/FWO.Report/ReportGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,12 @@ await report.Generate(0, apiConnection,
173173
SetRelevantManagements(report.ReportData.ManagementData, reportTemplate.ReportParams.DeviceFilter);
174174
foreach (var mgm in report.ReportData.ManagementData.Where(mgt => !mgt.Ignore))
175175
{
176+
mgm.EnforceValidity();
176177
report.ReportData.GlobalStats.RuleStatistics.ObjectAggregate.ObjectCount += mgm.RuleStatistics.ObjectAggregate.ObjectCount;
177178
report.ReportData.GlobalStats.NetworkObjectStatistics.ObjectAggregate.ObjectCount += mgm.NetworkObjectStatistics.ObjectAggregate.ObjectCount;
178179
report.ReportData.GlobalStats.ServiceObjectStatistics.ObjectAggregate.ObjectCount += mgm.ServiceObjectStatistics.ObjectAggregate.ObjectCount;
179180
report.ReportData.GlobalStats.UserObjectStatistics.ObjectAggregate.ObjectCount += mgm.UserObjectStatistics.ObjectAggregate.ObjectCount;
181+
report.ReportData.GlobalStats.UnusedRulesStatistics.ObjectAggregate.ObjectCount += mgm.UnusedRulesStatistics.ObjectAggregate.ObjectCount;
180182
}
181183
return Task.CompletedTask;
182184
}, token);

roles/lib/files/FWO.Report/ReportStatistics.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ public override async Task Generate(int _, ApiConnection apiConnection, Func<Rep
4141

4242
foreach (ManagementReport mgm in ReportData.ManagementData.Where(mgt => !mgt.Ignore))
4343
{
44+
mgm.EnforceValidity();
4445
globalStatisticsManagement.RuleStatistics.ObjectAggregate.ObjectCount += mgm.RuleStatistics.ObjectAggregate.ObjectCount;
4546
globalStatisticsManagement.NetworkObjectStatistics.ObjectAggregate.ObjectCount += mgm.NetworkObjectStatistics.ObjectAggregate.ObjectCount;
4647
globalStatisticsManagement.ServiceObjectStatistics.ObjectAggregate.ObjectCount += mgm.ServiceObjectStatistics.ObjectAggregate.ObjectCount;
4748
globalStatisticsManagement.UserObjectStatistics.ObjectAggregate.ObjectCount += mgm.UserObjectStatistics.ObjectAggregate.ObjectCount;
49+
globalStatisticsManagement.UnusedRulesStatistics.ObjectAggregate.ObjectCount += mgm.UnusedRulesStatistics.ObjectAggregate.ObjectCount;
4850
}
4951
}
5052

roles/tests-unit/files/FWO.Test/ExportTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ public void RulesGenerateJson()
435435
"\"usr\": {\"user_id\": 2,\"user_uid\": \"\",\"user_name\": \"TestUser2\",\"user_comment\": \"\",\"user_lastname\": \"\",\"user_firstname\": \"\",\"usr_typ_id\": 0,\"type\": {\"usr_typ_name\": \"group\"},\"user_create\": 0,\"user_create_time\": {\"time\": \"0001-01-01T00:00:00\"},\"user_last_seen\": 0,\"user_member_names\": \"\",\"user_member_refs\": \"\",\"usergrps\": [],\"usergrp_flats\": []}}]," +
436436
"\"rule_action\": \"deny\",\"rule_track\": \"none\",\"section_header\": \"\"," +
437437
"\"rule_metadatum\": {\"rule_metadata_id\": 0,\"rule_created\": null,\"rule_last_modified\": null,\"rule_first_hit\": null,\"rule_last_hit\": null,\"rule_last_certified\": null,\"rule_last_certifier_dn\": \"\",\"rule_to_be_removed\": false,\"rule_decert_date\": null,\"rule_recertification_comment\": \"\",\"recertification\": [],\"recert_history\": [],\"dev_id\": 0,\"rule_uid\": \"\",\"NextRecert\": \"0001-01-01T00:00:00\",\"LastCertifierName\": \"\",\"Recert\": false,\"Style\": \"\"}," +
438-
"\"translate\": {\"rule_svc_neg\": false,\"rule_svc\": \"\",\"rule_services\": [],\"rule_src_neg\": false,\"rule_src\": \"\",\"rule_froms\": [],\"rule_dst_neg\": false,\"rule_dst\": \"\",\"rule_tos\": []},\"owner_name\": \"\",\"owner_id\": null,\"matches\": \"\",\"dev_id\": 0,\"rule_custom_fields\": \"\",\"DisplayOrderNumber\": 2,\"Certified\": false,\"DeviceName\": \"\",\"DisregardedFroms\": [],\"DisregardedTos\": [],\"DisregardedServices\": [],\"ShowDisregarded\": false}],\"changelog_rules\": null,\"rules_aggregate\": {\"aggregate\": {\"count\": 0}}}]," +
438+
"\"translate\": {\"rule_svc_neg\": false,\"rule_svc\": \"\",\"rule_services\": [],\"rule_src_neg\": false,\"rule_src\": \"\",\"rule_froms\": [],\"rule_dst_neg\": false,\"rule_dst\": \"\",\"rule_tos\": []},\"owner_name\": \"\",\"owner_id\": null,\"matches\": \"\",\"dev_id\": 0,\"rule_custom_fields\": \"\",\"DisplayOrderNumber\": 2,\"Certified\": false,\"DeviceName\": \"\",\"DisregardedFroms\": [],\"DisregardedTos\": [],\"DisregardedServices\": [],\"ShowDisregarded\": false}],\"changelog_rules\": null,\"rules_aggregate\": {\"aggregate\": {\"count\": 0}},\"unusedRules_Count\": {\"aggregate\": {\"count\": 0}}}]," +
439439
"\"import\": {\"aggregate\": {\"max\": {\"id\": null}}},\"RelevantImportId\": null," +
440440
"\"networkObjects\": [],\"serviceObjects\": [],\"userObjects\": []," +
441441
"\"reportNetworkObjects\": [{\"obj_id\": 1,\"obj_name\": \"TestIp1\",\"obj_ip\": \"1.2.3.4/32\",\"obj_ip_end\": \"1.2.3.4/32\",\"obj_uid\": \"\",\"zone\": {\"zone_id\": 0,\"zone_name\": \"\"},\"active\": false,\"obj_create\": 0,\"obj_create_time\": {\"time\": \"0001-01-01T00:00:00\"},\"obj_last_seen\": 0,\"type\": {\"name\": \"network\"},\"obj_comment\": \"\",\"obj_member_names\": \"\",\"obj_member_refs\": \"\",\"objgrps\": [],\"objgrp_flats\": []}," +
@@ -447,7 +447,7 @@ public void RulesGenerateJson()
447447
"\"service_type\": {\"name\": \"\"},\"svc_comment\": \"\",\"svc_color_id\": null,\"ip_proto_id\": null,\"protocol_name\": {\"id\": 17,\"name\": \"UDP\"},\"svc_member_names\": \"\",\"svc_member_refs\": \"\",\"svcgrps\": [],\"svcgrp_flats\": []}]," +
448448
"\"reportUserObjects\": [{\"user_id\": 1,\"user_uid\": \"\",\"user_name\": \"TestUser1\",\"user_comment\": \"\",\"user_lastname\": \"\",\"user_firstname\": \"\",\"usr_typ_id\": 0,\"type\": {\"usr_typ_name\": \"\"},\"user_create\": 0,\"user_create_time\": {\"time\": \"0001-01-01T00:00:00\"},\"user_last_seen\": 0,\"user_member_names\": \"\",\"user_member_refs\": \"\",\"usergrps\": [],\"usergrp_flats\": []}," +
449449
"{\"user_id\": 2,\"user_uid\": \"\",\"user_name\": \"TestUser2\",\"user_comment\": \"\",\"user_lastname\": \"\",\"user_firstname\": \"\",\"usr_typ_id\": 0,\"type\": {\"usr_typ_name\": \"group\"},\"user_create\": 0,\"user_create_time\": {\"time\": \"0001-01-01T00:00:00\"},\"user_last_seen\": 0,\"user_member_names\": \"\",\"user_member_refs\": \"\",\"usergrps\": [],\"usergrp_flats\": []}]," +
450-
"\"ReportedRuleIds\": [],\"ReportedNetworkServiceIds\": [],\"objects_aggregate\": {\"aggregate\": {\"count\": 0}},\"services_aggregate\": {\"aggregate\": {\"count\": 0}},\"usrs_aggregate\": {\"aggregate\": {\"count\": 0}},\"rules_aggregate\": {\"aggregate\": {\"count\": 0}}," +
450+
"\"ReportedRuleIds\": [],\"ReportedNetworkServiceIds\": [],\"objects_aggregate\": {\"aggregate\": {\"count\": 0}},\"services_aggregate\": {\"aggregate\": {\"count\": 0}},\"usrs_aggregate\": {\"aggregate\": {\"count\": 0}},\"rules_aggregate\": {\"aggregate\": {\"count\": 0}},\"unusedRules_Count\": {\"aggregate\": {\"count\": 0}}," +
451451
"\"Ignore\": false}]";
452452
// Log.WriteInfo("Test Log", removeLinebreaks((removeGenDate(reportRules.ExportToJson(), true, true))));
453453
ClassicAssert.AreEqual(expectedJsonResult, RemoveLinebreaks(RemoveGenDate(reportRules.ExportToJson(), false, true)));
@@ -590,10 +590,10 @@ public void ChangesGenerateJson()
590590
"\"rule_services\": [],\"rule_svc_neg\": false,\"rule_svc\": \"\",\"rule_src_neg\": false,\"rule_src\": \"\",\"src_zone\": {\"zone_id\": 0,\"zone_name\": \"\"},\"rule_froms\": [],\"rule_dst_neg\": false,\"rule_dst\": \"\",\"dst_zone\": {\"zone_id\": 0,\"zone_name\": \"\"},\"rule_tos\": [],\"rule_action\": \"\",\"rule_track\": \"\",\"section_header\": \"\"," +
591591
"\"rule_metadatum\": {\"rule_metadata_id\": 0,\"rule_created\": null,\"rule_last_modified\": null,\"rule_first_hit\": null,\"rule_last_hit\": null,\"rule_last_certified\": null,\"rule_last_certifier_dn\": \"\",\"rule_to_be_removed\": false,\"rule_decert_date\": null,\"rule_recertification_comment\": \"\",\"recertification\": [],\"recert_history\": [],\"dev_id\": 0,\"rule_uid\": \"\",\"NextRecert\": \"0001-01-01T00:00:00\",\"LastCertifierName\": \"\",\"Recert\": false,\"Style\": \"\"}," +
592592
"\"translate\": {\"rule_svc_neg\": false,\"rule_svc\": \"\",\"rule_services\": [],\"rule_src_neg\": false,\"rule_src\": \"\",\"rule_froms\": [],\"rule_dst_neg\": false,\"rule_dst\": \"\",\"rule_tos\": []}," +
593-
"\"owner_name\": \"\",\"owner_id\": null,\"matches\": \"\",\"dev_id\": 0,\"rule_custom_fields\": \"\",\"DisplayOrderNumber\": 0,\"Certified\": false,\"DeviceName\": \"\",\"DisregardedFroms\": [],\"DisregardedTos\": [],\"DisregardedServices\": [],\"ShowDisregarded\": false},\"DeviceName\": \"\"}],\"rules_aggregate\": {\"aggregate\": {\"count\": 0}}}]," +
593+
"\"owner_name\": \"\",\"owner_id\": null,\"matches\": \"\",\"dev_id\": 0,\"rule_custom_fields\": \"\",\"DisplayOrderNumber\": 0,\"Certified\": false,\"DeviceName\": \"\",\"DisregardedFroms\": [],\"DisregardedTos\": [],\"DisregardedServices\": [],\"ShowDisregarded\": false},\"DeviceName\": \"\"}],\"rules_aggregate\": {\"aggregate\": {\"count\": 0}},\"unusedRules_Count\": {\"aggregate\": {\"count\": 0}}}]," +
594594
"\"import\": {\"aggregate\": {\"max\": {\"id\": null}}},\"RelevantImportId\": null," +
595595
"\"networkObjects\": [],\"serviceObjects\": [],\"userObjects\": [],\"reportNetworkObjects\": [],\"reportServiceObjects\": [],\"reportUserObjects\": [],\"ReportedRuleIds\": [],\"ReportedNetworkServiceIds\": [],\"objects_aggregate\": {\"aggregate\": {\"count\": 0}}," +
596-
"\"services_aggregate\": {\"aggregate\": {\"count\": 0}},\"usrs_aggregate\": {\"aggregate\": {\"count\": 0}},\"rules_aggregate\": {\"aggregate\": {\"count\": 0}}," +
596+
"\"services_aggregate\": {\"aggregate\": {\"count\": 0}},\"usrs_aggregate\": {\"aggregate\": {\"count\": 0}},\"rules_aggregate\": {\"aggregate\": {\"count\": 0}},\"unusedRules_Count\": {\"aggregate\": {\"count\": 0}}," +
597597
"\"Ignore\": false}]";
598598
ClassicAssert.AreEqual(expectedJsonResult, RemoveLinebreaks(RemoveGenDate(reportChanges.ExportToJson(), false, true)));
599599
}

roles/ui/files/FWO.UI/Pages/Reporting/Reports/StatisticsReport.razor

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<Column TableItem="ManagementReport" Title="@(userConfig.GetText("service_objects"))" Field="@(m => m.ServiceObjectStatistics.ObjectAggregate.ObjectCount)" />
1515
<Column TableItem="ManagementReport" Title="@(userConfig.GetText("user_objects"))" Field="@(m => m.UserObjectStatistics.ObjectAggregate.ObjectCount)" />
1616
<Column TableItem="ManagementReport" Title="@(userConfig.GetText("rules"))" Field="@(m => m.RuleStatistics.ObjectAggregate.ObjectCount)" />
17+
<Column TableItem="ManagementReport" Title="@(userConfig.GetText("unused_rules"))" Field="@(m => m.UnusedRulesStatistics.ObjectAggregate.ObjectCount)" />
1718
</Table>
1819
</div>
1920
}
@@ -27,13 +28,15 @@
2728
<Column TableItem="ManagementReport" Title="@(userConfig.GetText("service_objects"))" Field="@(m => m.ServiceObjectStatistics.ObjectAggregate.ObjectCount)" />
2829
<Column TableItem="ManagementReport" Title="@(userConfig.GetText("user_objects"))" Field="@(m => m.UserObjectStatistics.ObjectAggregate.ObjectCount)" />
2930
<Column TableItem="ManagementReport" Title="@(userConfig.GetText("rules"))" Field="@(m => m.RuleStatistics.ObjectAggregate.ObjectCount)" />
31+
<Column TableItem="ManagementReport" Title="@(userConfig.GetText("unused_rules"))" Field="@(m => m.UnusedRulesStatistics.ObjectAggregate.ObjectCount)" />
3032
</Table>
3133
</div>
3234
<h6>@(userConfig.GetText("no_rules_gtw"))</h6>
3335
<div class="d-flex">
3436
<Table style="font-size:small" TableClass="table table-bordered table-sm th-bg-secondary table-responsive" TableItem="DeviceReport" Items="managementReport.Devices" PageSize="0">
3537
<Column TableItem="DeviceReport" Title="@(userConfig.GetText("gateway"))" Field="@(d => d.Name)" />
3638
<Column TableItem="DeviceReport" Title="@(userConfig.GetText("no_of_rules"))" Field="@(d => d.RuleStatistics.ObjectAggregate.ObjectCount)" />
39+
<Column TableItem="DeviceReport" Title="@(userConfig.GetText("unused_rules"))" Field="@(d => d.UnusedRulesStatistics.ObjectAggregate.ObjectCount)" />
3740
</Table>
3841
</div>
3942
</Collapse>

0 commit comments

Comments
 (0)