feat(synthetics): bill status page checks against a monthly protocol grant (#14271)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Loakesh Indiran
2026-09-15 13:15:09 +00:00
committed by GitHub
co-authored by github-actions[bot]
parent 2f95b6b23d
commit dfeb6ea731
28 changed files with 1301 additions and 82 deletions
@@ -8435,6 +8435,7 @@
"metrics": "ee6d251a11211d7e",
"metricsDesc": "f400ccd9a75ae417",
"or": "304d31e4fd1575bd",
"profiles": "535e52e4a2616ebe",
"shippers": "2fb41e96ccbf1cc4",
"title": "0b5176a5e1844d67",
"traces": "7cb4b2b4b59736f4",
@@ -9298,6 +9299,7 @@
"otlpGrpc": "6e039a65134b7f7b",
"otlpHttp": "c7fcbcefe82c2d5a",
"parametersToEnterInAwsWizard": "3559f49af02df001",
"profilesLabel": "535e52e4a2616ebe",
"prometheusDocLinkText": "a454c921984f8a3b",
"python": "18885f27b5af9012",
"recommendedLabel": "d70604e843046137",
@@ -10006,6 +10008,7 @@
"labelLogs": "ea2100dc89ae9fe2",
"labelMetadata": "9eddf573cb509f1f",
"labelMetrics": "a58da793c7250f1b",
"labelProfiles": "535e52e4a2616ebe",
"labelTraces": "998741a58ed2b411",
"lastIngested": "0d820e9fa2cfde94",
"mapping": "1ff9a5f8b4bd530e",
@@ -10387,6 +10390,7 @@
"navigationLink": "4f7a4f883a01a04c",
"onCall": "3004e1251d8890eb",
"pipeline": "d1a8fffeb935847e",
"profiles": "535e52e4a2616ebe",
"reliability": "71d0acd465564246",
"report": "dacca3cba3f346a4",
"rum": "28aff5e3489eacae",
@@ -13762,6 +13766,35 @@
"type": "baaddf70fb5d432b",
"viewError": "dcfb19244b8b0c98"
},
"profiles": {
"addFilter": "e520df698000f85f",
"collapse": "be6eb1fc3b05bf9d",
"cpuDuration": "5ccaa4b9c47e65d0",
"dataSource": "548c5b4d99727c76",
"errors": {
"loadFailed": "8dfd389c73dc729f"
},
"expand": "07548c2c6511d4d1",
"filters": "546ebb8eb993ea56",
"noResults": "8d78bfc2a318212b",
"noSeries": "416188111c924f67",
"noStreams": "80fa05a3381de8ca",
"percent": "bbf3f11cb5b43e70",
"profileType": "3a2cfc3fe434e0e4",
"resultTitle": "b160af80dd83d1c9",
"runQuery": "1837e00c45e8663d",
"self": "85bc0f95858d7572",
"service": "d677190e0a9990e7",
"stream": "1eec3071dd26e224",
"tagKey": "034df31a6ad168fb",
"tagValue": "16f64f1f7c7fa66b",
"timeSeriesTitle": "38d465cbff87aea3",
"title": "535e52e4a2616ebe",
"total": "c9b3c38247f744e1",
"viewFlame": "d776d0b395c67943",
"viewTop": "1fa45d080800e023",
"viewTree": "9d25dd2b5d417e58"
},
"promql": {
"operations": {
"absDoc": "7da10ff035493bc8",
@@ -14956,6 +14989,7 @@
"filterBy": "0eadca7d4f93d753",
"foundInLogs": "a98652173f2161ff",
"foundInMetrics": "19b03af9aa422d83",
"foundInProfiles": "a15836cbacbc2c1a",
"foundInTraces": "392289106cf51dfb",
"goToConfiguration": "f39be26febb2b95a",
"identityConfigNoSets": "e2c8a7137249c794",
@@ -15298,17 +15332,22 @@
"saveCredits": "ebccf6ab3710613a",
"saveSteps": "e69e897def1b7bbb",
"setAiCreditsSubtitle": "53b695a8425d08ef",
"setSyntheticsStatusStepsSubtitle": "f8c40d09ceeafcce",
"setSyntheticsStepsSubtitle": "ea912a7589fe8a3b",
"setUsageLimits": "f9d770698a90d178",
"statusStepsTotal": "88038e69338bf088",
"statusStepsUsed": "ffdc93dff61f0d25",
"steps": "b7595e2a863957fc",
"storageEnabled": "b9130eb4a95b39b0",
"storageEnabledSuccess": "1dafd453e23e36b2",
"subtitle": "6c2be37323274fe3",
"syntheticsBrowserStepsTab": "ef03697f28c551c5",
"syntheticsProtocolStepsTab": "8b956ee50aee0481",
"syntheticsStatusStepsTab": "26cd6200f7f8fea6",
"totalAiCredits": "24aadffb67e37032",
"totalBrowserSteps": "fac6a5f9d6a54a20",
"totalProtocolSteps": "b6bfad0b639f2000",
"totalStatusSteps": "2ceaf7fd4b1f6e06",
"trialExtendedSuccess": "13ac584aa5dc3c9b",
"weeks": "cf64435fbb0dc732"
},
@@ -15410,6 +15449,7 @@
"setAiCreditsFor": "a03b8ef357afa817",
"setSyntheticsBrowserStepsFor": "ed88883a8eaa70ee",
"setSyntheticsProtocolStepsFor": "c7d4305532b08a89",
"setSyntheticsStatusStepsFor": "fb446d02353eec8c",
"somethingWentWrong": "ab827e3fe17da220",
"spanIdFieldName": "c173041159652841",
"ssoDomainRestrictions": "346a58682faef602",
@@ -252,6 +252,8 @@ pub async fn all_organizations(
browser_steps_limit: synthetics.browser_limit,
protocol_steps_used: synthetics.protocol_used,
protocol_steps_limit: synthetics.protocol_limit,
status_steps_used: synthetics.status_used,
status_steps_limit: synthetics.status_limit,
created_at: org.created_at,
updated_at: org.updated_at,
trial_expires_at: Some(org.trial_ends_at),
@@ -673,7 +675,7 @@ async fn set_pool_limit(
security(("Authorization" = [])),
params(
("org_id" = String, Path, description = "Must be _meta"),
("pool" = String, Path, description = "ai_credits | synthetics_browser_steps | synthetics_protocol_steps (the pre-split key `synthetics_steps` is accepted as an alias for the protocol pool)"),
("pool" = String, Path, description = "ai_credits | synthetics_browser_steps | synthetics_protocol_steps | synthetics_status_protocol (the pre-split key `synthetics_steps` is accepted as an alias for the protocol pool)"),
),
request_body(content = inline(SetQuotaUsageLimitRequest), content_type = "application/json"),
responses(
+24
View File
@@ -193,7 +193,11 @@ pub struct AllOrgListDetails {
#[cfg(feature = "cloud")]
pub protocol_steps_used: u64,
#[cfg(feature = "cloud")]
pub status_steps_used: u64,
#[cfg(feature = "cloud")]
pub protocol_steps_limit: u64,
#[cfg(feature = "cloud")]
pub status_steps_limit: u64,
pub trial_expires_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contract_end_date: Option<i64>,
@@ -846,7 +850,11 @@ mod tests {
#[cfg(feature = "cloud")]
protocol_steps_used: 0,
#[cfg(feature = "cloud")]
status_steps_used: 0,
#[cfg(feature = "cloud")]
protocol_steps_limit: 0,
#[cfg(feature = "cloud")]
status_steps_limit: 0,
trial_expires_at: None,
contract_end_date: None,
billing_provider: String::new(),
@@ -910,7 +918,11 @@ mod tests {
#[cfg(feature = "cloud")]
protocol_steps_used: 0,
#[cfg(feature = "cloud")]
status_steps_used: 0,
#[cfg(feature = "cloud")]
protocol_steps_limit: 0,
#[cfg(feature = "cloud")]
status_steps_limit: 0,
trial_expires_at: None,
contract_end_date: None,
billing_provider: String::new(),
@@ -939,7 +951,11 @@ mod tests {
#[cfg(feature = "cloud")]
protocol_steps_used: 0,
#[cfg(feature = "cloud")]
status_steps_used: 0,
#[cfg(feature = "cloud")]
protocol_steps_limit: 0,
#[cfg(feature = "cloud")]
status_steps_limit: 0,
trial_expires_at: Some(1641081600),
contract_end_date: None,
billing_provider: String::new(),
@@ -1023,7 +1039,11 @@ mod tests {
#[cfg(feature = "cloud")]
protocol_steps_used: 0,
#[cfg(feature = "cloud")]
status_steps_used: 0,
#[cfg(feature = "cloud")]
protocol_steps_limit: 0,
#[cfg(feature = "cloud")]
status_steps_limit: 0,
trial_expires_at: Some(1641081600),
contract_end_date: Some(1893456000000000),
billing_provider: "no_op".to_string(),
@@ -1062,7 +1082,11 @@ mod tests {
#[cfg(feature = "cloud")]
protocol_steps_used: 0,
#[cfg(feature = "cloud")]
status_steps_used: 0,
#[cfg(feature = "cloud")]
protocol_steps_limit: 0,
#[cfg(feature = "cloud")]
status_steps_limit: 0,
trial_expires_at: Some(1641081600),
contract_end_date: None,
billing_provider: String::new(),
+17
View File
@@ -73,6 +73,14 @@ pub fn second_micros(n: i64) -> i64 {
.unwrap()
}
/// The `YYYYMM` of a microsecond timestamp, UTC. One encoding, or a period compares against
/// something that was written with another.
#[inline(always)]
pub fn month_of(micros: i64) -> i32 {
let at = DateTime::from_timestamp_micros(micros).unwrap_or_default();
at.year() * 100 + at.month() as i32
}
#[inline(always)]
pub fn get_ymdh_from_micros(n: i64, hour_format: HourFormat) -> String {
let n = if n > 0 {
@@ -355,6 +363,15 @@ pub fn parse_interval_to_minutes(s: &str) -> i64 {
#[cfg(test)]
mod tests {
#[test]
fn test_month_of_reads_the_utc_month() {
assert_eq!(month_of(1_788_800_853_300_917), 202609);
assert_eq!(month_of(0), 197001);
// A month boundary is the one place an off-by-one lands on the wrong grant.
assert_eq!(month_of(1_767_225_599_000_000), 202512);
assert_eq!(month_of(1_767_225_600_000_000), 202601);
}
use super::*;
#[test]
+61 -15
View File
@@ -22,7 +22,7 @@
//!
//! ## Pools
//!
//! [`TrialQuotaPool::AiCredits`] and the two synthetics step pools are
//! [`TrialQuotaPool::AiCredits`] and the three synthetics step pools are
//! independent: every counter, limit, DB read and HA message is keyed
//! `(org, pool)`, so spending one grant cannot drain the other, and
//! [`TrialQuotaFeature::pool`] is the only feature-to-pool mapping. No
@@ -107,8 +107,9 @@ struct FlushRecord {
cost: i64,
}
/// A lifetime free grant, and the unit of isolation between features. Every counter, limit and DB
/// read is keyed per `(org, pool)` via [`scope`]: an org that spends its AI credits must not
/// A free grant per org, and the unit of isolation between features. Every pool is lifetime except
/// [`TrialQuotaPool::SyntheticsStatusProtocol`], which resets each month. Every counter, limit and
/// DB read is keyed per `(org, pool)` via [`scope`]: an org that spends its AI credits must not
/// thereby lose its synthetics budget.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TrialQuotaPool {
@@ -120,6 +121,9 @@ pub enum TrialQuotaPool {
/// from the browser grant because a browser step costs ~52x a protocol one,
/// so a shared pool let the org's mix decide our free-tier cost.
SyntheticsProtocolSteps,
/// Protocol steps of checks attached to a status page. The only pool that
/// resets: its row carries the `YYYYMM` its count belongs to.
SyntheticsStatusProtocol,
}
impl TrialQuotaPool {
@@ -128,6 +132,7 @@ impl TrialQuotaPool {
TrialQuotaPool::AiCredits,
TrialQuotaPool::SyntheticsBrowserSteps,
TrialQuotaPool::SyntheticsProtocolSteps,
TrialQuotaPool::SyntheticsStatusProtocol,
];
/// Stable identifier used in the [`scope`] key and on the HA wire.
@@ -136,6 +141,7 @@ impl TrialQuotaPool {
TrialQuotaPool::AiCredits => "ai_credits",
TrialQuotaPool::SyntheticsBrowserSteps => "synthetics_browser_steps",
TrialQuotaPool::SyntheticsProtocolSteps => "synthetics_protocol_steps",
TrialQuotaPool::SyntheticsStatusProtocol => "synthetics_status_protocol",
}
}
@@ -148,6 +154,7 @@ impl TrialQuotaPool {
"synthetics_steps" | "synthetics_protocol_steps" => {
Some(TrialQuotaPool::SyntheticsProtocolSteps)
}
"synthetics_status_protocol" => Some(TrialQuotaPool::SyntheticsStatusProtocol),
_ => None,
}
}
@@ -164,10 +171,17 @@ impl TrialQuotaPool {
pub fn is_synthetics(self) -> bool {
matches!(
self,
TrialQuotaPool::SyntheticsBrowserSteps | TrialQuotaPool::SyntheticsProtocolSteps
TrialQuotaPool::SyntheticsBrowserSteps
| TrialQuotaPool::SyntheticsProtocolSteps
| TrialQuotaPool::SyntheticsStatusProtocol
)
}
/// The one pool whose count belongs to a month rather than to the lifetime of the org.
pub fn is_monthly(self) -> bool {
matches!(self, TrialQuotaPool::SyntheticsStatusProtocol)
}
/// Every `trial_quota_usage.feature` value that spends from this pool; each
/// per-pool query uses this set as an `IN` filter. MUST stay in sync with
/// [`TrialQuotaFeature::pool`] (`every_feature_is_listed_by_its_own_pool`).
@@ -180,6 +194,7 @@ impl TrialQuotaPool {
TrialQuotaPool::SyntheticsProtocolSteps => {
&["synthetics_protocol_steps", "synthetics_steps"]
}
TrialQuotaPool::SyntheticsStatusProtocol => &["synthetics_status_protocol"],
}
}
@@ -202,6 +217,7 @@ impl TrialQuotaPool {
TrialQuotaPool::AiCredits => cfg.cloud.ai_free_credit_pool,
TrialQuotaPool::SyntheticsBrowserSteps => cfg.cloud.synthetics_free_browser_step_pool,
TrialQuotaPool::SyntheticsProtocolSteps => cfg.cloud.synthetics_free_protocol_step_pool,
TrialQuotaPool::SyntheticsStatusProtocol => cfg.cloud.synthetics_free_status_step_pool,
}
}
}
@@ -1234,6 +1250,8 @@ pub struct SyntheticsQuota {
pub browser_limit: u64,
pub protocol_used: u64,
pub protocol_limit: u64,
pub status_used: u64,
pub status_limit: u64,
}
/// Every `trial_quota_usage.feature` value that spends from a synthetics pool.
@@ -1257,7 +1275,12 @@ pub async fn synthetics_remaining_for_orgs(org_ids: Vec<String>) -> HashMap<Stri
let Some(rows) = read_synthetics_rows(&org_ids).await else {
return HashMap::new();
};
fold_synthetics_remaining(&org_ids, &rows)
fold_synthetics_remaining(&org_ids, &rows, current_month())
}
/// The month the monthly pool is spending right now, UTC.
fn current_month() -> i32 {
config::utils::time::month_of(config::utils::time::now_micros())
}
/// Each requested org's synthetics spend and grant, for the `_meta` org listing — SPEC §11 #5.
@@ -1267,7 +1290,7 @@ pub async fn synthetics_quota_for_orgs(org_ids: Vec<String>) -> HashMap<String,
let Some(rows) = read_synthetics_rows(&org_ids).await else {
return HashMap::new();
};
fold_synthetics_quota(&org_ids, &rows)
fold_synthetics_quota(&org_ids, &rows, current_month())
}
/// The rows both batched readers fold — `None` when the table cannot be reached.
@@ -1294,8 +1317,9 @@ async fn read_synthetics_rows(
pub(crate) fn fold_synthetics_remaining(
org_ids: &[String],
rows: &[infra::table::entity::trial_quota_usage::Model],
month: i32,
) -> HashMap<String, StepRemaining> {
fold_synthetics_quota(org_ids, rows)
fold_synthetics_quota(org_ids, rows, month)
.into_iter()
.map(|(org_id, quota)| {
(
@@ -1304,6 +1328,7 @@ pub(crate) fn fold_synthetics_remaining(
StepRemaining {
browser: quota.browser_limit.saturating_sub(quota.browser_used),
protocol: quota.protocol_limit.saturating_sub(quota.protocol_used),
status: quota.status_limit.saturating_sub(quota.status_used),
},
)
})
@@ -1315,6 +1340,7 @@ pub(crate) fn fold_synthetics_remaining(
pub(crate) fn fold_synthetics_quota(
org_ids: &[String],
rows: &[infra::table::entity::trial_quota_usage::Model],
month: i32,
) -> HashMap<String, SyntheticsQuota> {
let mut used: HashMap<(&str, TrialQuotaPool), u64> = HashMap::new();
let mut limits: HashMap<(&str, TrialQuotaPool), u64> = HashMap::new();
@@ -1329,8 +1355,14 @@ pub(crate) fn fold_synthetics_quota(
.and_modify(|current| *current = (*current).max(limit))
.or_insert(limit);
}
// A monthly row from a closed month has already been forgiven, so its spend reads as 0.
let spent = if pool.is_monthly() && row.period != month {
0
} else {
u64::try_from(row.usage_count).unwrap_or(0)
};
let entry = used.entry((row.org_id.as_str(), pool)).or_default();
*entry = entry.saturating_add(u64::try_from(row.usage_count).unwrap_or(0));
*entry = entry.saturating_add(spent);
}
org_ids
@@ -1349,6 +1381,7 @@ pub(crate) fn fold_synthetics_quota(
};
let (browser_used, browser_limit) = spent(TrialQuotaPool::SyntheticsBrowserSteps);
let (protocol_used, protocol_limit) = spent(TrialQuotaPool::SyntheticsProtocolSteps);
let (status_used, status_limit) = spent(TrialQuotaPool::SyntheticsStatusProtocol);
(
org_id.clone(),
SyntheticsQuota {
@@ -1356,6 +1389,8 @@ pub(crate) fn fold_synthetics_quota(
browser_limit,
protocol_used,
protocol_limit,
status_used,
status_limit,
},
)
})
@@ -1383,6 +1418,7 @@ mod tests {
TrialQuotaPool::AiCredits => 0,
TrialQuotaPool::SyntheticsBrowserSteps => 1,
TrialQuotaPool::SyntheticsProtocolSteps => 2,
TrialQuotaPool::SyntheticsStatusProtocol => 3,
}
}
@@ -1757,6 +1793,10 @@ mod tests {
get_limit_for_pool(org_id, TrialQuotaPool::SyntheticsProtocolSteps),
cfg.cloud.synthetics_free_protocol_step_pool,
);
assert_eq!(
get_limit_for_pool(org_id, TrialQuotaPool::SyntheticsStatusProtocol),
cfg.cloud.synthetics_free_status_step_pool,
);
assert_eq!(
get_limit_for_pool(org_id, TrialQuotaPool::AiCredits),
cfg.cloud.ai_free_credit_pool,
@@ -1764,6 +1804,8 @@ mod tests {
// §6.1: 10,000 browser steps and 20,000 protocol; the AI pool is 1,000 credits.
assert_eq!(cfg.cloud.synthetics_free_browser_step_pool, 10_000);
assert_eq!(cfg.cloud.synthetics_free_protocol_step_pool, 20_000);
// 43,200 protocol steps a month for status-page checks, and nothing for browser ones.
assert_eq!(cfg.cloud.synthetics_free_status_step_pool, 43_200);
// The three grants must not collapse onto one knob.
assert_ne!(
cfg.cloud.synthetics_free_browser_step_pool,
@@ -1785,6 +1827,7 @@ mod tests {
usage_count: i64,
) -> infra::table::entity::trial_quota_usage::Model {
infra::table::entity::trial_quota_usage::Model {
period: 0,
org_id: org_id.to_string(),
feature: feature.to_string(),
usage_count,
@@ -1963,6 +2006,7 @@ mod tests {
.feature_keys()
.iter()
.chain(TrialQuotaPool::SyntheticsProtocolSteps.feature_keys())
.chain(TrialQuotaPool::SyntheticsStatusProtocol.feature_keys())
.copied()
.collect();
features.sort_unstable();
@@ -1985,7 +2029,7 @@ mod tests {
let org_id = steps_org("fold-empty", 700);
set_cached_limit(&org_id, TrialQuotaPool::SyntheticsProtocolSteps, 900);
let remaining = fold_synthetics_remaining(std::slice::from_ref(&org_id), &[]);
let remaining = fold_synthetics_remaining(std::slice::from_ref(&org_id), &[], 0);
let r = remaining
.get(&org_id)
.expect("an org with no rows has not used the feature — it is not absent");
@@ -2003,7 +2047,7 @@ mod tests {
db_row(&org_id, "synthetics_protocol_steps", 11),
];
let remaining = fold_synthetics_remaining(std::slice::from_ref(&org_id), &rows);
let remaining = fold_synthetics_remaining(std::slice::from_ref(&org_id), &rows, 0);
let r = remaining.get(&org_id).expect("the org was requested");
assert_eq!(
r.browser, 0,
@@ -2023,7 +2067,7 @@ mod tests {
db_row(&org_id, "synthetics_browser_steps", 100),
];
let remaining = fold_synthetics_remaining(std::slice::from_ref(&org_id), &rows);
let remaining = fold_synthetics_remaining(std::slice::from_ref(&org_id), &rows, 0);
let r = remaining.get(&org_id).expect("the org was requested");
assert_eq!(
r.protocol, 500,
@@ -2051,6 +2095,7 @@ mod tests {
let remaining = fold_synthetics_remaining(
std::slice::from_ref(&org_id),
&[browser, protocol, pre_split],
0,
);
let r = remaining.get(&org_id).expect("the org was requested");
assert_eq!(
@@ -2081,7 +2126,7 @@ mod tests {
protocol,
];
let quota = fold_synthetics_quota(std::slice::from_ref(&org_id), &rows);
let quota = fold_synthetics_quota(std::slice::from_ref(&org_id), &rows, 0);
let q = quota.get(&org_id).expect("the org was requested");
assert_eq!(
(q.browser_used, q.browser_limit),
@@ -2110,7 +2155,7 @@ mod tests {
db_row(&org_id, "synthetics_protocol_steps", 11),
];
let quota = fold_synthetics_quota(std::slice::from_ref(&org_id), &rows);
let quota = fold_synthetics_quota(std::slice::from_ref(&org_id), &rows, 0);
let q = quota.get(&org_id).expect("the org was requested");
assert_eq!(
(q.browser_used, q.browser_limit),
@@ -2132,6 +2177,7 @@ mod tests {
let quota = fold_synthetics_quota(
std::slice::from_ref(&org_id),
&[db_row(&other, "synthetics_browser_steps", 4)],
0,
);
assert_eq!(
@@ -2163,7 +2209,7 @@ mod tests {
pre_split.usage_limit = Some(5_000);
let rows = vec![browser, protocol, pre_split];
let quota = fold_synthetics_quota(std::slice::from_ref(&org_id), &rows);
let quota = fold_synthetics_quota(std::slice::from_ref(&org_id), &rows, 0);
let q = quota.get(&org_id).expect("the org was requested");
assert_eq!(
q.browser_used, 900,
@@ -2171,7 +2217,7 @@ mod tests {
they agree on",
);
let remaining = fold_synthetics_remaining(std::slice::from_ref(&org_id), &rows);
let remaining = fold_synthetics_remaining(std::slice::from_ref(&org_id), &rows, 0);
let r = remaining.get(&org_id).expect("the org was requested");
// Every pool here is under its grant, so the two halves must add back up to it.
assert_eq!(
@@ -11,6 +11,8 @@ pub struct Model {
pub usage_limit: Option<i64>,
pub updated_at: i64,
pub notified_checkpoint: i16,
/// `0` is a lifetime row. A monthly pool carries the `YYYYMM` its count belongs to.
pub period: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@@ -31,10 +33,12 @@ mod tests {
usage_limit: Some(1_000),
updated_at: 1000,
notified_checkpoint: 0,
period: 0,
};
assert_eq!(m.org_id, "org");
assert_eq!(m.usage_count, 100);
assert_eq!(m.usage_limit, Some(1_000));
assert_eq!(m.notified_checkpoint, 0);
assert_eq!(m.period, 0);
}
}
+108
View File
@@ -20,6 +20,8 @@
//! request-time callers. The public serving path reads only
//! [`get_page_by_slug`] and [`get_snapshot`].
use std::collections::HashSet;
use sea_orm::{
ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, IntoActiveModel, PaginatorTrait,
QueryFilter, QueryOrder, QuerySelect, prelude::Expr,
@@ -960,3 +962,109 @@ pub async fn get_domain_claim_by_host<C: ConnectionTrait>(
.one(conn)
.await?)
}
/// The check ids attached to a status page, narrowed to the ids the caller asks about.
///
/// `Some(&[])` answers with nothing rather than falling back to an unfiltered read: reading the
/// whole join table would hand every check in the cluster the status allowance.
pub async fn mapped_check_ids<C: ConnectionTrait>(
conn: &C,
filter: Option<&[String]>,
) -> Result<HashSet<String>, errors::Error> {
if filter.is_some_and(<[String]>::is_empty) {
return Ok(HashSet::new());
}
let mut query = status_page_component_checks::Entity::find()
.select_only()
.column(status_page_component_checks::Column::SyntheticsId)
.distinct();
if let Some(ids) = filter {
query = query.filter(
status_page_component_checks::Column::SyntheticsId
.is_in(ids.iter().map(String::as_str)),
);
}
let ids: Vec<String> = query.into_tuple().all(conn).await?;
Ok(ids.into_iter().collect())
}
#[cfg(test)]
mod tests {
use sea_orm::{ActiveValue, ConnectOptions, Database, DatabaseConnection, Schema};
use super::*;
/// One connection, not a pool: two connections to `sqlite::memory:` are two databases.
async fn db() -> DatabaseConnection {
let mut opts = ConnectOptions::new("sqlite::memory:".to_string());
opts.max_connections(1);
let db = Database::connect(opts).await.unwrap();
let backend = db.get_database_backend();
let schema = Schema::new(backend);
db.execute(
backend.build(&schema.create_table_from_entity(status_page_component_checks::Entity)),
)
.await
.unwrap();
db
}
async fn map_check(db: &DatabaseConnection, id: &str, component_id: &str, check_id: &str) {
status_page_component_checks::ActiveModel {
id: ActiveValue::Set(id.to_string()),
component_id: ActiveValue::Set(component_id.to_string()),
synthetics_id: ActiveValue::Set(check_id.to_string()),
org_id: ActiveValue::Set("acme".to_string()),
}
.insert(db)
.await
.unwrap();
}
#[tokio::test]
async fn mapped_check_ids_filters_to_the_given_ids() {
let db = db().await;
map_check(&db, "m1", "c1", "chk_1").await;
map_check(&db, "m2", "c1", "chk_2").await;
let asked = ["chk_1".to_string(), "chk_9".to_string()];
let found = mapped_check_ids(&db, Some(&asked)).await.unwrap();
assert_eq!(found, HashSet::from(["chk_1".to_string()]));
}
#[tokio::test]
async fn mapped_check_ids_deduplicates_a_check_on_two_components() {
let db = db().await;
map_check(&db, "m1", "c1", "chk_1").await;
map_check(&db, "m2", "c2", "chk_1").await;
let asked = ["chk_1".to_string()];
assert_eq!(
mapped_check_ids(&db, Some(&asked)).await.unwrap(),
HashSet::from(["chk_1".to_string()]),
);
}
#[tokio::test]
async fn mapped_check_ids_of_an_empty_filter_returns_nothing() {
let db = db().await;
map_check(&db, "m1", "c1", "chk_1").await;
assert!(
mapped_check_ids(&db, Some(&[])).await.unwrap().is_empty(),
"an empty ask must not degrade into reading the whole table",
);
}
#[tokio::test]
async fn mapped_check_ids_reads_every_row_when_nothing_is_asked_for() {
let db = db().await;
map_check(&db, "m1", "c1", "chk_1").await;
map_check(&db, "m2", "c1", "chk_2").await;
let found = mapped_check_ids(&db, None).await.unwrap();
assert_eq!(found.len(), 2);
}
}
+168 -3
View File
@@ -15,7 +15,7 @@
use sea_orm::{
ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QuerySelect, TransactionTrait,
sea_query::{Expr, Func, OnConflict},
sea_query::{CaseStatement, Expr, Func, OnConflict},
};
use crate::{
@@ -25,11 +25,17 @@ use crate::{
pub const SYNTHETICS_BROWSER_FEATURE: &str = "synthetics_browser_steps";
pub const SYNTHETICS_PROTOCOL_FEATURE: &str = "synthetics_protocol_steps";
pub const SYNTHETICS_STATUS_FEATURE: &str = "synthetics_status_protocol";
/// One settled window's free steps per pool.
/// The `YYYYMM` a lifetime row carries, meaning the count belongs to no month.
pub const LIFETIME_PERIOD: i32 = 0;
/// One settled window's free steps per pool. `month` scopes the status pool alone.
pub struct SyntheticsDeltas {
pub browser: i64,
pub protocol: i64,
pub status: i64,
pub month: i32,
}
/// Additively upsert one `(org_id, feature, delta)` triple per record.
@@ -172,6 +178,8 @@ async fn set_usage_limit_for_org_in<C: ConnectionTrait + TransactionTrait>(
usage_limit: sea_orm::ActiveValue::Set(Some(usage_limit)),
updated_at: sea_orm::ActiveValue::Set(now),
notified_checkpoint: sea_orm::ActiveValue::Set(0),
// A raised limit belongs to no month; the monthly row keeps its own period.
period: sea_orm::ActiveValue::Set(LIFETIME_PERIOD),
};
trial_quota_usage::Entity::insert(active_model)
@@ -383,6 +391,58 @@ pub async fn apply_synthetics_deltas_in<C: ConnectionTrait>(
increment_lifetime_row(txn, org_id, feature, delta, now).await?;
}
}
if deltas.status != 0 {
increment_monthly_row(txn, org_id, deltas.status, deltas.month, now).await?;
}
Ok(())
}
/// The reset rides the increment, so no pass adds to a month and a later one zeroes it.
///
/// A row from an older month starts the new month at this window's draw. A row from a NEWER month
/// only accumulates: a late replay of an old window must never rewind the period it settled into.
async fn increment_monthly_row<C: ConnectionTrait>(
conn: &C,
org_id: &str,
delta: i64,
month: i32,
now: i64,
) -> Result<(), sea_orm::DbErr> {
let period = Expr::col((trial_quota_usage::Entity, trial_quota_usage::Column::Period));
let stale = period.clone().lt(month);
let count = CaseStatement::new().case(stale.clone(), delta).finally(
Expr::col((
trial_quota_usage::Entity,
trial_quota_usage::Column::UsageCount,
))
.add(delta),
);
// Stands in for `GREATEST`, which SQLite lacks and Postgres spells as an aggregate.
let advance = CaseStatement::new().case(stale, month).finally(period);
let active_model = trial_quota_usage::ActiveModel {
org_id: sea_orm::ActiveValue::Set(org_id.to_string()),
feature: sea_orm::ActiveValue::Set(SYNTHETICS_STATUS_FEATURE.to_string()),
usage_count: sea_orm::ActiveValue::Set(delta),
usage_limit: sea_orm::ActiveValue::NotSet,
updated_at: sea_orm::ActiveValue::Set(now),
notified_checkpoint: sea_orm::ActiveValue::Set(0),
period: sea_orm::ActiveValue::Set(month),
};
trial_quota_usage::Entity::insert(active_model)
.on_conflict(
OnConflict::columns([
trial_quota_usage::Column::OrgId,
trial_quota_usage::Column::Feature,
])
.value(trial_quota_usage::Column::UsageCount, count)
.value(trial_quota_usage::Column::Period, advance)
.value(trial_quota_usage::Column::UpdatedAt, Expr::value(now))
.to_owned(),
)
.exec(conn)
.await?;
Ok(())
}
@@ -400,6 +460,7 @@ async fn increment_lifetime_row<C: ConnectionTrait>(
usage_limit: sea_orm::ActiveValue::NotSet,
updated_at: sea_orm::ActiveValue::Set(now),
notified_checkpoint: sea_orm::ActiveValue::Set(0),
period: sea_orm::ActiveValue::Set(LIFETIME_PERIOD),
};
trial_quota_usage::Entity::insert(active_model)
@@ -433,6 +494,8 @@ mod tests {
use super::*;
const ORG: &str = "acme";
const STATUS: &str = SYNTHETICS_STATUS_FEATURE;
const AI: &str = "ai_chat";
const BROWSER: &str = "synthetics_browser_steps";
const PROTOCOL: &str = "synthetics_protocol_steps";
@@ -473,6 +536,7 @@ mod tests {
usage_limit: ActiveValue::Set(usage_limit),
updated_at: ActiveValue::Set(0),
notified_checkpoint: ActiveValue::Set(notified_checkpoint),
period: ActiveValue::Set(LIFETIME_PERIOD),
}
}
@@ -502,7 +566,21 @@ mod tests {
}
fn deltas(browser: i64, protocol: i64) -> SyntheticsDeltas {
SyntheticsDeltas { browser, protocol }
SyntheticsDeltas {
browser,
protocol,
status: 0,
month: LIFETIME_PERIOD,
}
}
fn status_deltas(status: i64, month: i32) -> SyntheticsDeltas {
SyntheticsDeltas {
browser: 0,
protocol: 0,
status,
month,
}
}
async fn apply(db: &DatabaseConnection, org_id: &str, d: SyntheticsDeltas, now: i64) {
@@ -511,6 +589,12 @@ mod tests {
.unwrap();
}
async fn apply_status(db: &DatabaseConnection, org_id: &str, status: i64, month: i32) {
apply_synthetics_deltas_in(db, org_id, &status_deltas(status, month), 0)
.await
.unwrap();
}
async fn row_of(
db: &DatabaseConnection,
org_id: &str,
@@ -867,4 +951,85 @@ mod tests {
assert!(has_no_row(&db, "acme", BROWSER).await, "browser");
assert!(has_no_row(&db, "acme", PROTOCOL).await, "protocol");
}
#[tokio::test]
async fn status_upsert_inserts_with_the_month() {
let db = db().await;
apply_status(&db, ORG, 40, 202610).await;
let row = row_of(&db, ORG, STATUS).await;
assert_eq!((row.usage_count, row.period), (40, 202610));
}
#[tokio::test]
async fn status_upsert_adds_within_month() {
let db = db().await;
apply_status(&db, ORG, 12_480, 202609).await;
apply_status(&db, ORG, 410, 202609).await;
let row = row_of(&db, ORG, STATUS).await;
assert_eq!((row.usage_count, row.period), (12_890, 202609));
}
#[tokio::test]
async fn status_upsert_resets_on_a_newer_month() {
let db = db().await;
apply_status(&db, ORG, 12_890, 202609).await;
apply_status(&db, ORG, 90, 202610).await;
let row = row_of(&db, ORG, STATUS).await;
assert_eq!(
(row.usage_count, row.period),
(90, 202610),
"a new month starts the count again instead of adding to the old one",
);
}
#[tokio::test]
async fn status_upsert_adds_a_late_older_month_to_the_current_month() {
let db = db().await;
apply_status(&db, ORG, 90, 202610).await;
apply_status(&db, ORG, 5, 202609).await;
let row = row_of(&db, ORG, STATUS).await;
assert_eq!(
(row.usage_count, row.period),
(95, 202610),
"a late replay counts against the current month and never rewinds the period",
);
}
#[tokio::test]
async fn status_upsert_never_touches_usage_limit() {
let db = db().await;
seed_row(&db, ORG, STATUS, 0, Some(1_000)).await;
for (draw, month) in [(40, 202609), (10, 202609), (7, 202610), (1, 202608)] {
apply_status(&db, ORG, draw, month).await;
assert_eq!(
row_of(&db, ORG, STATUS).await.usage_limit,
Some(1_000),
"an administrator override outlives every reset",
);
}
}
#[tokio::test]
async fn lifetime_rows_keep_period_at_zero() {
let db = db().await;
apply(&db, ORG, deltas(10, 20), 0).await;
apply(&db, ORG, deltas(5, 5), 1).await;
for feature in [BROWSER, PROTOCOL] {
assert_eq!(row_of(&db, ORG, feature).await.period, LIFETIME_PERIOD);
}
}
#[tokio::test]
async fn a_zero_status_delta_writes_no_row() {
let db = db().await;
apply_status(&db, ORG, 0, 202609).await;
assert!(has_no_row(&db, ORG, STATUS).await);
}
}
+3 -1
View File
@@ -32,7 +32,7 @@ use std::{collections::HashMap, future::Future, pin::Pin, sync::OnceLock};
/// Set once, by [`crate::init`], before any worker is spawned.
static HOOKS: OnceLock<StepPoolHooks> = OnceLock::new();
/// Steps left in one org's two synthetics grants — SPEC §6.1, §6.6.
/// Steps left in one org's three synthetics grants — SPEC §6.1, §6.6.
///
/// Deliberately NOT `Default`: a zeroed value for an org missing from the batch
/// read means "grant spent" and blacks that org's monitoring out, where absent
@@ -41,6 +41,8 @@ static HOOKS: OnceLock<StepPoolHooks> = OnceLock::new();
pub struct StepRemaining {
pub browser: u64,
pub protocol: u64,
/// The monthly allowance for status-page checks, already scoped to this month.
pub status: u64,
}
/// The batch read's result, boxed because a `fn` pointer cannot name an
+150 -5
View File
@@ -167,6 +167,8 @@ pub enum PoolGate {
pub(crate) struct GateContext {
pub remaining: HashMap<String, crate::pool::StepRemaining>,
pub policies: HashMap<String, PoolExhaustionPolicy>,
/// The claimed checks that feed a status page, read once per tick.
pub status_attached: std::collections::HashSet<String>,
}
/// The per-run values every slot of one fan-out shares.
@@ -607,15 +609,18 @@ pub(crate) fn distinct_org_ids(checks: &[synthetics_checks::DueCheck]) -> Vec<St
pub(crate) fn gate_decision(
gate: Option<(PoolExhaustionPolicy, crate::pool::StepRemaining)>,
is_browser: bool,
is_status_attached: bool,
) -> PoolGate {
let Some((policy, remaining)) = gate else {
return PoolGate::Run;
};
// A status check reaches the monthly allowance only after the shared one-time pool is spent,
// so either pool having room is enough to run it. Browser has no monthly allowance at all.
let has_room = if is_browser {
remaining.browser > 0
} else {
remaining.protocol > 0
remaining.protocol > 0 || (is_status_attached && remaining.status > 0)
};
match policy {
@@ -639,6 +644,7 @@ pub(crate) fn slot_verdict(
if is_private {
return PoolGate::Run;
}
let is_status_attached = ctx.is_some_and(|c| c.status_attached.contains(&check.id));
gate_decision(
ctx.and_then(|c| {
Some((
@@ -647,6 +653,7 @@ pub(crate) fn slot_verdict(
))
}),
check.check_type == SyntheticType::Browser,
is_status_attached,
)
}
@@ -779,9 +786,29 @@ async fn resolve_gate_context(checks: &[synthetics_checks::DueCheck]) -> Option<
let remaining = (hooks.remaining_for_orgs)(org_ids).await;
// One read for the whole tick: inside the fan-out it would run once per claimed check.
let claimed: Vec<String> = checks.iter().map(|check| check.id.clone()).collect();
let status_attached = infra::table::status_pages::mapped_check_ids(
infra::db::get_orm_client_ro().await,
Some(&claimed),
)
.await
.unwrap_or_else(|e| {
// A check billed as regular is the safe answer: it draws no allowance it did not earn.
tracing::error!("[synthetics scheduler] status page mapping unreadable: {e}");
std::collections::HashSet::new()
});
tracing::debug!(
claimed = claimed.len(),
status_attached = status_attached.len(),
"[synthetics scheduler] status page membership for this tick"
);
Some(GateContext {
remaining,
policies,
status_attached,
})
}
@@ -2154,7 +2181,11 @@ mod pool_gate_tests {
const A_LOCATION: &str = "us-east-1";
fn remaining(browser: u64, protocol: u64) -> StepRemaining {
StepRemaining { browser, protocol }
StepRemaining {
browser,
protocol,
status: 0,
}
}
fn due_check() -> DueCheck {
@@ -2191,6 +2222,7 @@ mod pool_gate_tests {
rows: &[(&str, StepRemaining)],
) -> GateContext {
GateContext {
status_attached: std::collections::HashSet::new(),
remaining: rows
.iter()
.map(|(org, r)| ((*org).to_string(), *r))
@@ -2228,7 +2260,7 @@ mod pool_gate_tests {
for (is_browser, r, has_room) in rows {
let case = format!("browser={is_browser} remaining={r:?}");
assert_eq!(
gate_decision(Some((SubscriptionRequired, *r)), *is_browser),
gate_decision(Some((SubscriptionRequired, *r)), *is_browser, false),
if *has_room {
PoolGate::Run
} else {
@@ -2237,7 +2269,7 @@ mod pool_gate_tests {
"T30/E15, a Free org's slot is skipped only when the grant is spent: {case}",
);
assert_eq!(
gate_decision(Some((MeteredOverage, *r)), *is_browser),
gate_decision(Some((MeteredOverage, *r)), *is_browser, false),
if *has_room {
PoolGate::Run
} else {
@@ -2246,7 +2278,7 @@ mod pool_gate_tests {
"T31/E16, a Rate or Enterprise org is never skipped: {case}",
);
assert_eq!(
gate_decision(Some((AdditionalCreditsRequired, *r)), *is_browser),
gate_decision(Some((AdditionalCreditsRequired, *r)), *is_browser, false),
PoolGate::RunAndNotify,
"T36/E18, a contract org is never pool-gated: {case}",
);
@@ -2268,6 +2300,119 @@ mod pool_gate_tests {
}
}
/// The reason the gate learned about the status pool at all: a free org whose one-time pool is
/// spent must keep the checks its status page is built on.
#[test]
fn a_status_check_runs_on_the_monthly_pool_when_the_one_time_pool_is_empty() {
let spent = StepRemaining {
browser: 0,
protocol: 0,
status: 43_200,
};
assert_eq!(
gate_decision(
Some((PoolExhaustionPolicy::SubscriptionRequired, spent)),
false,
true
),
PoolGate::Run,
);
}
#[test]
fn a_regular_check_ignores_the_monthly_pool() {
let spent = StepRemaining {
browser: 0,
protocol: 0,
status: 43_200,
};
assert_eq!(
gate_decision(
Some((PoolExhaustionPolicy::SubscriptionRequired, spent)),
false,
false
),
PoolGate::Skip,
);
}
/// There is no monthly browser allowance, so attachment must not rescue a browser check.
#[test]
fn a_browser_check_on_a_status_page_reads_only_the_browser_grant() {
let spent = StepRemaining {
browser: 0,
protocol: 500,
status: 43_200,
};
assert_eq!(
gate_decision(
Some((PoolExhaustionPolicy::SubscriptionRequired, spent)),
true,
true
),
PoolGate::Skip,
);
}
#[test]
fn an_exhausted_monthly_pool_skips_a_status_check_again() {
let spent = StepRemaining {
browser: 0,
protocol: 0,
status: 0,
};
assert_eq!(
gate_decision(
Some((PoolExhaustionPolicy::SubscriptionRequired, spent)),
false,
true
),
PoolGate::Skip,
);
}
/// A metered org is never blocked, whichever pool is empty.
#[test]
fn a_metered_org_runs_a_status_check_as_overage() {
let spent = StepRemaining {
browser: 0,
protocol: 0,
status: 0,
};
assert_eq!(
gate_decision(
Some((PoolExhaustionPolicy::MeteredOverage, spent)),
false,
true
),
PoolGate::RunAsOverage,
);
}
#[test]
fn slot_verdict_reads_status_membership_off_the_check_id() {
let check = protocol_check();
let with_monthly_room = StepRemaining {
browser: 0,
protocol: 0,
status: 10,
};
let mut ctx = ctx(
&[(&check.org_id, PoolExhaustionPolicy::SubscriptionRequired)],
&[(&check.org_id, with_monthly_room)],
);
assert_eq!(slot_verdict(Some(&ctx), &check, false), PoolGate::Skip);
ctx.status_attached.insert(check.id.clone());
assert_eq!(slot_verdict(Some(&ctx), &check, false), PoolGate::Run);
}
/// T17/E13 — the customer's own hardware ran it, so we never paid and must never stop it.
#[test]
fn a_private_venue_is_never_gated() {
@@ -303,7 +303,7 @@ describe("OrganizationManagement.vue", () => {
it("should have correct column configuration", () => {
wrapper = createWrapper();
const columns = wrapper.vm.columns;
expect(columns).toHaveLength(15);
expect(columns).toHaveLength(17);
expect(columns[0].id).toBe("name");
expect(columns[1].id).toBe("identifier");
expect(columns[2].id).toBe("subscription_status");
@@ -315,10 +315,12 @@ describe("OrganizationManagement.vue", () => {
expect(columns[8].id).toBe("browser_steps_total");
expect(columns[9].id).toBe("protocol_steps_used");
expect(columns[10].id).toBe("protocol_steps_total");
expect(columns[11].id).toBe("created_on");
expect(columns[12].id).toBe("trial_expiry");
expect(columns[13].id).toBe("contract_end_date");
expect(columns[14].id).toBe("actions");
expect(columns[11].id).toBe("status_steps_used");
expect(columns[12].id).toBe("status_steps_total");
expect(columns[13].id).toBe("created_on");
expect(columns[14].id).toBe("trial_expiry");
expect(columns[15].id).toBe("contract_end_date");
expect(columns[16].id).toBe("actions");
});
it("should have subscription plans mapping", () => {
@@ -468,6 +470,8 @@ describe("OrganizationManagement.vue", () => {
browser_steps_limit: 0,
protocol_steps_used: 0,
protocol_steps_limit: 0,
status_steps_used: 0,
status_steps_limit: 0,
created_at: "2023-12-01",
trial_expires_at: "2023-12-01",
contract_end_date: 0,
@@ -479,6 +483,42 @@ describe("OrganizationManagement.vue", () => {
});
});
it("should carry every synthetics pool from the response into the row", async () => {
mockGetAdminOrg.mockResolvedValue({
data: {
data: [
{
id: 1,
name: "Test Org",
identifier: "test-org",
plan: "1",
created_at: 1701388800000000,
trial_expires_at: 1701388800000000,
browser_steps_used: 400,
browser_steps_limit: 10000,
protocol_steps_used: 900,
protocol_steps_limit: 20000,
status_steps_used: 12000,
status_steps_limit: 43200,
},
],
},
});
wrapper = createWrapper();
await wrapper.vm.getData();
// A dropped field opens the limit dialog at 0, and saving that wipes the grant.
expect(wrapper.vm.tabledata[0]).toMatchObject({
browser_steps_used: 400,
browser_steps_limit: 10000,
protocol_steps_used: 900,
protocol_steps_limit: 20000,
status_steps_used: 12000,
status_steps_limit: 43200,
});
});
it("should set resultTotal correctly", async () => {
const mockResponse = {
data: {
@@ -94,6 +94,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template #cell-protocol_steps_total="{ row }">
{{ formatCredits(row.protocol_steps_limit) }}
</template>
<template #cell-status_steps_used="{ row }">
{{ formatCredits(row.status_steps_used) }}
</template>
<template #cell-status_steps_total="{ row }">
{{ formatCredits(row.status_steps_limit) }}
</template>
<template #cell-status="{ row }">
<OBadge
:variant="
@@ -371,6 +377,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:label="t('settings.organizationManagementPage.syntheticsProtocolStepsTab')"
data-test="org-management-set-synthetics-protocol-steps-btn"
/>
<OTab
name="synthetics_status_protocol"
:label="t('settings.organizationManagementPage.syntheticsStatusStepsTab')"
data-test="org-management-set-synthetics-status-steps-btn"
/>
</OTabs>
<!-- One OForm per pool (own schema, own submit). v-if, so only the active
@@ -410,11 +421,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
name="stepsLimit"
type="number"
data-test="synthetics-steps-limit-input"
:label="
isBrowserStepsTab
? t('settings.organizationManagementPage.totalBrowserSteps')
: t('settings.organizationManagementPage.totalProtocolSteps')
"
:label="stepsInputLabel"
required
/>
<div class="text-text-secondary text-xs">
@@ -492,7 +499,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>
<script lang="ts">
import { ref, onMounted, watch, defineComponent, computed } from "vue";
import { useI18nTyped, type I18nText } from "@/types/i18n";
import { useI18nTyped, type I18nKey, type I18nText } from "@/types/i18n";
import OEmptyState from "@/lib/core/EmptyState/OEmptyState.vue";
import OBadge from "@/lib/core/Badge/OBadge.vue";
import OrgCleanupTasksDialog from "@/components/iam/organizations/OrgCleanupTasksDialog.vue";
@@ -534,7 +541,11 @@ import {
} from "./OrganizationManagement.schema";
/** Backend TrialQuotaPool keys — also the usage-allowance dialog's tab names. */
type QuotaPool = "ai_credits" | "synthetics_browser_steps" | "synthetics_protocol_steps";
type QuotaPool =
| "ai_credits"
| "synthetics_browser_steps"
| "synthetics_protocol_steps"
| "synthetics_status_protocol";
export default defineComponent({
name: "PageAlerts",
@@ -578,15 +589,35 @@ export default defineComponent({
const usageLimitsRow = ref<any>({});
const usageLimitsTab = ref<QuotaPool>("ai_credits");
const isAiCreditsTab = computed(() => usageLimitsTab.value === "ai_credits");
// Browser and protocol are independent grants, so the step tabs differ only in
// which pair of row fields they read and write.
// The three step grants are independent, so the tabs differ only in which pair
// of row fields they read and write, and in their wording.
const isBrowserStepsTab = computed(() => usageLimitsTab.value === "synthetics_browser_steps");
const stepFields = computed(() =>
isBrowserStepsTab.value
? { used: "browser_steps_used", limit: "browser_steps_limit" }
: { used: "protocol_steps_used", limit: "protocol_steps_limit" },
const isStatusStepsTab = computed(() => usageLimitsTab.value === "synthetics_status_protocol");
const STEP_FIELDS: Record<string, { used: string; limit: string }> = {
synthetics_browser_steps: { used: "browser_steps_used", limit: "browser_steps_limit" },
synthetics_protocol_steps: { used: "protocol_steps_used", limit: "protocol_steps_limit" },
synthetics_status_protocol: { used: "status_steps_used", limit: "status_steps_limit" },
};
const stepFields = computed(
() => STEP_FIELDS[usageLimitsTab.value] ?? STEP_FIELDS.synthetics_protocol_steps,
);
const stepsInputLabel = computed(() => {
if (isBrowserStepsTab.value) {
return t("settings.organizationManagementPage.totalBrowserSteps");
}
return isStatusStepsTab.value
? t("settings.organizationManagementPage.totalStatusSteps")
: t("settings.organizationManagementPage.totalProtocolSteps");
});
const stepsUsed = computed(() => usageLimitsRow.value?.[stepFields.value.used] ?? 0);
const stepsTitleKey = computed((): I18nKey => {
if (isBrowserStepsTab.value) {
return "settings.setSyntheticsBrowserStepsFor";
}
return isStatusStepsTab.value
? "settings.setSyntheticsStatusStepsFor"
: "settings.setSyntheticsProtocolStepsFor";
});
const aiCreditsFormDefaults = computed(() =>
aiCreditsDefaults(usageLimitsRow.value?.credits_limit ?? 0),
);
@@ -614,18 +645,18 @@ export default defineComponent({
const usageLimitsTitle = computed(() =>
isAiCreditsTab.value
? t("settings.setAiCreditsFor", { name: usageLimitsRow.value?.name })
: t(
isBrowserStepsTab.value
? "settings.setSyntheticsBrowserStepsFor"
: "settings.setSyntheticsProtocolStepsFor",
{ name: usageLimitsRow.value?.name },
),
);
const usageLimitsSubtitle = computed(() =>
isAiCreditsTab.value
? t("settings.organizationManagementPage.setAiCreditsSubtitle")
: t("settings.organizationManagementPage.setSyntheticsStepsSubtitle"),
: t(stepsTitleKey.value, { name: usageLimitsRow.value?.name }),
);
const usageLimitsSubtitle = computed(() => {
if (isAiCreditsTab.value) {
return t("settings.organizationManagementPage.setAiCreditsSubtitle");
}
// The status grant is the one pool that resets, so it cannot borrow the
// lifetime wording the other two share.
return isStatusStepsTab.value
? t("settings.organizationManagementPage.setSyntheticsStatusStepsSubtitle")
: t("settings.organizationManagementPage.setSyntheticsStepsSubtitle");
});
const usageLimitsPrimaryLabel = computed(() =>
isAiCreditsTab.value
? t("settings.organizationManagementPage.saveCredits")
@@ -773,6 +804,26 @@ export default defineComponent({
size: COL.count,
meta: { align: "right" },
},
{
id: "status_steps_used",
header: t("settings.organizationManagementPage.statusStepsUsed"),
accessorKey: "status_steps_used",
sortable: true,
resizable: true,
hideable: true,
size: COL.count,
meta: { align: "right" },
},
{
id: "status_steps_total",
header: t("settings.organizationManagementPage.statusStepsTotal"),
accessorKey: "status_steps_limit",
sortable: true,
resizable: true,
hideable: true,
size: COL.count,
meta: { align: "right" },
},
{
id: "created_on",
header: t("settings.created_on"),
@@ -861,6 +912,8 @@ export default defineComponent({
browser_steps_limit: Number(responseData[i].browser_steps_limit ?? 0),
protocol_steps_used: Number(responseData[i].protocol_steps_used ?? 0),
protocol_steps_limit: Number(responseData[i].protocol_steps_limit ?? 0),
status_steps_used: Number(responseData[i].status_steps_used ?? 0),
status_steps_limit: Number(responseData[i].status_steps_limit ?? 0),
created_at: timestampToTimezoneDate(responseData[i].created_at, "UTC", "yyyy-MM-dd"),
trial_expires_at: timestampToTimezoneDate(
responseData[i].trial_expires_at,
@@ -1276,6 +1329,7 @@ export default defineComponent({
aiCreditsSchema,
submitAiCredits,
isBrowserStepsTab,
stepsInputLabel,
stepsUsed,
syntheticsStepsFormDefaults,
syntheticsStepsSchema,
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "قواعد البيانات",
"hosts": "المضيفون",
"kubernetes": "Kubernetes",
"topQueries": "أهم الاستعلامات"
"topQueries": "أهم الاستعلامات",
"profiles": "التوصيف"
},
"profiles": {
"title": "التوصيف",
"stream": "الجدول",
"dataSource": "مصدر البيانات",
"service": "الخدمة",
"profileType": "نوع التوصيف",
"tagKey": "مفتاح الوسم",
"tagValue": "قيمة الوسم",
"addFilter": "إضافة عامل تصفية",
"runQuery": "تشغيل الاستعلام",
"timeSeriesTitle": "قيم التوصيف عبر الزمن",
"resultTitle": "نتيجة التوصيف",
"viewTop": "الجدول الأعلى",
"viewFlame": "مخطط اللهب",
"viewTree": "شجرة المكدس",
"self": "الذاتي",
"total": "الإجمالي",
"cpuDuration": "مدة CPU",
"percent": "%",
"noStreams": "لا توجد جداول توصيف متاحة",
"noSeries": "لا توجد بيانات سلاسل زمنية في هذا النطاق",
"noResults": "لا توجد نتيجة توصيف في هذا النطاق",
"filters": "عوامل التصفية",
"expand": "توسيع",
"collapse": "طي",
"errors": {
"loadFailed": "فشل تحميل بيانات التوصيف"
}
},
"rum": {
"noSessionReplay": "لا توجد إعادة تشغيل متاحة",
@@ -2564,6 +2594,7 @@
"labelLogs": "السجلات",
"labelMetrics": "المقاييس",
"labelTraces": "آثار",
"labelProfiles": "التوصيف",
"labelMetadata": "البيانات الوصفية",
"add": "تدفق جديد",
"dataRetention": "الاحتفاظ بالبيانات (بأيام)",
@@ -3633,6 +3664,7 @@
"customLabel": "العرف",
"logsLabel": "السجلات",
"metricsLabel": "المقاييس",
"profilesLabel": "التوصيف",
"tracesLabel": "آثار",
"redirectionIngestionMsg": "ملاحظة: للوصول إلى الصفحات غير IAM، يجب بدء إدخال البيانات داخل المؤسسة الحالية.",
"databaseLabel": "قواعد البيانات",
@@ -5936,7 +5968,8 @@
"or": "أو:",
"aws": "AWS / كلاودووتش",
"aiIntegrations": "تكاملات الذكاء الاصطناعي",
"shippers": "شاحنو الأخشاب"
"shippers": "شاحنو الأخشاب",
"profiles": "التوصيف"
},
"indexSizeLbl": "حجم الفهرس",
"docsCountLbl": "الفعاليات",
@@ -8925,6 +8958,7 @@
"foundInLogs": "العثور عليه في السجلات",
"foundInTraces": "العثور عليه في آثار",
"foundInMetrics": "موجود في المقاييس",
"foundInProfiles": "موجود في التوصيف",
"distinguishByLabel": "تمييز الخدمات بواسطة",
"distinguishByHelp": "اختر الحقول التي تحدد مكان تشغيل خدماتك. على سبيل المثال، تجمع + مساحة الأسماء + النشر معا ينشئ هوية فريدة لكل خدمة. حتى 5 حقول.",
"serviceOptionalLabel": "ارتباط بدون صفة الخدمة",
@@ -9093,14 +9127,19 @@
"credits": "الاعتمادات",
"syntheticsBrowserStepsTab": "خطوات المتصفح",
"syntheticsProtocolStepsTab": "خطوات البروتوكول",
"syntheticsStatusStepsTab": "خطوات الحالة",
"setSyntheticsStepsSubtitle": "حدد بدل الخطوات الصناعي مدى الحياة للمنظمة. المتصفح والبروتوكول هما منحان منفصلان.",
"setSyntheticsStatusStepsSubtitle": "حدد الحصة الشهرية للمؤسسة لفحوصات البروتوكول المرتبطة بصفحة الحالة. يُعاد ضبط العدد كل شهر.",
"saveSteps": "احفظ الخطوات",
"totalBrowserSteps": "إجمالي خطوات المتصفح",
"totalProtocolSteps": "خطوات البروتوكول الكاملة",
"totalStatusSteps": "إجمالي خطوات الحالة",
"browserStepsUsed": "خطوات المتصفح المستخدمة",
"browserStepsTotal": "إجمالي خطوات المتصفح",
"protocolStepsUsed": "خطوات البروتوكول المستخدمة",
"protocolStepsTotal": "إجمالي خطوات البروتوكول",
"statusStepsUsed": "خطوات الحالة المستخدمة",
"statusStepsTotal": "إجمالي خطوات الحالة",
"steps": "الخطوات",
"revoke": "إلغاء",
"enableStorage": "تمكين التخزين",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "تعيين أرصدة الذكاء الاصطناعي لـ {name}",
"setSyntheticsBrowserStepsFor": "تعيين خطوات المتصفح لـ {name}",
"setSyntheticsProtocolStepsFor": "تعيين خطوات البروتوكول لـ {name}",
"setSyntheticsStatusStepsFor": "تعيين خطوات الحالة لـ {name}",
"limitExceeded": "تم تجاوز الحد",
"contractEndDateRequired": "تاريخ الانتهاء مطلوب.",
"contractNewEndDateRequired": "تاريخ الانتهاء الجديد مطلوب.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Datenbanken",
"hosts": "Hosts",
"kubernetes": "Kubernetes",
"topQueries": "Top-Abfragen"
"topQueries": "Top-Abfragen",
"profiles": "Profile"
},
"profiles": {
"title": "Profile",
"stream": "Stream",
"dataSource": "Datenquelle",
"service": "Service",
"profileType": "Profiltyp",
"tagKey": "Tag-Schlüssel",
"tagValue": "Tag-Wert",
"addFilter": "Filter hinzufügen",
"runQuery": "Abfrage ausführen",
"timeSeriesTitle": "Profilwerte im Zeitverlauf",
"resultTitle": "Profilergebnis",
"viewTop": "Top-Tabelle",
"viewFlame": "Flammengraph",
"viewTree": "Stack-Baum",
"self": "Eigen",
"total": "Gesamt",
"cpuDuration": "CPU-Dauer",
"percent": "%",
"noStreams": "Keine Profil-Streams verfügbar",
"noSeries": "Keine Zeitreihendaten in diesem Bereich",
"noResults": "Kein Profilergebnis in diesem Bereich",
"filters": "Filter",
"expand": "Erweitern",
"collapse": "Einklappen",
"errors": {
"loadFailed": "Profildaten konnten nicht geladen werden"
}
},
"rum": {
"noSessionReplay": "Kein Session-Replay verfügbar",
@@ -2564,6 +2594,7 @@
"labelLogs": "Logs",
"labelMetrics": "Metriken",
"labelTraces": "Traces",
"labelProfiles": "Profile",
"labelMetadata": "Metadaten",
"add": "Stream hinzufügen",
"dataRetention": "Datenspeicherung (in Tagen)",
@@ -3633,6 +3664,7 @@
"customLabel": "Benutzerdefiniert",
"logsLabel": "Logs",
"metricsLabel": "Metriken",
"profilesLabel": "Profile",
"tracesLabel": "Traces",
"redirectionIngestionMsg": "Hinweis: Um auf andere Seiten als das IAM zuzugreifen, muss die Datenaufnahme innerhalb der aktuellen Organisation initiiert werden.",
"databaseLabel": "Datenbanken",
@@ -5936,7 +5968,8 @@
"or": "Oder:",
"aws": "AWS//CloudWatch",
"aiIntegrations": "KI-Integrationen",
"shippers": "Holzversender"
"shippers": "Holzversender",
"profiles": "Profile"
},
"indexSizeLbl": "Indexgröße",
"docsCountLbl": "Ereignisse",
@@ -8925,6 +8958,7 @@
"foundInLogs": "In Logs gefunden",
"foundInTraces": "In Traces gefunden",
"foundInMetrics": "In Metriken gefunden",
"foundInProfiles": "In Profilen gefunden",
"distinguishByLabel": "Dienste unterscheiden nach",
"distinguishByHelp": "Wählen Sie Felder, die festlegen, wo Ihre Dienste ausgeführt werden. Beispielsweise erzeugen Cluster + Namespace + Deployment zusammen eine eindeutige Identität für jeden Dienst. Bis zu 5 Felder.",
"serviceOptionalLabel": "Ohne Dienstattribut korrelieren",
@@ -9093,14 +9127,19 @@
"credits": "Credits",
"syntheticsBrowserStepsTab": "Browser-Schritte",
"syntheticsProtocolStepsTab": "Protokoll-Schritte",
"syntheticsStatusStepsTab": "Status-Schritte",
"setSyntheticsStepsSubtitle": "Legen Sie das lebenslange Synthetics-Schrittkontingent der Organisation fest. Browser- und Protokoll-Schritte sind separate Kontingente.",
"setSyntheticsStatusStepsSubtitle": "Legen Sie das monatliche Kontingent der Organisation für Protokollprüfungen fest, die an eine Statusseite angehängt sind. Die Anzahl wird jeden Monat zurückgesetzt.",
"saveSteps": "Schritte speichern",
"totalBrowserSteps": "Browser-Schritte insgesamt",
"totalProtocolSteps": "Protokoll-Schritte insgesamt",
"totalStatusSteps": "Status-Schritte gesamt",
"browserStepsUsed": "Verwendete Browser-Schritte",
"browserStepsTotal": "Browser-Schritte insgesamt",
"protocolStepsUsed": "Verwendete Protokoll-Schritte",
"protocolStepsTotal": "Protokoll-Schritte insgesamt",
"statusStepsUsed": "Verwendete Status-Schritte",
"statusStepsTotal": "Status-Schritte gesamt",
"steps": "Schritte",
"revoke": "Widerrufen",
"enableStorage": "Speicher aktivieren",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "KI-Guthaben für {name} festlegen",
"setSyntheticsBrowserStepsFor": "Browser-Schritte für {name} festlegen",
"setSyntheticsProtocolStepsFor": "Protokoll-Schritte für {name} festlegen",
"setSyntheticsStatusStepsFor": "Status-Schritte für {name} festlegen",
"limitExceeded": "Limit überschritten",
"contractEndDateRequired": "Enddatum ist erforderlich.",
"contractNewEndDateRequired": "Neues Enddatum ist erforderlich.",
+6
View File
@@ -9127,14 +9127,19 @@
"credits": "credits",
"syntheticsBrowserStepsTab": "Browser Steps",
"syntheticsProtocolStepsTab": "Protocol Steps",
"syntheticsStatusStepsTab": "Status Steps",
"setSyntheticsStepsSubtitle": "Set the organization's lifetime synthetics step allowance. Browser and protocol are separate grants.",
"setSyntheticsStatusStepsSubtitle": "Set the organization's monthly allowance for protocol checks attached to a status page. The count resets each month.",
"saveSteps": "Save Steps",
"totalBrowserSteps": "Total Browser Steps",
"totalProtocolSteps": "Total Protocol Steps",
"totalStatusSteps": "Total Status Steps",
"browserStepsUsed": "Browser Steps Used",
"browserStepsTotal": "Browser Steps Total",
"protocolStepsUsed": "Protocol Steps Used",
"protocolStepsTotal": "Protocol Steps Total",
"statusStepsUsed": "Status Steps Used",
"statusStepsTotal": "Status Steps Total",
"steps": "steps",
"revoke": "Revoke",
"enableStorage": "Enable Storage",
@@ -9307,6 +9312,7 @@
"setAiCreditsFor": "Set AI Credits for {name}",
"setSyntheticsBrowserStepsFor": "Set Browser Steps for {name}",
"setSyntheticsProtocolStepsFor": "Set Protocol Steps for {name}",
"setSyntheticsStatusStepsFor": "Set Status Steps for {name}",
"limitExceeded": "Limit Exceeded",
"contractEndDateRequired": "End date is required.",
"contractNewEndDateRequired": "New end date is required.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Bases de datos",
"hosts": "Hosts",
"kubernetes": "Kubernetes",
"topQueries": "Consultas principales"
"topQueries": "Consultas principales",
"profiles": "Perfiles"
},
"profiles": {
"title": "Perfiles",
"stream": "Stream",
"dataSource": "Fuente de datos",
"service": "Servicio",
"profileType": "Tipo de perfil",
"tagKey": "Clave de etiqueta",
"tagValue": "Valor de etiqueta",
"addFilter": "Agregar filtro",
"runQuery": "Ejecutar consulta",
"timeSeriesTitle": "Valores de perfil a lo largo del tiempo",
"resultTitle": "Resultado del perfil",
"viewTop": "Tabla Top",
"viewFlame": "Gráfico de llama",
"viewTree": "Árbol de pila",
"self": "Propio",
"total": "Total",
"cpuDuration": "Duración de CPU",
"percent": "%",
"noStreams": "No hay streams de perfiles disponibles",
"noSeries": "No hay datos de series temporales en este rango",
"noResults": "No hay resultados de perfil en este rango",
"filters": "Filtros",
"expand": "Expandir",
"collapse": "Contraer",
"errors": {
"loadFailed": "No se pudieron cargar los datos del perfil"
}
},
"rum": {
"noSessionReplay": "No hay repetición de sesión disponible",
@@ -2564,6 +2594,7 @@
"labelLogs": "Registros",
"labelMetrics": "Métricas",
"labelTraces": "Trazas",
"labelProfiles": "Perfiles",
"labelMetadata": "Metadatos",
"add": "Agregar transmisión",
"dataRetention": "Retención de datos (en días)",
@@ -3633,6 +3664,7 @@
"customLabel": "Personalizado",
"logsLabel": "Registros",
"metricsLabel": "Métricas",
"profilesLabel": "Perfiles",
"tracesLabel": "Trazas",
"redirectionIngestionMsg": "Nota: Para acceder a páginas distintas de IAM, la ingestión de datos debe iniciarse dentro de la organización actual.",
"databaseLabel": "bases de datos",
@@ -5936,7 +5968,8 @@
"or": "O:",
"aws": "AWS/CloudWatch",
"aiIntegrations": "Integraciones de IA",
"shippers": "Transportistas de troncos"
"shippers": "Transportistas de troncos",
"profiles": "Perfiles"
},
"indexSizeLbl": "Tamaño del índice",
"docsCountLbl": "Eventos",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Encontrado en Logs",
"foundInTraces": "Encontrado en trazas",
"foundInMetrics": "Encontrado en Métricas",
"foundInProfiles": "Encontrado en Perfiles",
"distinguishByLabel": "Distinguir servicios por",
"distinguishByHelp": "Elige campos que definan dónde se ejecutan tus servicios. Por ejemplo, clúster + namespace + deployment juntos crean una identidad única para cada servicio. Hasta 5 campos.",
"serviceOptionalLabel": "Correlacionar sin atributo de servicio",
@@ -9093,14 +9127,19 @@
"credits": "créditos",
"syntheticsBrowserStepsTab": "Pasos de navegador",
"syntheticsProtocolStepsTab": "Pasos de protocolo",
"syntheticsStatusStepsTab": "Pasos de estado",
"setSyntheticsStepsSubtitle": "Establece la asignación de por vida de pasos de sintéticos de la organización. Los pasos de navegador y de protocolo son asignaciones separadas.",
"setSyntheticsStatusStepsSubtitle": "Establezca la asignación mensual de la organización para las comprobaciones de protocolo vinculadas a una página de estado. El recuento se restablece cada mes.",
"saveSteps": "Guardar pasos",
"totalBrowserSteps": "Total de pasos de navegador",
"totalProtocolSteps": "Total de pasos de protocolo",
"totalStatusSteps": "Pasos de estado totales",
"browserStepsUsed": "Pasos de navegador utilizados",
"browserStepsTotal": "Total de pasos de navegador",
"protocolStepsUsed": "Pasos de protocolo utilizados",
"protocolStepsTotal": "Total de pasos de protocolo",
"statusStepsUsed": "Pasos de estado usados",
"statusStepsTotal": "Total de pasos de estado",
"steps": "pasos",
"revoke": "Revocar",
"enableStorage": "Habilitar almacenamiento",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "Establecer créditos de IA para {name}",
"setSyntheticsBrowserStepsFor": "Establecer pasos de navegador para {name}",
"setSyntheticsProtocolStepsFor": "Establecer pasos de protocolo para {name}",
"setSyntheticsStatusStepsFor": "Establecer pasos de estado para {name}",
"limitExceeded": "Límite excedido",
"contractEndDateRequired": "La fecha de finalización es obligatoria.",
"contractNewEndDateRequired": "La nueva fecha de finalización es obligatoria.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Bases de données",
"hosts": "Hôtes",
"kubernetes": "Kubernetes",
"topQueries": "Requêtes principales"
"topQueries": "Requêtes principales",
"profiles": "Profils"
},
"profiles": {
"title": "Profils",
"stream": "Flux",
"dataSource": "Source de données",
"service": "Service",
"profileType": "Type de profil",
"tagKey": "Clé de tag",
"tagValue": "Valeur de tag",
"addFilter": "Ajouter un filtre",
"runQuery": "Exécuter la requête",
"timeSeriesTitle": "Valeurs de profil dans le temps",
"resultTitle": "Résultat du profil",
"viewTop": "Tableau des principaux",
"viewFlame": "Graphique de flammes",
"viewTree": "Arborescence de pile",
"self": "Propre",
"total": "Total",
"cpuDuration": "Durée CPU",
"percent": "%",
"noStreams": "Aucun flux de profils disponible",
"noSeries": "Aucune donnée de série temporelle dans cette plage",
"noResults": "Aucun résultat de profil dans cette plage",
"filters": "Filtres",
"expand": "Développer",
"collapse": "Réduire",
"errors": {
"loadFailed": "Échec du chargement des données de profil"
}
},
"rum": {
"noSessionReplay": "Aucune relecture de session disponible",
@@ -2564,6 +2594,7 @@
"labelLogs": "Logs",
"labelMetrics": "Métriques",
"labelTraces": "Traces",
"labelProfiles": "Profils",
"labelMetadata": "Métadonnées",
"add": "Ajouter un stream",
"dataRetention": "Conservation des données (en jours)",
@@ -3633,6 +3664,7 @@
"customLabel": "Personnalisé",
"logsLabel": "Logs",
"metricsLabel": "Métriques",
"profilesLabel": "Profils",
"tracesLabel": "Traces",
"redirectionIngestionMsg": "Remarque : Pour accéder à des pages autres que l'IAM, l'ingestion de données doit être initiée au sein de l'organisation actuelle.",
"databaseLabel": "bases de données",
@@ -5936,7 +5968,8 @@
"or": "Ou :",
"aws": "AWS/CloudWatch",
"aiIntegrations": "Intégrations d'IA",
"shippers": "Expéditeurs de grumes"
"shippers": "Expéditeurs de grumes",
"profiles": "Profils"
},
"indexSizeLbl": "Taille de l'indice",
"docsCountLbl": "Événements",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Trouvé dans les logs",
"foundInTraces": "Trouvé dans les traces",
"foundInMetrics": "Trouvé dans les métriques",
"foundInProfiles": "Trouvé dans Profils",
"distinguishByLabel": "Distinguer les services par",
"distinguishByHelp": "Choisissez les champs qui définissent où vos services s'exécutent. Par exemple, cluster + namespace + déploiement créent ensemble une identité unique pour chaque service. Jusqu'à 5 champs.",
"serviceOptionalLabel": "Corréler sans attribut de service",
@@ -9093,14 +9127,19 @@
"credits": "crédits",
"syntheticsBrowserStepsTab": "Étapes navigateur",
"syntheticsProtocolStepsTab": "Étapes de protocole",
"syntheticsStatusStepsTab": "Étapes de statut",
"setSyntheticsStepsSubtitle": "Définissez l'allocation à vie d'étapes synthétiques de l'organisation. Le navigateur et le protocole sont des allocations distinctes.",
"setSyntheticsStatusStepsSubtitle": "Définissez l'allocation mensuelle de l'organisation pour les contrôles de protocole associés à une page de statut. Le compteur est réinitialisé chaque mois.",
"saveSteps": "Enregistrer les étapes",
"totalBrowserSteps": "Total des étapes navigateur",
"totalProtocolSteps": "Total des étapes de protocole",
"totalStatusSteps": "Total des étapes de statut",
"browserStepsUsed": "Étapes navigateur utilisées",
"browserStepsTotal": "Total des étapes navigateur",
"protocolStepsUsed": "Étapes de protocole utilisées",
"protocolStepsTotal": "Total des étapes de protocole",
"statusStepsUsed": "Étapes de statut utilisées",
"statusStepsTotal": "Total des étapes de statut",
"steps": "étapes",
"revoke": "Révoquer",
"enableStorage": "Activer le stockage",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "Définir les crédits IA pour {name}",
"setSyntheticsBrowserStepsFor": "Définir les étapes navigateur pour {name}",
"setSyntheticsProtocolStepsFor": "Définir les étapes de protocole pour {name}",
"setSyntheticsStatusStepsFor": "Définir les étapes de statut pour {name}",
"limitExceeded": "Limite dépassée",
"contractEndDateRequired": "La date de fin est requise.",
"contractNewEndDateRequired": "La nouvelle date de fin est requise.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Database",
"hosts": "Host",
"kubernetes": "Kubernetes",
"topQueries": "Query principali"
"topQueries": "Query principali",
"profiles": "Profili"
},
"profiles": {
"title": "Profili",
"stream": "Stream",
"dataSource": "Origine dei dati",
"service": "Servizio",
"profileType": "Tipo di profilo",
"tagKey": "Chiave tag",
"tagValue": "Valore tag",
"addFilter": "Aggiungi filtro",
"runQuery": "Esegui query",
"timeSeriesTitle": "Valori dei profili nel tempo",
"resultTitle": "Risultato del profilo",
"viewTop": "Tabella Top",
"viewFlame": "Grafico a fiamma",
"viewTree": "Albero degli stack",
"self": "Self",
"total": "Totale",
"cpuDuration": "Durata CPU",
"percent": "%",
"noStreams": "Nessuno stream di profili disponibile",
"noSeries": "Nessun dato delle serie temporali in questo intervallo",
"noResults": "Nessun risultato del profilo in questo intervallo",
"filters": "Filtri",
"expand": "Espandi",
"collapse": "Comprimi",
"errors": {
"loadFailed": "Impossibile caricare i dati del profilo"
}
},
"rum": {
"noSessionReplay": "Nessuna sessione replay disponibile",
@@ -2564,6 +2594,7 @@
"labelLogs": "Log",
"labelMetrics": "Metriche",
"labelTraces": "Trace",
"labelProfiles": "Profili",
"labelMetadata": "Metadati",
"add": "Aggiungi stream",
"dataRetention": "Conservazione dei dati (in giorni)",
@@ -3633,6 +3664,7 @@
"customLabel": "Personalizzato",
"logsLabel": "Log",
"metricsLabel": "Metriche",
"profilesLabel": "Profili",
"tracesLabel": "Tracce",
"redirectionIngestionMsg": "Nota: per accedere a pagine diverse da IAM, l'inserimento dei dati deve essere avviato all'interno dell'organizzazione corrente.",
"databaseLabel": "Database",
@@ -5936,7 +5968,8 @@
"or": "Oppure:",
"aws": "AWS/CloudWatch",
"aiIntegrations": "Integrazioni AI",
"shippers": "Spedizionieri di log"
"shippers": "Spedizionieri di log",
"profiles": "Profili"
},
"indexSizeLbl": "Dimensione dell'indice",
"docsCountLbl": "Eventi",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Trovato nei log",
"foundInTraces": "Trovato nelle tracce",
"foundInMetrics": "Trovato nelle metriche",
"foundInProfiles": "Trovato nei profili",
"distinguishByLabel": "Distingui i servizi per",
"distinguishByHelp": "Scegli i campi che definiscono dove vengono eseguiti i tuoi servizi. Ad esempio, cluster + namespace + deployment insieme creano un'identità univoca per ciascun servizio. Fino a 5 campi.",
"serviceOptionalLabel": "Correla senza attributo del servizio",
@@ -9093,14 +9127,19 @@
"credits": "crediti",
"syntheticsBrowserStepsTab": "Passi del browser",
"syntheticsProtocolStepsTab": "Passi del protocollo",
"syntheticsStatusStepsTab": "Passaggi di stato",
"setSyntheticsStepsSubtitle": "Imposta la quota complessiva dei passi Synthetics dell'organizzazione. Browser e protocollo sono concessioni separate.",
"setSyntheticsStatusStepsSubtitle": "Imposta la quota mensile dell'organizzazione per i controlli di protocollo associati a una pagina di stato. Il conteggio viene reimpostato ogni mese.",
"saveSteps": "Salva passi",
"totalBrowserSteps": "Totale passi browser",
"totalProtocolSteps": "Totale passi protocollo",
"totalStatusSteps": "Passaggi di stato totali",
"browserStepsUsed": "Passi browser utilizzati",
"browserStepsTotal": "Passi browser totali",
"protocolStepsUsed": "Passi protocollo utilizzati",
"protocolStepsTotal": "Passi protocollo totali",
"statusStepsUsed": "Passaggi di stato utilizzati",
"statusStepsTotal": "Totale passaggi di stato",
"steps": "passi",
"revoke": "Revoca",
"enableStorage": "Abilita archiviazione",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "Imposta crediti AI per {name}",
"setSyntheticsBrowserStepsFor": "Imposta passi browser per {name}",
"setSyntheticsProtocolStepsFor": "Imposta passi protocollo per {name}",
"setSyntheticsStatusStepsFor": "Imposta i passaggi di stato per {name}",
"limitExceeded": "Limite superato",
"contractEndDateRequired": "La data di fine è obbligatoria.",
"contractNewEndDateRequired": "La nuova data di fine è obbligatoria.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "データベース",
"hosts": "ホスト",
"kubernetes": "Kubernetes",
"topQueries": "トップクエリ"
"topQueries": "トップクエリ",
"profiles": "プロファイル"
},
"profiles": {
"title": "プロファイル",
"stream": "ストリーム",
"dataSource": "データソース",
"service": "サービス",
"profileType": "プロファイルタイプ",
"tagKey": "タグキー",
"tagValue": "タグ値",
"addFilter": "フィルターを追加",
"runQuery": "クエリを実行",
"timeSeriesTitle": "プロファイル値の推移",
"resultTitle": "プロファイル結果",
"viewTop": "トップテーブル",
"viewFlame": "フレームグラフ",
"viewTree": "スタックツリー",
"self": "セルフ",
"total": "合計",
"cpuDuration": "CPU 時間",
"percent": "%",
"noStreams": "利用可能なプロファイルストリームがありません",
"noSeries": "この期間に時系列データがありません",
"noResults": "この期間にプロファイル結果がありません",
"filters": "フィルター",
"expand": "展開",
"collapse": "折りたたむ",
"errors": {
"loadFailed": "プロファイルデータの読み込みに失敗しました"
}
},
"rum": {
"noSessionReplay": "セッションリプレイは利用できません",
@@ -2564,6 +2594,7 @@
"labelLogs": "ログ",
"labelMetrics": "メトリックス",
"labelTraces": "トレース",
"labelProfiles": "プロファイル",
"labelMetadata": "メタデータ",
"add": "ストリームを追加",
"dataRetention": "データ保持期間 (日数)",
@@ -3633,6 +3664,7 @@
"customLabel": "カスタム",
"logsLabel": "ログ",
"metricsLabel": "メトリクス",
"profilesLabel": "プロファイル",
"tracesLabel": "トレース",
"redirectionIngestionMsg": "注: IAM以外のページにアクセスするには、現在の組織でデータの取り込みを開始する必要があります。",
"databaseLabel": "データベース",
@@ -5936,7 +5968,8 @@
"or": "または:",
"aws": "AWS/CloudWatch",
"aiIntegrations": "AI インテグレーション",
"shippers": "ログシッパー"
"shippers": "ログシッパー",
"profiles": "プロファイル"
},
"indexSizeLbl": "インデックスサイズ",
"docsCountLbl": "イベント",
@@ -8925,6 +8958,7 @@
"foundInLogs": "ログ内で発見",
"foundInTraces": "トレース内で検出",
"foundInMetrics": "メトリクス内で発見",
"foundInProfiles": "プロファイルで見つかりました",
"distinguishByLabel": "サービスの区別基準",
"distinguishByHelp": "サービスが稼働する場所を定義するフィールドを選択します。たとえば、cluster + namespace + deployment を組み合わせると、各サービスに一意のアイデンティティが作成されます。最大5フィールド。",
"serviceOptionalLabel": "サービス属性なしで相関付け",
@@ -9093,14 +9127,19 @@
"credits": "クレジット",
"syntheticsBrowserStepsTab": "ブラウザステップ",
"syntheticsProtocolStepsTab": "プロトコルステップ",
"syntheticsStatusStepsTab": "ステータスステップ",
"setSyntheticsStepsSubtitle": "組織の合成モニタリングステップの総許容量を設定します。ブラウザとプロトコルは別々の割り当てです。",
"setSyntheticsStatusStepsSubtitle": "ステータスページに紐づけられたプロトコルチェックの月間許容量を組織に設定します。カウントは毎月リセットされます。",
"saveSteps": "ステップを保存",
"totalBrowserSteps": "ブラウザステップ合計",
"totalProtocolSteps": "プロトコルステップ合計",
"totalStatusSteps": "ステータスステップ合計",
"browserStepsUsed": "ブラウザステップ使用量",
"browserStepsTotal": "ブラウザステップ総数",
"protocolStepsUsed": "プロトコルステップ使用量",
"protocolStepsTotal": "プロトコルステップ総数",
"statusStepsUsed": "使用済みステータスステップ",
"statusStepsTotal": "ステータスステップ総数",
"steps": "ステップ",
"revoke": "取り消し",
"enableStorage": "ストレージを有効にする",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "{name} の AI クレジットを設定",
"setSyntheticsBrowserStepsFor": "{name} のブラウザステップを設定",
"setSyntheticsProtocolStepsFor": "{name} のプロトコルステップを設定",
"setSyntheticsStatusStepsFor": "{name} のステータスステップを設定",
"limitExceeded": "制限を超えました",
"contractEndDateRequired": "終了日が必要です。",
"contractNewEndDateRequired": "新しい終了日が必要です。",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "데이터베이스",
"hosts": "호스트",
"kubernetes": "쿠버네티스",
"topQueries": "상위 쿼리"
"topQueries": "상위 쿼리",
"profiles": "프로파일"
},
"profiles": {
"title": "프로파일",
"stream": "스트림",
"dataSource": "데이터 소스",
"service": "서비스",
"profileType": "프로파일 유형",
"tagKey": "태그 키",
"tagValue": "태그 값",
"addFilter": "필터 추가",
"runQuery": "쿼리 실행",
"timeSeriesTitle": "시간 경과에 따른 프로파일 값",
"resultTitle": "프로파일 결과",
"viewTop": "상위 테이블",
"viewFlame": "플레임 그래프",
"viewTree": "스택 트리",
"self": "자체",
"total": "합계",
"cpuDuration": "CPU 지속 시간",
"percent": "%",
"noStreams": "사용 가능한 프로파일 스트림이 없습니다",
"noSeries": "이 범위에 시계열 데이터가 없습니다",
"noResults": "이 범위에 프로파일 결과가 없습니다",
"filters": "필터",
"expand": "펼치기",
"collapse": "접기",
"errors": {
"loadFailed": "프로파일 데이터를 불러오지 못했습니다"
}
},
"rum": {
"noSessionReplay": "사용 가능한 세션 재생이 없습니다",
@@ -2564,6 +2594,7 @@
"labelLogs": "로그",
"labelMetrics": "메트릭",
"labelTraces": "트레이스",
"labelProfiles": "프로파일",
"labelMetadata": "메타데이터",
"add": "스트림 추가",
"dataRetention": "데이터 보존 (일 단위)",
@@ -3633,6 +3664,7 @@
"customLabel": "커스텀",
"logsLabel": "로그",
"metricsLabel": "메트릭",
"profilesLabel": "프로파일",
"tracesLabel": "트레이스",
"redirectionIngestionMsg": "참고: IAM 이외의 페이지에 액세스하려면 현재 조직 내에서 데이터 수집을 시작해야 합니다.",
"databaseLabel": "데이터베이스",
@@ -5936,7 +5968,8 @@
"or": "또는:",
"aws": "AWS/ 클라우드워치",
"aiIntegrations": "AI 통합",
"shippers": "로그 화주"
"shippers": "로그 화주",
"profiles": "프로파일"
},
"indexSizeLbl": "인덱스 크기",
"docsCountLbl": "이벤트",
@@ -8925,6 +8958,7 @@
"foundInLogs": "로그에서 발견됨",
"foundInTraces": "트레이스에서 발견됨",
"foundInMetrics": "메트릭에서 발견됨",
"foundInProfiles": "프로파일에서 발견됨",
"distinguishByLabel": "서비스 구분 기준",
"distinguishByHelp": "서비스가 실행되는 위치를 정의하는 필드를 선택하세요. 예를 들어 cluster + namespace + deployment를 함께 사용하면 각 서비스에 대한 고유 ID가 생성됩니다. 최대 5개 필드까지 가능합니다.",
"serviceOptionalLabel": "서비스 속성 없이 상관 분석",
@@ -9093,14 +9127,19 @@
"credits": "크레딧",
"syntheticsBrowserStepsTab": "브라우저 단계",
"syntheticsProtocolStepsTab": "프로토콜 단계",
"syntheticsStatusStepsTab": "상태 단계",
"setSyntheticsStepsSubtitle": "조직의 전체 Synthetics 단계 허용량을 설정합니다. 브라우저와 프로토콜은 별도로 부여됩니다.",
"setSyntheticsStatusStepsSubtitle": "상태 페이지에 연결된 프로토콜 검사에 대한 조직의 월간 할당량을 설정합니다. 사용량은 매월 초기화됩니다.",
"saveSteps": "단계 저장",
"totalBrowserSteps": "총 브라우저 단계",
"totalProtocolSteps": "총 프로토콜 단계",
"totalStatusSteps": "총 상태 단계",
"browserStepsUsed": "사용된 브라우저 단계",
"browserStepsTotal": "브라우저 단계 총계",
"protocolStepsUsed": "사용된 프로토콜 단계",
"protocolStepsTotal": "프로토콜 단계 총계",
"statusStepsUsed": "사용된 상태 단계",
"statusStepsTotal": "상태 단계 합계",
"steps": "단계",
"revoke": "취소",
"enableStorage": "스토리지 활성화",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "{name}의 AI 크레딧 설정",
"setSyntheticsBrowserStepsFor": "{name}에 대한 브라우저 단계 설정",
"setSyntheticsProtocolStepsFor": "{name}에 대한 프로토콜 단계 설정",
"setSyntheticsStatusStepsFor": "{name}의 상태 단계 설정",
"limitExceeded": "한도 초과",
"contractEndDateRequired": "종료 날짜는 필수입니다.",
"contractNewEndDateRequired": "새 종료 날짜는 필수입니다.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Databases",
"hosts": "Hosts",
"kubernetes": "Kubernetes",
"topQueries": "Top Queries"
"topQueries": "Top Queries",
"profiles": "Profielen"
},
"profiles": {
"title": "Profielen",
"stream": "Stream",
"dataSource": "Gegevensbron",
"service": "Service",
"profileType": "Profieltype",
"tagKey": "Tag-sleutel",
"tagValue": "Tag-waarde",
"addFilter": "Filter toevoegen",
"runQuery": "Query uitvoeren",
"timeSeriesTitle": "Profielwaarden in de tijd",
"resultTitle": "Profielresultaat",
"viewTop": "Toptabel",
"viewFlame": "Vlammengrafiek",
"viewTree": "Stackboom",
"self": "Zelf",
"total": "Totaal",
"cpuDuration": "CPU-duur",
"percent": "%",
"noStreams": "Geen profielstreams beschikbaar",
"noSeries": "Geen tijdreeksgegevens in dit bereik",
"noResults": "Geen profielresultaat in dit bereik",
"filters": "Filters",
"expand": "Uitvouwen",
"collapse": "Inklappen",
"errors": {
"loadFailed": "Kan profielgegevens niet laden"
}
},
"rum": {
"noSessionReplay": "Geen sessieherhaling beschikbaar",
@@ -2564,6 +2594,7 @@
"labelLogs": "Logboeken",
"labelMetrics": "Metrieken",
"labelTraces": "Traces",
"labelProfiles": "Profielen",
"labelMetadata": "Metagegevens",
"add": "Stream toevoegen",
"dataRetention": "Bewaring van gegevens (in dagen)",
@@ -3633,6 +3664,7 @@
"customLabel": "Op maat",
"logsLabel": "Logboeken",
"metricsLabel": "Metrieken",
"profilesLabel": "Profielen",
"tracesLabel": "Traces",
"redirectionIngestionMsg": "Opmerking: Om toegang te krijgen tot andere pagina's dan de IAM, moet de gegevensinvoer binnen de huidige organisatie worden gestart.",
"databaseLabel": "Databanken",
@@ -5936,7 +5968,8 @@
"or": "Of:",
"aws": "AWS/CloudWatch",
"aiIntegrations": "AI-integraties",
"shippers": "Verzenders van logboeken"
"shippers": "Verzenders van logboeken",
"profiles": "Profielen"
},
"indexSizeLbl": "Indexgrootte",
"docsCountLbl": "Gebeurtenissen",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Gevonden in Logs",
"foundInTraces": "Gevonden in traces",
"foundInMetrics": "Gevonden in Metrics",
"foundInProfiles": "Gevonden in Profielen",
"distinguishByLabel": "Services onderscheiden op",
"distinguishByHelp": "Kies velden die bepalen waar je services draaien. Bijvoorbeeld: cluster + namespace + deployment vormen samen een unieke identiteit voor elke service. Maximaal 5 velden.",
"serviceOptionalLabel": "Correleren zonder service-attribuut",
@@ -9093,14 +9127,19 @@
"credits": "credits",
"syntheticsBrowserStepsTab": "Browserstappen",
"syntheticsProtocolStepsTab": "Protocolstappen",
"syntheticsStatusStepsTab": "Statusstappen",
"setSyntheticsStepsSubtitle": "Stel het levenslange synthetics-stappenquotum van de organisatie in. Browser en protocol zijn afzonderlijke toewijzingen.",
"setSyntheticsStatusStepsSubtitle": "Stel de maandelijkse limiet van de organisatie in voor protocolcontroles die aan een statuspagina zijn gekoppeld. De telling wordt elke maand gereset.",
"saveSteps": "Stappen opslaan",
"totalBrowserSteps": "Browserstappen totaal",
"totalProtocolSteps": "Protocolstappen totaal",
"totalStatusSteps": "Totaal statusstappen",
"browserStepsUsed": "Browserstappen gebruikt",
"browserStepsTotal": "Browserstappen totaal",
"protocolStepsUsed": "Protocolstappen gebruikt",
"protocolStepsTotal": "Protocolstappen totaal",
"statusStepsUsed": "Gebruikte statusstappen",
"statusStepsTotal": "Statusstappen totaal",
"steps": "stappen",
"revoke": "Intrekken",
"enableStorage": "Opslag inschakelen",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "AI-credits instellen voor {name}",
"setSyntheticsBrowserStepsFor": "Browserstappen instellen voor {name}",
"setSyntheticsProtocolStepsFor": "Protocolstappen instellen voor {name}",
"setSyntheticsStatusStepsFor": "Statusstappen instellen voor {name}",
"limitExceeded": "Limiet overschreden",
"contractEndDateRequired": "Einddatum is verplicht.",
"contractNewEndDateRequired": "Nieuwe einddatum is verplicht.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Bazy danych",
"hosts": "Hosty",
"kubernetes": "Kubernetes",
"topQueries": "Najczęstsze zapytania"
"topQueries": "Najczęstsze zapytania",
"profiles": "Profile"
},
"profiles": {
"title": "Profile",
"stream": "Strumień",
"dataSource": "Źródło danych",
"service": "Usługa",
"profileType": "Typ profilu",
"tagKey": "Klucz tagu",
"tagValue": "Wartość tagu",
"addFilter": "Dodaj filtr",
"runQuery": "Uruchom zapytanie",
"timeSeriesTitle": "Wartości profilu w czasie",
"resultTitle": "Wynik profilu",
"viewTop": "Tabela Top",
"viewFlame": "Graf płomieniowy",
"viewTree": "Drzewo stosu",
"self": "Własne",
"total": "Łączne",
"cpuDuration": "Czas CPU",
"percent": "%",
"noStreams": "Brak dostępnych strumieni profili",
"noSeries": "Brak danych szeregów czasowych w tym zakresie",
"noResults": "Brak wyników profilu w tym zakresie",
"filters": "Filtry",
"expand": "Rozwiń",
"collapse": "Zwiń",
"errors": {
"loadFailed": "Nie udało się wczytać danych profilu"
}
},
"rum": {
"noSessionReplay": "Brak odtwarzania sesji",
@@ -2564,6 +2594,7 @@
"labelLogs": "Logi",
"labelMetrics": "Metryki",
"labelTraces": "Trace",
"labelProfiles": "Profile",
"labelMetadata": "Metadane",
"add": "Nowy strumień",
"dataRetention": "Przechowywanie danych (w dniach)",
@@ -3633,6 +3664,7 @@
"customLabel": "Niestandardowe",
"logsLabel": "Logi",
"metricsLabel": "Metryki",
"profilesLabel": "Profile",
"tracesLabel": "Trace",
"redirectionIngestionMsg": "Uwaga: aby uzyskać dostęp do stron innych niż IAM, należy rozpocząć pobieranie danych w bieżącej organizacji.",
"databaseLabel": "Bazy danych",
@@ -5936,7 +5968,8 @@
"or": "Lub:",
"aws": "AWS / CloudWatch",
"aiIntegrations": "Integracje AI",
"shippers": "Dostawcy logów"
"shippers": "Dostawcy logów",
"profiles": "Profile"
},
"indexSizeLbl": "Rozmiar indeksu",
"docsCountLbl": "Zdarzenia",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Znaleziono w logach",
"foundInTraces": "Znaleziono w śladach",
"foundInMetrics": "Znaleziono w metrykach",
"foundInProfiles": "Znaleziono w Profilach",
"distinguishByLabel": "Rozróżniaj usługi według",
"distinguishByHelp": "Wybierz pola, które definiują, gdzie działają usługi. Na przykład klaster + przestrzeń nazw + wdrożenie razem tworzą unikalną tożsamość dla każdej usługi. Maksymalnie 5 pól.",
"serviceOptionalLabel": "Koreluj bez atrybutu usługi",
@@ -9093,14 +9127,19 @@
"credits": "kredyty",
"syntheticsBrowserStepsTab": "Kroki przeglądarki",
"syntheticsProtocolStepsTab": "Kroki protokołu",
"syntheticsStatusStepsTab": "Kroki statusu",
"setSyntheticsStepsSubtitle": "Ustaw całkowity limit kroków syntetycznych organizacji. Kroki przeglądarki i protokołu to oddzielne pule.",
"setSyntheticsStatusStepsSubtitle": "Ustaw miesięczny limit organizacji dla kontroli protokołów przypisanych do strony statusu. Licznik resetuje się co miesiąc.",
"saveSteps": "Zapisz kroki",
"totalBrowserSteps": "Łączna liczba kroków przeglądarki",
"totalProtocolSteps": "Łączna liczba kroków protokołu",
"totalStatusSteps": "Łączna liczba kroków statusu",
"browserStepsUsed": "Wykorzystane kroki przeglądarki",
"browserStepsTotal": "Całkowita liczba kroków przeglądarki",
"protocolStepsUsed": "Wykorzystane kroki protokołu",
"protocolStepsTotal": "Całkowita liczba kroków protokołu",
"statusStepsUsed": "Wykorzystane kroki statusu",
"statusStepsTotal": "Kroki statusu — łącznie",
"steps": "kroki",
"revoke": "Odwołaj",
"enableStorage": "Włącz przechowywanie",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "Ustaw kredyty AI dla {name}",
"setSyntheticsBrowserStepsFor": "Ustaw kroki przeglądarki dla {name}",
"setSyntheticsProtocolStepsFor": "Ustaw kroki protokołu dla {name}",
"setSyntheticsStatusStepsFor": "Ustaw kroki statusu dla {name}",
"limitExceeded": "Przekroczono limit",
"contractEndDateRequired": "Data zakończenia jest wymagana.",
"contractNewEndDateRequired": "Nowa data zakończenia jest wymagana.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Bancos de dados",
"hosts": "Hosts",
"kubernetes": "Kubernetes",
"topQueries": "Principais consultas"
"topQueries": "Principais consultas",
"profiles": "Perfis"
},
"profiles": {
"title": "Perfis",
"stream": "Stream",
"dataSource": "Fonte de dados",
"service": "Serviço",
"profileType": "Tipo de perfil",
"tagKey": "Chave da tag",
"tagValue": "Valor da tag",
"addFilter": "Adicionar filtro",
"runQuery": "Executar consulta",
"timeSeriesTitle": "Valores de perfil ao longo do tempo",
"resultTitle": "Resultado do perfil",
"viewTop": "Tabela top",
"viewFlame": "Gráfico de chamas",
"viewTree": "Árvore de pilha",
"self": "Próprio",
"total": "Total",
"cpuDuration": "Duração de CPU",
"percent": "%",
"noStreams": "Nenhum stream de perfil disponível",
"noSeries": "Nenhum dado de série temporal neste intervalo",
"noResults": "Nenhum resultado de perfil neste intervalo",
"filters": "Filtros",
"expand": "Expandir",
"collapse": "Recolher",
"errors": {
"loadFailed": "Falha ao carregar os dados de perfil"
}
},
"rum": {
"noSessionReplay": "Nenhum replay de sessão disponível",
@@ -2564,6 +2594,7 @@
"labelLogs": "Registros",
"labelMetrics": "Métricas",
"labelTraces": "Rastreamentos",
"labelProfiles": "Perfis",
"labelMetadata": "Metadados",
"add": "Adicionar stream",
"dataRetention": "Retenção de dados (em dias)",
@@ -3633,6 +3664,7 @@
"customLabel": "Personalizado",
"logsLabel": "Registros",
"metricsLabel": "Métricas",
"profilesLabel": "Perfis",
"tracesLabel": "Rastreamentos",
"redirectionIngestionMsg": "Observação: para acessar páginas diferentes do IAM, a ingestão de dados deve ser iniciada na organização atual.",
"databaseLabel": "bancos de dados",
@@ -5936,7 +5968,8 @@
"or": "Ou:",
"aws": "AWS/CloudWatch",
"aiIntegrations": "Integrações de IA",
"shippers": "Expedidores de toras"
"shippers": "Expedidores de toras",
"profiles": "Perfis"
},
"indexSizeLbl": "Tamanho do índice",
"docsCountLbl": "Eventos",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Encontrado em Logs",
"foundInTraces": "Encontrado em Traces",
"foundInMetrics": "Encontrado em Métricas",
"foundInProfiles": "Encontrado em Perfis",
"distinguishByLabel": "Distinguir Serviços Por",
"distinguishByHelp": "Escolha campos que definam onde seus serviços são executados. Por exemplo, cluster + namespace + deployment juntos criam uma identidade única para cada serviço. Até 5 campos.",
"serviceOptionalLabel": "Correlacionar sem o atributo de serviço",
@@ -9093,14 +9127,19 @@
"credits": "créditos",
"syntheticsBrowserStepsTab": "Etapas do Navegador",
"syntheticsProtocolStepsTab": "Etapas do Protocolo",
"syntheticsStatusStepsTab": "Etapas de status",
"setSyntheticsStepsSubtitle": "Defina a cota vitalícia de etapas de sintéticos da organização. Navegador e protocolo são cotas separadas.",
"setSyntheticsStatusStepsSubtitle": "Defina a cota mensal da organização para verificações de protocolo anexadas a uma página de status. A contagem é redefinida a cada mês.",
"saveSteps": "Salvar etapas",
"totalBrowserSteps": "Total de Etapas do Navegador",
"totalProtocolSteps": "Total de Etapas do Protocolo",
"totalStatusSteps": "Total de etapas de status",
"browserStepsUsed": "Etapas do Navegador Usadas",
"browserStepsTotal": "Etapas do Navegador Totais",
"protocolStepsUsed": "Etapas do Protocolo Usadas",
"protocolStepsTotal": "Etapas do Protocolo Totais",
"statusStepsUsed": "Etapas de status usadas",
"statusStepsTotal": "Etapas de status totais",
"steps": "etapas",
"revoke": "Revogar",
"enableStorage": "Habilitar Armazenamento",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "Definir créditos de IA para {name}",
"setSyntheticsBrowserStepsFor": "Definir Etapas do Navegador para {name}",
"setSyntheticsProtocolStepsFor": "Definir Etapas do Protocolo para {name}",
"setSyntheticsStatusStepsFor": "Definir etapas de status para {name}",
"limitExceeded": "Limite excedido",
"contractEndDateRequired": "A data final é obrigatória.",
"contractNewEndDateRequired": "A nova data final é obrigatória.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Базы данных",
"hosts": "Хосты",
"kubernetes": "Kubernetes",
"topQueries": "Топ запросов"
"topQueries": "Топ запросов",
"profiles": "Профили"
},
"profiles": {
"title": "Профили",
"stream": "Поток",
"dataSource": "Источник данных",
"service": "Сервис",
"profileType": "Тип профиля",
"tagKey": "Ключ тега",
"tagValue": "Значение тега",
"addFilter": "Добавить фильтр",
"runQuery": "Выполнить запрос",
"timeSeriesTitle": "Значения профиля во времени",
"resultTitle": "Результат профиля",
"viewTop": "Топ-таблица",
"viewFlame": "Граф пламени",
"viewTree": "Дерево стека",
"self": "Собственное",
"total": "Всего",
"cpuDuration": "Длительность CPU",
"percent": "%",
"noStreams": "Нет доступных потоков профилей",
"noSeries": "Нет данных временных рядов в этом диапазоне",
"noResults": "Нет результата профиля в этом диапазоне",
"filters": "Фильтры",
"expand": "Развернуть",
"collapse": "Свернуть",
"errors": {
"loadFailed": "Не удалось загрузить данные профиля"
}
},
"rum": {
"noSessionReplay": "Нет доступного воспроизведения сеанса",
@@ -2564,6 +2594,7 @@
"labelLogs": "Логи",
"labelMetrics": "Метрики",
"labelTraces": "Трейсы",
"labelProfiles": "Профили",
"labelMetadata": "Метаданные",
"add": "Новый поток",
"dataRetention": "Срок хранения данных (в днях)",
@@ -3633,6 +3664,7 @@
"customLabel": "Пользовательский",
"logsLabel": "Логи",
"metricsLabel": "Метрики",
"profilesLabel": "Профили",
"tracesLabel": "Трейсы",
"redirectionIngestionMsg": "Примечание: Для доступа к страницам, кроме IAM, необходимо инициировать прием данных в рамках текущей организации.",
"databaseLabel": "Базы данных",
@@ -5936,7 +5968,8 @@
"or": "Или:",
"aws": "AWS / CloudWatch",
"aiIntegrations": "Интеграции с ИИ",
"shippers": "Отправители логов"
"shippers": "Отправители логов",
"profiles": "Профили"
},
"indexSizeLbl": "Размер индекса",
"docsCountLbl": "События",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Найдено в логах",
"foundInTraces": "Найдено в трассировках",
"foundInMetrics": "Найдено в метриках",
"foundInProfiles": "Найдено в Профилях",
"distinguishByLabel": "Различать сервисы по",
"distinguishByHelp": "Выберите поля, определяющие, где работают ваши сервисы. Например, кластер + пространство имен + развертывание вместе создают уникальную идентичность для каждого сервиса. Не более 5 полей.",
"serviceOptionalLabel": "Коррелировать без атрибута сервиса",
@@ -9093,14 +9127,19 @@
"credits": "кредиты",
"syntheticsBrowserStepsTab": "Браузерные шаги",
"syntheticsProtocolStepsTab": "Протокольные шаги",
"syntheticsStatusStepsTab": "Шаги статуса",
"setSyntheticsStepsSubtitle": "Установите общую квоту шагов синтетики для организации. Браузерные и протокольные шаги предоставляются отдельно.",
"setSyntheticsStatusStepsSubtitle": "Задайте месячный лимит организации на проверки протоколов, привязанные к странице статуса. Счётчик сбрасывается каждый месяц.",
"saveSteps": "Сохранить шаги",
"totalBrowserSteps": "Всего браузерных шагов",
"totalProtocolSteps": "Всего протокольных шагов",
"totalStatusSteps": "Всего шагов статуса",
"browserStepsUsed": "Использовано браузерных шагов",
"browserStepsTotal": "Всего браузерных шагов",
"protocolStepsUsed": "Использовано протокольных шагов",
"protocolStepsTotal": "Всего протокольных шагов",
"statusStepsUsed": "Использовано шагов статуса",
"statusStepsTotal": "Итого шагов статуса",
"steps": "шаги",
"revoke": "Отозвать",
"enableStorage": "Включить хранилище",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "Установить AI-кредиты для {name}",
"setSyntheticsBrowserStepsFor": "Установить браузерные шаги для {name}",
"setSyntheticsProtocolStepsFor": "Установить протокольные шаги для {name}",
"setSyntheticsStatusStepsFor": "Задать шаги статуса для {name}",
"limitExceeded": "Превышен лимит",
"contractEndDateRequired": "Требуется дата окончания.",
"contractNewEndDateRequired": "Требуется новая дата окончания.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Veritabanları",
"hosts": "Ana Bilgisayarlar",
"kubernetes": "Kubernetes",
"topQueries": "En Çok Kullanılan Sorgular"
"topQueries": "En Çok Kullanılan Sorgular",
"profiles": "Profiller"
},
"profiles": {
"title": "Profiller",
"stream": "Akış",
"dataSource": "Veri kaynağı",
"service": "Hizmet",
"profileType": "Profil türü",
"tagKey": "Etiket anahtarı",
"tagValue": "Etiket değeri",
"addFilter": "Filtre ekle",
"runQuery": "Sorguyu çalıştır",
"timeSeriesTitle": "Zaman içinde profil değerleri",
"resultTitle": "Profil sonucu",
"viewTop": "En üst tablo",
"viewFlame": "Alev grafiği",
"viewTree": "Yığın ağacı",
"self": "Kendi",
"total": "Toplam",
"cpuDuration": "CPU süresi",
"percent": "%",
"noStreams": "Kullanılabilir profil akışı yok",
"noSeries": "Bu aralıkta zaman serisi verisi yok",
"noResults": "Bu aralıkta profil sonucu yok",
"filters": "Filtreler",
"expand": "Genişlet",
"collapse": "Daralt",
"errors": {
"loadFailed": "Profil verileri yüklenemedi"
}
},
"rum": {
"noSessionReplay": "Oturum tekrarı mevcut değil",
@@ -2564,6 +2594,7 @@
"labelLogs": "Günlükler",
"labelMetrics": "Metrikler",
"labelTraces": "İzler",
"labelProfiles": "Profiller",
"labelMetadata": "Meta veriler",
"add": "Akış Ekle",
"dataRetention": "Veri Saklama (gün cinsinden)",
@@ -3633,6 +3664,7 @@
"customLabel": "Özel",
"logsLabel": "Günlükler",
"metricsLabel": "Metrikler",
"profilesLabel": "Profiller",
"tracesLabel": "İzler",
"redirectionIngestionMsg": "Not: IAM dışındaki sayfalara erişmek için, veri alımının geçerli kuruluş içinde başlatılması gerekir.",
"databaseLabel": "Veritabanları",
@@ -5936,7 +5968,8 @@
"or": "Veya:",
"aws": "AWS/CloudWatch",
"aiIntegrations": "Yapay zeka entegrasyonları",
"shippers": "Kütük göndericileri"
"shippers": "Kütük göndericileri",
"profiles": "Profiller"
},
"indexSizeLbl": "Endeks Boyutu",
"docsCountLbl": "Olaylar",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Loglarda Bulundu",
"foundInTraces": "İzlerde Bulundu",
"foundInMetrics": "Metriklerde Bulundu",
"foundInProfiles": "Profillerde bulundu",
"distinguishByLabel": "Servisleri Şuna Göre Ayırt Et",
"distinguishByHelp": "Servislerinizin nerede çalıştığını tanımlayan alanları seçin. Örneğin, küme + ad alanı + dağıtım birlikte her servis için benzersiz bir kimlik oluşturur. En fazla 5 alan.",
"serviceOptionalLabel": "Servis özniteliği olmadan ilişkilendir",
@@ -9093,14 +9127,19 @@
"credits": "kredi",
"syntheticsBrowserStepsTab": "Tarayıcı Adımları",
"syntheticsProtocolStepsTab": "Protokol Adımları",
"syntheticsStatusStepsTab": "Durum Adımları",
"setSyntheticsStepsSubtitle": "Kuruluşun ömür boyu sentetik adım hakkını ayarlayın. Tarayıcı ve protokol ayrı tahsislerdir.",
"setSyntheticsStatusStepsSubtitle": "Bir durum sayfasına bağlı protokol kontrolleri için kuruluşun aylık kotasını belirleyin. Sayaç her ay sıfırlanır.",
"saveSteps": "Adımları Kaydet",
"totalBrowserSteps": "Toplam Tarayıcı Adımları",
"totalProtocolSteps": "Toplam Protokol Adımları",
"totalStatusSteps": "Toplam Durum Adımı",
"browserStepsUsed": "Kullanılan Tarayıcı Adımları",
"browserStepsTotal": "Tarayıcı Adımları Toplamı",
"protocolStepsUsed": "Kullanılan Protokol Adımları",
"protocolStepsTotal": "Protokol Adımları Toplamı",
"statusStepsUsed": "Kullanılan Durum Adımları",
"statusStepsTotal": "Toplam Durum Adımları",
"steps": "adım",
"revoke": "İptal Et",
"enableStorage": "Depolamayı Etkinleştir",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "{name} için AI Kredilerini Ayarla",
"setSyntheticsBrowserStepsFor": "{name} için Tarayıcı Adımlarını Ayarla",
"setSyntheticsProtocolStepsFor": "{name} için Protokol Adımlarını Ayarla",
"setSyntheticsStatusStepsFor": "{name} için Durum Adımlarını Belirle",
"limitExceeded": "Sınır Aşıldı",
"contractEndDateRequired": "Bitiş tarihi gereklidir.",
"contractNewEndDateRequired": "Yeni bitiş tarihi gereklidir.",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "Cơ sở dữ liệu",
"hosts": "Máy chủ",
"kubernetes": "Kubernetes",
"topQueries": "Truy vấn hàng đầu"
"topQueries": "Truy vấn hàng đầu",
"profiles": "Profiles"
},
"profiles": {
"title": "Profiles",
"stream": "Stream",
"dataSource": "Nguồn dữ liệu",
"service": "Dịch vụ",
"profileType": "Loại profile",
"tagKey": "Khóa tag",
"tagValue": "Giá trị tag",
"addFilter": "Thêm bộ lọc",
"runQuery": "Chạy truy vấn",
"timeSeriesTitle": "Giá trị profile theo thời gian",
"resultTitle": "Kết quả profile",
"viewTop": "Bảng top",
"viewFlame": "Biểu đồ flame",
"viewTree": "Cây stack",
"self": "Riêng",
"total": "Tổng",
"cpuDuration": "Thời lượng CPU",
"percent": "%",
"noStreams": "Không có stream profile nào",
"noSeries": "Không có dữ liệu chuỗi thời gian trong khoảng này",
"noResults": "Không có kết quả profile trong khoảng này",
"filters": "Bộ lọc",
"expand": "Mở rộng",
"collapse": "Thu gọn",
"errors": {
"loadFailed": "Không tải được dữ liệu profile"
}
},
"rum": {
"noSessionReplay": "Không có bản phát lại phiên nào",
@@ -2564,6 +2594,7 @@
"labelLogs": "Nhật ký",
"labelMetrics": "Chỉ số",
"labelTraces": "Trace",
"labelProfiles": "Profiles",
"labelMetadata": "Siêu dữ liệu",
"add": "Luồng mới",
"dataRetention": "Lưu giữ dữ liệu (ngày)",
@@ -3633,6 +3664,7 @@
"customLabel": "Tùy chỉnh",
"logsLabel": "Nhật ký",
"metricsLabel": "Chỉ số",
"profilesLabel": "Profiles",
"tracesLabel": "Truy vết",
"redirectionIngestionMsg": "Lưu ý: Để truy cập các trang khác ngoài IAM, việc đưa dữ liệu vào phải được khởi tạo trong tổ chức hiện tại.",
"databaseLabel": "Cơ sở dữ liệu",
@@ -5936,7 +5968,8 @@
"or": "Hoặc:",
"aws": "AWS / CloudWatch",
"aiIntegrations": "Tích hợp AI",
"shippers": "Trình gửi nhật ký"
"shippers": "Trình gửi nhật ký",
"profiles": "Profiles"
},
"indexSizeLbl": "Kích thước chỉ mục",
"docsCountLbl": "Sự kiện",
@@ -8925,6 +8958,7 @@
"foundInLogs": "Tìm thấy trong Nhật ký",
"foundInTraces": "Tìm thấy trong Trace",
"foundInMetrics": "Tìm thấy trong Metrics",
"foundInProfiles": "Tìm thấy trong Profiles",
"distinguishByLabel": "Phân biệt Dịch vụ Theo",
"distinguishByHelp": "Chọn các trường xác định nơi dịch vụ của bạn chạy. Ví dụ: cluster + namespace + deployment cùng tạo ra một định danh duy nhất cho mỗi dịch vụ. Tối đa 5 trường.",
"serviceOptionalLabel": "Tương quan mà không có thuộc tính dịch vụ",
@@ -9093,14 +9127,19 @@
"credits": "tín dụng",
"syntheticsBrowserStepsTab": "Bước trình duyệt",
"syntheticsProtocolStepsTab": "Bước giao thức",
"syntheticsStatusStepsTab": "Bước trạng thái",
"setSyntheticsStepsSubtitle": "Đặt hạn mức bước Synthetics trọn đời của tổ chức. Trình duyệt và giao thức được cấp riêng biệt.",
"setSyntheticsStatusStepsSubtitle": "Đặt hạn mức hàng tháng của tổ chức cho các kiểm tra giao thức gắn với trang trạng thái. Số lượng sẽ được đặt lại mỗi tháng.",
"saveSteps": "Lưu bước",
"totalBrowserSteps": "Tổng bước trình duyệt",
"totalProtocolSteps": "Tổng bước giao thức",
"totalStatusSteps": "Tổng số bước trạng thái",
"browserStepsUsed": "Bước trình duyệt đã dùng",
"browserStepsTotal": "Tổng bước trình duyệt",
"protocolStepsUsed": "Bước giao thức đã dùng",
"protocolStepsTotal": "Tổng bước giao thức",
"statusStepsUsed": "Bước trạng thái đã dùng",
"statusStepsTotal": "Tổng số bước trạng thái",
"steps": "bước",
"revoke": "Thu hồi",
"enableStorage": "Bật Lưu trữ",
@@ -9273,6 +9312,7 @@
"setAiCreditsFor": "Đặt tín dụng AI cho {name}",
"setSyntheticsBrowserStepsFor": "Đặt bước trình duyệt cho {name}",
"setSyntheticsProtocolStepsFor": "Đặt bước giao thức cho {name}",
"setSyntheticsStatusStepsFor": "Đặt bước trạng thái cho {name}",
"limitExceeded": "Đã vượt quá giới hạn",
"contractEndDateRequired": "Ngày kết thúc là bắt buộc.",
"contractNewEndDateRequired": "Ngày kết thúc mới là bắt buộc.",
+6
View File
@@ -9127,14 +9127,19 @@
"credits": "积分",
"syntheticsBrowserStepsTab": "浏览器步骤",
"syntheticsProtocolStepsTab": "协议步骤",
"syntheticsStatusStepsTab": "状态步骤",
"setSyntheticsStepsSubtitle": "设置组织的合成监控步骤总配额。浏览器和协议分别独立授权。",
"setSyntheticsStatusStepsSubtitle": "设置组织每月可用于状态页附加协议检查的配额。该计数每月重置。",
"saveSteps": "保存步骤",
"totalBrowserSteps": "浏览器步骤总数",
"totalProtocolSteps": "协议步骤总数",
"totalStatusSteps": "状态步骤总数",
"browserStepsUsed": "已用浏览器步骤",
"browserStepsTotal": "浏览器步骤总计",
"protocolStepsUsed": "已用协议步骤",
"protocolStepsTotal": "协议步骤总计",
"statusStepsUsed": "已用状态步骤",
"statusStepsTotal": "状态步骤总量",
"steps": "步骤",
"revoke": "撤销",
"enableStorage": "启用存储",
@@ -9307,6 +9312,7 @@
"setAiCreditsFor": "为 {name} 设置 AI 信用额度",
"setSyntheticsBrowserStepsFor": "为 {name} 设置浏览器步骤",
"setSyntheticsProtocolStepsFor": "为 {name} 设置协议步骤",
"setSyntheticsStatusStepsFor": "为 {name} 设置状态步骤",
"limitExceeded": "超出限制",
"contractEndDateRequired": "必须填写结束日期。",
"contractNewEndDateRequired": "必须填写新的结束日期。",
+42 -2
View File
@@ -1287,7 +1287,37 @@
"databases": "資料庫",
"hosts": "主機",
"kubernetes": "Kubernetes",
"topQueries": "熱門查詢"
"topQueries": "熱門查詢",
"profiles": "效能剖析"
},
"profiles": {
"title": "效能剖析",
"stream": "串流",
"dataSource": "資料來源",
"service": "服務",
"profileType": "剖析類型",
"tagKey": "標籤鍵",
"tagValue": "標籤值",
"addFilter": "新增篩選條件",
"runQuery": "執行查詢",
"timeSeriesTitle": "剖析值隨時間變化",
"resultTitle": "剖析結果",
"viewTop": "排名表格",
"viewFlame": "火焰圖",
"viewTree": "堆疊樹",
"self": "自身",
"total": "總計",
"cpuDuration": "CPU 持續時間",
"percent": "%",
"noStreams": "沒有可用的剖析串流",
"noSeries": "此範圍內沒有時間序列資料",
"noResults": "此範圍內沒有剖析結果",
"filters": "篩選條件",
"expand": "展開",
"collapse": "收合",
"errors": {
"loadFailed": "載入剖析資料失敗"
}
},
"rum": {
"noSessionReplay": "沒有可用的工作階段重播",
@@ -2564,6 +2594,7 @@
"labelLogs": "日誌",
"labelMetrics": "指標",
"labelTraces": "追蹤",
"labelProfiles": "效能剖析",
"labelMetadata": "中繼資料",
"add": "新增串流",
"dataRetention": "資料保留期限 (天)",
@@ -3633,6 +3664,7 @@
"customLabel": "自訂",
"logsLabel": "日誌",
"metricsLabel": "指標",
"profilesLabel": "效能剖析",
"tracesLabel": "追蹤",
"redirectionIngestionMsg": "注意:若要存取 IAM 以外的頁面,必須先在目前組織中啟動資料攝取。",
"databaseLabel": "資料庫",
@@ -5931,7 +5963,8 @@
"or": "或:",
"aws": "AWS / CloudWatch",
"aiIntegrations": "AI 整合",
"shippers": "日誌傳送工具"
"shippers": "日誌傳送工具",
"profiles": "效能剖析"
},
"indexSizeLbl": "索引大小",
"docsCountLbl": "事件",
@@ -8920,6 +8953,7 @@
"foundInLogs": "在日誌中發現",
"foundInTraces": "在追蹤中發現",
"foundInMetrics": "在指標中發現",
"foundInProfiles": "在效能剖析中找到",
"distinguishByLabel": "服務區分依據",
"distinguishByHelp": "選擇定義服務執行位置的欄位。例如,叢集 + 命名空間 + 部署一起為每個服務建立唯一識別。最多 5 個欄位。",
"serviceOptionalLabel": "不使用服務屬性進行關聯",
@@ -9088,14 +9122,19 @@
"credits": "額度",
"syntheticsBrowserStepsTab": "瀏覽器步驟",
"syntheticsProtocolStepsTab": "協定步驟",
"syntheticsStatusStepsTab": "狀態步驟",
"setSyntheticsStepsSubtitle": "設定組織的合成監控步驟終身配額。瀏覽器與協定分別計算。",
"setSyntheticsStatusStepsSubtitle": "設定組織附加至狀態頁面的通訊協定檢查每月額度。數量每月重設。",
"saveSteps": "儲存步驟",
"totalBrowserSteps": "瀏覽器步驟總數",
"totalProtocolSteps": "協定步驟總數",
"totalStatusSteps": "狀態步驟總數",
"browserStepsUsed": "已使用瀏覽器步驟",
"browserStepsTotal": "瀏覽器步驟總計",
"protocolStepsUsed": "已使用協定步驟",
"protocolStepsTotal": "協定步驟總計",
"statusStepsUsed": "已使用狀態步驟",
"statusStepsTotal": "狀態步驟總數",
"steps": "步驟",
"revoke": "撤銷",
"enableStorage": "啟用儲存",
@@ -9268,6 +9307,7 @@
"setAiCreditsFor": "設定 {name} 的 AI 額度",
"setSyntheticsBrowserStepsFor": "設定 {name} 的瀏覽器步驟",
"setSyntheticsProtocolStepsFor": "設定 {name} 的協定步驟",
"setSyntheticsStatusStepsFor": "設定 {name} 的狀態步驟",
"limitExceeded": "超出限制",
"contractEndDateRequired": "結束日期為必填。",
"contractNewEndDateRequired": "新的結束日期為必填。",