@@ -74,7 +89,9 @@
import { computed, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { apiFetch, type EventDetail } from "../api";
+import RdgQwinstaModal from "../components/RdgQwinstaModal.vue";
import ReportBodyCard from "../components/ReportBodyCard.vue";
+import { useRdgQwinsta } from "../composables/useRdgQwinsta";
import { eventsBackQueryFromDetail } from "../utils/eventsListQuery";
import { dailyReportTypeLabel, isDailyReportType } from "../utils/reportDisplay";
@@ -84,6 +101,7 @@ const route = useRoute();
const event = ref
(null);
const loading = ref(false);
const error = ref("");
+const { qwinstaLoadingId, qwinstaModal, closeQwinstaModal, runQwinsta, runLogoff } = useRdgQwinsta();
const fromReports = computed(() => route.query.from === "reports");
const isReport = computed(() => (event.value ? isDailyReportType(event.value.type) : false));
diff --git a/frontend/src/views/EventsView.vue b/frontend/src/views/EventsView.vue
index 11e4052..f54c86f 100644
--- a/frontend/src/views/EventsView.vue
+++ b/frontend/src/views/EventsView.vue
@@ -38,6 +38,7 @@
Severity |
Type |
Title |
+ Действия |
@@ -53,9 +54,25 @@
{{ e.severity }} |
{{ e.type }} |
{{ e.title }} |
+
+
+ |
+
Стр. {{ page }}
@@ -75,6 +92,8 @@
import { computed, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { apiFetch, type EventListResponse } from "../api";
+import RdgQwinstaModal from "../components/RdgQwinstaModal.vue";
+import { useRdgQwinsta } from "../composables/useRdgQwinsta";
import {
apiParamsFromState,
buildEventsListQuery,
@@ -86,6 +105,7 @@ import { formatServerName } from "../utils/hostDisplay";
const route = useRoute();
const router = useRouter();
+const { qwinstaLoadingId, qwinstaModal, closeQwinstaModal, runQwinsta, runLogoff } = useRdgQwinsta();
const data = ref(null);
const loading = ref(false);
@@ -217,4 +237,13 @@ watch(
.events-date-field input[type="date"] {
min-width: 9.5rem;
}
+
+.events-actions {
+ white-space: nowrap;
+}
+
+.events-qwinsta-btn {
+ font-size: 0.85rem;
+ padding: 0.2rem 0.55rem;
+}
diff --git a/tools/Invoke-SacRemoteSudo.ps1 b/tools/Invoke-SacRemoteSudo.ps1
new file mode 100644
index 0000000..1cc1fa1
--- /dev/null
+++ b/tools/Invoke-SacRemoteSudo.ps1
@@ -0,0 +1,38 @@
+#Requires -Version 5.1
+<#
+.SYNOPSIS
+ Удалённый sudo на SAC-сервере без интерактивного ввода.
+ Пароль: Password из ssh-setup/local/kalinamall-sac.conf
+#>
+[CmdletBinding()]
+param(
+ [string]$HostAlias = 'sac.kalinamall.ru',
+ [Parameter(Mandatory = $true)]
+ [string]$RemoteCommand,
+ [string]$PasswordFile = (Join-Path $HOME 'Projects\Answer.and.other.shit\scripts\ssh-setup\local\kalinamall-sac.conf')
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+function Read-ConfigFile {
+ param([string]$Path)
+ $data = @{}
+ if (-not (Test-Path -LiteralPath $Path)) { throw "Config not found: $Path" }
+ Get-Content -LiteralPath $Path | ForEach-Object {
+ if ($_ -match '^([^#=]+)=(.*)$') {
+ $data[$Matches[1].Trim()] = $Matches[2].Trim()
+ }
+ }
+ return $data
+}
+
+$cfg = Read-ConfigFile -Path $PasswordFile
+$password = $cfg['Password']
+if (-not $password) {
+ throw "Password not found in $PasswordFile"
+}
+
+$inner = $RemoteCommand.Replace("'", "'\\''")
+$remote = "sudo -S -p '' bash -lc '$inner'"
+$password | & ssh -o BatchMode=yes -o ConnectTimeout=15 $HostAlias $remote