Add origin field and filter for ban sources (Tasks 1 & 2)
- Task 1: Mark imported blocklist IP addresses
- Add BanOrigin type and _derive_origin() to ban.py model
- Populate origin field in ban_service list_bans() and bans_by_country()
- BanTable and MapPage companion table show origin badge column
- Tests: origin derivation in test_ban_service.py and test_dashboard.py
- Task 2: Add origin filter to dashboard and world map
- ban_service: _origin_sql_filter() helper; origin param on list_bans()
and bans_by_country()
- dashboard router: optional origin query param forwarded to service
- Frontend: BanOriginFilter type + BAN_ORIGIN_FILTER_LABELS in ban.ts
- fetchBans / fetchBansByCountry forward origin to API
- useBans / useMapData accept and pass origin; page resets on change
- BanTable accepts origin prop; DashboardPage adds segmented filter
- MapPage adds origin Select next to time-range picker
- Tests: origin filter assertions in test_ban_service and test_dashboard
This commit is contained in:
99
frontend/src/utils/mapColors.ts
Normal file
99
frontend/src/utils/mapColors.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Map color utilities for World Map visualization.
|
||||
*
|
||||
* Provides color interpolation logic that maps ban counts to colors based on
|
||||
* configurable thresholds. Countries with zero bans remain transparent;
|
||||
* non-zero counts are interpolated through green → yellow → red color stops.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interpolate a value between two numbers.
|
||||
*
|
||||
* @param start - Start value
|
||||
* @param end - End value
|
||||
* @param t - Interpolation factor in [0, 1]
|
||||
* @returns The interpolated value
|
||||
*/
|
||||
function lerp(start: number, end: number, t: number): number {
|
||||
return start + (end - start) * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB values to hex color string.
|
||||
*
|
||||
* @param r - Red component (0-255)
|
||||
* @param g - Green component (0-255)
|
||||
* @param b - Blue component (0-255)
|
||||
* @returns Hex color string in format "#RRGGBB"
|
||||
*/
|
||||
function rgbToHex(r: number, g: number, b: number): string {
|
||||
const toHex = (n: number): string => {
|
||||
const hex = Math.round(Math.max(0, Math.min(255, n))).toString(16);
|
||||
return hex.length === 1 ? "0" + hex : hex;
|
||||
};
|
||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the fill color for a country based on its ban count.
|
||||
*
|
||||
* Returns "transparent" for zero bans. For non-zero counts, interpolates
|
||||
* through color stops:
|
||||
* - 1 to threshold_low: light green → full green
|
||||
* - threshold_low to threshold_medium: green → yellow
|
||||
* - threshold_medium to threshold_high: yellow → red
|
||||
* - threshold_high or more: solid red
|
||||
*
|
||||
* @param banCount - Number of bans for the country
|
||||
* @param thresholdLow - Ban count for green coloring (default: 20)
|
||||
* @param thresholdMedium - Ban count for yellow coloring (default: 50)
|
||||
* @param thresholdHigh - Ban count for red coloring (default: 100)
|
||||
* @returns Hex color string or "transparent"
|
||||
*/
|
||||
export function getBanCountColor(
|
||||
banCount: number,
|
||||
thresholdLow: number = 20,
|
||||
thresholdMedium: number = 50,
|
||||
thresholdHigh: number = 100,
|
||||
): string {
|
||||
// Zero bans → transparent (no fill)
|
||||
if (banCount === 0) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
// Color stops
|
||||
const lightGreen = { r: 144, g: 238, b: 144 }; // #90EE90
|
||||
const green = { r: 0, g: 128, b: 0 }; // #008000
|
||||
const yellow = { r: 255, g: 255, b: 0 }; // #FFFF00
|
||||
const red = { r: 220, g: 20, b: 60 }; // #DC143C (crimson)
|
||||
|
||||
// 1 to threshold_low: interpolate light green → green
|
||||
if (banCount <= thresholdLow) {
|
||||
const t = (banCount - 1) / (thresholdLow - 1);
|
||||
const r = lerp(lightGreen.r, green.r, t);
|
||||
const g = lerp(lightGreen.g, green.g, t);
|
||||
const b = lerp(lightGreen.b, green.b, t);
|
||||
return rgbToHex(r, g, b);
|
||||
}
|
||||
|
||||
// threshold_low to threshold_medium: interpolate green → yellow
|
||||
if (banCount <= thresholdMedium) {
|
||||
const t = (banCount - thresholdLow) / (thresholdMedium - thresholdLow);
|
||||
const r = lerp(green.r, yellow.r, t);
|
||||
const g = lerp(green.g, yellow.g, t);
|
||||
const b = lerp(green.b, yellow.b, t);
|
||||
return rgbToHex(r, g, b);
|
||||
}
|
||||
|
||||
// threshold_medium to threshold_high: interpolate yellow → red
|
||||
if (banCount <= thresholdHigh) {
|
||||
const t = (banCount - thresholdMedium) / (thresholdHigh - thresholdMedium);
|
||||
const r = lerp(yellow.r, red.r, t);
|
||||
const g = lerp(yellow.g, red.g, t);
|
||||
const b = lerp(yellow.b, red.b, t);
|
||||
return rgbToHex(r, g, b);
|
||||
}
|
||||
|
||||
// threshold_high or more: solid red
|
||||
return rgbToHex(red.r, red.g, red.b);
|
||||
}
|
||||
Reference in New Issue
Block a user