﻿<template>
  <p><RouterLink to="/hosts">← Хосты</RouterLink></p>
  <p v-if="error" class="error">{{ error }}</p>
  <p v-else-if="loading">Загрузка…</p>
  <template v-else-if="host">
    <h1>{{ host.display_name || host.hostname }}</h1>
    <div class="card report-meta">
      <dl class="detail-fields">
        <div class="detail-field">
          <dt>Hostname</dt>
          <dd>{{ host.hostname }}</dd>
        </div>
        <div class="detail-field">
          <dt>ID</dt>
          <dd>{{ host.id }}</dd>
        </div>
        <div class="detail-field">
          <dt>Агент</dt>
          <dd><span :class="'agent-' + host.agent_status">{{ agentLabel(host.agent_status) }}</span></dd>
        </div>
        <div class="detail-field">
          <dt>Версия агента</dt>
          <dd>
            <span :class="versionStatusClass">{{ host.product_version || "—" }}</span>
            <span v-if="host.version_status === 'outdated'" class="muted version-hint"> (устарела)</span>
            <span v-if="host.pending_agent_update" class="pending-update-badge">обновление запрошено</span>
          </dd>
        </div>
        <div v-if="host.agent_update_state" class="detail-field">
          <dt>Обновление</dt>
          <dd>
            {{ host.agent_update_state }}
            <span v-if="host.pending_update_target_version"> → {{ host.pending_update_target_version }}</span>
            <span v-if="host.agent_update_last_at" class="muted"> ({{ formatDt(host.agent_update_last_at) }})</span>
          </dd>
        </div>
        <div v-if="host.agent_update_last_error" class="detail-field">
          <dt>Ошибка обновления</dt>
          <dd class="error-inline">{{ host.agent_update_last_error }}</dd>
        </div>
        <div class="detail-field">
          <dt>OS</dt>
          <dd>{{ host.os_family }} {{ host.os_version || "" }}</dd>
        </div>
        <div class="detail-field">
          <dt>IPv4</dt>
          <dd>{{ host.ipv4 || "—" }}</dd>
        </div>
        <div class="detail-field">
          <dt>Heartbeat</dt>
          <dd>{{ host.last_heartbeat_at ? formatDt(host.last_heartbeat_at) : "—" }}</dd>
        </div>
        <div class="detail-field">
          <dt>Last seen</dt>
          <dd>{{ formatDt(host.last_seen_at) }}</dd>
        </div>
        <div class="detail-field">
          <dt>Инвентаризация</dt>
          <dd>{{ host.last_inventory_at ? formatDt(host.last_inventory_at) : "—" }}</dd>
        </div>
        <div class="detail-field">
          <dt>Событий</dt>
          <dd>{{ host.event_count ?? 0 }}</dd>
        </div>
      </dl>
      <div v-if="isWindowsHost" class="host-actions">
        <button
          type="button"
          class="secondary"
          :disabled="testingWinRm"
          @click="runWinRmTest"
        >
          {{ testingWinRm ? "Проверка…" : "Проверить WinRM (admin)" }}
        </button>
        <p v-if="showWinRmTestFeedback && winRmTestMessage" :class="winRmTestOk ? 'success' : 'error'">{{ winRmTestMessage }}</p>
        <div class="host-actions-row">
          <button
            type="button"
            class="secondary"
            :disabled="requestingAgentUpdate"
            @click="runRequestAgentUpdate"
          >
            {{ requestingAgentUpdate ? "Запрос…" : "Запросить обновление (SAC)" }}
          </button>
          <button
            type="button"
            class="secondary"
            :disabled="runningAgentFallback"
            @click="runAgentFallback"
          >
            {{ runningAgentFallback ? "Обновление…" : "Обновить через WinRM" }}
          </button>
        </div>
        <p v-if="agentControlMessage && !hostRemoteActionLog.open" :class="agentControlOk ? 'success' : 'error'">{{ agentControlMessage }}</p>
      </div>
      <div v-if="isLinuxHost" class="host-actions">
        <div class="host-actions-row">
          <div class="host-action-item">
            <button
              type="button"
              class="secondary"
              :disabled="testingSsh"
              @click="runSshTestManual"
            >
              {{ testingSsh ? "Проверка…" : "Проверить SSH" }}
            </button>
            <span
              v-if="sshVerified"
              class="host-action-ok"
              :title="sshVerifiedTitle"
            >✓</span>
          </div>
          <button
            type="button"
            class="secondary"
            :disabled="updatingAgent"
            @click="runAgentUpdate"
          >
            {{ updatingAgent ? "Обновление…" : "Обновить ssh-monitor (SSH)" }}
          </button>
          <button
            type="button"
            class="secondary"
            :disabled="requestingAgentUpdate"
            @click="runRequestAgentUpdate"
          >
            {{ requestingAgentUpdate ? "Запрос…" : "Запросить обновление (SAC)" }}
          </button>
          <button
            type="button"
            class="secondary"
            :disabled="runningAgentFallback"
            @click="runAgentFallback"
          >
            {{ runningAgentFallback ? "Fallback…" : "Fallback SSH" }}
          </button>
        </div>
        <p v-if="agentControlMessage && !hostRemoteActionLog.open" :class="agentControlOk ? 'success' : 'error'">{{ agentControlMessage }}</p>
        <div v-if="showSshTestFeedback && (sshTestMessage || sshTestOutput)" class="host-action-feedback">
          <p :class="sshTestOk ? 'success' : 'error'">{{ sshTestMessage }}</p>
          <pre v-if="sshTestOutput" class="host-ssh-output">{{ sshTestOutput }}</pre>
        </div>
      </div>
    </div>

    <section v-if="showAgentConfig" class="card host-agent-config">
      <h2>Desired config (poll агента)</h2>
      <p class="muted">Агент заберёт настройки при следующем poll. Revision: {{ host.agent_config_revision ?? 0 }}</p>
      <form class="agent-config-form" @submit.prevent="saveAgentConfig">
        <label v-if="isWindowsHost" class="config-field">
          ServerDisplayName
          <input v-model="configForm.serverDisplayName" type="text" />
        </label>
        <label v-if="isLinuxHost" class="config-field">
          SERVER_DISPLAY_NAME
          <input v-model="configForm.serverDisplayName" type="text" />
        </label>
        <label v-if="isWindowsHost" class="config-inline">
          <input v-model="configForm.getInventory" type="checkbox" />
          GetInventory
        </label>
        <button type="submit" :disabled="savingAgentConfig">
          {{ savingAgentConfig ? "Сохранение…" : "Сохранить config" }}
        </button>
        <p v-if="agentConfigMessage" :class="agentConfigOk ? 'success' : 'error'">{{ agentConfigMessage }}</p>
      </form>
    </section>

    <section v-if="inventory" class="host-inventory card">
      <h2>Железо и ПО</h2>
      <div v-if="windowsInfo" class="inv-block">
        <h3>Windows</h3>
        <dl class="detail-fields detail-fields-compact">
          <div v-for="item in windowsFields" :key="item.label" class="detail-field">
            <dt>{{ item.label }}</dt>
            <dd>{{ item.value }}</dd>
          </div>
        </dl>
      </div>
      <div v-if="computerName" class="inv-block">
        <p><strong>Имя компьютера:</strong> {{ computerName }}</p>
      </div>
      <div v-if="processors.length" class="inv-block">
        <h3>Процессор</h3>
        <ul>
          <li v-for="(p, i) in processors" :key="'cpu-' + i">
            {{ p.name }}
            <span v-if="p.cores != null"> — {{ p.cores }} ядер / {{ p.logical }} потоков</span>
          </li>
        </ul>
      </div>
      <div v-if="motherboard" class="inv-block">
        <h3>Материнская плата</h3>
        <p>{{ motherboard }}</p>
      </div>
      <div v-if="memoryGb != null" class="inv-block">
        <h3>Память</h3>
        <p>{{ memoryGb }} GB</p>
      </div>
      <div v-if="disks.length" class="inv-block">
        <h3>Диски</h3>
        <table class="dash-table">
          <thead>
            <tr>
              <th>Имя</th>
              <th>Тип</th>
              <th>Размер, GB</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(d, i) in disks" :key="'disk-' + i">
              <td>{{ d.friendly_name || "—" }}</td>
              <td>{{ d.media_type || "—" }}</td>
              <td>{{ d.size_gb ?? "—" }}</td>
            </tr>
          </tbody>
        </table>
      </div>
      <div v-if="video.length" class="inv-block">
        <h3>Видео</h3>
        <ul>
          <li v-for="(v, i) in video" :key="'gpu-' + i">{{ v.name }}</li>
        </ul>
      </div>
      <div v-if="ipv4List.length" class="inv-block">
        <h3>IPv4</h3>
        <p>{{ ipv4List.join(", ") }}</p>
      </div>
    </section>
    <p v-else class="muted">Инвентаризация ещё не поступала с агента (agent.inventory).</p>

    <h2>События хоста</h2>
    <p v-if="eventsLoading">Загрузка событий…</p>
    <p v-else-if="eventsError" class="error">{{ eventsError }}</p>
    <template v-else>
      <p>Всего: {{ events?.total ?? 0 }}</p>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Время</th>
            <th>Severity</th>
            <th>Type</th>
            <th>Title</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="e in events?.items ?? []" :key="e.id">
            <td>
              <RouterLink :to="{ path: `/events/${e.id}`, query: eventsBackQuery }">{{ e.id }}</RouterLink>
            </td>
            <td>{{ formatDt(e.occurred_at) }}</td>
            <td :class="'sev-' + e.severity">{{ e.severity }}</td>
            <td><code>{{ e.type }}</code></td>
            <td>{{ e.title }}</td>
          </tr>
        </tbody>
      </table>
      <div class="filters" style="margin-top: 1rem">
        <button type="button" class="secondary" :disabled="eventsPage <= 1" @click="loadEvents(eventsPage - 1)">
          Назад
        </button>
        <span>Стр. {{ eventsPage }}</span>
        <button
          type="button"
          class="secondary"
          :disabled="!events || eventsPage * eventsPageSize >= events.total"
          @click="loadEvents(eventsPage + 1)"
        >
          Вперёд
        </button>
      </div>
    </template>
  </template>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useSacLiveEventStream } from "../composables/useSacLiveEventStream";
import {
  hostRemoteActionLog,
  runHostRemoteAction,
} from "../composables/useHostRemoteAction";
import {
  apiFetch,
  fetchHost,
  fetchRecentEvents,
  testHostWinRm,
  testHostSsh,
  requestHostAgentUpdate,
  patchHostAgentConfig,
  type EventListResponse,
  type HostDetail,
} from "../api";
import { emitHostPatch } from "../utils/hostPatchBus";

const HOST_ACTION_FEEDBACK_MS = 30_000;

const props = defineProps<{ id: string }>();
const route = useRoute();

const host = ref<HostDetail | null>(null);
const events = ref<EventListResponse | null>(null);
const loading = ref(false);
const eventsLoading = ref(false);
const error = ref("");
const eventsError = ref("");
const eventsPage = ref(1);
const eventsPageSize = 50;
const testingWinRm = ref(false);
const winRmTestMessage = ref("");
const winRmTestOk = ref(false);
const showWinRmTestFeedback = ref(false);
const testingSsh = ref(false);
const updatingAgent = ref(false);
const showSshTestFeedback = ref(false);
const sshTestMessage = ref("");
const sshTestOk = ref(false);
const sshTestOutput = ref("");
const requestingAgentUpdate = ref(false);
const runningAgentFallback = ref(false);
const agentControlMessage = ref("");
const agentControlOk = ref(false);
const savingAgentConfig = ref(false);
const agentConfigMessage = ref("");
const agentConfigOk = ref(false);
const configForm = reactive({
  serverDisplayName: "",
  getInventory: true,
});
let sshTestHideTimer: ReturnType<typeof setTimeout> | null = null;
let winRmTestHideTimer: ReturnType<typeof setTimeout> | null = null;
let sshProbeSeq = 0;
let refreshingLive = false;

useSacLiveEventStream(() => {
  void refreshFromLatestEvent();
});

const hostId = computed(() => parseInt(props.id, 10));

const isWindowsHost = computed(() => {
  const h = host.value;
  if (!h) return false;
  if ((h.os_family || "").toLowerCase() === "windows") return true;
  return h.product === "rdp-login-monitor";
});

const isLinuxHost = computed(() => {
  const h = host.value;
  if (!h) return false;
  if ((h.os_family || "").toLowerCase() === "linux") return true;
  return h.product === "ssh-monitor";
});

const sshVerified = computed(() => host.value?.ssh_admin_ok === true);

const sshVerifiedTitle = computed(() => {
  const checkedAt = host.value?.ssh_admin_checked_at;
  if (checkedAt) {
    return `SSH доступен (проверено ${formatDt(checkedAt)})`;
  }
  return "SSH доступен";
});

const versionStatusClass = computed(() => {
  const status = host.value?.version_status;
  if (status === "outdated") return "agent-version-outdated";
  if (status === "ok") return "agent-version-ok";
  return "";
});

const showAgentConfig = computed(() => isWindowsHost.value || isLinuxHost.value);

function syncConfigFormFromHost(detail: HostDetail) {
  const cfg = detail.agent_config ?? {};
  configForm.serverDisplayName = String(
    cfg.ServerDisplayName ?? cfg.SERVER_DISPLAY_NAME ?? "",
  );
  configForm.getInventory = cfg.GetInventory !== false;
}

const inventory = computed(() => host.value?.inventory ?? null);

const windowsFields = computed(() => {
  const w = inventory.value?.windows as Record<string, string> | undefined;
  if (!w) return [];
  const labels: Record<string, string> = {
    product_name: "Продукт",
    version: "Версия",
    os_hardware_abstraction_layer: "HAL",
  };
  return Object.entries(labels)
    .map(([key, label]) => ({ label, value: w[key]?.trim() || "" }))
    .filter((item) => item.value);
});

const windowsInfo = computed(() => windowsFields.value.length > 0);

const computerName = computed(() => {
  const v = inventory.value?.computer_name;
  return typeof v === "string" && v.trim() ? v : "";
});

const processors = computed(() => {
  const raw = inventory.value?.processor;
  if (!Array.isArray(raw)) return [];
  return raw
    .filter((p): p is Record<string, unknown> => p != null && typeof p === "object")
    .map((p) => ({
      name: String(p.name ?? "—"),
      cores: typeof p.cores === "number" ? p.cores : null,
      logical: typeof p.logical_processors === "number" ? p.logical_processors : null,
    }));
});

const motherboard = computed(() => {
  const m = inventory.value?.motherboard as Record<string, string> | undefined;
  if (!m) return "";
  return [m.manufacturer, m.product].filter(Boolean).join(" ");
});

const memoryGb = computed(() => {
  const v = inventory.value?.memory_gb;
  return typeof v === "number" ? v : null;
});

const disks = computed(() => {
  const raw = inventory.value?.disks;
  if (!Array.isArray(raw)) return [];
  return raw.filter((d): d is Record<string, unknown> => d != null && typeof d === "object");
});

const video = computed(() => {
  const raw = inventory.value?.video;
  if (!Array.isArray(raw)) return [];
  return raw.filter((v): v is Record<string, string> => v != null && typeof v === "object");
});

const ipv4List = computed(() => {
  const raw = inventory.value?.ipv4;
  if (!Array.isArray(raw)) return [];
  return raw.map(String).filter(Boolean);
});

const eventsBackQuery = computed(() => ({ host_id: String(hostId.value) }));

function formatDt(iso: string) {
  return new Date(iso).toLocaleString("ru-RU");
}

function agentLabel(status: string) {
  if (status === "online") return "online";
  if (status === "stale") return "stale";
  return "unknown";
}

function clearSshTestFeedbackTimer() {
  if (sshTestHideTimer != null) {
    clearTimeout(sshTestHideTimer);
    sshTestHideTimer = null;
  }
}

function isLinuxHostDetail(detail: HostDetail): boolean {
  if ((detail.os_family || "").toLowerCase() === "linux") return true;
  return detail.product === "ssh-monitor";
}

function applySshAdminStatus(result: { ok: boolean; ssh_admin_ok?: boolean | null }) {
  if (!host.value) return;
  host.value = {
    ...host.value,
    ssh_admin_ok: result.ssh_admin_ok ?? result.ok,
    ssh_admin_checked_at: new Date().toISOString(),
  };
}

function clearWinRmTestFeedbackTimer() {
  if (winRmTestHideTimer != null) {
    clearTimeout(winRmTestHideTimer);
    winRmTestHideTimer = null;
  }
}

function scheduleSshTestFeedbackHide() {
  clearSshTestFeedbackTimer();
  sshTestHideTimer = setTimeout(() => {
    sshTestMessage.value = "";
    sshTestOutput.value = "";
    showSshTestFeedback.value = false;
    sshTestHideTimer = null;
  }, HOST_ACTION_FEEDBACK_MS);
}

function scheduleWinRmTestFeedbackHide() {
  clearWinRmTestFeedbackTimer();
  winRmTestHideTimer = setTimeout(() => {
    winRmTestMessage.value = "";
    showWinRmTestFeedback.value = false;
    winRmTestHideTimer = null;
  }, HOST_ACTION_FEEDBACK_MS);
}

async function runSshTest(options: { silent?: boolean } = {}) {
  const silent = options.silent === true;
  const probeId = ++sshProbeSeq;

  if (!silent) {
    testingSsh.value = true;
    clearSshTestFeedbackTimer();
    sshTestMessage.value = "";
    sshTestOutput.value = "";
    showSshTestFeedback.value = false;
  }

  try {
    const result = await testHostSsh(hostId.value);
    if (probeId !== sshProbeSeq) return;

    applySshAdminStatus(result);

    if (!silent) {
      showSshTestFeedback.value = true;
      sshTestOk.value = result.ok;
      sshTestMessage.value = result.message;
      sshTestOutput.value = formatSshOutput(result);
      if (result.ok) {
        scheduleSshTestFeedbackHide();
      }
    }
  } catch (e) {
    if (probeId !== sshProbeSeq) return;
    if (host.value) {
      host.value = { ...host.value, ssh_admin_ok: false, ssh_admin_checked_at: new Date().toISOString() };
    }
    if (!silent) {
      showSshTestFeedback.value = true;
      sshTestOk.value = false;
      sshTestMessage.value = e instanceof Error ? e.message : "Ошибка проверки SSH";
    }
  } finally {
    if (!silent && probeId === sshProbeSeq) {
      testingSsh.value = false;
    }
  }
}

function runSshTestManual() {
  void runSshTest({ silent: false });
}

async function probeSshOnOpen() {
  await runSshTest({ silent: true });
}

async function runWinRmTest() {
  testingWinRm.value = true;
  clearWinRmTestFeedbackTimer();
  winRmTestMessage.value = "";
  showWinRmTestFeedback.value = false;
  try {
    const result = await testHostWinRm(hostId.value);
    winRmTestOk.value = result.ok;
    winRmTestMessage.value = result.message;
    showWinRmTestFeedback.value = true;
    if (result.ok) {
      scheduleWinRmTestFeedbackHide();
    }
  } catch (e) {
    winRmTestOk.value = false;
    winRmTestMessage.value = e instanceof Error ? e.message : "Ошибка проверки WinRM";
    showWinRmTestFeedback.value = true;
  } finally {
    testingWinRm.value = false;
  }
}

function formatSshOutput(result: { stdout: string | null; stderr: string | null; exit_code: number | null }) {
  const parts: string[] = [];
  if (result.stdout?.trim()) {
    parts.push(result.stdout.trim());
  }
  if (result.stderr?.trim()) {
    parts.push(result.stderr.trim());
  }
  if (result.exit_code != null) {
    parts.push(`exit code: ${result.exit_code}`);
  }
  return parts.join("\n\n");
}

function applyHostDetail(detail: HostDetail) {
  host.value = detail;
  syncConfigFormFromHost(detail);
  emitHostPatch(detail);
}

async function runRequestAgentUpdate() {
  requestingAgentUpdate.value = true;
  agentControlMessage.value = "";
  try {
    const result = await requestHostAgentUpdate(hostId.value);
    agentControlOk.value = true;
    agentControlMessage.value = result.target_version
      ? `Запрошено обновление до ${result.target_version}`
      : "Запрошено обновление агента";
    await loadHost();
  } catch (e) {
    agentControlOk.value = false;
    agentControlMessage.value = e instanceof Error ? e.message : "Не удалось запросить обновление";
  } finally {
    requestingAgentUpdate.value = false;
  }
}

async function runAgentFallback() {
  runningAgentFallback.value = true;
  agentControlMessage.value = "";
  const title = isWindowsHost.value ? "Обновление через WinRM" : "Fallback SSH (ssh-monitor)";
  try {
    const job = await runHostRemoteAction(hostId.value, title, "fallback");
    if (job) {
      agentControlOk.value = job.ok ?? false;
      if (job.ok) {
        agentControlMessage.value = "";
        await loadHost();
      } else {
        agentControlMessage.value = job.message || "";
      }
    }
  } finally {
    runningAgentFallback.value = false;
  }
}

async function saveAgentConfig() {
  savingAgentConfig.value = true;
  agentConfigMessage.value = "";
  const settings: Record<string, unknown> = {};
  if (isWindowsHost.value) {
    if (configForm.serverDisplayName.trim()) {
      settings.ServerDisplayName = configForm.serverDisplayName.trim();
    }
    settings.GetInventory = configForm.getInventory;
  } else if (isLinuxHost.value) {
    if (configForm.serverDisplayName.trim()) {
      settings.SERVER_DISPLAY_NAME = configForm.serverDisplayName.trim();
    }
  }
  try {
    const result = await patchHostAgentConfig(hostId.value, settings);
    agentConfigOk.value = true;
    agentConfigMessage.value = `Config сохранён (revision ${result.config_revision})`;
    await loadHost();
  } catch (e) {
    agentConfigOk.value = false;
    agentConfigMessage.value = e instanceof Error ? e.message : "Ошибка сохранения config";
  } finally {
    savingAgentConfig.value = false;
  }
}

async function loadHost() {
  loading.value = true;
  error.value = "";
  winRmTestMessage.value = "";
  clearWinRmTestFeedbackTimer();
  showWinRmTestFeedback.value = false;
  sshProbeSeq += 1;
  clearSshTestFeedbackTimer();
  showSshTestFeedback.value = false;
  sshTestMessage.value = "";
  sshTestOutput.value = "";
  try {
    const detail = await fetchHost(hostId.value);
    applyHostDetail(detail);
    if (isLinuxHostDetail(detail)) {
      void probeSshOnOpen();
    }
  } catch (e) {
    error.value = e instanceof Error ? e.message : "Ошибка загрузки хоста";
  } finally {
    loading.value = false;
  }
}

async function runAgentUpdate() {
  updatingAgent.value = true;
  try {
    const job = await runHostRemoteAction(
      hostId.value,
      "Обновление ssh-monitor (SSH)",
      "ssh-update",
    );
    if (job?.ok) {
      const detail = await fetchHost(hostId.value);
      if (job.product_version) {
        detail.product_version = job.product_version;
      }
      applyHostDetail(detail);
    }
  } finally {
    updatingAgent.value = false;
  }
}

async function refreshFromLatestEvent() {
  if (refreshingLive) return;
  refreshingLive = true;
  try {
    const recent = await fetchRecentEvents(1);
    const ev = recent[0];
    if (!ev || ev.host_id !== hostId.value) return;
    applyHostDetail(await fetchHost(hostId.value));
    if (eventsPage.value === 1) {
      await loadEvents(1);
    }
  } catch {
    /* ignore transient errors */
  } finally {
    refreshingLive = false;
  }
}

async function loadEvents(page = 1) {
  eventsLoading.value = true;
  eventsError.value = "";
  eventsPage.value = page;
  try {
    const params = new URLSearchParams({
      page: String(page),
      page_size: String(eventsPageSize),
      host_id: String(hostId.value),
    });
    events.value = await apiFetch<EventListResponse>(`/api/v1/events?${params}`);
  } catch (e) {
    eventsError.value = e instanceof Error ? e.message : "Ошибка загрузки событий";
  } finally {
    eventsLoading.value = false;
  }
}

onMounted(async () => {
  await loadHost();
  await loadEvents(1);
});

onUnmounted(() => {
  clearSshTestFeedbackTimer();
  clearWinRmTestFeedbackTimer();
});

watch(
  () => route.params.id,
  async () => {
    await loadHost();
    await loadEvents(1);
  },
);
</script>

<style scoped>
.host-inventory {
  margin: 1rem 0 1.5rem;
}

.inv-block {
  margin-bottom: 1rem;
}

.inv-block h3 {
  margin: 0 0 0.35rem;
  font-size: 1rem;
}

.muted {
  color: #9aa4b2;
}

.host-actions {
  margin-top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.host-actions-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}

.host-action-item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.host-action-ok {
  color: #3fb950;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1;
}

.host-action-feedback {
  width: 100%;
}

.host-ssh-output {
  margin-top: 0.75rem;
  max-height: 16rem;
  overflow: auto;
  padding: 0.75rem;
  background: #0d1117;
  border-radius: 6px;
  font-size: 0.85rem;
  white-space: pre-wrap;
}

.agent-version-outdated {
  color: #f0883e;
}

.agent-version-ok {
  color: #3fb950;
}

.pending-update-badge {
  margin-left: 0.5rem;
  font-size: 0.85rem;
  color: #58a6ff;
}

.version-hint {
  font-size: 0.9rem;
}

.error-inline {
  color: #f85149;
}

.host-agent-config {
  margin-top: 1rem;
}

.agent-config-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  max-width: 28rem;
}

.config-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.config-inline {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
</style>
