chore: sync dev to master
Some checks failed
Build and Push Docker image / 🔧 Prepare Metadata (push) Has been cancelled
Build and Push Docker image / 🐳 Docker Build (${{ matrix.arch }}) (push) Has been cancelled
Build and Push Docker image / 🪟 Windows Build (amd64) (push) Has been cancelled
Build and Push Docker image / 🔗 Docker Manifest (push) Has been cancelled
Build and Push Docker image / 🚀 Release Artifacts (push) Has been cancelled

This commit is contained in:
github-actions[bot]
2026-06-02 01:37:24 +00:00
8 changed files with 886 additions and 173 deletions

View File

@@ -175,6 +175,8 @@ flush_interval=5
;none records all countries as unknown. ;none records all countries as unknown.
geo_provider=header geo_provider=header
country_headers=CF-IPCountry,X-Geo-Country,X-Vercel-IP-Country,CloudFront-Viewer-Country country_headers=CF-IPCountry,X-Geo-Country,X-Vercel-IP-Country,CloudFront-Viewer-Country
;Used for China regional statistics when visitor location headers are added by a trusted edge.
china_region_headers=CF-Region-Code,cf-region-code,X-Geo-Subdivision
;Optional Basic authentication for /dashboard and /dashboard/data. ;Optional Basic authentication for /dashboard and /dashboard/data.
;Only applies when statistics enabled=true. ;Only applies when statistics enabled=true.
;Missing or false keeps the dashboard password disabled. ;Missing or false keeps the dashboard password disabled.

View File

@@ -95,6 +95,8 @@ statistics:
# none records all countries as unknown. # none records all countries as unknown.
provider: header provider: header
country_headers: ["CF-IPCountry", "X-Geo-Country", "X-Vercel-IP-Country", "CloudFront-Viewer-Country"] country_headers: ["CF-IPCountry", "X-Geo-Country", "X-Vercel-IP-Country", "CloudFront-Viewer-Country"]
# Used for China regional statistics when visitor location headers are added by a trusted edge.
china_region_headers: ["CF-Region-Code", "cf-region-code", "X-Geo-Subdivision"]
dashboard_auth: dashboard_auth:
# Optional Basic authentication for /dashboard and /dashboard/data. # Optional Basic authentication for /dashboard and /dashboard/data.
# Only applies when statistics.enabled is true. # Only applies when statistics.enabled is true.

View File

@@ -8,8 +8,8 @@
#ifndef CPPHTTPLIB_HTTPLIB_H #ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.46.0" #define CPPHTTPLIB_VERSION "0.46.1"
#define CPPHTTPLIB_VERSION_NUM "0x002e00" #define CPPHTTPLIB_VERSION_NUM "0x002e01"
#ifdef _WIN32 #ifdef _WIN32
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00

File diff suppressed because it is too large Load Diff

View File

@@ -1030,10 +1030,10 @@ std::string page(Request &request, Response &response) {
<footer> <footer>
<span data-lang="en">SubConverter-Extended )html" + <span data-lang="en">SubConverter-Extended )html" +
std::string(VERSION) + std::string(VERSION) +
R"html( · <a href="/version">Version</a></span> R"html( · <a href="/version">Version</a> · Source Code: <a href="https://github.com/Aethersailor/SubConverter-Extended" target="_blank" rel="noopener noreferrer">GitHub</a> · License: <a href="https://www.gnu.org/licenses/gpl-3.0.html" target="_blank" rel="noopener noreferrer">GPL-3.0</a></span>
<span data-lang="zh">SubConverter-Extended )html" + <span data-lang="zh">SubConverter-Extended )html" +
std::string(VERSION) + std::string(VERSION) +
R"html( · <a href="/version">版本信息</a></span> R"html( · <a href="/version">版本信息</a> · 源代码:<a href="https://github.com/Aethersailor/SubConverter-Extended" target="_blank" rel="noopener noreferrer">GitHub</a> · 许可证:<a href="https://www.gnu.org/licenses/gpl-3.0.html" target="_blank" rel="noopener noreferrer">GPL-3.0</a></span>
</footer> </footer>
</main> </main>

View File

@@ -708,6 +708,12 @@ void readYAMLConf(YAML::Node &node) {
if (!country_headers.empty()) if (!country_headers.empty())
global.statisticsCountryHeaders = country_headers; global.statisticsCountryHeaders = country_headers;
} }
if (stats["geo"]["china_region_headers"].IsSequence()) {
string_array region_headers;
stats["geo"]["china_region_headers"] >> region_headers;
if (!region_headers.empty())
global.statisticsChinaRegionHeaders = region_headers;
}
} }
if (stats["dashboard_auth"].IsDefined()) { if (stats["dashboard_auth"].IsDefined()) {
YAML::Node auth = stats["dashboard_auth"]; YAML::Node auth = stats["dashboard_auth"];
@@ -942,6 +948,10 @@ void readTOMLConf(toml::value &root) {
section_statistics_geo, "country_headers", string_array{}); section_statistics_geo, "country_headers", string_array{});
if (!country_headers.empty()) if (!country_headers.empty())
global.statisticsCountryHeaders = country_headers; global.statisticsCountryHeaders = country_headers;
string_array region_headers = toml::find_or<string_array>(
section_statistics_geo, "china_region_headers", string_array{});
if (!region_headers.empty())
global.statisticsChinaRegionHeaders = region_headers;
auto section_dashboard_auth = auto section_dashboard_auth =
toml::find_or(section_statistics, "dashboard_auth", toml::find_or(section_statistics, "dashboard_auth",
toml::value(toml::table())); toml::value(toml::table()));
@@ -978,6 +988,8 @@ void readConf() {
global.statisticsCountryHeaders = {"CF-IPCountry", "X-Geo-Country", global.statisticsCountryHeaders = {"CF-IPCountry", "X-Geo-Country",
"X-Vercel-IP-Country", "X-Vercel-IP-Country",
"CloudFront-Viewer-Country"}; "CloudFront-Viewer-Country"};
global.statisticsChinaRegionHeaders = {"CF-Region-Code", "cf-region-code",
"X-Geo-Subdivision"};
global.dashboardAuthEnabled = false; global.dashboardAuthEnabled = false;
global.dashboardAuthUsername.clear(); global.dashboardAuthUsername.clear();
global.dashboardAuthPassword.clear(); global.dashboardAuthPassword.clear();
@@ -1273,6 +1285,18 @@ void readConf() {
if (!country_headers.empty()) if (!country_headers.empty())
global.statisticsCountryHeaders = country_headers; global.statisticsCountryHeaders = country_headers;
} }
if (ini.item_exist("china_region_headers")) {
string_array region_headers =
split(ini.get("china_region_headers"), ",");
for (std::string &header : region_headers)
header = trimWhitespace(header, true, true);
region_headers.erase(
std::remove_if(region_headers.begin(), region_headers.end(),
[](const std::string &value) { return value.empty(); }),
region_headers.end());
if (!region_headers.empty())
global.statisticsChinaRegionHeaders = region_headers;
}
ini.get_bool_if_exist("dashboard_auth_enabled", ini.get_bool_if_exist("dashboard_auth_enabled",
global.dashboardAuthEnabled); global.dashboardAuthEnabled);
ini.get_if_exist("dashboard_auth_username", ini.get_if_exist("dashboard_auth_username",

View File

@@ -90,6 +90,8 @@ struct Settings {
string_array statisticsCountryHeaders = { string_array statisticsCountryHeaders = {
"CF-IPCountry", "X-Geo-Country", "X-Vercel-IP-Country", "CF-IPCountry", "X-Geo-Country", "X-Vercel-IP-Country",
"CloudFront-Viewer-Country"}; "CloudFront-Viewer-Country"};
string_array statisticsChinaRegionHeaders = {
"CF-Region-Code", "cf-region-code", "X-Geo-Subdivision"};
bool dashboardAuthEnabled = false; bool dashboardAuthEnabled = false;
std::string dashboardAuthUsername, dashboardAuthPassword; std::string dashboardAuthUsername, dashboardAuthPassword;
int dashboardAuthMaxFailures = 5, dashboardAuthWindowSeconds = 300, int dashboardAuthMaxFailures = 5, dashboardAuthWindowSeconds = 300,

View File

@@ -49,12 +49,14 @@ struct Bucket {
int64_t minute = 0; int64_t minute = 0;
Counters counters; Counters counters;
std::vector<CountryBucketEntry> countries; std::vector<CountryBucketEntry> countries;
std::vector<CountryBucketEntry> china_regions;
}; };
struct DailyBucket { struct DailyBucket {
int64_t day = 0; int64_t day = 0;
Counters counters; Counters counters;
std::vector<CountryBucketEntry> countries; std::vector<CountryBucketEntry> countries;
std::vector<CountryBucketEntry> china_regions;
}; };
struct SnapshotCountry { struct SnapshotCountry {
@@ -62,6 +64,11 @@ struct SnapshotCountry {
CountryCounters counters; CountryCounters counters;
}; };
struct GeoLocation {
std::string country_code;
std::string china_region_code;
};
struct State { struct State {
bool initialized = false; bool initialized = false;
bool dirty = false; bool dirty = false;
@@ -76,6 +83,8 @@ struct State {
Counters lifetime; Counters lifetime;
std::map<std::string, CountryCounters> startup_countries; std::map<std::string, CountryCounters> startup_countries;
std::map<std::string, CountryCounters> lifetime_countries; std::map<std::string, CountryCounters> lifetime_countries;
std::map<std::string, CountryCounters> startup_china_regions;
std::map<std::string, CountryCounters> lifetime_china_regions;
std::array<Bucket, kBucketCount> buckets; std::array<Bucket, kBucketCount> buckets;
std::array<DailyBucket, kDailyBucketCount> daily_buckets; std::array<DailyBucket, kDailyBucketCount> daily_buckets;
}; };
@@ -175,6 +184,30 @@ std::string normalizeCountryCode(std::string value) {
return value; return value;
} }
bool validChinaRegionSuffix(const std::string &value) {
static const std::array<const char *, 35> codes = {
"AH", "BJ", "CQ", "FJ", "GD", "GS", "GX", "GZ", "HA", "HB",
"HE", "HI", "HK", "HL", "HN", "JL", "JS", "JX", "LN", "MO",
"NM", "NX", "QH", "SC", "SD", "SH", "SN", "SX", "TJ", "TW",
"XJ", "XZ", "YN", "ZJ", "XX"};
return std::any_of(codes.begin(), codes.end(), [&](const char *code) {
return value == code;
});
}
std::string normalizeChinaRegionCode(std::string value) {
value = toUpper(trimWhitespace(value, true, true));
for (char &ch : value) {
if (ch == '_')
ch = '-';
}
if (value.rfind("CN-", 0) == 0)
value = value.substr(3);
if (!validChinaRegionSuffix(value))
return "";
return "CN-" + value;
}
std::string countryFromHeaders(const Request &request) { std::string countryFromHeaders(const Request &request) {
if (toLower(global.statisticsGeoProvider) == "none") if (toLower(global.statisticsGeoProvider) == "none")
return "ZZ"; return "ZZ";
@@ -190,6 +223,33 @@ std::string countryFromHeaders(const Request &request) {
return "ZZ"; return "ZZ";
} }
std::string chinaRegionFromHeaders(const Request &request,
const std::string &country) {
if (country == "HK" || country == "MO" || country == "TW")
return "CN-" + country;
if (country != "CN")
return "";
for (const std::string &header : global.statisticsChinaRegionHeaders) {
auto iter = request.headers.find(header);
if (iter == request.headers.end())
continue;
std::string code = normalizeChinaRegionCode(iter->second);
if (!code.empty())
return code;
}
return "CN-XX";
}
GeoLocation geoLocationFromHeaders(const Request &request) {
GeoLocation location;
location.country_code = countryFromHeaders(request);
if (toLower(global.statisticsGeoProvider) != "none")
location.china_region_code =
chinaRegionFromHeaders(request, location.country_code);
return location;
}
void addCounters(Counters &target, uint64_t requests, uint64_t rules) { void addCounters(Counters &target, uint64_t requests, uint64_t rules) {
target.subscription_requests += requests; target.subscription_requests += requests;
target.rule_conversions += rules; target.rule_conversions += rules;
@@ -254,6 +314,29 @@ std::vector<SnapshotCountry> countryWindowLocked(int64_t now_minute,
return result; return result;
} }
std::vector<SnapshotCountry> chinaRegionWindowLocked(int64_t now_minute,
int minutes) {
std::map<std::string, CountryCounters> totals;
if (!g_state)
return {};
int64_t earliest = now_minute - minutes + 1;
for (const Bucket &bucket : g_state->buckets) {
if (bucket.minute < earliest || bucket.minute > now_minute)
continue;
for (const CountryBucketEntry &entry : bucket.china_regions) {
addCountryCounters(totals, entry.code,
entry.counters.subscription_requests,
entry.counters.rule_conversions);
}
}
std::vector<SnapshotCountry> result;
result.reserve(totals.size());
for (const auto &entry : totals)
result.push_back({entry.first, entry.second});
return result;
}
Counters dailyWindowCountersLocked(int64_t now_day, int days) { Counters dailyWindowCountersLocked(int64_t now_day, int days) {
Counters result; Counters result;
if (!g_state) if (!g_state)
@@ -291,6 +374,29 @@ std::vector<SnapshotCountry> countryDailyWindowLocked(int64_t now_day,
return result; return result;
} }
std::vector<SnapshotCountry> chinaRegionDailyWindowLocked(int64_t now_day,
int days) {
std::map<std::string, CountryCounters> totals;
if (!g_state)
return {};
int64_t earliest = now_day - days + 1;
for (const DailyBucket &bucket : g_state->daily_buckets) {
if (bucket.day < earliest || bucket.day > now_day)
continue;
for (const CountryBucketEntry &entry : bucket.china_regions) {
addCountryCounters(totals, entry.code,
entry.counters.subscription_requests,
entry.counters.rule_conversions);
}
}
std::vector<SnapshotCountry> result;
result.reserve(totals.size());
for (const auto &entry : totals)
result.push_back({entry.first, entry.second});
return result;
}
std::vector<SnapshotCountry> std::vector<SnapshotCountry>
countrySnapshotLocked(const std::map<std::string, CountryCounters> &source) { countrySnapshotLocked(const std::map<std::string, CountryCounters> &source) {
std::vector<SnapshotCountry> result; std::vector<SnapshotCountry> result;
@@ -374,6 +480,7 @@ void seedDailyBucketsFromMinuteBucketsLocked() {
g_state->daily_buckets[index].day = day; g_state->daily_buckets[index].day = day;
g_state->daily_buckets[index].counters = Counters(); g_state->daily_buckets[index].counters = Counters();
g_state->daily_buckets[index].countries.clear(); g_state->daily_buckets[index].countries.clear();
g_state->daily_buckets[index].china_regions.clear();
} }
addCounters(g_state->daily_buckets[index].counters, addCounters(g_state->daily_buckets[index].counters,
bucket.counters.subscription_requests, bucket.counters.subscription_requests,
@@ -383,6 +490,11 @@ void seedDailyBucketsFromMinuteBucketsLocked() {
entry.counters.subscription_requests, entry.counters.subscription_requests,
entry.counters.rule_conversions); entry.counters.rule_conversions);
} }
for (const CountryBucketEntry &entry : bucket.china_regions) {
addCountryCounters(g_state->daily_buckets[index].china_regions,
entry.code, entry.counters.subscription_requests,
entry.counters.rule_conversions);
}
} }
} }
@@ -406,7 +518,7 @@ void loadLocked() {
try { try {
json root = json::parse(content); json root = json::parse(content);
int schema = root.value("schema", 1); int schema = root.value("schema", 1);
if (schema < 1 || schema > 3) if (schema < 1 || schema > 4)
return; return;
g_state->last_flush = root.value("updated_at", 0LL); g_state->last_flush = root.value("updated_at", 0LL);
@@ -436,6 +548,20 @@ void loadLocked() {
g_state->lifetime_countries[code] = counters; g_state->lifetime_countries[code] = counters;
} }
auto china_regions = root.value("china_regions", json::object());
for (auto iter = china_regions.begin(); iter != china_regions.end();
++iter) {
std::string code = normalizeChinaRegionCode(iter.key());
if (code.empty())
continue;
CountryCounters counters;
counters.subscription_requests =
iter.value().value("subscription_requests", 0ULL);
counters.rule_conversions = iter.value().value("rule_conversions", 0ULL);
if (counters.subscription_requests || counters.rule_conversions)
g_state->lifetime_china_regions[code] = counters;
}
auto buckets = root.value("buckets", json::array()); auto buckets = root.value("buckets", json::array());
for (const auto &item : buckets) { for (const auto &item : buckets) {
int64_t minute = item.value("minute", 0LL); int64_t minute = item.value("minute", 0LL);
@@ -448,6 +574,7 @@ void loadLocked() {
g_state->buckets[index].counters.rule_conversions = g_state->buckets[index].counters.rule_conversions =
item.value("rule_conversions", 0ULL); item.value("rule_conversions", 0ULL);
g_state->buckets[index].countries.clear(); g_state->buckets[index].countries.clear();
g_state->buckets[index].china_regions.clear();
auto country_items = item.value("countries", json::array()); auto country_items = item.value("countries", json::array());
for (const auto &country_item : country_items) { for (const auto &country_item : country_items) {
@@ -460,6 +587,20 @@ void loadLocked() {
addCountryCounters(g_state->buckets[index].countries, code, requests, addCountryCounters(g_state->buckets[index].countries, code, requests,
rules); rules);
} }
auto region_items = item.value("china_regions", json::array());
for (const auto &region_item : region_items) {
std::string code =
normalizeChinaRegionCode(region_item.value("code", ""));
if (code.empty())
continue;
uint64_t requests =
region_item.value("subscription_requests", 0ULL);
uint64_t rules = region_item.value("rule_conversions", 0ULL);
if (requests || rules)
addCountryCounters(g_state->buckets[index].china_regions, code,
requests, rules);
}
} }
bool loaded_daily_buckets = false; bool loaded_daily_buckets = false;
@@ -475,6 +616,7 @@ void loadLocked() {
g_state->daily_buckets[index].counters.rule_conversions = g_state->daily_buckets[index].counters.rule_conversions =
item.value("rule_conversions", 0ULL); item.value("rule_conversions", 0ULL);
g_state->daily_buckets[index].countries.clear(); g_state->daily_buckets[index].countries.clear();
g_state->daily_buckets[index].china_regions.clear();
auto country_items = item.value("countries", json::array()); auto country_items = item.value("countries", json::array());
for (const auto &country_item : country_items) { for (const auto &country_item : country_items) {
@@ -487,6 +629,19 @@ void loadLocked() {
addCountryCounters(g_state->daily_buckets[index].countries, code, addCountryCounters(g_state->daily_buckets[index].countries, code,
requests, rules); requests, rules);
} }
auto region_items = item.value("china_regions", json::array());
for (const auto &region_item : region_items) {
std::string code =
normalizeChinaRegionCode(region_item.value("code", ""));
if (code.empty())
continue;
uint64_t requests =
region_item.value("subscription_requests", 0ULL);
uint64_t rules = region_item.value("rule_conversions", 0ULL);
if (requests || rules)
addCountryCounters(g_state->daily_buckets[index].china_regions, code,
requests, rules);
}
if (g_state->daily_buckets[index].counters.subscription_requests || if (g_state->daily_buckets[index].counters.subscription_requests ||
g_state->daily_buckets[index].counters.rule_conversions) g_state->daily_buckets[index].counters.rule_conversions)
loaded_daily_buckets = true; loaded_daily_buckets = true;
@@ -513,7 +668,7 @@ bool flushLocked(bool stopping, int64_t now) {
g_state->last_stopped_at = now; g_state->last_stopped_at = now;
json root; json root;
root["schema"] = 3; root["schema"] = 4;
root["updated_at"] = now; root["updated_at"] = now;
root["runtime"] = { root["runtime"] = {
{"first_started_at", g_state->first_started_at}, {"first_started_at", g_state->first_started_at},
@@ -525,6 +680,8 @@ bool flushLocked(bool stopping, int64_t now) {
{"last_stopped_at", g_state->last_stopped_at}}; {"last_stopped_at", g_state->last_stopped_at}};
root["lifetime"] = countersJson(g_state->lifetime); root["lifetime"] = countersJson(g_state->lifetime);
root["countries"] = countriesObjectJson(g_state->lifetime_countries); root["countries"] = countriesObjectJson(g_state->lifetime_countries);
root["china_regions"] =
countriesObjectJson(g_state->lifetime_china_regions);
json buckets = json::array(); json buckets = json::array();
for (const Bucket &bucket : g_state->buckets) { for (const Bucket &bucket : g_state->buckets) {
@@ -544,12 +701,24 @@ bool flushLocked(bool stopping, int64_t now) {
{"rule_conversions", {"rule_conversions",
entry.counters.rule_conversions}}); entry.counters.rule_conversions}});
} }
json china_regions = json::array();
for (const CountryBucketEntry &entry : bucket.china_regions) {
if (!entry.counters.subscription_requests &&
!entry.counters.rule_conversions)
continue;
china_regions.push_back({{"code", entry.code},
{"subscription_requests",
entry.counters.subscription_requests},
{"rule_conversions",
entry.counters.rule_conversions}});
}
buckets.push_back({{"minute", bucket.minute}, buckets.push_back({{"minute", bucket.minute},
{"subscription_requests", {"subscription_requests",
bucket.counters.subscription_requests}, bucket.counters.subscription_requests},
{"rule_conversions", {"rule_conversions",
bucket.counters.rule_conversions}, bucket.counters.rule_conversions},
{"countries", countries}}); {"countries", countries},
{"china_regions", china_regions}});
} }
root["buckets"] = buckets; root["buckets"] = buckets;
@@ -571,12 +740,24 @@ bool flushLocked(bool stopping, int64_t now) {
{"rule_conversions", {"rule_conversions",
entry.counters.rule_conversions}}); entry.counters.rule_conversions}});
} }
json china_regions = json::array();
for (const CountryBucketEntry &entry : bucket.china_regions) {
if (!entry.counters.subscription_requests &&
!entry.counters.rule_conversions)
continue;
china_regions.push_back({{"code", entry.code},
{"subscription_requests",
entry.counters.subscription_requests},
{"rule_conversions",
entry.counters.rule_conversions}});
}
daily_buckets.push_back({{"day", bucket.day}, daily_buckets.push_back({{"day", bucket.day},
{"subscription_requests", {"subscription_requests",
bucket.counters.subscription_requests}, bucket.counters.subscription_requests},
{"rule_conversions", {"rule_conversions",
bucket.counters.rule_conversions}, bucket.counters.rule_conversions},
{"countries", countries}}); {"countries", countries},
{"china_regions", china_regions}});
} }
root["daily_buckets"] = daily_buckets; root["daily_buckets"] = daily_buckets;
@@ -662,7 +843,7 @@ void recordSubscriptionConversion(const Request &request,
int64_t now = nowSeconds(); int64_t now = nowSeconds();
int64_t minute = now / 60; int64_t minute = now / 60;
int64_t day = now / (24 * 60 * 60); int64_t day = now / (24 * 60 * 60);
std::string country = countryFromHeaders(request); GeoLocation location = geoLocationFromHeaders(request);
std::lock_guard<std::mutex> lock(g_mutex); std::lock_guard<std::mutex> lock(g_mutex);
if (!g_state || !g_state->initialized) if (!g_state || !g_state->initialized)
@@ -670,30 +851,45 @@ void recordSubscriptionConversion(const Request &request,
addCounters(g_state->startup, 1, rule_conversions); addCounters(g_state->startup, 1, rule_conversions);
addCounters(g_state->lifetime, 1, rule_conversions); addCounters(g_state->lifetime, 1, rule_conversions);
addCountryCounters(g_state->startup_countries, country, 1, rule_conversions); addCountryCounters(g_state->startup_countries, location.country_code, 1,
addCountryCounters(g_state->lifetime_countries, country, 1,
rule_conversions); rule_conversions);
addCountryCounters(g_state->lifetime_countries, location.country_code, 1,
rule_conversions);
if (!location.china_region_code.empty()) {
addCountryCounters(g_state->startup_china_regions,
location.china_region_code, 1, rule_conversions);
addCountryCounters(g_state->lifetime_china_regions,
location.china_region_code, 1, rule_conversions);
}
size_t index = static_cast<size_t>(minute % kBucketCount); size_t index = static_cast<size_t>(minute % kBucketCount);
if (g_state->buckets[index].minute != minute) { if (g_state->buckets[index].minute != minute) {
g_state->buckets[index].minute = minute; g_state->buckets[index].minute = minute;
g_state->buckets[index].counters = Counters(); g_state->buckets[index].counters = Counters();
g_state->buckets[index].countries.clear(); g_state->buckets[index].countries.clear();
g_state->buckets[index].china_regions.clear();
} }
addCounters(g_state->buckets[index].counters, 1, rule_conversions); addCounters(g_state->buckets[index].counters, 1, rule_conversions);
addCountryCounters(g_state->buckets[index].countries, country, 1, addCountryCounters(g_state->buckets[index].countries, location.country_code,
rule_conversions); 1, rule_conversions);
if (!location.china_region_code.empty())
addCountryCounters(g_state->buckets[index].china_regions,
location.china_region_code, 1, rule_conversions);
size_t daily_index = static_cast<size_t>(day % kDailyBucketCount); size_t daily_index = static_cast<size_t>(day % kDailyBucketCount);
if (g_state->daily_buckets[daily_index].day != day) { if (g_state->daily_buckets[daily_index].day != day) {
g_state->daily_buckets[daily_index].day = day; g_state->daily_buckets[daily_index].day = day;
g_state->daily_buckets[daily_index].counters = Counters(); g_state->daily_buckets[daily_index].counters = Counters();
g_state->daily_buckets[daily_index].countries.clear(); g_state->daily_buckets[daily_index].countries.clear();
g_state->daily_buckets[daily_index].china_regions.clear();
} }
addCounters(g_state->daily_buckets[daily_index].counters, 1, addCounters(g_state->daily_buckets[daily_index].counters, 1,
rule_conversions); rule_conversions);
addCountryCounters(g_state->daily_buckets[daily_index].countries, country, 1, addCountryCounters(g_state->daily_buckets[daily_index].countries,
rule_conversions); location.country_code, 1, rule_conversions);
if (!location.china_region_code.empty())
addCountryCounters(g_state->daily_buckets[daily_index].china_regions,
location.china_region_code, 1, rule_conversions);
g_state->dirty = true; g_state->dirty = true;
} }
@@ -758,6 +954,30 @@ std::string dashboardData(RESPONSE_CALLBACK_ARGS) {
root["country_windows"] = country_windows; root["country_windows"] = country_windows;
root["countries"] = country_windows["lifetime"]; root["countries"] = country_windows["lifetime"];
json china_region_windows = json::object();
china_region_windows["startup"] =
countriesJson(g_state
? countrySnapshotLocked(g_state->startup_china_regions)
: std::vector<SnapshotCountry>());
china_region_windows["hour"] =
countriesJson(chinaRegionWindowLocked(now_minute, 60));
china_region_windows["day"] =
countriesJson(chinaRegionWindowLocked(now_minute, 24 * 60));
china_region_windows["seven_days"] =
countriesJson(chinaRegionWindowLocked(now_minute, 7 * 24 * 60));
china_region_windows["thirty_days"] =
countriesJson(chinaRegionWindowLocked(now_minute, 30 * 24 * 60));
china_region_windows["half_year"] =
countriesJson(chinaRegionDailyWindowLocked(now_day, 183));
china_region_windows["year"] =
countriesJson(chinaRegionDailyWindowLocked(now_day, 365));
china_region_windows["lifetime"] =
countriesJson(g_state ? countrySnapshotLocked(
g_state->lifetime_china_regions)
: std::vector<SnapshotCountry>());
root["china_region_windows"] = china_region_windows;
root["china_regions"] = china_region_windows["lifetime"];
json series = json::array(); json series = json::array();
std::vector<Counters> hourly = hourlySeriesLocked(now_minute, 24); std::vector<Counters> hourly = hourlySeriesLocked(now_minute, 24);
int64_t current_hour = now_minute / 60; int64_t current_hour = now_minute / 60;