mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 00:08:55 +00:00
fix(incidents): viewer token access, reopen, multi-field update and status confirms (#15994)
This commit is contained in:
@@ -84,10 +84,9 @@ impl IncidentStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update payload enum for incident field updates
|
||||
/// Ensures only one field can be updated per request
|
||||
/// One field per request; `deny_unknown_fields` rejects a payload that sends a second one.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(untagged)]
|
||||
#[serde(untagged, deny_unknown_fields)]
|
||||
pub enum UpdatePayload {
|
||||
/// Update incident title
|
||||
Title { title: String },
|
||||
@@ -2305,6 +2304,21 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_payload_rejects_a_second_field() {
|
||||
for json in [
|
||||
r#"{"title":"x","status":"resolved"}"#,
|
||||
r#"{"severity":"P4","status":"resolved"}"#,
|
||||
r#"{"title":"x","severity":"P1"}"#,
|
||||
r#"{"status":"resolved","note":"x"}"#,
|
||||
] {
|
||||
assert!(
|
||||
serde_json::from_str::<UpdatePayload>(json).is_err(),
|
||||
"{json} must not deserialize to one field"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_status_serde_roundtrip() {
|
||||
for status in [
|
||||
|
||||
@@ -3112,6 +3112,10 @@ pub async fn update_status_as(
|
||||
} else {
|
||||
infra::table::alert_incidents::update_status(org_id, incident_id, status).await?
|
||||
};
|
||||
// The ack guard left a resolved row alone: record and replicate nothing.
|
||||
if status == "acknowledged" && updated.status != "acknowledged" {
|
||||
return model_to_incident(updated).await;
|
||||
}
|
||||
if status == "acknowledged" && updated.acknowledged_by.as_deref() == Some(user_id) {
|
||||
ack_incident_alerts(org_id, incident_id, user_id).await;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ struct PendingMigrations {
|
||||
ai_chat_shares: bool,
|
||||
fleet: bool,
|
||||
public_dashboards: bool,
|
||||
incident_integrations: bool,
|
||||
missing_ownership: bool,
|
||||
profiles: bool,
|
||||
}
|
||||
@@ -575,6 +576,9 @@ fn all_org_ownership_keys(pending: &PendingMigrations) -> Vec<&'static str> {
|
||||
if pending.profiles {
|
||||
keys.push("profiles");
|
||||
}
|
||||
if pending.incident_integrations {
|
||||
keys.push("incident_integrations");
|
||||
}
|
||||
keys
|
||||
}
|
||||
|
||||
@@ -766,6 +770,11 @@ fn pending_migrations(latest: &str, existing: &str) -> PendingMigrations {
|
||||
log::info!("[OFGA:Local] profiles stream permissions migration needed");
|
||||
pending.profiles = true;
|
||||
}
|
||||
let v0_0_62 = version_compare::Version::from("0.0.62").unwrap();
|
||||
if meta_version >= v0_0_62 && existing_model_version < v0_0_62 {
|
||||
log::info!("[OFGA:Local] incident integrations permissions migration needed");
|
||||
pending.incident_integrations = true;
|
||||
}
|
||||
|
||||
pending
|
||||
}
|
||||
@@ -884,4 +893,16 @@ mod tests {
|
||||
);
|
||||
assert!(OFGA_MODELS.contains_key("profiles"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incident_integrations_back_fill_runs_once_across_0_0_62() {
|
||||
let pending = pending_migrations("0.0.62", "0.0.61");
|
||||
assert!(pending.incident_integrations && !pending.profiles);
|
||||
assert_eq!(
|
||||
all_org_ownership_keys(&pending),
|
||||
vec!["incident_integrations"]
|
||||
);
|
||||
assert!(!pending_migrations("0.0.62", "0.0.62").incident_integrations);
|
||||
assert!(!pending_migrations("0.0.61", "0.0.60").incident_integrations);
|
||||
}
|
||||
}
|
||||
|
||||
+35
-3
@@ -75,6 +75,13 @@ impl RemovalTarget {
|
||||
}
|
||||
}
|
||||
|
||||
/// True when `requested` is not the same set of custom roles as `current`.
|
||||
#[cfg(feature = "enterprise")]
|
||||
fn custom_roles_differ(requested: &[String], current: &[String]) -> bool {
|
||||
let requested: std::collections::HashSet<&String> = requested.iter().collect();
|
||||
requested != current.iter().collect()
|
||||
}
|
||||
|
||||
fn redact_token(token: &str) -> String {
|
||||
if token.is_empty() {
|
||||
return String::new();
|
||||
@@ -501,9 +508,20 @@ pub async fn update_user(
|
||||
is_org_updated |= local_user.role.ne(&new_user.role);
|
||||
#[cfg(feature = "enterprise")]
|
||||
if let Some(cr) = new_org_role.custom_role {
|
||||
custom_roles_need_change = true;
|
||||
custom_roles.extend(cr);
|
||||
is_org_updated = true;
|
||||
// Only an admin grants custom roles; a self-update may resend its own unchanged.
|
||||
if update_mode.is_self_update()
|
||||
&& !matches!(local_user.role, UserRole::Admin | UserRole::Root)
|
||||
&& custom_roles_differ(
|
||||
&cr,
|
||||
&o2_openfga::authorizer::roles::get_roles_for_org_user(org_id, email).await,
|
||||
)
|
||||
{
|
||||
message = "Self custom roles cannot be changed";
|
||||
} else {
|
||||
custom_roles_need_change = true;
|
||||
custom_roles.extend(cr);
|
||||
is_org_updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1577,6 +1595,20 @@ mod tests {
|
||||
// Mutex to ensure test setup is serialized to prevent race conditions
|
||||
static TEST_SETUP_LOCK: tokio::sync::OnceCell<Mutex<()>> = tokio::sync::OnceCell::const_new();
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[test]
|
||||
fn a_self_update_may_resend_its_custom_roles_but_not_change_them() {
|
||||
let current = ["a".to_string(), "b".to_string()];
|
||||
assert!(!custom_roles_differ(&["b".into(), "a".into()], ¤t));
|
||||
assert!(custom_roles_differ(
|
||||
&["a".into(), "b".into(), "c".into()],
|
||||
¤t
|
||||
));
|
||||
assert!(custom_roles_differ(&["a".into()], ¤t));
|
||||
assert!(custom_roles_differ(&["c".into()], &[]));
|
||||
assert!(!custom_roles_differ(&[], &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_redact_token_exactly_four_chars() {
|
||||
// 4 visible chars + 8 stars = 12 total
|
||||
|
||||
@@ -327,7 +327,7 @@ pub async fn update_status(
|
||||
update_status_with(get_orm_client_rw().await, org_id, id, status).await
|
||||
}
|
||||
|
||||
/// [`update_status`] on a given connection; a resolve ends the downtime mute.
|
||||
/// [`update_status`] on a given connection; resolve ends the mute, reopen clears resolve and ack.
|
||||
pub async fn update_status_with<C: ConnectionTrait>(
|
||||
conn: &C,
|
||||
org_id: &str,
|
||||
@@ -343,6 +343,10 @@ pub async fn update_status_with<C: ConnectionTrait>(
|
||||
.map_err(|e| Error::DbError(DbError::SeaORMError(e.to_string())))?
|
||||
.ok_or_else(|| Error::DbError(DbError::SeaORMError("Incident not found".to_string())))?;
|
||||
|
||||
// Like `acknowledge()`, an ack only takes an open incident, so a replica cannot undo a resolve.
|
||||
if status == "acknowledged" && incident.status != "open" {
|
||||
return Ok(incident);
|
||||
}
|
||||
let mut active: alert_incidents::ActiveModel = incident.into();
|
||||
active.status = Set(status.to_string());
|
||||
active.updated_at = Set(now);
|
||||
@@ -351,6 +355,10 @@ pub async fn update_status_with<C: ConnectionTrait>(
|
||||
active.resolved_at = Set(Some(now));
|
||||
// A manual reopen must run as a live incident, not stay muted forever.
|
||||
active.muted_by_downtime_id = Set(None);
|
||||
} else if status == "open" {
|
||||
active.resolved_at = Set(None);
|
||||
active.acknowledged_by = Set(None);
|
||||
active.acknowledged_at = Set(None);
|
||||
}
|
||||
|
||||
active
|
||||
@@ -1459,6 +1467,45 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_reopen_clears_the_resolve_and_the_acknowledgement() {
|
||||
let db = incidents_db().await;
|
||||
let mut incident = new_incident(
|
||||
"acme",
|
||||
"P2",
|
||||
serde_json::json!({"service": "payments"}),
|
||||
"service",
|
||||
1,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
incident.status = Set("acknowledged".to_string());
|
||||
incident.acknowledged_by = Set(Some("ana@example.com".to_string()));
|
||||
incident.acknowledged_at = Set(Some(2));
|
||||
let incident = incident.insert(&db).await.unwrap();
|
||||
|
||||
let resolved = update_status_with(&db, "acme", &incident.id, "resolved")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(resolved.resolved_at.is_some());
|
||||
assert_eq!(resolved.acknowledged_by.as_deref(), Some("ana@example.com"));
|
||||
|
||||
// A replicated ack never clears the resolve; only a reopen does.
|
||||
let acked = update_status_with(&db, "acme", &incident.id, "acknowledged")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(acked.status, "resolved");
|
||||
assert_eq!(acked.resolved_at, resolved.resolved_at);
|
||||
|
||||
let reopened = update_status_with(&db, "acme", &incident.id, "open")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(reopened.status, "open");
|
||||
assert_eq!(reopened.resolved_at, None);
|
||||
assert_eq!(reopened.acknowledged_by, None);
|
||||
assert_eq!(reopened.acknowledged_at, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn the_list_carries_the_muting_downtime_for_the_muted_chip() {
|
||||
let db = incidents_db().await;
|
||||
|
||||
@@ -207,6 +207,7 @@ export class AlertsPage {
|
||||
alertCloneButton: '[data-test="alert-list-{alertName}-clone-alert"]',
|
||||
alertMoreOptions: '[data-test="alert-list-{alertName}-more-options"]',
|
||||
confirmButton: '[data-test="confirm-dialog"] [data-test="o-dialog-primary-btn"]',
|
||||
confirmDialogProviderPrimary: '[data-test="confirm-dialog-provider"] [data-test="o-dialog-primary-btn"]',
|
||||
|
||||
// Alert movement locators
|
||||
selectAllCheckboxRowName: '# Name Owner Period Frequency',
|
||||
@@ -2388,6 +2389,7 @@ export class AlertsPage {
|
||||
testLogger.info(`Target lifecycle row index: ${targetIndex}`);
|
||||
|
||||
await allRows.nth(targetIndex).locator(this.locators.incidentAckButton).click();
|
||||
await this.page.locator(this.locators.confirmDialogProviderPrimary).click();
|
||||
await this.page.waitForTimeout(1000);
|
||||
testLogger.info('Clicked acknowledge button');
|
||||
}
|
||||
@@ -2409,6 +2411,7 @@ export class AlertsPage {
|
||||
const resolveButton = row.locator(this.locators.incidentResolveButton);
|
||||
await resolveButton.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await resolveButton.click();
|
||||
await this.page.locator(this.locators.confirmDialogProviderPrimary).click();
|
||||
await this.page.waitForTimeout(1000);
|
||||
testLogger.info('Clicked resolve button');
|
||||
}
|
||||
@@ -2422,6 +2425,7 @@ export class AlertsPage {
|
||||
const reopenButton = row.locator(this.locators.incidentReopenButton);
|
||||
await reopenButton.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await reopenButton.click();
|
||||
await this.page.locator(this.locators.confirmDialogProviderPrimary).click();
|
||||
await this.page.waitForTimeout(1000);
|
||||
testLogger.info('Clicked reopen button');
|
||||
}
|
||||
|
||||
@@ -487,6 +487,49 @@ describe("IncidentDetailDrawer.vue", () => {
|
||||
expect(incidentsService.updateStatus).toHaveBeenCalledWith("default", "1", "open");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["resolveIncident", "Resolve incident?"],
|
||||
["reopenIncident", "Reopen incident?"],
|
||||
])("%s asks first and changes nothing when cancelled", async (action, title) => {
|
||||
mockConfirm.mockResolvedValueOnce(false);
|
||||
|
||||
await wrapper.vm[action]();
|
||||
|
||||
expect(mockConfirm).toHaveBeenLastCalledWith(expect.objectContaining({ title }));
|
||||
expect(incidentsService.updateStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["resolved", "Resolve incident?"],
|
||||
["open", "Reopen incident?"],
|
||||
])(
|
||||
"the Manage chip for %s asks first and changes nothing when cancelled",
|
||||
async (status, title) => {
|
||||
mockConfirm.mockResolvedValueOnce(false);
|
||||
|
||||
await wrapper.vm.handleStatusChange(status);
|
||||
|
||||
expect(mockConfirm).toHaveBeenLastCalledWith(expect.objectContaining({ title }));
|
||||
expect(incidentsService.updateStatus).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("a reopen from the Manage chip drops the cleared resolve and acknowledgement", async () => {
|
||||
wrapper.vm.incidentDetails.status = "resolved";
|
||||
wrapper.vm.incidentDetails.resolved_at = 1700000000000000;
|
||||
wrapper.vm.incidentDetails.acknowledged_by = "ana@example.com";
|
||||
vi.mocked(incidentsService.updateStatus).mockResolvedValueOnce({
|
||||
data: { status: "open", resolved_at: null, acknowledged_by: null, acknowledged_at: null },
|
||||
} as never);
|
||||
|
||||
await wrapper.vm.handleStatusChange("open");
|
||||
|
||||
expect(incidentsService.updateStatus).toHaveBeenCalledWith("default", "1", "open");
|
||||
expect(wrapper.vm.incidentDetails.status).toBe("open");
|
||||
expect(wrapper.vm.incidentDetails.resolved_at).toBeUndefined();
|
||||
expect(wrapper.vm.incidentDetails.acknowledged_by).toBeNull();
|
||||
});
|
||||
|
||||
it("should set updating state during status update", async () => {
|
||||
// acknowledgeIncident awaits the confirm dialog before it ever touches
|
||||
// `updating`, so a fixed number of microtask hops can't be assumed here —
|
||||
|
||||
@@ -2692,6 +2692,7 @@ export default defineComponent({
|
||||
incidentDetails.value.status = response.data.status;
|
||||
incidentDetails.value.acknowledged_by = response.data.acknowledged_by;
|
||||
incidentDetails.value.acknowledged_at = response.data.acknowledged_at;
|
||||
incidentDetails.value.resolved_at = response.data.resolved_at ?? undefined;
|
||||
incidentDetails.value.updated_at = response.data.updated_at || Date.now() * 1000;
|
||||
editableStatus.value = response.data.status;
|
||||
toast({
|
||||
@@ -2734,8 +2735,29 @@ export default defineComponent({
|
||||
// The caller's promise must cover the update, not just the confirm dialog.
|
||||
return updateStatus("acknowledged");
|
||||
};
|
||||
const resolveIncident = () => updateStatus("resolved");
|
||||
const reopenIncident = () => updateStatus("open");
|
||||
// Resolving closes the incident, so it confirms like Acknowledge; Reopen too.
|
||||
const resolveIncident = async () => {
|
||||
const ok = await confirm({
|
||||
title: t("alerts.incidents.resolveConfirmTitle"),
|
||||
message: t("alerts.incidents.resolveConfirmMessage"),
|
||||
confirmLabel: t("alerts.incidents.resolve"),
|
||||
cancelLabel: t("common.cancel"),
|
||||
persistent: false,
|
||||
});
|
||||
if (!ok) return;
|
||||
return updateStatus("resolved");
|
||||
};
|
||||
const reopenIncident = async () => {
|
||||
const ok = await confirm({
|
||||
title: t("alerts.incidents.reopenConfirmTitle"),
|
||||
message: t("alerts.incidents.reopenConfirmMessage"),
|
||||
confirmLabel: t("alerts.incidents.reopen"),
|
||||
cancelLabel: t("common.cancel"),
|
||||
persistent: false,
|
||||
});
|
||||
if (!ok) return;
|
||||
return updateStatus("open");
|
||||
};
|
||||
|
||||
// Title editing. OInlineEdit owns the open/close and the Escape-restores
|
||||
// behaviour; this only has to persist a committed value. `isEditingTitle`
|
||||
@@ -2883,45 +2905,15 @@ export default defineComponent({
|
||||
return typeof conditionData === "string" ? conditionData : "--";
|
||||
};
|
||||
|
||||
// Handle status change from dropdown
|
||||
const handleStatusChange = async (newStatus: "open" | "acknowledged" | "resolved") => {
|
||||
if (!incidentDetails.value || updating.value) return;
|
||||
|
||||
updating.value = true;
|
||||
try {
|
||||
const response = await updateIncidentStatus.mutateAsync({
|
||||
id: incidentDetails.value.id,
|
||||
status: newStatus,
|
||||
});
|
||||
|
||||
// Update local state with the actual status from the API response
|
||||
incidentDetails.value.status = response.data.status;
|
||||
editableStatus.value = response.data.status;
|
||||
|
||||
toast({
|
||||
variant: "success",
|
||||
message: t("toastMessages.alerts.incidentStatusUpdatedTo", {
|
||||
status: response.data.status,
|
||||
}),
|
||||
});
|
||||
// Mark data as stale so incident list will refresh
|
||||
store.dispatch("incidents/setShouldRefresh", true);
|
||||
|
||||
// Refresh events in timeline if Activity tab is active
|
||||
if (activeTab.value === "activity") {
|
||||
timelineRefreshTrigger.value++;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Failed to update status:", error);
|
||||
toast({
|
||||
variant: "error",
|
||||
message: error?.response?.data?.message || t("alerts.incidents.statusUpdateFailed"),
|
||||
});
|
||||
// Revert on error
|
||||
editableStatus.value = incidentDetails.value.status;
|
||||
} finally {
|
||||
updating.value = false;
|
||||
}
|
||||
// The Manage panel chips take the same confirmed path as the header buttons.
|
||||
const handleStatusChange = (newStatus: "open" | "acknowledged" | "resolved") => {
|
||||
if (updating.value) return;
|
||||
const actions = {
|
||||
open: reopenIncident,
|
||||
acknowledged: acknowledgeIncident,
|
||||
resolved: resolveIncident,
|
||||
};
|
||||
return actions[newStatus]();
|
||||
};
|
||||
|
||||
// Handle trigger row click - find the alert index by name
|
||||
|
||||
@@ -460,6 +460,22 @@ describe("IncidentList.vue", () => {
|
||||
expect(incidentsService.updateStatus).toHaveBeenCalledWith("default", incident.id, "open");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["resolveIncident", "Resolve incident?"],
|
||||
["reopenIncident", "Reopen incident?"],
|
||||
] as const)("%s asks first and changes nothing when cancelled", async (action, title) => {
|
||||
// The instance exposes setup() bindings that the mounted type does not declare.
|
||||
const vm = wrapper.vm as unknown as Record<
|
||||
typeof action,
|
||||
(incident: Incident) => Promise<void>
|
||||
> & { allIncidents: Incident[] };
|
||||
mockConfirm.mockResolvedValueOnce(false);
|
||||
await vm[action](vm.allIncidents[0]);
|
||||
await flushPromises();
|
||||
expect(mockConfirm).toHaveBeenLastCalledWith(expect.objectContaining({ title }));
|
||||
expect(incidentsService.updateStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reloads incidents after successful status update", async () => {
|
||||
const incident = (wrapper.vm as any).allIncidents[0];
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -914,11 +914,27 @@ export default defineComponent({
|
||||
updateStatus(incident, "acknowledged");
|
||||
};
|
||||
|
||||
const resolveIncident = (incident: Incident) => {
|
||||
const resolveIncident = async (incident: Incident) => {
|
||||
const ok = await confirm({
|
||||
title: t("alerts.incidents.resolveConfirmTitle"),
|
||||
message: t("alerts.incidents.resolveConfirmMessage"),
|
||||
confirmLabel: t("alerts.incidents.resolve"),
|
||||
cancelLabel: t("common.cancel"),
|
||||
persistent: false,
|
||||
});
|
||||
if (!ok) return;
|
||||
updateStatus(incident, "resolved");
|
||||
};
|
||||
|
||||
const reopenIncident = (incident: Incident) => {
|
||||
const reopenIncident = async (incident: Incident) => {
|
||||
const ok = await confirm({
|
||||
title: t("alerts.incidents.reopenConfirmTitle"),
|
||||
message: t("alerts.incidents.reopenConfirmMessage"),
|
||||
confirmLabel: t("alerts.incidents.reopen"),
|
||||
cancelLabel: t("common.cancel"),
|
||||
persistent: false,
|
||||
});
|
||||
if (!ok) return;
|
||||
updateStatus(incident, "open");
|
||||
};
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
:columns="columns"
|
||||
row-key="email"
|
||||
:loading="props.loading"
|
||||
:forbidden="forbidden"
|
||||
:global-filter="userSearchKey"
|
||||
pagination="client"
|
||||
:page-size="100"
|
||||
@@ -158,6 +159,9 @@ const userSearchKey = ref("");
|
||||
|
||||
const hasFetchedOrgServiceAccounts = ref(false);
|
||||
|
||||
// A viewer cannot list service accounts; show the no-access state instead of failing.
|
||||
const forbidden = ref(false);
|
||||
|
||||
const groupUsersMap = ref(new Set());
|
||||
|
||||
const { serviceAccountsState } = usePermissions();
|
||||
@@ -220,10 +224,18 @@ const updateUserTable = async (value: string) => {
|
||||
const fetchOrgServiceAccounts = async () => {
|
||||
// fetch group users
|
||||
hasFetchedOrgServiceAccounts.value = true;
|
||||
const data: any = await serviceAccountsState.getServiceAccounts(
|
||||
store.state.selectedOrganization.identifier,
|
||||
);
|
||||
|
||||
forbidden.value = false;
|
||||
let data: Array<{ email: string; is_system?: boolean }>;
|
||||
try {
|
||||
data = (await serviceAccountsState.getServiceAccounts(
|
||||
store.state.selectedOrganization.identifier,
|
||||
)) as typeof data;
|
||||
} catch (e: unknown) {
|
||||
const status = (e as { response?: { status?: number } } | undefined)?.response?.status;
|
||||
if (status !== 403) throw e;
|
||||
forbidden.value = true;
|
||||
data = [];
|
||||
}
|
||||
serviceAccountsState.service_accounts_users = cloneDeep(
|
||||
data
|
||||
.filter((user: any) => user.is_system !== true) // Filter out system accounts
|
||||
|
||||
@@ -112,6 +112,7 @@ const GROUP_OF: Record<string, ModuleGroupId> = {
|
||||
template: "alerting",
|
||||
destination: "alerting",
|
||||
incidents: "alerting",
|
||||
incident_integrations: "alerting",
|
||||
oncall: "alerting",
|
||||
oncall_response: "alerting",
|
||||
pipeline: "pipelines",
|
||||
@@ -181,6 +182,7 @@ const ICON_OF: Record<string, IconName> = {
|
||||
template: "article",
|
||||
destination: "webhook",
|
||||
incidents: "shield-alert-outline",
|
||||
incident_integrations: "webhook",
|
||||
oncall: "person-pin-circle",
|
||||
oncall_response: "forum",
|
||||
pipeline: "lan",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "يؤدي هذا إلى وضع الحادث كقيد العمل. يمكنك إعادة فتحه لاحقًا إذا لزم الأمر.",
|
||||
"acknowledgeConfirmLabel": "إقرار",
|
||||
"acknowledgeConfirmCancelLabel": "إلغاء",
|
||||
"resolveConfirmTitle": "حل الحادث؟",
|
||||
"resolveConfirmMessage": "يؤدي هذا إلى إغلاق الحادث وإزالته من القائمة النشطة. يمكنك إعادة فتحه لاحقًا إذا لزم الأمر.",
|
||||
"reopenConfirmTitle": "إعادة فتح الحادث؟",
|
||||
"reopenConfirmMessage": "يؤدي هذا إلى إعادة الحادث إلى حالة مفتوح ومسح حله والإقرار به.",
|
||||
"acknowledgeAriaLabel": "الإقرار بالحادث",
|
||||
"acknowledgedBy": "تم الإقرار بواسطة",
|
||||
"resolve": "العزم",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "تم استيراد {count} قالب بنجاح | تم استيراد {count} من القوالب بنجاح",
|
||||
"aiAnalysisIsAlreadyRunningFor": "تحليل الذكاء الاصطناعي جار بالفعل لهذا الحادث",
|
||||
"incidentSeverityUpdatedTo": "تم تحديث خطورة الحادث إلى {severity}",
|
||||
"incidentStatusUpdatedTo": "تم تحديث حالة الحادث إلى {status}",
|
||||
"failedToLoadIncidentDetails": "فشل في تحميل تفاصيل الحادث",
|
||||
"incidentsRefreshed": "الحوادث التي تم تحديثها",
|
||||
"failedToPostComment": "فشل في نشر التعليق",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Damit wird der Vorfall als in Bearbeitung markiert. Sie können ihn später bei Bedarf wieder öffnen.",
|
||||
"acknowledgeConfirmLabel": "Bestätigen",
|
||||
"acknowledgeConfirmCancelLabel": "Abbrechen",
|
||||
"resolveConfirmTitle": "Vorfall auflösen?",
|
||||
"resolveConfirmMessage": "Dadurch wird der Vorfall geschlossen und aus der aktiven Liste entfernt. Sie können ihn bei Bedarf später wieder öffnen.",
|
||||
"reopenConfirmTitle": "Vorfall wieder öffnen?",
|
||||
"reopenConfirmMessage": "Dadurch wird der Vorfall wieder auf offen gesetzt und seine Auflösung und Bestätigung werden entfernt.",
|
||||
"acknowledgeAriaLabel": "Vorfall bestätigen",
|
||||
"acknowledgedBy": "Bestätigt von",
|
||||
"resolve": "Auflösen",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "Erfolgreich {count} Vorlage importiert | Erfolgreich {count} Vorlagen importiert",
|
||||
"aiAnalysisIsAlreadyRunningFor": "Für diesen Vorfall läuft bereits eine KI-Analyse",
|
||||
"incidentSeverityUpdatedTo": "Schweregrad des Vorfalls auf {severity} aktualisiert",
|
||||
"incidentStatusUpdatedTo": "Status des Vorfalls auf {status} aktualisiert",
|
||||
"failedToLoadIncidentDetails": "Fehler beim Laden der Vorfall-Details",
|
||||
"incidentsRefreshed": "Vorfälle aktualisiert",
|
||||
"failedToPostComment": "Fehler beim Posten des Kommentars",
|
||||
|
||||
@@ -10268,6 +10268,10 @@
|
||||
"acknowledgeConfirmMessage": "This marks the incident as being worked on. You can reopen it later if needed.",
|
||||
"acknowledgeConfirmLabel": "Acknowledge",
|
||||
"acknowledgeConfirmCancelLabel": "Cancel",
|
||||
"resolveConfirmTitle": "Resolve incident?",
|
||||
"resolveConfirmMessage": "This closes the incident and removes it from the active list. You can reopen it later if needed.",
|
||||
"reopenConfirmTitle": "Reopen incident?",
|
||||
"reopenConfirmMessage": "This sets the incident back to open and clears its resolution and acknowledgement.",
|
||||
"acknowledgeAriaLabel": "Acknowledge incident",
|
||||
"acknowledgedBy": "Acknowledged by",
|
||||
"resolve": "Resolve",
|
||||
@@ -20995,7 +20999,6 @@
|
||||
"successfullyImportedTemplates": "Successfully imported {count} template | Successfully imported {count} templates",
|
||||
"aiAnalysisIsAlreadyRunningFor": "AI analysis is already running for this incident",
|
||||
"incidentSeverityUpdatedTo": "Incident severity updated to {severity}",
|
||||
"incidentStatusUpdatedTo": "Incident status updated to {status}",
|
||||
"failedToLoadIncidentDetails": "Failed to load incident details",
|
||||
"incidentsRefreshed": "Incidents refreshed",
|
||||
"failedToPostComment": "Failed to post comment",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Esto marca el incidente como en curso. Puedes reabrirlo más tarde si es necesario.",
|
||||
"acknowledgeConfirmLabel": "Reconocer",
|
||||
"acknowledgeConfirmCancelLabel": "Cancelar",
|
||||
"resolveConfirmTitle": "¿Resolver incidente?",
|
||||
"resolveConfirmMessage": "Esto cierra el incidente y lo quita de la lista activa. Puedes reabrirlo más tarde si es necesario.",
|
||||
"reopenConfirmTitle": "¿Reabrir incidente?",
|
||||
"reopenConfirmMessage": "Esto devuelve el incidente al estado abierto y borra su resolución y reconocimiento.",
|
||||
"acknowledgeAriaLabel": "Reconocer incidente",
|
||||
"acknowledgedBy": "Reconocido por",
|
||||
"resolve": "Resolver",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "Se importó correctamente {count} plantilla | Se importaron correctamente {count} plantillas",
|
||||
"aiAnalysisIsAlreadyRunningFor": "El análisis de IA ya se está ejecutando para este incidente",
|
||||
"incidentSeverityUpdatedTo": "Gravedad del incidente actualizada a {severity}",
|
||||
"incidentStatusUpdatedTo": "Estado del incidente actualizado a {status}",
|
||||
"failedToLoadIncidentDetails": "No se pudieron cargar los detalles del incidente",
|
||||
"incidentsRefreshed": "Incidentes actualizados",
|
||||
"failedToPostComment": "No se pudo publicar el comentario",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Cela marque l'incident comme étant en cours de traitement. Vous pourrez le rouvrir plus tard si nécessaire.",
|
||||
"acknowledgeConfirmLabel": "Acquitter",
|
||||
"acknowledgeConfirmCancelLabel": "Annuler",
|
||||
"resolveConfirmTitle": "Résoudre l'incident ?",
|
||||
"resolveConfirmMessage": "Cela ferme l'incident et le retire de la liste active. Vous pourrez le rouvrir plus tard si nécessaire.",
|
||||
"reopenConfirmTitle": "Rouvrir l'incident ?",
|
||||
"reopenConfirmMessage": "Cela remet l'incident à l'état ouvert et efface sa résolution et son acquittement.",
|
||||
"acknowledgeAriaLabel": "Acquitter l'incident",
|
||||
"acknowledgedBy": "Acquitté par",
|
||||
"resolve": "Résoudre",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "{count} modèle importé avec succès | {count} modèles importés avec succès",
|
||||
"aiAnalysisIsAlreadyRunningFor": "Une analyse IA est déjà en cours pour cet incident",
|
||||
"incidentSeverityUpdatedTo": "La gravité de l'incident a été mise à jour vers {severity}",
|
||||
"incidentStatusUpdatedTo": "Le statut de l'incident a été mis à jour vers {status}",
|
||||
"failedToLoadIncidentDetails": "Échec du chargement des détails de l'incident",
|
||||
"incidentsRefreshed": "Incidents actualisés",
|
||||
"failedToPostComment": "Échec de l'envoi du commentaire",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Questa azione contrassegna l'incidente come preso in carico. Puoi riaprirlo in seguito, se necessario.",
|
||||
"acknowledgeConfirmLabel": "Prendi in carico",
|
||||
"acknowledgeConfirmCancelLabel": "Annulla",
|
||||
"resolveConfirmTitle": "Risolvere l'incidente?",
|
||||
"resolveConfirmMessage": "Questo chiude l'incidente e lo rimuove dall'elenco attivo. Puoi riaprirlo più tardi se necessario.",
|
||||
"reopenConfirmTitle": "Riaprire l'incidente?",
|
||||
"reopenConfirmMessage": "Questo riporta l'incidente allo stato aperto e cancella la risoluzione e la presa in carico.",
|
||||
"acknowledgeAriaLabel": "Prendi in carico l'incidente",
|
||||
"acknowledgedBy": "Preso in carico da",
|
||||
"resolve": "Risolvi",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "{count} template importato con successo | {count} template importati con successo",
|
||||
"aiAnalysisIsAlreadyRunningFor": "L'analisi AI è già in esecuzione per questo incidente",
|
||||
"incidentSeverityUpdatedTo": "Gravità dell'incidente aggiornata a {severity}",
|
||||
"incidentStatusUpdatedTo": "Stato dell'incidente aggiornato a {status}",
|
||||
"failedToLoadIncidentDetails": "Impossibile caricare i dettagli dell'incidente",
|
||||
"incidentsRefreshed": "Incidenti aggiornati",
|
||||
"failedToPostComment": "Impossibile pubblicare il commento",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "これによりインシデントは対応中としてマークされます。必要に応じて後で再度オープンできます。",
|
||||
"acknowledgeConfirmLabel": "確認済みにする",
|
||||
"acknowledgeConfirmCancelLabel": "キャンセル",
|
||||
"resolveConfirmTitle": "インシデントを解決しますか?",
|
||||
"resolveConfirmMessage": "インシデントをクローズし、アクティブな一覧から削除します。必要に応じて後で再オープンできます。",
|
||||
"reopenConfirmTitle": "インシデントを再オープンしますか?",
|
||||
"reopenConfirmMessage": "インシデントをオープン状態に戻し、解決と確認済みの情報をクリアします。",
|
||||
"acknowledgeAriaLabel": "インシデントを確認済みにする",
|
||||
"acknowledgedBy": "確認者",
|
||||
"resolve": "解決",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "テンプレートを{count}件正常にインポートしました | テンプレートを{count}件正常にインポートしました",
|
||||
"aiAnalysisIsAlreadyRunningFor": "このインシデントに対してAI分析が既に実行されています",
|
||||
"incidentSeverityUpdatedTo": "インシデントの重要度が{severity}に更新されました",
|
||||
"incidentStatusUpdatedTo": "インシデントのステータスが{status}に更新されました",
|
||||
"failedToLoadIncidentDetails": "インシデント詳細の読み込みに失敗しました",
|
||||
"incidentsRefreshed": "インシデントを更新しました",
|
||||
"failedToPostComment": "コメントの投稿に失敗しました",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "이 작업은 인시던트가 작업 중임을 표시합니다. 필요한 경우 나중에 다시 열 수 있습니다.",
|
||||
"acknowledgeConfirmLabel": "확인",
|
||||
"acknowledgeConfirmCancelLabel": "취소",
|
||||
"resolveConfirmTitle": "인시던트를 해결하시겠습니까?",
|
||||
"resolveConfirmMessage": "인시던트를 종료하고 활성 목록에서 제거합니다. 필요하면 나중에 다시 열 수 있습니다.",
|
||||
"reopenConfirmTitle": "인시던트를 다시 여시겠습니까?",
|
||||
"reopenConfirmMessage": "인시던트를 열린 상태로 되돌리고 해결 및 확인 정보를 지웁니다.",
|
||||
"acknowledgeAriaLabel": "인시던트 확인",
|
||||
"acknowledgedBy": "확인자",
|
||||
"resolve": "해결",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "{count}개 템플릿을 성공적으로 가져왔습니다. | {count}개 템플릿을 성공적으로 가져왔습니다.",
|
||||
"aiAnalysisIsAlreadyRunningFor": "이 인시던트에 대해 AI 분석이 이미 실행 중입니다.",
|
||||
"incidentSeverityUpdatedTo": "인시던트 심각도가 {severity}(으)로 업데이트되었습니다.",
|
||||
"incidentStatusUpdatedTo": "인시던트 상태가 {status}(으)로 업데이트되었습니다.",
|
||||
"failedToLoadIncidentDetails": "인시던트 세부 정보를 불러오지 못했습니다.",
|
||||
"incidentsRefreshed": "인시던트가 새로 고쳐졌습니다.",
|
||||
"failedToPostComment": "댓글을 게시하지 못했습니다.",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Hiermee wordt het incident gemarkeerd als in behandeling. Je kunt het later opnieuw openen als dat nodig is.",
|
||||
"acknowledgeConfirmLabel": "Bevestigen",
|
||||
"acknowledgeConfirmCancelLabel": "Annuleren",
|
||||
"resolveConfirmTitle": "Incident oplossen?",
|
||||
"resolveConfirmMessage": "Hiermee wordt het incident gesloten en uit de actieve lijst verwijderd. U kunt het later zo nodig heropenen.",
|
||||
"reopenConfirmTitle": "Incident heropenen?",
|
||||
"reopenConfirmMessage": "Hiermee wordt het incident weer op open gezet en worden de oplossing en bevestiging gewist.",
|
||||
"acknowledgeAriaLabel": "Incident bevestigen",
|
||||
"acknowledgedBy": "Bevestigd door",
|
||||
"resolve": "Oplossen",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "{count} sjabloon succesvol geïmporteerd | {count} sjablonen succesvol geïmporteerd",
|
||||
"aiAnalysisIsAlreadyRunningFor": "AI-analyse draait al voor dit incident",
|
||||
"incidentSeverityUpdatedTo": "Ernst van incident bijgewerkt naar {severity}",
|
||||
"incidentStatusUpdatedTo": "Incidentstatus bijgewerkt naar {status}",
|
||||
"failedToLoadIncidentDetails": "Kan incidentdetails niet laden",
|
||||
"incidentsRefreshed": "Incidenten vernieuwd",
|
||||
"failedToPostComment": "Kan reactie niet plaatsen",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "To oznacza incydent jako będący w trakcie obsługi. W razie potrzeby możesz go później ponownie otworzyć.",
|
||||
"acknowledgeConfirmLabel": "Potwierdź",
|
||||
"acknowledgeConfirmCancelLabel": "Anuluj",
|
||||
"resolveConfirmTitle": "Rozwiązać incydent?",
|
||||
"resolveConfirmMessage": "Spowoduje to zamknięcie incydentu i usunięcie go z listy aktywnych. W razie potrzeby możesz otworzyć go ponownie później.",
|
||||
"reopenConfirmTitle": "Ponownie otworzyć incydent?",
|
||||
"reopenConfirmMessage": "Spowoduje to przywrócenie incydentu do stanu otwartego i wyczyszczenie jego rozwiązania i potwierdzenia.",
|
||||
"acknowledgeAriaLabel": "Potwierdź incydent",
|
||||
"acknowledgedBy": "Potwierdzone przez",
|
||||
"resolve": "Rozwiąż",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "Pomyślnie zaimportowano {count} szablon | Pomyślnie zaimportowano szablony: {count}",
|
||||
"aiAnalysisIsAlreadyRunningFor": "Analiza AI już trwa dla tego incydentu",
|
||||
"incidentSeverityUpdatedTo": "Poziom ważności incydentu zaktualizowano na {severity}",
|
||||
"incidentStatusUpdatedTo": "Status incydentu zaktualizowano na {status}",
|
||||
"failedToLoadIncidentDetails": "Nie udało się wczytać szczegółów incydentu",
|
||||
"incidentsRefreshed": "Odświeżono incydenty",
|
||||
"failedToPostComment": "Nie udało się dodać komentarza",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Isto marca o incidente como em tratamento. Pode reabri-lo depois, se necessário.",
|
||||
"acknowledgeConfirmLabel": "Reconhecer",
|
||||
"acknowledgeConfirmCancelLabel": "Cancelar",
|
||||
"resolveConfirmTitle": "Resolver incidente?",
|
||||
"resolveConfirmMessage": "Isto fecha o incidente e remove-o da lista ativa. Pode reabri-lo mais tarde, se necessário.",
|
||||
"reopenConfirmTitle": "Reabrir incidente?",
|
||||
"reopenConfirmMessage": "Isto volta a colocar o incidente como aberto e limpa a resolução e o reconhecimento.",
|
||||
"acknowledgeAriaLabel": "Reconhecer incidente",
|
||||
"acknowledgedBy": "Reconhecido por",
|
||||
"resolve": "Resolver",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "Importado com sucesso {count} modelo | Importados com sucesso {count} modelos",
|
||||
"aiAnalysisIsAlreadyRunningFor": "A análise de IA já está em curso para este incidente",
|
||||
"incidentSeverityUpdatedTo": "Severidade do incidente atualizada para {severity}",
|
||||
"incidentStatusUpdatedTo": "Estado do incidente atualizado para {status}",
|
||||
"failedToLoadIncidentDetails": "Falha ao carregar detalhes do incidente",
|
||||
"incidentsRefreshed": "Incidentes atualizados",
|
||||
"failedToPostComment": "Falha ao publicar comentário",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Инцидент будет помечен как взятый в работу. При необходимости его можно будет переоткрыть позже.",
|
||||
"acknowledgeConfirmLabel": "Подтвердить",
|
||||
"acknowledgeConfirmCancelLabel": "Отмена",
|
||||
"resolveConfirmTitle": "Разрешить инцидент?",
|
||||
"resolveConfirmMessage": "Инцидент будет закрыт и удалён из списка активных. При необходимости его можно открыть повторно позже.",
|
||||
"reopenConfirmTitle": "Открыть инцидент повторно?",
|
||||
"reopenConfirmMessage": "Инцидент вернётся в состояние «открыт», а его разрешение и подтверждение будут сброшены.",
|
||||
"acknowledgeAriaLabel": "Подтвердить инцидент",
|
||||
"acknowledgedBy": "Кем подтверждено",
|
||||
"resolve": "Разрешить",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "Успешно импортирован {count} шаблон | Успешно импортировано шаблонов: {count}",
|
||||
"aiAnalysisIsAlreadyRunningFor": "Анализ ИИ уже выполняется для этого инцидента",
|
||||
"incidentSeverityUpdatedTo": "Критичность инцидента обновлена до {severity}",
|
||||
"incidentStatusUpdatedTo": "Статус инцидента обновлен до {status}",
|
||||
"failedToLoadIncidentDetails": "Не удалось загрузить сведения об инциденте",
|
||||
"incidentsRefreshed": "Инциденты обновлены",
|
||||
"failedToPostComment": "Не удалось опубликовать комментарий",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Bu, vakanın üzerinde çalışıldığını gösterir. Gerekirse daha sonra yeniden açabilirsiniz.",
|
||||
"acknowledgeConfirmLabel": "Onayla",
|
||||
"acknowledgeConfirmCancelLabel": "İptal",
|
||||
"resolveConfirmTitle": "Vaka çözülsün mü?",
|
||||
"resolveConfirmMessage": "Bu işlem vakayı kapatır ve etkin listeden kaldırır. Gerekirse daha sonra yeniden açabilirsiniz.",
|
||||
"reopenConfirmTitle": "Vaka yeniden açılsın mı?",
|
||||
"reopenConfirmMessage": "Bu işlem vakayı yeniden açık durumuna getirir ve çözüm ile onay bilgilerini temizler.",
|
||||
"acknowledgeAriaLabel": "Vakayı onayla",
|
||||
"acknowledgedBy": "Onaylayan",
|
||||
"resolve": "Çöz",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "{count} şablon başarıyla içe aktarıldı | {count} şablon başarıyla içe aktarıldı",
|
||||
"aiAnalysisIsAlreadyRunningFor": "Bu vaka için AI analizi zaten çalışıyor",
|
||||
"incidentSeverityUpdatedTo": "Vaka önem derecesi {severity} olarak güncellendi",
|
||||
"incidentStatusUpdatedTo": "Vaka durumu {status} olarak güncellendi",
|
||||
"failedToLoadIncidentDetails": "Vaka ayrıntıları yüklenemedi",
|
||||
"incidentsRefreshed": "Vakalar yenilendi",
|
||||
"failedToPostComment": "Yorum gönderilemedi",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "Thao tác này đánh dấu sự cố là đang được xử lý. Bạn có thể mở lại sau nếu cần.",
|
||||
"acknowledgeConfirmLabel": "Xác nhận",
|
||||
"acknowledgeConfirmCancelLabel": "Hủy",
|
||||
"resolveConfirmTitle": "Giải quyết sự cố?",
|
||||
"resolveConfirmMessage": "Thao tác này đóng sự cố và xóa nó khỏi danh sách đang hoạt động. Bạn có thể mở lại sau nếu cần.",
|
||||
"reopenConfirmTitle": "Mở lại sự cố?",
|
||||
"reopenConfirmMessage": "Thao tác này đưa sự cố về trạng thái mở và xóa thông tin giải quyết và xác nhận.",
|
||||
"acknowledgeAriaLabel": "Xác nhận sự cố",
|
||||
"acknowledgedBy": "Được xác nhận bởi",
|
||||
"resolve": "Giải quyết",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "Đã nhập thành công {count} mẫu | Đã nhập thành công {count} mẫu",
|
||||
"aiAnalysisIsAlreadyRunningFor": "Đã có phân tích AI đang chạy cho sự cố này",
|
||||
"incidentSeverityUpdatedTo": "Mức độ nghiêm trọng của sự cố đã được cập nhật thành {severity}",
|
||||
"incidentStatusUpdatedTo": "Trạng thái sự cố đã được cập nhật thành {status}",
|
||||
"failedToLoadIncidentDetails": "Không thể tải chi tiết sự cố",
|
||||
"incidentsRefreshed": "Đã làm mới sự cố",
|
||||
"failedToPostComment": "Không thể đăng bình luận",
|
||||
|
||||
@@ -10270,6 +10270,10 @@
|
||||
"acknowledgeConfirmMessage": "此操作会将事件标记为正在处理。如有需要,之后可重新打开。",
|
||||
"acknowledgeConfirmLabel": "确认",
|
||||
"acknowledgeConfirmCancelLabel": "取消",
|
||||
"resolveConfirmTitle": "解决事件?",
|
||||
"resolveConfirmMessage": "这将关闭事件并将其从活动列表中移除。如有需要,您可以稍后重新打开。",
|
||||
"reopenConfirmTitle": "重新打开事件?",
|
||||
"reopenConfirmMessage": "这会将事件恢复为打开状态,并清除其解决和确认信息。",
|
||||
"acknowledgeAriaLabel": "确认事件",
|
||||
"acknowledgedBy": "确认人",
|
||||
"resolve": "解决",
|
||||
@@ -20997,7 +21001,6 @@
|
||||
"successfullyImportedTemplates": "成功导入 {count} 个模板 | 成功导入 {count} 个模板",
|
||||
"aiAnalysisIsAlreadyRunningFor": "此事件已在进行 AI 分析",
|
||||
"incidentSeverityUpdatedTo": "事件严重级别已更新为 {severity}",
|
||||
"incidentStatusUpdatedTo": "事件状态已更新为 {status}",
|
||||
"failedToLoadIncidentDetails": "加载事件详情失败",
|
||||
"incidentsRefreshed": "事件已刷新",
|
||||
"failedToPostComment": "发布评论失败",
|
||||
|
||||
@@ -10265,6 +10265,10 @@
|
||||
"acknowledgeConfirmMessage": "此操作會將事件標記為處理中。之後如有需要,可以重新開啟。",
|
||||
"acknowledgeConfirmLabel": "確認",
|
||||
"acknowledgeConfirmCancelLabel": "取消",
|
||||
"resolveConfirmTitle": "解決事件?",
|
||||
"resolveConfirmMessage": "這將關閉事件並將其從作用中清單移除。如有需要,您可以稍後重新開啟。",
|
||||
"reopenConfirmTitle": "重新開啟事件?",
|
||||
"reopenConfirmMessage": "這會將事件恢復為開啟狀態,並清除其解決與確認資訊。",
|
||||
"acknowledgeAriaLabel": "確認事件",
|
||||
"acknowledgedBy": "確認者",
|
||||
"resolve": "解決",
|
||||
@@ -20992,7 +20996,6 @@
|
||||
"successfullyImportedTemplates": "已成功匯入 {count} 個範本 | 已成功匯入 {count} 個範本",
|
||||
"aiAnalysisIsAlreadyRunningFor": "此事件的 AI 分析已在執行中",
|
||||
"incidentSeverityUpdatedTo": "事件嚴重性已更新為 {severity}",
|
||||
"incidentStatusUpdatedTo": "事件狀態已更新為 {status}",
|
||||
"failedToLoadIncidentDetails": "無法載入事件詳細資訊",
|
||||
"incidentsRefreshed": "事件已重新整理",
|
||||
"failedToPostComment": "張貼留言失敗",
|
||||
|
||||
Reference in New Issue
Block a user