fix: ui improvement (#13106)

This commit is contained in:
ktx-vaidehi
2026-07-09 11:00:22 +00:00
committed by GitHub
parent d04df5c72d
commit 5ec3e960c7
71 changed files with 718 additions and 687 deletions
+17 -32
View File
@@ -15,39 +15,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div data-test="add-action-script-section">
<div class="w-full h-full px-[0.625rem] pb-[0.625rem] pt-1">
<div class="card-container">
<div
class="flex items-center justify-between py-3 pl-4 pr-2 h-[68px]"
>
<div
data-test="add-action-script-back-btn"
class="flex justify-center items-center mr-3 cursor-pointer"
title="Go Back"
@click="router.back()"
>
<OIcon name="arrow-back-ios-new" size="xs" />
<div
v-if="isEditingActionScript"
class="text-xl font-semibold pl-2"
data-test="add-action-script-title"
>
{{ t("actions.update") }}
</div>
<div
v-else
class="text-xl font-semibold pl-2"
data-test="add-action-script-title"
>
{{ t("actions.add") }}
</div>
</div>
</div>
</div>
</div>
<div data-test="add-action-script-section" class="w-full h-full flex flex-col">
<AppPageHeader
:title="isEditingActionScript ? t('actions.update') : t('actions.add')"
:back="{
onClick: () => router.back(),
dataTest: 'add-action-script-back-btn',
}"
class="px-4 border-b border-border-default"
>
<template #title>
<span data-test="add-action-script-title">{{
isEditingActionScript ? t("actions.update") : t("actions.add")
}}</span>
</template>
</AppPageHeader>
<div class="w-full h-full px-[0.625rem] pb-[0.625rem]">
<div class="w-full flex-1 min-h-0 px-2.5 pb-2.5 pt-1">
<div
class="card-container overflow-auto"
style="max-height: calc(100vh - var(--navbar-height) - 157px)"
@@ -505,6 +489,7 @@ import type { Ref } from "vue";
import { DateTime as _DateTime } from "luxon";
import actions from "@/services/action_scripts";
import ConfirmDialog from "@/components/ConfirmDialog.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
import CronExpressionParser from "cron-parser";
import { convertDateToTimestamp } from "@/utils/date";
import service_accounts from "@/services/service_accounts";
@@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0" style="min-height: inherit">
<div class="p-0" style="min-height: inherit">
<!-- Header -->
<AppPageHeader
:title="isEditing ? t('aiToolset.update') : t('aiToolset.add')"
+2 -2
View File
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0 o2-custom-bg"
<div class="p-0 o2-custom-bg"
style="
height: calc(100vh - 48px);
min-height: inherit;
@@ -28,7 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
label: t('alert_destinations.header'),
onClick: () => emit('cancel:hideform'),
}"
class="card-container px-3 border-b border-border-default"
class="px-3 border-b border-border-default"
style="flex-shrink: 0"
>
<template #title>
+2 -2
View File
@@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0 o2-custom-bg"
<div class="p-0 o2-custom-bg"
style="height: calc(100vh - 48px); min-height: inherit"
>
<AppPageHeader
@@ -22,7 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
label: t('alert_templates.header'),
onClick: () => emit('cancel:hideform'),
}"
class="card-container px-3 border-b border-border-default"
class="px-3 border-b border-border-default"
>
<template #title>
<span data-test="add-template-title">
+73 -91
View File
@@ -17,101 +17,82 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template>
<div
data-test="alert-history-page"
class="p-0 flex"
class="w-full h-full flex flex-col min-h-0"
>
<div class="w-full h-full px-2.5 pt-[0.325rem]">
<div class="card-container mb-2.5">
<div
class="flex justify-between w-full h-17 px-2 py-3"
<AppPageHeader
:title="t('alerts.history')"
:back="{ onClick: goBack, dataTest: 'alert-history-back-btn' }"
class="shrink-0 px-4 border-b border-border-default"
>
<template #title>
<span data-test="alerts-history-title">{{ t("alerts.history") }}</span>
</template>
<template #actions>
<DateTime
ref="dateTimeRef"
auto-apply
:default-type="dateTimeType"
:default-absolute-time="{
startTime: absoluteTime.startTime,
endTime: absoluteTime.endTime,
}"
:default-relative-time="relativeTime"
data-test="alert-history-date-picker"
@on:date-change="updateDateTime"
/>
<OSelect
v-model="selectedAlert"
:options="filteredAlertOptions"
labelKey="label"
valueKey="value"
@update:model-value="onAlertSelected"
:placeholder="t(`alerts.searcHistory`) || 'Select or search alert...'"
data-test="alert-history-search-select"
class="o2-search-input min-w-62.5"
clearable
@clear="clearSearch"
>
<div class="flex items-center">
<OButton
padding="xs"
variant="outline"
size="icon-sm"
icon-left="arrow-back-ios-new"
@click="goBack"
data-test="alert-history-back-btn"
<template #icon-left>
<OIcon
class="o2-search-input-icon"
:class="
store.state.theme === 'dark'
? 'o2-search-input-icon-dark'
: 'o2-search-input-icon-light'
"
name="search" size="sm"
/>
<div
class="text-xl tracking-[0.005em] font-semibold ml-2"
data-test="alerts-history-title"
>
{{ t(`alerts.history`) }}
</template>
<template #empty>
<div class="px-3 py-2 text-muted-foreground">
No alerts found
</div>
</div>
<div class="flex ml-auto items-center">
<div class="mr-2">
<DateTime
ref="dateTimeRef"
auto-apply
:default-type="dateTimeType"
:default-absolute-time="{
startTime: absoluteTime.startTime,
endTime: absoluteTime.endTime,
}"
:default-relative-time="relativeTime"
data-test="alert-history-date-picker"
@on:date-change="updateDateTime"
/>
</div>
<OSelect
v-model="selectedAlert"
:options="filteredAlertOptions"
labelKey="label"
valueKey="value"
@update:model-value="onAlertSelected"
:placeholder="t(`alerts.searcHistory`) || 'Select or search alert...'"
data-test="alert-history-search-select"
class="o2-search-input mr-2"
style="min-width: 250px"
clearable
@clear="clearSearch"
>
<template #icon-left>
<OIcon
class="o2-search-input-icon"
:class="
store.state.theme === 'dark'
? 'o2-search-input-icon-dark'
: 'o2-search-input-icon-light'
"
name="search" size="sm"
/>
</template>
<template #empty>
<div class="px-3 py-2 text-muted-foreground">
No alerts found
</div>
</template>
</OSelect>
<OButton
variant="ghost"
icon-left="search"
size="icon-sm"
@click="manualSearch"
data-test="alert-history-manual-search-btn"
:disabled="loading"
class="mr-2"
>
<OTooltip :content="t('common.search') || 'Search'" />
</OButton>
<OButton
variant="ghost"
size="icon-sm"
icon-left="refresh"
@click="refreshData"
data-test="alert-history-refresh-btn"
:loading="loading"
>
<OTooltip :content="t('common.refresh') || 'Refresh'" />
</OButton>
</div>
</div>
</div>
</div>
<div class="w-full h-full px-2.5">
<div class="alert-history-table card-container h-[calc(100vh-130px)]">
</template>
</OSelect>
<OButton
variant="ghost"
icon-left="search"
size="icon-sm"
@click="manualSearch"
data-test="alert-history-manual-search-btn"
:disabled="loading"
>
<OTooltip :content="t('common.search') || 'Search'" />
</OButton>
<OButton
variant="ghost"
size="icon-sm"
icon-left="refresh"
@click="refreshData"
data-test="alert-history-refresh-btn"
:loading="loading"
>
<OTooltip :content="t('common.refresh') || 'Refresh'" />
</OButton>
</template>
</AppPageHeader>
<div class="flex-1 min-h-0 px-2.5 pt-2">
<div class="alert-history-table card-container h-full">
<OTable
data-test="alert-history-table"
:data="rows"
@@ -478,6 +459,7 @@ import OTimeCell from "@/lib/core/Table/cells/OTimeCell.vue";
import type { OTableColumnDef } from "@/lib/core/Table/OTable.types";
import alertsService from "@/services/alerts";
import NoData from "@/components/shared/grid/NoData.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
import OIcon from "@/lib/core/Icon/OIcon.vue";
import OTag from "@/lib/core/Badge/OTag.vue";
import OButton from '@/lib/core/Button/OButton.vue';
+45 -60
View File
@@ -15,61 +15,45 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div>
<div class="px-[0.625rem] py-[0.1rem]">
<div class="card-container mb-[0.625rem]">
<!-- Header -->
<div
class="flex justify-between items-center px-2 py-2"
<div class="w-full h-full flex flex-col min-h-0">
<AppPageHeader
:title="t('alerts.insights.title')"
:back="{ onClick: goBack, dataTest: 'alert-insights-back-btn' }"
tabs-below
class="shrink-0 px-4"
>
<template #actions>
<date-time
ref="dateTimeRef"
auto-apply
:default-type="dateTimeType"
:default-absolute-time="{
startTime: timeRange.__global.start_time.getTime(),
endTime: timeRange.__global.end_time.getTime(),
}"
:default-relative-time="relativeTime"
@on:date-change="updateDateTime"
@on:timezone-change="updateTimezone"
data-test="alert-insights-datetime"
/>
<OButton
@click="refreshDashboard"
:loading="isLoading"
variant="ghost"
size="icon-sm"
data-test="alert-insights-refresh-btn"
>
<div class="flex items-center">
<OButton
variant="outline"
size="icon-sm"
class="hideOnPrintMode el-border"
@click="goBack"
data-test="alert-insights-back-btn"
>
<OIcon name="arrow-back-ios-new" size="sm" />
</OButton>
<div class="text-xl tracking-[0.005em] font-[600] ml-2">{{ t("alerts.insights.title") }}</div>
</div>
<OIcon name="refresh" size="sm" />
<OTooltip :content="t('common.refresh')" />
</OButton>
</template>
<div class="flex items-center">
<date-time
ref="dateTimeRef"
auto-apply
:default-type="dateTimeType"
:default-absolute-time="{
startTime: timeRange.__global.start_time.getTime(),
endTime: timeRange.__global.end_time.getTime(),
}"
:default-relative-time="relativeTime"
@on:date-change="updateDateTime"
@on:timezone-change="updateTimezone"
class="mr-2"
data-test="alert-insights-datetime"
/>
<OButton
@click="refreshDashboard"
:loading="isLoading"
variant="ghost"
size="icon-sm"
class="mr-1 el-border"
data-test="alert-insights-refresh-btn"
>
<OIcon name="refresh" size="sm" />
<OTooltip :content="t('common.refresh')" />
</OButton>
</div>
</div>
<!-- Tabs -->
<template #tabs>
<OTabs
v-model="currentTab"
dense
class="alert-insights-tabs ml-2"
class="alert-insights-tabs"
align="left"
data-test="alert-insights-tabs"
>
@@ -88,12 +72,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
<OTab name="quality" :label="t('alerts.insights.tabs.quality')" data-test="tab-quality" />
</OTabs>
</template>
</AppPageHeader>
<!-- Filters Section -->
<div
v-if="show"
class="flex items-center gap-2 flex-wrap px-4 py-3 border-b border-(--q-border-color)"
>
<!-- Filters Section -->
<div
v-if="show"
class="flex items-center gap-2 flex-wrap px-4 py-3 border-b border-(--q-border-color) shrink-0"
>
<span class="text-sm font-semibold relative top-1">{{ t("common.filters") }}:</span>
<!-- Failed Only Toggle -->
@@ -166,14 +152,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
{{ t('alerts.insights.filters.clearAll') }}
</OButton>
</div>
</div>
</div>
<!-- Action Buttons Row -->
<div
v-if="selectedAlertForAction"
class="bg-primary bg-opacity-10 flex items-center px-4 py-3 gap-3 border-b border-(--q-border-color)"
class="bg-primary bg-opacity-10 flex items-center px-4 py-3 gap-3 border-b border-(--q-border-color) shrink-0"
data-test="action-buttons-row"
>
<OIcon name="campaign" size="sm" />
@@ -228,8 +212,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
<!-- Dashboard Content -->
<div class="w-full h-full px-[0.625rem] pb-[0.625rem]">
<div class="card-container mb-[0.625rem] h-[calc(100vh-208px)]">
<div class="flex-1 min-h-0 px-2.5 pb-2.5">
<div class="card-container mb-2.5 h-[calc(100vh-13rem)]">
<div
@contextmenu="handleNativeContextMenu"
>
@@ -283,6 +267,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import OTabs from '@/lib/navigation/Tabs/OTabs.vue'
import OTab from '@/lib/navigation/Tabs/OTab.vue'
import OButton from '@/lib/core/Button/OButton.vue';
import AppPageHeader from "@/components/common/AppPageHeader.vue";
import { ref, computed, onMounted, watch, nextTick, reactive, provide } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useStore } from "vuex";
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md flex flex-col h-full p-0">
<div class="flex flex-col h-full p-0">
<div v-if="!showDestinationEditor && !showImportDestination" class="flex flex-col h-full">
<AppPageHeader
@@ -106,7 +106,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>
<template #bottom="{ totalRows }">
<span class="o2-table-footer-title text-primary">
<span class="o2-table-footer-title">
{{ totalRows.toLocaleString() }} {{ t('alert_destinations.header') }}
</span>
<OButton
@@ -232,7 +232,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
@get:destinations="getDestinations"
/>
</div>
<div v-else>
<div v-else class="flex-1 min-h-0">
<ImportDestination
:destinations="destinations"
:templates="templates"
+6 -11
View File
@@ -19,13 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
ref="baseImportRef"
title="Import Alert"
test-prefix="alert"
class="flex-1 min-h-0"
:is-importing="isAlertImporting"
:editor-heights="{
urlEditor: 'calc(100vh - 285px)',
fileEditor: 'calc(100vh - 282px)',
outputContainer: 'calc(100vh - 110px)',
errorReport: 'calc(100vh - 192px)',
}"
@back="router.back()"
@cancel="router.back()"
@import="importJson"
@@ -81,16 +76,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- Output Section with Alert-specific Error Display -->
<template #output-content>
<div class="w-full h-full border-l border-border-default" style="min-width: 400px;">
<div class="w-full h-full flex flex-col border-l border-border-default" style="min-width: 400px;">
<div
v-if="alertErrorsToDisplay.length > 0"
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
>
Error Validations
</div>
<div v-else class="text-center text-xl font-semibold py-2">Output Messages</div>
<OSeparator class="mr-4 mt-4" />
<div class="error-report-container" style="height: calc(100vh - 192px) !important; overflow: auto; resize: none;">
<div v-else class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0">Output Messages</div>
<OSeparator class="mt-1 shrink-0" />
<div class="error-report-container flex-1 min-h-0">
<!-- Alert Errors Section -->
<div class="p-2.5 mb-2.5" v-if="alertErrorsToDisplay.length > 0">
<div class="error-list">
@@ -21,29 +21,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
test-prefix="destination"
:is-importing="isDestinationImporting"
container-class=""
container-style="height: calc(100vh - 50px);"
:editor-heights="{
urlEditor: 'calc(100vh - 285px)',
fileEditor: 'calc(100vh - 282px)',
outputContainer: 'calc(100vh - 110px)',
errorReport: 'calc(100vh - 128px)',
}"
container-style=""
@back="arrowBackFn"
@cancel="router.back()"
@import="importJson"
>
<!-- Output Section with Destination-specific Error Display -->
<template #output-content>
<div class="w-full h-full border-l border-border-default" style="min-width: 400px;">
<div class="w-full h-full flex flex-col border-l border-border-default" style="min-width: 400px;">
<div
v-if="destinationErrorsToDisplay.length > 0 || destinationCreators.length > 0"
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
>
{{ destinationErrorsToDisplay.length > 0 ? 'Error Validations' : 'Output Messages' }}
</div>
<div v-else class="text-center text-xl font-semibold py-2">Output Messages</div>
<OSeparator class="mr-4 mt-4" />
<div class="h-[calc(60vh-8px)] overflow-auto [resize:none] w-full min-w-100">
<div v-else class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0">Output Messages</div>
<OSeparator class="mt-1 shrink-0" />
<div class="flex-1 min-h-0 overflow-auto [resize:none] w-full min-w-100">
<!-- Destination Errors Section -->
<div
class="error-section p-2.5 mb-2.5"
@@ -21,10 +21,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
test-prefix="semantic-groups"
:is-importing="isImporting"
:show-splitter="false"
:editor-heights="{
urlEditor: 'calc(100vh - 286px)',
fileEditor: 'calc(100vh - 308px)',
}"
container-class=""
container-style="height: calc(100vh - var(--navbar-height));"
@back="handleBack"
@cancel="handleBack"
@import="handleImport"
@@ -32,7 +30,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
<!-- Full-width content for diff view -->
<template #full-width-content>
<div class="w-full p-2 h-[calc(100vh-140px)] flex flex-col overflow-hidden">
<div class="w-full p-2 h-full flex-1 min-h-0 flex flex-col overflow-hidden">
<!-- Compact Header with File Upload -->
<div class="card-container p-2 mb-2">
<div class="flex items-center">
+6 -12
View File
@@ -21,29 +21,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
test-prefix="template"
:is-importing="isTemplateImporting"
container-class=""
container-style="height: calc(100vh - 50px);"
:editor-heights="{
urlEditor: 'calc(100vh - 285px)',
fileEditor: 'calc(100vh - 282px)',
outputContainer: 'calc(100vh - 110px)',
errorReport: 'calc(100vh - 130px)',
}"
container-style=""
@back="arrowBackFn"
@cancel="router.back()"
@import="importJson"
>
<!-- Output Section with Template-specific Error Display -->
<template #output-content>
<div class="w-full h-full border-l border-border-default" style="min-width: 400px;">
<div class="w-full h-full flex flex-col border-l border-border-default" style="min-width: 400px;">
<div
v-if="templateErrorsToDisplay.length > 0 || tempalteCreators.length > 0"
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
>
{{ templateErrorsToDisplay.length > 0 ? 'Error Validations' : 'Output Messages' }}
</div>
<div v-else class="text-center text-xl font-semibold py-2">Output Messages</div>
<OSeparator class="mr-4 mt-4" />
<div class="h-[calc(60vh-8px)] overflow-auto [resize:none] w-full min-w-[400px]">
<div v-else class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0">Output Messages</div>
<OSeparator class="mt-1 shrink-0" />
<div class="flex-1 min-h-0 overflow-auto [resize:none] w-full min-w-[400px]">
<!-- Template Errors Section -->
<div
class="error-section p-2.5 mb-2.5"
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md flex flex-col h-full p-0">
<div class="flex flex-col h-full p-0">
<div v-if="!showDestinationEditor" class="flex flex-col h-full">
<!-- Standard section header: title + actions only. Search moved to toolbar. -->
<AppPageHeader
+2 -2
View File
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md flex flex-col h-full p-0">
<div class="flex flex-col h-full p-0">
<div v-if="!showImportTemplate && !showTemplateEditor" class="flex flex-col h-full">
<!-- Standard section header: title + actions only. Search moved to toolbar. -->
<AppPageHeader
@@ -207,7 +207,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
@get:templates="getTemplates"
/>
</div>
<div v-else>
<div v-else class="flex-1 min-h-0">
<ImportTemplate :templates="templates" @update:templates="getTemplates" />
</div>
@@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0" style="min-height: inherit;">
<div class="p-0" style="min-height: inherit;">
<AppPageHeader
:back="{
label: t('cipherKey.header'),
+1 -1
View File
@@ -80,7 +80,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<slot name="back">
<button
type="button"
class="inline-flex items-center justify-center shrink-0 w-9.5 h-9.5 rounded-[0.625rem] text-text-secondary transition-colors hover:bg-surface-subtle hover:text-text-primary outline-none focus-visible:ring-4 focus-visible:ring-primary-500/25 focus-visible:ring-inset"
class="inline-flex items-center justify-center shrink-0 w-9.5 h-9.5 rounded-[0.625rem] bg-surface-subtle text-text-primary transition-colors hover:bg-button-ghost-hover-bg outline-none focus-visible:ring-4 focus-visible:ring-primary-500/25 focus-visible:ring-inset"
:title="backLabel"
:aria-label="backLabel"
:data-test="props.back?.dataTest ?? 'app-page-header-back'"
+12 -10
View File
@@ -325,16 +325,18 @@ describe("BaseImport.vue", () => {
expect(style.height).toBe("100%");
});
it("outputContainerStyle uses editorHeights.outputContainer", () => {
wrapper = createWrapper({
editorHeights: {
outputContainer: "500px",
urlEditor: "300px",
fileEditor: "300px",
errorReport: "200px",
},
});
expect((wrapper.vm as any).outputContainerStyle.height).toBe("500px");
it("output editor container fills its pane via flex instead of a fixed height", () => {
wrapper = createWrapper();
const outputEditor = wrapper.find(
'[data-test="dashboard-import-output-editor"]',
);
expect(outputEditor.exists()).toBe(true);
// The pane fills the splitter height with flex (no brittle calc(100vh-Npx)).
const classes = outputEditor.classes();
expect(classes).toContain("h-full");
expect(classes).toContain("flex");
expect(classes).toContain("flex-col");
expect(outputEditor.attributes("style") || "").not.toContain("calc(");
});
});
+20 -36
View File
@@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template>
<div
class="w-full"
class="w-full h-full flex flex-col overflow-hidden min-h-0"
:class="[containerClass]"
:style="containerStyle"
>
@@ -54,20 +54,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>
</AppPageHeader>
<div class="flex" :class="contentWrapperClass">
<div class="flex" :style="contentStyle">
<div class="flex flex-1 min-h-0" :class="contentWrapperClass">
<div class="flex w-full min-h-0" :style="contentStyle">
<OSplitter
v-if="showSplitter"
class="logs-search-splitter"
class="logs-search-splitter w-full h-full min-h-0"
v-model="splitterModel"
:style="splitterStyle"
:limits="[30, 60]"
:horizontal="false"
>
<template #before>
<div class="w-full h-full border-r border-border-default">
<div class="w-full h-full flex flex-col border-r border-border-default">
<!-- Tabs Section -->
<div class="card-container py-2 px-2 mb-[0.625rem]">
<div class="card-container py-2 px-2 mb-1 shrink-0">
<div class="app-tabs-container h-[36px] w-fit">
<app-tabs
:data-test="`${testPrefix}-import-tabs`"
@@ -82,12 +82,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- URL Import Tab -->
<div
v-if="activeTab === 'import_json_url'"
class="editor-container-url card-container py-1"
class="editor-container-url card-container py-1 flex-1 min-h-0 flex flex-col"
>
<div class="mx-2 pb-2">
<div class="mx-2 mt-1 pb-2 flex flex-col flex-1 min-h-0">
<!-- Slot for custom URL input section -->
<slot name="url-input-section" :url="url" :updateUrl="updateUrl">
<div class="flex mt-3 mb-3">
<div class="flex mb-3 shrink-0">
<OInput
:data-test="`${testPrefix}-import-url-input`"
v-model="url"
@@ -102,7 +102,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:data-test="`${testPrefix}-import-sql-editor`"
ref="queryEditorRef"
:editor-id="`${testPrefix}-import-query-editor`"
class="import-editor-shell import-url-editor mx-2"
class="import-editor-shell import-url-editor mx-2 flex-1 min-h-0"
:debounceTime="300"
v-model:query="jsonStr"
language="json"
@@ -111,12 +111,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
<div
v-if="activeTab === 'import_json_file'"
class="editor-container-json card-container py-1"
class="editor-container-json card-container py-1 flex-1 min-h-0 flex flex-col"
>
<div class="mx-2 mt-3 pb-2">
<div class="mx-2 mt-1 pb-2 flex flex-col flex-1 min-h-0">
<!-- Slot for custom file input section -->
<slot name="file-input-section" :jsonFiles="jsonFiles" :updateFiles="updateFiles">
<div style="width: calc(100% - 10px)" class="mb-1 flex">
<div style="width: calc(100% - 10px)" class="mb-1 flex shrink-0">
<div style="width: 100%" class="pr-2">
<OFile
:data-test="`${testPrefix}-import-json-file-input`"
@@ -147,7 +147,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:data-test="`${testPrefix}-import-sql-editor`"
ref="queryEditorRef"
:editor-id="`${testPrefix}-import-query-editor`"
class="import-editor-shell import-file-editor mx-2"
class="import-editor-shell import-file-editor mx-2 flex-1 min-h-0"
:debounceTime="300"
v-model:query="jsonStr"
language="json"
@@ -162,16 +162,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template #after>
<div
:data-test="`${testPrefix}-import-output-editor`"
class="card-container w-full"
:style="outputContainerStyle"
class="card-container w-full h-full flex flex-col min-h-0"
>
<!-- Slot for complete output section customization -->
<slot name="output-section">
<!-- Default output section - only shown if slot not used -->
<slot name="output-content">
<div class="text-center text-xl font-semibold py-2">Output Messages</div>
<OSeparator class="mr-4 mt-4" />
<div class="error-report-container">
<div class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0">Output Messages</div>
<OSeparator class="mt-1 shrink-0" />
<div class="error-report-container flex-1 min-h-0 overflow-auto">
<div class="text-center p-3 text-gray-400">
No messages to display
</div>
@@ -365,12 +364,6 @@ export default defineComponent({
};
});
const outputContainerStyle = computed(() => {
return {
height: props.editorHeights.outputContainer,
};
});
// Methods
const handleBack = () => {
emit("back");
@@ -542,7 +535,6 @@ export default defineComponent({
updateJsonArray,
contentStyle,
splitterStyle,
outputContainerStyle,
};
},
});
@@ -561,23 +553,15 @@ export default defineComponent({
box-sizing: border-box;
/* w-full (100%) + mx-2 (1rem total) would overflow by 1rem and add a
horizontal scrollbar; subtract the margins so the box stays inside and
keeps a right-side gap. */
keeps a right-side gap. The height comes from flex (flex-1 min-h-0) so the
editor grows to fill the pane instead of a brittle calc(100vh - Npx). */
width: calc(100% - 1rem);
border: 1px solid var(--o2-border-color);
border-radius: 0.375rem;
overflow: hidden;
}
.import-url-editor {
height: v-bind('editorHeights.urlEditor');
}
.import-file-editor {
height: v-bind('editorHeights.fileEditor');
}
.error-report-container {
height: v-bind('editorHeights.errorReport') !important;
overflow: auto;
resize: none;
}
@@ -269,7 +269,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template #bottom>
<div class="flex items-center justify-between w-full py-2">
<div class="flex items-center font-bold text-[14px] mr-4">
<div class="flex items-center o2-table-footer-title mr-4">
{{ resultTotal }} {{ t('function.enrichmentTables') }}
</div>
<OButton
@@ -130,7 +130,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template #bottom="scope">
<div class="flex items-center justify-between w-full py-2">
<div class="flex items-center font-bold text-[14px] mr-4">
<div class="flex items-center o2-table-footer-title mr-4">
{{ resultTotal }} {{ t('function.header') }}
</div>
<OButton
+1 -1
View File
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0 h-full flex flex-col">
<div class="p-0 h-full flex flex-col">
<!-- Standard section header: title + description + Create action. -->
<AppPageHeader
icon="key"
+2 -2
View File
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0 h-full flex flex-col">
<div class="p-0 h-full flex flex-col">
<!-- Standard page header: title + actions only. Search moved into the
table's own toolbar (built-in global filter). -->
<AppPageHeader
@@ -116,7 +116,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
</template>
<template #bottom>
<span class="o2-table-footer-title text-text-primary">{{ rows.length }} {{ t('iam.groups') }}</span>
<span class="o2-table-footer-title">{{ rows.length }} {{ t('iam.groups') }}</span>
<OButton
v-if="selectedGroups.length > 0"
data-test="iam-groups-bulk-delete-btn"
@@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- eslint-disable vue/v-on-event-hyphenation -->
<!-- eslint-disable vue/attribute-hyphenation -->
<template>
<div class="rounded-md p-0 h-full flex flex-col">
<div class="p-0 h-full flex flex-col">
<!-- Standard page header: title + actions only. Search moved into the
table's own toolbar (built-in global filter). -->
<AppPageHeader
+1 -1
View File
@@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<!-- TODO: we need to completely remove the store.state.theme based styling on this page as we have moved it to central place app.scss -->
<template>
<div class="rounded-md quota-page text-left h-full flex flex-col"
<div class="quota-page text-left h-full flex flex-col"
:class="
store.state.theme === 'dark' ? 'dark-theme-page' : 'light-theme-page'
"
+1 -1
View File
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0 h-full flex flex-col">
<div class="p-0 h-full flex flex-col">
<!-- Standard page header: title + actions only. Search moved into the
table's own toolbar (built-in global filter). -->
<AppPageHeader
+1 -1
View File
@@ -137,7 +137,7 @@ const columns: OTableColumnDef[] = [
</template>
<template #bottom>
<span class="o2-table-footer-title text-text-primary">{{ data.length }} {{ t("iam.roles") }}</span>
<span class="o2-table-footer-title">{{ data.length }} {{ t("iam.roles") }}</span>
<OButton
v-if="(selectedIds?.length ?? 0) > 0"
data-test="iam-roles-bulk-delete-btn"
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template>
<div class="rounded-md p-0 h-full flex flex-col">
<div class="p-0 h-full flex flex-col">
<!-- Standard page header: title + actions only. Search moved into the
table's own toolbar (built-in global filter). -->
<AppPageHeader
@@ -171,7 +171,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>
<template #bottom>
<span class="o2-table-footer-title text-text-primary">{{ serviceAccountsState.service_accounts_users.length }} {{ t('serviceAccounts.header') }}</span>
<span class="o2-table-footer-title">{{ serviceAccountsState.service_accounts_users.length }} {{ t('serviceAccounts.header') }}</span>
<OButton
v-if="selectedAccounts.length > 0"
data-test="service-accounts-list-delete-accounts-btn"
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0 h-full flex flex-col">
<div class="p-0 h-full flex flex-col">
<!-- Standard page header: title + icon + subtitle, matching the Users page. -->
<AppPageHeader
:title="t('invitation.pendingInvitations')"
@@ -108,7 +108,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
</template>
<template #bottom>
<span class="o2-table-footer-title text-text-primary">
<span class="o2-table-footer-title">
{{ resultTotal }} {{ t('invitation.pendingInvitations') }}
</span>
</template>
+2 -2
View File
@@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- eslint-disable vue/v-on-event-hyphenation -->
<!-- eslint-disable vue/attribute-hyphenation -->
<template>
<div class="rounded-md p-0 h-full flex flex-col">
<div class="p-0 h-full flex flex-col">
<!-- Standard page header: title + actions only. The user search moved into
the table's own toolbar (built-in global filter) per the layout system. -->
<AppPageHeader
@@ -175,7 +175,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</OButton>
</template>
<template #bottom>
<span class="o2-table-footer-title text-text-primary">{{ rows.length }} {{ isEnterpriseOrCloud ? (t('iam.organizationMembers') || 'Organization Members') : t('iam.basicUsers') }}</span>
<span class="o2-table-footer-title">{{ rows.length }} {{ isEnterpriseOrCloud ? (t('iam.organizationMembers') || 'Organization Members') : t('iam.basicUsers') }}</span>
<OButton
v-if="selectedUsers.length > 0"
data-test="users-list-delete-users-btn"
+6 -11
View File
@@ -20,29 +20,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
title="Import Pipeline"
test-prefix="pipeline"
hide-header
container-class=""
:is-importing="isPipelineImporting"
:editor-heights="{
urlEditor: 'calc(100vh - 285px)',
fileEditor: 'calc(100vh - 282px)',
outputContainer: 'calc(100vh - 112px)',
errorReport: 'calc(100vh - 132px)',
}"
@back="router.back()"
@cancel="router.back()"
@import="importJson"
>
<!-- Output Section with Pipeline-specific Error Display -->
<template #output-content>
<div class="w-full h-full border-l border-border-default" style="min-width: 400px;">
<div class="w-full h-full flex flex-col border-l border-border-default" style="min-width: 400px;">
<div
v-if="pipelineErrorsToDisplay.length > 0"
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
>
Error Validations
</div>
<div v-else class="text-center text-xl font-semibold py-2">Output Messages</div>
<OSeparator class="mr-4 mt-4" />
<div class="error-report-container" style="height: calc(100vh - 128px) !important; overflow: auto; resize: none;">
<div v-else class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0">Output Messages</div>
<OSeparator class="mt-1 shrink-0" />
<div class="error-report-container flex-1 min-h-0">
<!-- Pipeline Errors Section -->
<div
class="p-2.5 mb-2.5"
@@ -15,14 +15,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0"
<div class="p-0"
>
<AppPageHeader
:back="{
label: t('pipeline_destinations.header'),
onClick: () => emit('cancel'),
}"
class="rounded-lg px-3 border-b border-border-default"
class="px-3 border-b border-border-default"
>
<template #title>
<span data-test="pipeline-destination-editor-title">
@@ -273,7 +273,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class="flex items-center justify-between w-full py-1"
>
<div
class="flex items-center text-sm mr-4"
class="flex items-center o2-table-footer-title mr-4"
>
{{ bottomProps.totalRows }} {{ t("pipeline.header") }}
</div>
@@ -108,7 +108,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- Bottom footer -->
<template #bottom="{ totalRows }">
<div
class="flex items-center font-bold text-[14px] mr-4 py-2"
class="flex items-center o2-table-footer-title mr-4 py-2"
>
{{ totalRows }} Backfill Job{{ totalRows === 1 ? "" : "s" }}
</div>
@@ -226,7 +226,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template #bottom="{ totalRows }">
<div
class="flex items-center font-bold text-[14px] mr-4 py-2"
class="flex items-center o2-table-footer-title mr-4 py-2"
>
{{ totalRows }} {{ t("pipeline.header") }}
</div>
@@ -627,7 +627,11 @@ const columns = ref([
accessorKey: "status",
sortable: true,
hideable: true,
size: 150,
// Wide enough for the longest status chip ("Condition Not Satisfied");
// minSize stops the column shrinking and clipping the pill on narrow
// viewports (the cell clips non-wrapped content by design).
size: 200,
minSize: 200,
meta: { align: "left" as const },
},
{
@@ -83,13 +83,13 @@ describe("ErrorHeader", () => {
expect(wrapper.find('[data-test="back-button"]').exists()).toBe(true);
});
it("has title attribute Go Back on the back button", async () => {
it("has a Back title attribute on the back button", async () => {
// Arrange
wrapper = mountComponent();
await flushPromises();
// Assert
expect(wrapper.find('[data-test="back-button"]').attributes("title")).toBe("Go Back");
// Assert — AppPageHeader's back button uses the standard "Back" label.
expect(wrapper.find('[data-test="back-button"]').attributes("title")).toBe("Back");
});
it("calls router.back() when the back button is clicked", async () => {
@@ -17,35 +17,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template>
<div>
<div>
<div class="pt-2 pb-1 flex justify-start">
<div
data-test="back-button"
class="flex justify-center items-center mr-3 cursor-pointer hover:text-[var(--o2-primary-btn-bg)]"
style="
border: 1.5px solid;
border-radius: 50%;
width: 22px;
height: 22px;
"
title="Go Back"
@click="router.back()"
>
<OIcon name="arrow-back-ios-new" size="xs" />
</div>
<span class="font-bold">{{ t("rum.eventID") }}:</span>
<span
data-test="error-id"
:title="error.error_id"
class="pl-1 cursor-pointer"
>{{ error.error_id }}
<OIcon
size="xs"
name="content-copy"
class="hover:text-[var(--o2-primary-btn-bg)]"
@click="copyErrorId(error.error_id)"
/></span>
<span class="ml-4">{{ error.timestamp }}</span>
</div>
<AppPageHeader
:back="{ onClick: () => router.back(), dataTest: 'back-button' }"
>
<template #title>
<span class="font-bold">{{ t("rum.eventID") }}:</span>
<span
data-test="error-id"
:title="error.error_id"
class="pl-1 cursor-pointer"
>{{ error.error_id }}
<OIcon
size="xs"
name="content-copy"
class="hover:text-[var(--o2-primary-btn-bg)]"
@click="copyErrorId(error.error_id)"
/></span>
</template>
<template #actions>
<span>{{ error.timestamp }}</span>
</template>
</AppPageHeader>
<div class="flex items-center flex-nowrap my-1">
<div
data-test="error-header-error-type"
@@ -76,6 +68,7 @@ import { useRouter } from "vue-router";
import { copyToClipboard } from "@/utils/clipboard";
import { useI18n } from "vue-i18n";
import OIcon from "@/lib/core/Icon/OIcon.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
const { t } = useI18n();
const router = useRouter();
@@ -213,10 +213,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
name="testString"
data-test="add-regex-test-string-input"
class="regex-test-string-input"
:class="
store.state.theme === 'dark'
? 'dark-mode-regex-test-string-input'
: 'light-mode-regex-test-string-input bg-white! border-l! border-l-[#e6e6e6]! border-r! border-r-[#e6e6e6]! border-b! border-b-[#e6e6e6]!'"
tabindex="0"
style="width: 100%; resize: none"
placeholder="Eg. 1234567890"
@@ -243,10 +239,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:readonly="true"
data-test="add-regex-output-string-input"
class="regex-test-string-input"
:class="
store.state.theme === 'dark'
? 'dark-mode-regex-test-string-input'
: 'light-mode-regex-test-string-input bg-white! border-l! border-l-[#e6e6e6]! border-r! border-r-[#e6e6e6]! border-b! border-b-[#e6e6e6]!'"
tabindex="0"
style="width: 100%; resize: none"
placeholder="Output String"
@@ -254,12 +246,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
<div
v-else
class="flex flex-col items-center justify-center h-[111px]"
:class="
store.state.theme === 'dark'
? 'bg-(--o2-primary-background)! border-l-2! border-r-2! border-b-2! border-(--o2-primary-background)!'
: 'bg-white! border-l! border-r! border-b! border-[#e6e6e6]!'
"
class="flex flex-col items-center justify-center h-[111px] rounded-md border border-input-border bg-input-bg"
>
<div v-if="!testLoading && outputStringValue.length === 0">
<OIcon
@@ -640,10 +627,12 @@ export default defineComponent({
padding-left: 0.5rem !important;
}
.light-mode-regex-test-string-input .monaco-editor-background {
background-color: #ffffff !important;
}
.dark-mode-regex-test-string-input .monaco-editor-background {
background-color: var(--o2-primary-background) !important;
/* The section header strips (Write Pattern / Input string / Output) are flat
full-width bars. Square the textareas' own border box so each field reads as
one connected unit under its strip instead of a detached rounded box. */
.regex-pattern-input .rounded-md.border,
.regex-test-string-input .rounded-md.border {
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
}
</style>
+1 -1
View File
@@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- eslint-disable vue/x-invalid-end-tag -->
<template>
<div class="rounded-md p-0" style="min-height: inherit; height: calc(100vh - 88px);">
<div class="p-0" style="min-height: inherit; height: calc(100vh - 88px);">
<div v-if="!showAddDialog">
<!-- Standard section header: title + actions only. Search moved into the
table toolbar below. -->
@@ -143,8 +143,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template #bottom="bottomProps">
<div
class="flex items-center gap-4 py-2 px-1 text-xs"
style="color: var(--o2-text-muted)"
class="flex items-center gap-4 py-2 px-1 o2-table-footer-title"
>
<span
>{{ bottomProps.totalRows }} model{{
+1 -1
View File
@@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- eslint-disable vue/x-invalid-end-tag -->
<template>
<div class="rounded-md flex flex-col h-full p-0">
<div class="flex flex-col h-full p-0">
<div v-if="!showAddDialog" class="flex flex-col h-full">
<!-- Standard section header: title + actions only. Search moved into the
table's own toolbar below. -->
@@ -19,14 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:title="t('modelPricing.importTitle')"
test-prefix="model-pricing"
:is-importing="isImporting"
container-class=""
container-style="height: calc(100vh - 50px);"
:editor-heights="{
urlEditor: 'calc(100vh - 285px)',
fileEditor: 'calc(100vh - 282px)',
outputContainer: 'calc(100vh - 110px)',
errorReport: 'calc(100vh - 128px)',
}"
container-class="flex-1 min-h-0"
container-style=""
:tabs="allTabs"
@back="arrowBackFn"
@cancel="arrowBackFn"
@@ -34,16 +28,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
@update:active-tab="handleTabChange"
>
<template #output-content>
<div class="w-full h-full border-l border-border-default" style="min-width: 400px;">
<div class="w-full h-full flex flex-col border-l border-border-default" style="min-width: 400px;">
<div
v-if="modelPricingErrorsToDisplay.length > 0"
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
>
{{ t('modelPricing.errorValidations') }}
</div>
<div v-else class="text-center text-xl font-semibold py-2">{{ t('modelPricing.outputMessages') }}</div>
<OSeparator class="mr-4 mt-4" />
<div class="overflow-auto resize-none" style="height: calc(100vh - 200px)">
<div v-else class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0">{{ t('modelPricing.outputMessages') }}</div>
<OSeparator class="mt-1 shrink-0" />
<div class="flex-1 min-h-0 overflow-auto resize-none">
<!-- Model Pricing Errors Section -->
<div
class="p-2.5 mb-2.5"
@@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="flex flex-col" style="height: calc(100vh - 50px);">
<div class="flex flex-col flex-1 min-h-0 h-full">
<!-- Standard page header (AppPageHeader), matching Add Panel / Import Dashboard.
BaseImport's built-in header is hidden (hide-header) so this single header
serves all three tabs. The title preserves the existing per-tab text: the
@@ -51,15 +51,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
title="Import Regex Pattern"
test-prefix="regex-pattern"
hide-header
container-class=""
container-class="flex-1 min-h-0"
container-style=""
:is-importing="isImporting"
:editor-heights="{
urlEditor: 'calc(100vh - 285px)',
fileEditor: 'calc(100vh - 282px)',
outputContainer: 'calc(100vh - 108px)',
errorReport: 'calc(100vh - 128px)',
}"
:tabs="allTabs"
@back="arrowBackFn"
@cancel="arrowBackFn"
@@ -67,16 +61,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
@update:active-tab="handleTabChange"
>
<template #output-content>
<div class="w-full h-full border-l border-border-default" style="min-width: 400px;">
<div class="w-full h-full flex flex-col border-l border-border-default" style="min-width: 400px;">
<div
v-if="regexPatternErrorsToDisplay.length > 0"
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
>
Error Validations
</div>
<div v-else class="text-center text-xl font-semibold py-2">Output Messages</div>
<OSeparator class="mt-4" />
<div class="overflow-auto resize-none" style="height: calc(100vh - 200px)">
<div v-else class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0">Output Messages</div>
<OSeparator class="mt-1 shrink-0" />
<div class="flex-1 min-h-0 overflow-auto resize-none">
<!-- Regex Pattern Errors Section -->
<div
class="p-2.5 mb-2.5"
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md flex flex-col h-full p-0">
<div class="flex flex-col h-full p-0">
<!-- Full-page Import View -->
<ImportModelPricing
v-if="showImportModelPricingPage"
+1 -1
View File
@@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- eslint-disable vue/x-invalid-end-tag -->
<template>
<div class="rounded-md flex flex-col h-full overflow-hidden">
<div class="flex flex-col h-full overflow-hidden">
<!-- Standard page header on top (full-width). The filter panel (left) + table
(right) sit below in the splitter — the standard header + left + right model. -->
<AppPageHeader
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md flex flex-col h-full p-0">
<div class="flex flex-col h-full p-0">
<div class="flex flex-col h-full">
<!-- Standard section header: title only. Search moved into the table toolbar. -->
<AppPageHeader
@@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md flex flex-col h-full p-0">
<div class="flex flex-col h-full p-0">
<template v-if="!showImportRegexPatternDialog">
<!-- Standard section header: title + actions only. Search moved to toolbar. -->
<AppPageHeader
@@ -131,7 +131,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>
<template #bottom>
<div class="flex items-center gap-2">
<span class="text-xs text-text-primary font-medium">
<span class="o2-table-footer-title">
{{ resultTotal }} {{ t("regex_patterns.bottom_header") }}
</span>
<OButton
@@ -138,7 +138,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class="flex items-center justify-between w-full py-2"
>
<div
class="flex items-center font-bold text-[14px] mr-4"
class="flex items-center o2-table-footer-title mr-4"
>
{{ bottomProps.totalRows }} {{ t("evalTemplate.header") }}
</div>
@@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- eslint-disable vue/x-invalid-end-tag -->
<template>
<div class="rounded-md p-0 pt-1 overflow-hidden h-full flex flex-col">
<div class="p-0 pt-1 overflow-hidden h-full flex flex-col">
<!-- Standard page header: title + icon. Usage date / data-type controls live
in the toolbar row below. -->
<AppPageHeader
@@ -16,12 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template>
<div class="rounded-md px-4 pt-3" style="min-height: inherit; overflow: auto">
<div v-if="!isChildOrg" class="flex justify-between items-center">
<div>
<h1 class="text-2xl font-bold leading-9 text-(--o2-text-heading) mt-0 mr-0 mb-2 ml-0 block">{{ t("billing.title") }}</h1>
<p class="text-base font-semibold leading-[1.375rem] text-(--o2-text-secondary) m-0 block">{{ t("billing.subtitle") }}</p>
</div>
</div>
<!-- Page title is supplied by the parent Billing.vue AppPageHeader; no local title here. -->
<!-- Managed billing empty state for child orgs -->
<div
v-if="isChildOrg"
@@ -18,24 +18,24 @@ the Free Software Foundation, either version 3 of the License, or
@import="importJson"
>
<template #output-content>
<div class="w-full" style="min-width: 380px">
<div class="w-full h-full flex flex-col" style="min-width: 380px">
<div
v-if="errors.length"
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
data-test="score-config-import-errors-title"
>
{{ t("onlineEvals.scoreConfig.import.errors.title") }}
</div>
<div
v-else
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
data-test="score-config-import-output-title"
>
{{ t("onlineEvals.scoreConfig.import.outputMessages") }}
</div>
<OSeparator class="mx-4 mt-4" />
<OSeparator class="mt-1 shrink-0" />
<div class="overflow-auto">
<div class="flex-1 min-h-0 overflow-auto">
<div v-if="errors.length" class="p-2.5 mb-2.5">
<div class="error-list">
<div
@@ -18,24 +18,24 @@ the Free Software Foundation, either version 3 of the License, or
@import="importJson"
>
<template #output-content>
<div class="w-full" style="min-width: 380px">
<div class="w-full h-full flex flex-col" style="min-width: 380px">
<div
v-if="errors.length"
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
data-test="scorer-import-errors-title"
>
{{ t("onlineEvals.scorer.import.errors.title") }}
</div>
<div
v-else
class="text-center text-xl font-semibold py-2"
class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0"
data-test="scorer-import-output-title"
>
{{ t("onlineEvals.scorer.import.outputMessages") }}
</div>
<OSeparator class="mx-4 mt-4" />
<OSeparator class="mt-1 shrink-0" />
<div class="overflow-auto">
<div class="flex-1 min-h-0 overflow-auto">
<div v-if="errors.length" class="p-2.5 mb-2.5">
<div class="error-list">
<div
@@ -101,7 +101,7 @@
</template>
<template #bottom="{ totalRows }">
<span class="o2-table-footer-title text-primary">
<span class="o2-table-footer-title">
{{ totalRows.toLocaleString() }} {{ t("onlineEvals.scoreConfig.listTitle") }}
</span>
<OButton
@@ -93,7 +93,7 @@
</template>
<template #bottom="{ totalRows }">
<span class="o2-table-footer-title text-primary">
<span class="o2-table-footer-title">
{{ totalRows.toLocaleString() }} {{ t("onlineEvals.scorer.listTitle") }}
</span>
<OButton
@@ -6,7 +6,7 @@
onClick: () => $emit('cancel'),
dataTest: 'job-form-back-btn',
}"
class="card-container px-3 border-b border-border-default"
class="px-3 border-b border-border-default"
style="flex-shrink: 0"
>
<template #title>
@@ -1,46 +1,21 @@
<template>
<form class="flex flex-col flex-1 min-h-0 bg-card-bg border border-dialog-header-border rounded-md" @submit.prevent="save">
<div class="flex items-center gap-2.5 min-h-12 px-3.5 py-2 border-b border-dialog-header-border shrink-0">
<OButton
variant="outline"
size="icon-sm"
icon-left="arrow-back-ios-new"
data-test="provider-form-back-btn"
:title="t('onlineEvals.provider.backTo')"
@click="$emit('cancel')"
/>
<div class="m-0 text-[17px] font-semibold text-text-primary tracking-[0.005em] whitespace-nowrap">
{{
mode === "create"
? t("onlineEvals.provider.createTitle")
: t("onlineEvals.provider.editTitle")
}}
</div>
<span class="text-text-secondary text-xs overflow-hidden text-ellipsis whitespace-nowrap min-w-0">{{ t("onlineEvals.provider.subtitle") }}</span>
<div class="flex-1 min-w-2" />
<button
type="button"
class="provider-form__close inline-flex items-center justify-center w-7 h-7 p-0 text-text-secondary bg-transparent border-0 rounded-md cursor-pointer transition-[background,color] duration-150 hover:bg-[color-mix(in_srgb,var(--color-text-primary)_6%,transparent)] hover:text-[var(--color-primary-600,#3F7994)]"
:aria-label="t('onlineEvals.buttons.cancel')"
data-test="provider-form-close-btn"
@click="$emit('cancel')"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<form class="flex flex-col flex-1 min-h-0 bg-card-bg" @submit.prevent="save">
<AppPageHeader
:subtitle="t('onlineEvals.provider.subtitle')"
:back="{
label: t('onlineEvals.provider.backTo'),
onClick: () => $emit('cancel'),
dataTest: 'provider-form-back-btn',
}"
class="px-3 border-b border-border-default"
style="flex-shrink: 0"
>
<template #title>
<span data-test="provider-form-title">
{{ mode === "create" ? t("onlineEvals.provider.createTitle") : t("onlineEvals.provider.editTitle") }}
</span>
</template>
</AppPageHeader>
<div class="flex-1 min-h-0 overflow-auto px-6 py-4.5 [&_textarea]:max-h-[220px] [&_textarea]:overflow-y-auto [&_textarea]:font-mono">
<section class="mb-6">
@@ -180,6 +155,7 @@ import OButton from "@/lib/core/Button/OButton.vue";
import OIcon from "@/lib/core/Icon/OIcon.vue";
import OInput from "@/lib/forms/Input/OInput.vue";
import OSelect from "@/lib/forms/Select/OSelect.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
import { toast } from "@/lib/feedback/Toast/useToast";
import onlineEvalsService, { type Provider } from "@/services/online-evals.service";
import {
@@ -9,7 +9,7 @@
onClick: () => $emit('cancel'),
dataTest: 'scorer-form-back-btn',
}"
class="card-container px-3 border-b border-border-default"
class="px-3 border-b border-border-default"
style="flex-shrink: 0"
>
<template #title>
+16
View File
@@ -893,6 +893,9 @@ defineExpose({
props.horizontalScroll ? 'min-w-max' : ((useComputedWidth && frozen) ? '' : 'w-full'),
props.horizontalScroll || props.defaultColumns ? 'table-auto' : 'table-fixed',
(props.bordered && !props.columns.some((c) => c.pinned || c.isAction)) ? '' : 'border-separate border-spacing-0',
// Without a leading selection/expansion gutter the first column would
// hug the table's left edge; inset it so it aligns like gutter tables.
(!selection.isEnabled.value && !expansion.isEnabled.value) ? 'o2-table--inset-first' : '',
]"
:style="{
...columnSizeVars,
@@ -1163,3 +1166,16 @@ defineExpose({
</div> <!-- /bordered wrapper -->
</div>
</template>
<style scoped>
/* When the table has no leading selection/expansion gutter, the first data
column would otherwise sit flush against the left edge with only the default
cell padding (0.5rem). Inset the first header + body cell so these tables read
with the same comfortable left margin as tables that do have a checkbox
gutter. Targeted by data-test prefix so full-width colspan rows (expanded /
tree-warning) are left untouched. */
.o2-table--inset-first :deep(th[data-test^="o2-table-th-"]:first-child),
.o2-table--inset-first :deep(td[data-test^="o2-table-cell-"]:first-child) {
padding-left: 1rem;
}
</style>
@@ -55,8 +55,11 @@ const pageSizeSelectOptions = computed(() =>
:data-test="`o2-table-pagination-${position}`"
class="flex items-center flex-wrap justify-between gap-x-3 gap-y-1 py-1 px-3 border-t border-border-default min-h-10"
>
<!-- Left: bulk actions slot or row count -->
<div class="flex items-center gap-2">
<!-- Left: bulk actions slot or row count.
The footer-title typography lives on this wrapper so BOTH the default
row count and any custom #bottom (actions) slot content inherit it —
consumers don't need to remember to add `o2-table-footer-title`. -->
<div class="flex items-center gap-2 o2-table-footer-title">
<!-- Loading: always skeleton, regardless of slot/count -->
<span
v-if="loading"
@@ -65,10 +68,7 @@ const pageSizeSelectOptions = computed(() =>
data-test="o2-table-pagination-count-skel"
/>
<slot v-else-if="slots.actions" name="actions" />
<span
v-else
class="o2-table-footer-title text-primary"
>
<span v-else>
{{ totalCount.toLocaleString() }} {{ title }}
</span>
</div>
+19 -5
View File
@@ -697,6 +697,19 @@ const isOpen = computed(() =>
listboxModeEnabled.value ? popoverOpen.value : selectOpen.value,
);
// Clicking the field label opens the dropdown — mirroring how clicking an
// OInput's label focuses its input. Reka's triggers open on `pointerdown`, so
// the `click` a native <label for> forwards never opens them; open explicitly.
// `.prevent` (on the template handler) stops the redundant native forwarding.
function handleLabelClick() {
if (props.disabled) return;
if (listboxModeEnabled.value) {
popoverOpen.value = true;
} else {
selectOpen.value = true;
}
}
// When this OSelect is nested inside an ODropdown, signal its
// open/close so the parent ignores the pointer event that closes us.
@@ -960,8 +973,9 @@ const fieldWidthClass = computed(() => {
:for="inputId"
:class="[
'o-input-label text-sm font-semibold leading-tight flex items-center gap-1',
disabled && 'o-input-label--disabled',
disabled ? 'o-input-label--disabled' : 'cursor-pointer',
]"
@click.prevent="handleLabelClick"
>
{{ label }}<span v-if="required" aria-hidden="true" class="select-none">*</span>
<OIcon
@@ -1006,8 +1020,8 @@ const fieldWidthClass = computed(() => {
labelPosition === 'inside' && label ? '' : ($slots['icon-left'] ? 'ps-2' : 'ps-3'),
'bg-select-bg',
hasError
? 'border-select-border-error'
: 'border-select-border hover:border-select-border-hover focus:border-select-border-focus',
? 'border-select-border-error focus:ring-[0.125rem] focus:ring-select-border-error/30 data-[state=open]:ring-[0.125rem] data-[state=open]:ring-select-border-error/30'
: 'border-select-border hover:border-select-border-hover focus:border-select-border-focus focus:ring-[0.125rem] focus:ring-primary-500/25 data-[state=open]:border-select-border-focus data-[state=open]:ring-[0.125rem] data-[state=open]:ring-primary-500/25',
/* Keep the red error border on focus; focus border color applies only when there's no error. */
'focus:outline-none',
'transition-[color,background-color,border-color,box-shadow] duration-150',
@@ -1538,8 +1552,8 @@ const fieldWidthClass = computed(() => {
labelPosition === 'inside' && label ? '' : 'ps-3',
'bg-select-bg',
hasError
? 'border-select-border-error'
: 'border-select-border hover:border-select-border-hover focus:border-select-border-focus',
? 'border-select-border-error focus:ring-[0.125rem] focus:ring-select-border-error/30 data-[state=open]:ring-[0.125rem] data-[state=open]:ring-select-border-error/30'
: 'border-select-border hover:border-select-border-hover focus:border-select-border-focus focus:ring-[0.125rem] focus:ring-primary-500/25 data-[state=open]:border-select-border-focus data-[state=open]:ring-[0.125rem] data-[state=open]:ring-primary-500/25',
/* Keep the red error border on focus; focus border color applies only when there's no error. */
'focus:outline-none',
'transition-[color,background-color,border-color,box-shadow] duration-150',
+10 -4
View File
@@ -199,8 +199,8 @@
--color-field-list-expansion-border: var(--color-border-default);
/* Stepper */
--color-stepper-indicator-default: var(--color-border-strong);
--color-stepper-indicator-default-text: var(--color-text-primary);
--color-stepper-indicator-default: var(--color-surface-subtle);
--color-stepper-indicator-default-text: var(--color-text-secondary);
--color-stepper-indicator-active: var(--color-primary-600);
--color-stepper-indicator-done: var(--color-primary-600);
--color-stepper-indicator-error: var(--color-error-600);
@@ -4210,9 +4210,15 @@ button.date-time-button {
border-bottom: 0.03rem solid var(--o2-border-color);
}
/* Single source of truth for the count/label shown in a listing-table footer
(e.g. "20 Dashboards"). Deliberately sets NO color so it inherits the exact
same ambient color as the "Showing X of Y / Records per page" pagination
text sitting beside it — the two must always read identically. Only size and
weight are fixed here; consumers apply ONLY this class plus layout utilities,
never their own color/size/weight, so every listing footer reads the same. */
.o2-table-footer-title {
font-size: 0.875rem;
font-weight: 700;
font-size: 0.75rem;
font-weight: 400;
line-height: 1rem;
}
@@ -283,7 +283,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- Footer: selected count -->
<div
class="p-3 border-t border-solid border-[var(--o2-border-color)] o2-table-footer-title text-[var(--o2-text-4)]!"
class="p-3 border-t border-solid border-[var(--o2-border-color)] o2-table-footer-title"
>
{{ selectedMetricStreams.length }} of
{{ uniqueMetricStreams.length }} selected
@@ -785,7 +785,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- Footer: selected count -->
<div
class="p-3 border-t border-solid border-[var(--o2-border-color)] o2-table-footer-title text-[var(--o2-text-4)]!"
class="p-3 border-t border-solid border-[var(--o2-border-color)] o2-table-footer-title"
>
{{ selectedMetricStreams.length }} of
{{ uniqueMetricStreams.length }} selected
+42 -51
View File
@@ -15,64 +15,53 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="rounded-md p-0 bg-(--q-background)">
<div class="w-full flex flex-col h-full overflow-hidden">
<!-- Header Card -->
<div class="card-container mb-[0.625rem] mt-[0.325rem] mx-2.5 shrink-0">
<div class="flex justify-between w-full py-3 px-4 items-center">
<div class="flex items-center gap-3">
<div class="text-xl tracking-[0.005em] font-[600]" data-test="inspector-title">
Search Job Inspector
</div>
<div
v-if="profileData && !hasNoData"
<div class="p-0 bg-(--q-background) w-full h-full flex flex-col min-h-0">
<AppPageHeader
title="Search Job Inspector"
:back="{ onClick: goBack, dataTest: 'inspector-close-button' }"
class="shrink-0 px-4 border-b border-border-default"
>
<template #title>
<span data-test="inspector-title">Search Job Inspector</span>
</template>
<template #actions>
<div
v-if="profileData && !hasNoData"
:class="[
'flex items-center gap-1.5 px-2 py-1 rounded-md border',
store.state.theme === 'dark'
? 'bg-gray-800/50 border-gray-600'
: 'bg-gray-50 border-gray-200'
]"
>
<svg class="w-3.5 h-3.5 opacity-70" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="6" width="18" height="14" rx="2" :stroke="store.state.theme === 'dark' ? '#9CA3AF' : '#6B7280'" stroke-width="2"/>
<path d="M3 10h18M8 3v4M16 3v4" :stroke="store.state.theme === 'dark' ? '#9CA3AF' : '#6B7280'" stroke-width="2" stroke-linecap="round"/>
</svg>
<div class="flex items-center gap-1.5">
<span
:class="[
'flex items-center gap-1.5 px-2 py-1 rounded-md border',
'text-[10px] font-small px-1.5 py-0.5 rounded',
store.state.theme === 'dark'
? 'bg-gray-800/50 border-gray-600'
: 'bg-gray-50 border-gray-200'
? 'text-gray-300 bg-gray-700/50'
: 'text-gray-600 bg-gray-100'
]"
>
<svg class="w-[14px] h-[14px] opacity-70" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="6" width="18" height="14" rx="2" :stroke="store.state.theme === 'dark' ? '#9CA3AF' : '#6B7280'" stroke-width="2"/>
<path d="M3 10h18M8 3v4M16 3v4" :stroke="store.state.theme === 'dark' ? '#9CA3AF' : '#6B7280'" stroke-width="2" stroke-linecap="round"/>
</svg>
<div class="flex items-center gap-1.5">
<span
:class="[
'text-[10px] font-small px-1.5 py-0.5 rounded',
store.state.theme === 'dark'
? 'text-gray-300 bg-gray-700/50'
: 'text-gray-600 bg-gray-100'
]"
>
{{ store.state.timezone || 'UTC' }}
</span>
<div
:class="[
'text-xs font-semibold',
store.state.theme === 'dark' ? 'text-gray-200' : 'text-gray-800'
]"
>
{{ formatTimeRange(profileData.start_time, profileData.end_time) }}
</div>
</div>
{{ store.state.timezone || 'UTC' }}
</span>
<div
:class="[
'text-xs font-semibold',
store.state.theme === 'dark' ? 'text-gray-200' : 'text-gray-800'
]"
>
{{ formatTimeRange(profileData.start_time, profileData.end_time) }}
</div>
</div>
<div class="flex items-center">
<OButton
icon-left="close"
variant="ghost"
size="icon-sm"
@click="goBack"
data-test="inspector-close-button"
>
<OTooltip content="Close" />
</OButton>
</div>
</div>
</div>
</template>
</AppPageHeader>
<div class="w-full flex flex-col flex-1 min-h-0 overflow-hidden pt-2.5">
<!-- Summary Stats Card -->
<div v-if="!loading" class="mb-[0.625rem] mx-2.5 shrink-0">
<div class="grid gap-3" style="grid-template-columns: 1fr 1fr 1fr 1.6fr 0.9fr;">
@@ -370,6 +359,7 @@ import { useRouter, useRoute } from "vue-router";
import { useStore } from "vuex";
import searchService from "@/services/search";
import NoData from "@/components/shared/grid/NoData.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
import OButton from "@/lib/core/Button/OButton.vue";
import OIcon from "@/lib/core/Icon/OIcon.vue";
import ODrawer from "@/lib/overlay/Drawer/ODrawer.vue";
@@ -421,6 +411,7 @@ export default defineComponent({
name: "SearchJobInspector",
components: {
NoData,
AppPageHeader,
OButton,
ODrawer,
ODialog,
+159 -1
View File
@@ -29,8 +29,164 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"
>
<div v-if="showHeader" class="trace-combined-header-wrapper card-container border-b border-border-default">
<!-- New Modern Header -->
<!-- Standalone (routed) header: shared AppPageHeader -->
<AppPageHeader
v-if="mode === 'standalone'"
:title="traceTree[0]?.operationName || t('traces.loadingTrace')"
:back="
showBackButton
? {
onClick: handleBackOrClose,
dataTest: 'trace-details-back-btn',
}
: undefined
"
>
<template #title>
<span
data-test="trace-details-operation-name"
:title="traceTree[0]?.operationName"
>
{{ traceTree[0]?.operationName || t("traces.loadingTrace") }}
<OTooltip :content="traceTree[0]?.operationName" />
</span>
</template>
<template #subtitle>
<div
class="flex items-center space-x-2 text-[0.6875rem] text-(--o2-text-secondary) whitespace-nowrap"
>
<span>{{
formatTimestamp(traceStartTime, store.state.timezone)
}}</span>
<div class="bg-(--o2-text-3) py-0 w-px h-4" />
<span class="mr-1">
{{ t("traces.traceId") }}:
<span
data-test="trace-details-trace-id"
class="text-(--o2-text-primary) font-mono"
:title="effectiveTraceId"
>
{{ effectiveTraceId }}
</span>
</span>
<!-- Copy Trace ID Button -->
<OIcon
data-test="trace-details-copy-trace-id-btn"
name="content-copy"
size="xs"
class="cursor-pointer hover:text-(--o2-text-primary)"
:title="t('traces.copyTraceId')"
@click="copyTraceId"
/>
<!-- Session ID (LLM traces) -->
<template v-if="sessionId">
<div class="bg-(--o2-text-3) py-0 w-px h-4" />
<span class="mr-1">
Session ID:
<span
data-test="trace-details-session-id"
class="text-(--o2-text-primary) font-mono"
:title="sessionId"
>
{{ sessionId }}
</span>
</span>
<OIcon
data-test="trace-details-copy-session-id-btn"
name="content-copy"
size="xs"
class="cursor-pointer hover:text-(--o2-text-primary)"
title="Copy Session ID"
@click="copySessionId"
/>
</template>
<div class="bg-(--o2-text-3) py-0 w-px h-4" />
<!-- Span Count Badge -->
<span class="inline-flex">
<OTag
type="logsResultChip"
value="neutral"
data-test="trace-details-spans-count"
>
<span data-test="span-count-text">
{{ formatLargeNumber(effectiveSpanList.length) }}
{{ t("traces.spansLabel") }}
</span>
</OTag>
<OTooltip
:content="
effectiveSpanList.length + ' ' + t('traces.spansLabel')
"
/>
</span>
<div class="bg-(--o2-text-3) py-0 w-px h-4" />
<!-- Error Count Badge -->
<span class="inline-flex">
<OTag
type="logsResultChip"
value="error"
data-test="trace-details-error-spans-count"
>
<span
>{{ formatLargeNumber(errorSpansCount) }}
{{ t("traces.errorsLabel") }}</span
>
</OTag>
<OTooltip
:content="errorSpansCount + ' ' + t('traces.errorsLabel')"
/>
</span>
</div>
</template>
<template #actions>
<!-- Apply filters button -->
<OButton
v-if="areFiltersAdded"
data-test="trace-details-apply-filters-btn-right"
variant="outline"
size="xs"
@click="openFilterPopover"
>
<template #icon-left
><OIcon name="filter-alt" size="xs"
/></template>
<span class="text-[0.75rem]">{{ t("traces.viewFilters") }}</span>
<OTooltip :content="t('traces.reviewAndApplyFilters')" />
</OButton>
<!-- Share button -->
<share-button
v-if="showShareButton"
data-test="trace-details-share-link-btn"
:url="traceDetailsShareURL"
variant="outline"
size="icon-xs"
/>
<!-- Close button -->
<OButton
v-if="showCloseButton"
data-test="trace-details-close-btn"
variant="ghost"
size="icon-xs"
@click="handleBackOrClose"
>
<OIcon name="close" size="sm" />
<OTooltip :content="t('common.cancel')" />
</OButton>
</template>
</AppPageHeader>
<!-- Embedded (logs) header: existing inline header, kept as-is -->
<header
v-else
class="h-auto py-[0.125rem] flex! items-center justify-between bg-[var(--o2-surface)] pl-1"
>
<div class="flex items-center space-x-4 w-fit!">
@@ -803,6 +959,7 @@ import {
} from "vue";
import { cloneDeep } from "lodash-es";
import ShareButton from "@/components/common/ShareButton.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
import OTag from "@/lib/core/Badge/OTag.vue";
import useTraces from "@/composables/useTraces";
import TraceDetailsSidebar from "./TraceDetailsSidebar.vue";
@@ -967,6 +1124,7 @@ export default defineComponent({
},
components: {
ShareButton,
AppPageHeader,
OTag,
TraceDetailsSidebar,
TraceTree,
@@ -218,7 +218,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- Selected Count Footer -->
<div
class="p-3 border-t border-solid border-[var(--o2-border-color)] o2-table-footer-title text-[var(--o2-text-4)]!"
class="p-3 border-t border-solid border-[var(--o2-border-color)] o2-table-footer-title"
>
{{ selectedDimensions.length }}
{{ t("latencyInsights.dimensionsSelected") }}
+2 -7
View File
@@ -340,14 +340,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class="flex w-full justify-between items-center py-1"
>
<div
class="o2-table-footer-title flex items-center gap-2 shrink-0"
class="o2-table-footer-title flex items-center shrink-0"
>
<span class="text-text-primary">{{
resultTotal || 0
}}</span>
<span class="text-text-secondary">{{
t("dashboard.header")
}}</span>
{{ resultTotal || 0 }} {{ t("dashboard.header") }}
</div>
<div
v-if="selectedIds.length > 0"
+19 -21
View File
@@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="mx-2">
<div class="mx-2 flex flex-col h-[calc(100vh-var(--navbar-height))] min-h-0 overflow-hidden">
<AppPageHeader
:title="t('dashboard.importDashboard')"
:back="{ label: t('dashboard.header'), onClick: goBack }"
@@ -47,17 +47,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
</template>
</AppPageHeader>
<div class="flex w-full">
<div class="flex w-full min-w-0">
<div class="flex w-full flex-1 min-h-0">
<div class="flex w-full min-w-0 min-h-0">
<OSplitter
v-model="splitterModel"
class="w-full min-w-0"
class="w-full min-w-0 h-full min-h-0"
>
<template #before>
<OForm id="import-dashboard-form" :form="form">
<div class="w-full h-full">
<OForm id="import-dashboard-form" :form="form" class="h-full flex flex-col min-h-0">
<div class="w-full h-full flex flex-col min-h-0">
<div
class="card-container py-[0.625rem] pl-[0.625rem] mb-[0.625rem]"
class="card-container py-[0.625rem] pl-[0.625rem] mb-1 shrink-0"
>
<div class="app-tabs-container h-[36px] w-fit">
<app-tabs
@@ -71,10 +71,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
<div
v-if="activeTab == 'import_json_url'"
class="editor-container-url card-container py-1"
class="editor-container-url card-container py-1 flex-1 min-h-0 flex flex-col"
>
<div class="mx-2 my-2">
<div style="width: calc(100% - 10px)" class="flex gap-2 mt-2 w-full items-center">
<div class="mx-2 mt-1 mb-1 flex flex-col flex-1 min-h-0">
<div style="width: calc(100% - 10px)" class="flex gap-2 w-full items-center shrink-0">
<div
data-test="dashboard-import-url-input"
style="width: 69%"
@@ -102,7 +102,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
data-test="dashboard-import-url-editor"
ref="queryEditorFileRef"
editor-id="dashboards-query-editor-file"
class="my-4 h-[calc(100vh-285px)]! overflow-hidden resize-none border border-(--o2-border-color) rounded-md"
class="mt-2 flex-1 min-h-0 overflow-hidden resize-none border border-(--o2-border-color) rounded-md"
:debounceTime="300"
v-model:query="jsonStr"
language="json"
@@ -111,10 +111,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
<div
v-if="activeTab == 'import_json_file'"
class="dashboard-import-json-container card-container py-1"
class="dashboard-import-json-container card-container py-1 flex-1 min-h-0 flex flex-col"
>
<div class="mx-2 my-2">
<div style="width: calc(100% - 10px)" class="flex gap-2 w-full items-center">
<div class="mx-2 mt-1 mb-1 flex flex-col flex-1 min-h-0">
<div style="width: calc(100% - 10px)" class="flex gap-2 w-full items-center shrink-0">
<div
data-test="dashboard-import-file-input"
style="width: 69%"
@@ -159,13 +159,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
data-test="dashboard-import-json-file-editor"
ref="queryEditorJsonRef"
editor-id="dashboards-query-editor-json"
class="my-4 h-[calc(100vh-282px)]! overflow-hidden resize-none border border-(--o2-border-color) rounded-md"
class="mt-2 flex-1 min-h-0 overflow-hidden resize-none border border-(--o2-border-color) rounded-md"
:debounceTime="300"
v-model:query="jsonStr"
language="json"
/>
<div></div>
</div>
</div>
</div>
@@ -174,12 +172,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template #after>
<div
data-test="dashboard-import-error-container"
class="card-container h-[calc(100vh-110px)] border-l border-border-default"
class="card-container h-full flex flex-col min-h-0 border-l border-border-default"
>
<div class="text-center text-[1.0625rem] font-semibold leading-[1.45] tracking-[-0.02em] text-text-primary py-2">Error Validations</div>
<OSeparator class="mt-4" />
<div class="text-center text-[0.9375rem] font-semibold text-text-primary py-3 shrink-0">Error Validations</div>
<OSeparator class="mt-1 shrink-0" />
<div
class="error-section p-[10px] mb-[10px]"
class="error-section p-[10px] mb-[10px] flex-1 min-h-0 overflow-auto"
v-if="dashboardErrorsToDisplay.length > 0"
>
<div class="error-reporter-container">
+1 -1
View File
@@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- eslint-disable vue/x-invalid-end-tag -->
<template>
<div class="rounded-md ingestionPage h-full flex flex-col" data-test="ingestion-page">
<div class="ingestionPage h-full flex flex-col" data-test="ingestion-page">
<!-- Standard page header: title + icon. Search / token controls live in the
toolbar row below. -->
<!-- Standard page header: title + icon, with search / token controls and the
+1 -1
View File
@@ -215,7 +215,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class="flex items-center justify-between w-full py-2"
>
<div
class="flex items-center w-full font-bold text-[14px]"
class="flex items-center w-full o2-table-footer-title"
>
{{ scope.totalRows }} Stream(s)
<OButton
+56 -48
View File
@@ -17,55 +17,61 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<!-- eslint-disable vue/v-on-event-hyphenation -->
<!-- eslint-disable vue/attribute-hyphenation -->
<template>
<div :key="store.state.selectedOrganization.identifier">
<div class="pb-2.5">
<div class="card-container">
<div class="flex justify-between items-center py-2 px-3">
<div data-test="rum-performance-title" class="text-2xl">
{{ t("rum.performanceSummaryLabel") }}
</div>
<div class="flex items-center gap-2">
<DateTimePickerDashboard
class="rum-date-time-picker"
ref="dateTimePicker"
v-model="selectedDate"
menu-align="end"
/>
<AutoRefreshInterval
v-model="refreshInterval"
:min-refresh-interval="
store.state?.zoConfig?.min_auto_refresh_interval || 5
"
trigger
class="app-performance-auto-refresh-interval"
@trigger="refreshData"
/>
<OButton
icon-left="refresh"
:variant="isVariablesChanged ? 'ghost-warning' : 'outline'"
size="icon-toolbar"
data-test="rum-performance-refresh"
@click="refreshData"
>
<OTooltip :content="isVariablesChanged ? t('dashboard.refreshToApplyVariableChanges') : t('dashboard.refresh')" />
</OButton>
</div>
</div>
<OTabs
class="px-3"
v-model="activePerformanceTab"
align="left"
dense
<div
:key="store.state.selectedOrganization.identifier"
data-test="rum-performance-page"
class="w-full h-full flex flex-col min-h-0"
>
<AppPageHeader
:title="t('rum.performanceSummaryLabel')"
class="shrink-0 px-4 border-b border-border-default"
>
<template #title>
<span data-test="rum-performance-title">
{{ t("rum.performanceSummaryLabel") }}
</span>
</template>
<template #actions>
<DateTimePickerDashboard
class="rum-date-time-picker"
ref="dateTimePicker"
v-model="selectedDate"
menu-align="end"
/>
<AutoRefreshInterval
v-model="refreshInterval"
:min-refresh-interval="
store.state?.zoConfig?.min_auto_refresh_interval || 5
"
trigger
class="app-performance-auto-refresh-interval"
@trigger="refreshData"
/>
<OButton
icon-left="refresh"
:variant="isVariablesChanged ? 'ghost-warning' : 'outline'"
size="icon-toolbar"
data-test="rum-performance-refresh"
@click="refreshData"
>
<OTab
v-for="tab in tabs"
:key="tab.value"
:name="tab.value"
:label="tab.label"
/>
</OTabs>
</div>
</div>
<OTooltip :content="isVariablesChanged ? t('dashboard.refreshToApplyVariableChanges') : t('dashboard.refresh')" />
</OButton>
</template>
</AppPageHeader>
<OTabs
class="shrink-0 px-3 border-b border-border-default"
v-model="activePerformanceTab"
align="left"
dense
>
<OTab
v-for="tab in tabs"
:key="tab.value"
:name="tab.value"
:label="tab.label"
/>
</OTabs>
<router-view v-slot="{ Component }">
<keep-alive>
@@ -115,6 +121,7 @@ import usePerformance from "@/composables/rum/usePerformance";
import useRum from "@/composables/rum/useRum";
import OButton from "@/lib/core/Button/OButton.vue";
import OTooltip from "@/lib/overlay/Tooltip/OTooltip.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
export default defineComponent({
name: "AppPerformance",
@@ -125,6 +132,7 @@ export default defineComponent({
DateTimePickerDashboard,
OButton,
OTooltip,
AppPageHeader,
},
setup() {
const { t } = useI18n();
+2 -2
View File
@@ -181,7 +181,7 @@ describe("SessionViewer.vue", () => {
});
it('should render the "Go Back" navigation button', () => {
const backBtn = wrapper.find('[title="Go Back"]');
const backBtn = wrapper.find('[data-test="session-viewer-back-btn"]');
expect(backBtn.exists()).toBe(true);
});
@@ -222,7 +222,7 @@ describe("SessionViewer.vue", () => {
describe("navigation", () => {
it("should call router.back() when Go Back button is clicked", async () => {
const backSpy = vi.spyOn(router, "back");
const backBtn = wrapper.find('[title="Go Back"]');
const backBtn = wrapper.find('[data-test="session-viewer-back-btn"]');
expect(backBtn.exists()).toBe(true);
await backBtn.trigger("click");
expect(backSpy).toHaveBeenCalledTimes(1);
+53 -51
View File
@@ -16,59 +16,58 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template>
<div class="flex flex-col qp-2 h-full">
<div class="w-full flex items-end border-b border-border-default">
<div class="w-full flex card-container px-[0.625rem] py-[0.625rem]">
<div
class="flex justify-center items-center mr-3 cursor-pointer hover:text-[var(--o2-primary-btn-bg)] border-[1.5px] border-solid rounded-full w-[1.375rem] h-[1.375rem]"
title="Go Back"
@click="router.back()"
>
<OIcon name="arrow-back-ios-new" size="xs" />
</div>
<div class="text-xs truncate flex items-center gap-1.5 mr-3">
<OIcon name="language" size="sm" />
{{ sessionDetails.ip }}
</div>
<div class="text-xs truncate flex items-center gap-1.5 mr-3">
<OIcon name="calendar-month" size="sm" />
{{ sessionDetails.date }}
</div>
<div class="text-xs truncate flex items-center gap-1.5 mr-3">
<OIcon name="person" size="sm" />
{{ sessionDetails.user_email || "Unknown User" }}
</div>
<div class="text-xs truncate flex items-center gap-1.5 mr-3">
<OIcon name="location-on" size="sm" />
{{ sessionDetails.city }}, {{ sessionDetails.country }}
</div>
<div class="text-xs truncate flex items-center gap-1.5 mr-3">
<OIcon name="settings" size="sm" />
{{ sessionDetails.browser }}, {{ sessionDetails.os }}
</div>
<div
v-if="frustrationCount > 0"
class="text-xs truncate flex items-center"
:title="`${frustrationCount} frustration signal${frustrationCount > 1 ? 's' : ''} detected`"
data-test="session-viewer-frustration-summary"
>
<OIcon
name="sentiment-very-dissatisfied"
size="sm"
class="pr-1"
style="color: #fb923c"
data-test="frustration-summary-icon"
/>
<span
class="font-semibold"
style="color: #fb923c"
data-test="frustration-summary-text"
>{{ frustrationCount }} Frustration{{
frustrationCount > 1 ? "s" : ""
}}</span
<AppPageHeader
:title="sessionDetails.id || t('rum.sessionReplay')"
:back="{ onClick: () => router.back(), dataTest: 'session-viewer-back-btn' }"
class="shrink-0 border-b border-border-default"
>
<template #subtitle>
<div class="flex items-center flex-wrap gap-x-3 gap-y-1 min-w-0">
<div class="text-xs truncate flex items-center gap-1.5">
<OIcon name="language" size="sm" />
{{ sessionDetails.ip }}
</div>
<div class="text-xs truncate flex items-center gap-1.5">
<OIcon name="calendar-month" size="sm" />
{{ sessionDetails.date }}
</div>
<div class="text-xs truncate flex items-center gap-1.5">
<OIcon name="person" size="sm" />
{{ sessionDetails.user_email || "Unknown User" }}
</div>
<div class="text-xs truncate flex items-center gap-1.5">
<OIcon name="location-on" size="sm" />
{{ sessionDetails.city }}, {{ sessionDetails.country }}
</div>
<div class="text-xs truncate flex items-center gap-1.5">
<OIcon name="settings" size="sm" />
{{ sessionDetails.browser }}, {{ sessionDetails.os }}
</div>
<div
v-if="frustrationCount > 0"
class="text-xs truncate flex items-center"
:title="`${frustrationCount} frustration signal${frustrationCount > 1 ? 's' : ''} detected`"
data-test="session-viewer-frustration-summary"
>
<OIcon
name="sentiment-very-dissatisfied"
size="sm"
class="pr-1"
style="color: #fb923c"
data-test="frustration-summary-icon"
/>
<span
class="font-semibold"
style="color: #fb923c"
data-test="frustration-summary-text"
>{{ frustrationCount }} Frustration{{
frustrationCount > 1 ? "s" : ""
}}</span
>
</div>
</div>
</div>
</div>
</template>
</AppPageHeader>
<div
class="w-full flex card-container overflow-hidden h-[calc(100%-3.125)]! flex-1 min-h-0"
>
@@ -122,12 +121,14 @@ import { cloneDeep } from "lodash-es";
import { computed, onBeforeMount, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import { useI18n } from "vue-i18n";
import searchService from "@/services/search";
import useQuery from "@/composables/useQuery";
import useSessionsReplay from "@/composables/useSessionReplay";
import usePerformance from "@/composables/rum/usePerformance";
import OIcon from "@/lib/core/Icon/OIcon.vue";
import OSplitter from "@/lib/core/Splitter/OSplitter.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
import { formatDate } from "@/utils/date";
import { getUUID } from "@/utils/zincutils";
@@ -151,6 +152,7 @@ const sessionId = ref("1");
const currentTime = ref(0);
const router = useRouter();
const store = useStore();
const { t } = useI18n();
const isLoading = ref<boolean[]>([]);
const { buildQueryPayload, getTimeInterval, parseQuery } = useQuery();
const segments = ref<any[]>([]);
+7 -22
View File
@@ -15,29 +15,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="flex flex-col w-full h-full px-2.5 bg-(--q-background)">
<div class="w-full h-full flex flex-col min-h-0 px-2.5 bg-(--q-background)">
<!-- Top Header Bar -->
<div class="header-bar shrink-0 card-container flex items-center justify-between py-[0.675rem] h-16 px-[0.675rem] mb-[0.675rem] border-b border-[var(--o2-border-color)]">
<div class="flex items-center gap-3">
<div
data-test="add-alert-back-btn"
class="flex justify-center items-center mr-3 cursor-pointer"
style="
border: 1.5px solid;
border-radius: 50%;
width: 22px;
height: 22px;
"
title="Go Back"
@click="navigateBack()"
>
<OIcon name="arrow-back-ios-new" size="xs" />
</div>
<div>
<div class="text-xl font-semibold text-weight-medium">Upload Source Maps</div>
</div>
</div>
</div>
<AppPageHeader
title="Upload Source Maps"
:back="{ onClick: navigateBack, dataTest: 'add-alert-back-btn' }"
class="px-4 border-b border-border-default"
/>
<!-- Form Content Area -->
<div class="flex-1 overflow-y-auto card-container mb-[0.675rem] p-6" style="height: calc(100vh - 172px); overflow: auto">
@@ -164,6 +148,7 @@ import sourcemapsService from "@/services/sourcemaps";
import OButton from "@/lib/core/Button/OButton.vue";
import OInput from "@/lib/forms/Input/OInput.vue";
import OIcon from "@/lib/core/Icon/OIcon.vue";
import AppPageHeader from "@/components/common/AppPageHeader.vue";
import { toast } from "@/lib/feedback/Toast/useToast";
const store = useStore();