mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
### **User description** This Pr fixes the #10110 , #10345 , #9908 ___ ### **PR Type** Bug fix, Enhancement ___ ### **Description** - Fix org dropdown fixed-height layout - Improve alert cloning modal spacing/alignment - Add tooltips for truncated select values - Correct `search_history.results` i18n key ___ ### Diagram Walkthrough ```mermaid flowchart LR header["Header org dropdown"] alerts["Alerts UI (clone/settings)"] templates["Template select layout + tooltip"] history["Search History UI + i18n"] i18n["Locale JSON updates"] header -- "fixed height" --> header alerts -- "spacing tweaks" --> alerts templates -- "ellipsis + tooltip" --> templates history -- "tab container classes" --> history history -- "rename key to `results`" --> i18n ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Bug fix</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>Header.vue</strong><dd><code>Set fixed height for org dropdown table</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-d2152ac954fdd715c1562bd834bcf6f3d79c64fc2e7ac5315279d29af1ffd4d2">+1/-1</a> </td> </tr> <tr> <td><strong>AlertSettings.vue</strong><dd><code>Add tooltips and fix template select layout</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-1d5bfd6a1ea672884193649b1271d751d6f3b8ec84af759071c9bfd169a8ab28">+35/-3</a> </td> </tr> <tr> <td><strong>en.json</strong><dd><code>Rename `resuls` to `results` key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-892aa3d75ab6e129ef9116aa21bec1b9a15ba69ce5366d2a19128078c690d824">+1/-1</a> </td> </tr> </table></details></td></tr><tr><td><strong>Enhancement</strong></td><td><details><summary>2 files</summary><table> <tr> <td><strong>AlertList.vue</strong><dd><code>Tweak clone dialog padding and actions alignment</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-be3e41b823e163b226ffc270d95cc4981e59c600851e4e0d10ee5e4ee994ac8e">+2/-2</a> </td> </tr> <tr> <td><strong>SearchHistory.vue</strong><dd><code>Update expanded row tabs container styling</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-9dd167bd5e272b2057f53c16b905047eec00655a3552daad240a964d67a6f56d">+3/-3</a> </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>10 files</summary><table> <tr> <td><strong>de.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-78f24ecb2dbcfc38fdf3ec22b2559a5b40d236ea689d1c55f5d3e87168310264">+2/-1</a> </td> </tr> <tr> <td><strong>es.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-ac2a2167b8b973e3f776f9fe3b43bef4576470d8ad33f3d7cfaff6edff0e5778">+2/-1</a> </td> </tr> <tr> <td><strong>fr.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-db80e762b88ddf183820c050cdc6b215e6b02b365b5e73f5152af46b0d26f1a2">+2/-1</a> </td> </tr> <tr> <td><strong>it.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-2de78074a5f887afddce60d694ead9322e6ab223eb70251c13819f3028bdc70d">+2/-1</a> </td> </tr> <tr> <td><strong>ja.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-092e49c5f9fbc1b539ab9c2866d108d0ce1d314022b4c294eb4611b285e7bbd3">+2/-1</a> </td> </tr> <tr> <td><strong>ko.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-50e27ca1b4825a5026f3a251bfcf79c97cab07cfade2ebd512d332453d707c48">+2/-1</a> </td> </tr> <tr> <td><strong>nl.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-a1706021687ae5d8a1c006c49eab3946174fecd895f9b70a89c2e76d97250906">+2/-1</a> </td> </tr> <tr> <td><strong>pt.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-38e804f64cb5e0e451849fc84e9b46e53c954a4a4263d51c18665ad45f4b4815">+2/-1</a> </td> </tr> <tr> <td><strong>tr.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-6f0f131e9e2591480c4c5d1eaffc72d02dc31cbd8d3f90d8f012f08cabac8818">+2/-1</a> </td> </tr> <tr> <td><strong>zh.json</strong><dd><code>Add `search_history.results` translation key</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/10377/files#diff-8818055db58f83230aeea4ebfd4cec31d924e8c47b6f91298e356959ab25339e">+2/-1</a> </td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___ --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
863 lines
26 KiB
Vue
863 lines
26 KiB
Vue
<!-- Copyright 2023 OpenObserve Inc.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
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>
|
|
<q-toolbar>
|
|
<!-- LOGO SECTION: Displays custom or default OpenObserve logo -->
|
|
<!-- Shows custom logo/text if configured in enterprise mode -->
|
|
<div
|
|
class="flex relative-position q-mr-sm"
|
|
v-if="
|
|
(config.isEnterprise == 'true' &&
|
|
store.state.zoConfig.hasOwnProperty('custom_logo_text') &&
|
|
store.state.zoConfig.custom_logo_text != '') ||
|
|
(config.isEnterprise == 'true' &&
|
|
store.state.zoConfig.hasOwnProperty('custom_logo_img') &&
|
|
store.state.zoConfig.custom_logo_img != null) ||
|
|
(config.isEnterprise == 'true' &&
|
|
store.state.zoConfig.hasOwnProperty('custom_logo_dark_img') &&
|
|
store.state.zoConfig.custom_logo_dark_img != null)
|
|
"
|
|
>
|
|
<!-- Custom logo text -->
|
|
<span
|
|
v-if="
|
|
store.state.zoConfig.hasOwnProperty('custom_logo_text') &&
|
|
store.state.zoConfig?.custom_logo_text != ''
|
|
"
|
|
class="text-h6 text-bold q-pa-none cursor-pointer q-mr-sm tw:flex tw:items-center"
|
|
@click="goToHome"
|
|
>{{ store.state.zoConfig.custom_logo_text }}</span
|
|
>
|
|
|
|
<!-- Custom logo image - shows appropriate logo based on current theme -->
|
|
<div class="tw:flex tw:items-center">
|
|
<!-- Dark mode: Show dark logo, fallback to light logo -->
|
|
<img
|
|
v-if="
|
|
store.state.theme === 'dark' &&
|
|
store.state.zoConfig.hasOwnProperty('custom_logo_dark_img') &&
|
|
store.state.zoConfig?.custom_logo_dark_img != null
|
|
"
|
|
:src="
|
|
`data:image; base64, ` + store.state.zoConfig?.custom_logo_dark_img
|
|
"
|
|
style="max-width: 150px; max-height: 32px;"
|
|
/>
|
|
<!-- Light mode: Show light logo, fallback to dark logo -->
|
|
<img
|
|
v-else-if="
|
|
store.state.theme === 'light' &&
|
|
store.state.zoConfig.hasOwnProperty('custom_logo_img') &&
|
|
store.state.zoConfig?.custom_logo_img != null
|
|
"
|
|
:src="
|
|
`data:image; base64, ` + store.state.zoConfig?.custom_logo_img
|
|
"
|
|
style="max-width: 150px; max-height: 32px;"
|
|
/>
|
|
<!-- Fallback: Show whichever logo exists (dark or light) -->
|
|
<img
|
|
v-else-if="
|
|
store.state.zoConfig.hasOwnProperty('custom_logo_dark_img') &&
|
|
store.state.zoConfig?.custom_logo_dark_img != null
|
|
"
|
|
:src="
|
|
`data:image; base64, ` + store.state.zoConfig?.custom_logo_dark_img
|
|
"
|
|
style="max-width: 150px; max-height: 32px;"
|
|
/>
|
|
<img
|
|
v-else-if="
|
|
store.state.zoConfig.hasOwnProperty('custom_logo_img') &&
|
|
store.state.zoConfig?.custom_logo_img != null
|
|
"
|
|
:src="
|
|
`data:image; base64, ` + store.state.zoConfig?.custom_logo_img
|
|
"
|
|
style="max-width: 150px; max-height: 32px;"
|
|
/>
|
|
</div>
|
|
|
|
|
|
<!-- OpenObserve logo (shown alongside custom logo if configured) -->
|
|
<div
|
|
v-if="store.state.zoConfig.custom_hide_self_logo == false"
|
|
class="logo-container"
|
|
>
|
|
<img
|
|
class="openobserve-logo cursor-pointer"
|
|
:src="getImageURL(
|
|
store.state.theme === 'dark'
|
|
? 'images/common/openobserve_latest_dark_2.svg'
|
|
: 'images/common/openobserve_latest_light_2.svg'
|
|
)"
|
|
@click="goToHome"
|
|
alt="OpenObserve"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Default OpenObserve logo (when no custom logo) -->
|
|
<div v-else class="flex relative-position q-mr-sm logo-container">
|
|
<img
|
|
class="openobserve-logo cursor-pointer"
|
|
:src="getImageURL(
|
|
store.state.theme === 'dark'
|
|
? 'images/common/openobserve_latest_dark_2.svg'
|
|
: 'images/common/openobserve_latest_light_2.svg'
|
|
)"
|
|
@click="goToHome"
|
|
alt="OpenObserve"
|
|
/>
|
|
</div>
|
|
|
|
<q-toolbar-title></q-toolbar-title>
|
|
|
|
<!-- QUOTA WARNING SECTION: Shows warning when quota threshold is reached -->
|
|
<div
|
|
class="headerMenu float-left"
|
|
v-if="store.state.organizationData.quotaThresholdMsg"
|
|
>
|
|
<div
|
|
type="warning"
|
|
icon="cloud"
|
|
class="warning-msg"
|
|
style="display: inline"
|
|
>
|
|
<q-icon name="warning" size="xs" class="warning" />{{
|
|
store.state.organizationData.quotaThresholdMsg
|
|
}}
|
|
</div>
|
|
<q-btn
|
|
color="secondary"
|
|
size="sm"
|
|
style="display: inline; padding: 5px 10px"
|
|
rounded
|
|
borderless
|
|
dense
|
|
class="q-ma-xs"
|
|
@click="router.replace('/billings/plans')"
|
|
>Upgrade to PRO Plan</q-btn
|
|
>
|
|
</div>
|
|
|
|
<!-- HEADER MENU: Contains all header navigation and user controls -->
|
|
<div class="header-menu">
|
|
<!-- UPGRADE TO ENTERPRISE BUTTON: Shows for non-enterprise users -->
|
|
<q-btn
|
|
no-caps
|
|
flat
|
|
dense
|
|
class="upgrade-enterprise-btn q-px-sm q-mx-xs"
|
|
@click="openEnterpriseDialog"
|
|
data-test="upgrade-to-enterprise-btn"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<q-icon name="card_giftcard" size="16px" class="q-mr-xs" />
|
|
<span class="text-weight-medium">{{ enterpriseButtonText }}</span>
|
|
</div>
|
|
</q-btn>
|
|
|
|
<!-- INGESTION QUOTA WARNING: Shows when 85%+ of ingestion limit is used -->
|
|
<q-btn
|
|
v-if="
|
|
config.isEnterprise == 'true' &&
|
|
store.state.zoConfig.ingestion_quota_used >= 85
|
|
"
|
|
round
|
|
flat
|
|
dense
|
|
:ripple="false"
|
|
data-test="ingestion-quota-warning-icon"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<q-icon
|
|
name="warning"
|
|
size="24px"
|
|
class="header-icon"
|
|
:style="{ color: ingestionQuotaColor }"
|
|
></q-icon>
|
|
</div>
|
|
<q-tooltip anchor="top middle" self="bottom middle">
|
|
Warning: {{ ingestionQuotaPercentage }}% of ingestion limit used
|
|
</q-tooltip>
|
|
</q-btn>
|
|
|
|
<!-- AI CHAT TOGGLE: Enterprise feature to toggle AI chat panel -->
|
|
<q-btn
|
|
v-if="
|
|
config.isEnterprise == 'true' && store.state.zoConfig.ai_enabled
|
|
"
|
|
:ripple="false"
|
|
@click="toggleAIChat"
|
|
data-test="menu-link-ai-item"
|
|
no-caps
|
|
:borderless="true"
|
|
flat
|
|
dense
|
|
class="o2-button ai-hover-btn q-px-sm q-py-sm"
|
|
:class="store.state.isAiChatEnabled ? 'ai-btn-active' : ''"
|
|
style="border-radius: 100%"
|
|
@mouseenter="handleMouseEnter"
|
|
@mouseleave="handleMouseLeave"
|
|
>
|
|
<div class="row items-center no-wrap tw:gap-2">
|
|
<img :src="getBtnLogo" class="header-icon ai-icon" />
|
|
</div>
|
|
</q-btn>
|
|
|
|
<!-- ORGANIZATION SELECTOR: Dropdown to switch between organizations -->
|
|
<div data-test="navbar-organizations-select" class="q-mx-sm row">
|
|
<q-btn
|
|
style="max-width: 250px"
|
|
dense
|
|
no-caps
|
|
flat
|
|
class="tw:text-ellipsis tw:overflow-hidden"
|
|
>
|
|
<div class="row items-center no-wrap full-width">
|
|
<div class="col tw:truncate">
|
|
{{ userClickedOrg?.label || "" }}
|
|
</div>
|
|
<q-icon name="arrow_drop_down" class="q-ml-xs" />
|
|
</div>
|
|
|
|
<!-- Organization selection menu -->
|
|
<q-menu
|
|
anchor="bottom middle"
|
|
self="top middle"
|
|
class="organization-menu-o2"
|
|
>
|
|
<q-list data-test="organization-menu-list" style="width: 100%">
|
|
<q-item data-test="organization-menu-item" style="padding: 0">
|
|
<q-item-section data-test="organization-menu-item-section" class="column" style="padding: 0px">
|
|
<!-- Organization table with search functionality -->
|
|
<q-table
|
|
data-test="organization-menu-table"
|
|
:rows="filteredOrganizations"
|
|
:row-key="(row) => 'org_' + row.identifier"
|
|
:columns="[{ name: 'label', label: 'Organization', field: 'label', align: 'left' }]"
|
|
:visible-columns="['label']"
|
|
hide-header
|
|
:pagination="{ rowsPerPage }"
|
|
:rows-per-page-options="[]"
|
|
class="org-table"
|
|
style="width: 470px; min-height: 420px; height: 420px;"
|
|
>
|
|
<!-- Search input for filtering organizations -->
|
|
<template #top>
|
|
<div class="full-width">
|
|
<q-input
|
|
data-test="organization-search-input"
|
|
:model-value="searchQuery"
|
|
@update:model-value="(val) => $emit('update:searchQuery', val)"
|
|
data-cy="index-field-search-input"
|
|
borderless
|
|
dense
|
|
clearable
|
|
debounce="1"
|
|
autofocus
|
|
:placeholder="'Search Organization'"
|
|
>
|
|
<template #prepend>
|
|
<q-icon name="search" />
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Organization list item -->
|
|
<template v-slot:body-cell-label="props">
|
|
<q-td
|
|
:props="props"
|
|
class="org-list-item-cell"
|
|
@click="handleOrgSelection(props.row)"
|
|
>
|
|
<div
|
|
class="org-menu-item"
|
|
v-close-popup
|
|
data-test="organization-menu-item-label-item-label"
|
|
:class="{
|
|
'org-menu-item--active':
|
|
props.row.identifier === userClickedOrg?.identifier,
|
|
}"
|
|
>
|
|
{{
|
|
props.row.label.length > 30
|
|
? props.row.label.substring(0, 30) +
|
|
"... | " +
|
|
props.row.identifier
|
|
: props.row.label + " | " + props.row.identifier
|
|
}}
|
|
<q-tooltip
|
|
v-if="props.row.label.length > 30"
|
|
anchor="bottom middle"
|
|
self="top start"
|
|
>
|
|
{{ props.row.label }}
|
|
</q-tooltip>
|
|
</div>
|
|
</q-td>
|
|
</template>
|
|
|
|
<!-- No data message -->
|
|
<template v-slot:no-data>
|
|
<div
|
|
data-test="organization-menu-no-data"
|
|
class="text-center q-pa-sm tw:w-full tw:flex tw:justify-center"
|
|
>
|
|
No organizations found
|
|
</div>
|
|
</template>
|
|
</q-table>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
</div>
|
|
|
|
<!-- THEME SWITCHER: Toggle between light and dark mode -->
|
|
<ThemeSwitcher></ThemeSwitcher>
|
|
|
|
<!-- SLACK COMMUNITY LINK -->
|
|
<q-btn
|
|
round
|
|
flat
|
|
dense
|
|
:ripple="false"
|
|
@click="openSlack"
|
|
data-test="menu-link-slack-item"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<q-icon
|
|
><component :is="slackIcon" size="32px" class="header-icon"
|
|
/></q-icon>
|
|
</div>
|
|
<q-tooltip anchor="top middle" self="bottom middle">
|
|
{{ t("menu.slack") }}
|
|
</q-tooltip>
|
|
</q-btn>
|
|
|
|
<!-- HELP MENU: Contains links to docs, API, and about page -->
|
|
<q-btn
|
|
round
|
|
flat
|
|
dense
|
|
:ripple="false"
|
|
data-test="menu-link-help-item"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<q-icon name="help_outline" class="header-icon"></q-icon>
|
|
<q-tooltip anchor="top middle" self="bottom middle">
|
|
{{ t("menu.help") }}
|
|
</q-tooltip>
|
|
</div>
|
|
|
|
<q-menu
|
|
fit
|
|
anchor="bottom right"
|
|
self="top right"
|
|
transition-show="jump-down"
|
|
transition-hide="jump-up"
|
|
class="header-menu-bar"
|
|
>
|
|
<q-list style="min-width: 250px">
|
|
<!-- OpenAPI link (only for non-cloud deployments) -->
|
|
<div
|
|
v-if="
|
|
config.isCloud !== 'true' &&
|
|
!store.state.zoConfig?.custom_hide_menus
|
|
?.split(',')
|
|
?.includes('openapi')
|
|
"
|
|
>
|
|
<q-item clickable @click="navigateToOpenAPI(zoBackendUrl)">
|
|
<q-item-section>
|
|
<q-item-label>
|
|
{{ t(`menu.openapi`) }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-separator />
|
|
</div>
|
|
|
|
<!-- Documentation link -->
|
|
<q-item clickable @click="navigateToDocs()">
|
|
<q-item-section>
|
|
<q-item-label>
|
|
{{ t(`menu.docs`) }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-separator />
|
|
|
|
<!-- About page link -->
|
|
<q-item clickable @click="goToAbout" data-test="menu-link-about-item">
|
|
<q-item-section>
|
|
<q-item-label>
|
|
{{ t(`menu.about`) }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
|
|
<!-- SETTINGS BUTTON -->
|
|
<q-btn
|
|
data-test="menu-link-settings-item"
|
|
round
|
|
flat
|
|
dense
|
|
:ripple="false"
|
|
@click="router.push({ name: 'settings' })"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<q-icon :name="outlinedSettings" class="header-icon"></q-icon>
|
|
</div>
|
|
<q-tooltip anchor="top middle" self="bottom middle">
|
|
{{ t("menu.settings") }}
|
|
</q-tooltip>
|
|
</q-btn>
|
|
|
|
<!-- USER PROFILE MENU: Profile, language, theme, and logout -->
|
|
<q-btn
|
|
round
|
|
flat
|
|
dense
|
|
:ripple="false"
|
|
data-test="header-my-account-profile-icon"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<q-icon
|
|
:name="user.picture ? user.picture : 'person'"
|
|
class="header-icon"
|
|
></q-icon>
|
|
<q-tooltip anchor="top middle" self="bottom middle">
|
|
{{
|
|
user.given_name
|
|
? user.given_name + " " + user.family_name
|
|
: user.email
|
|
}}</q-tooltip
|
|
>
|
|
</div>
|
|
|
|
<q-menu
|
|
fit
|
|
anchor="bottom right"
|
|
self="top right"
|
|
transition-show="jump-down"
|
|
transition-hide="jump-up"
|
|
class="header-menu-bar"
|
|
>
|
|
<q-list style="min-width: 250px">
|
|
<!-- User information -->
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<q-icon
|
|
:name="user.picture ? user.picture : 'person'"
|
|
size="xs"
|
|
></q-icon>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{
|
|
user.given_name
|
|
? user.given_name + " " + user.family_name
|
|
: user.email
|
|
}}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-separator />
|
|
|
|
<!-- Language selector -->
|
|
<q-item clickable>
|
|
<q-item-section avatar>
|
|
<q-icon size="xs" name="language" class="padding-none" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="tw:w-[180px]">{{
|
|
t("menu.language")
|
|
}}</q-item-label>
|
|
</q-item-section>
|
|
<q-item-section></q-item-section>
|
|
<q-item-section side>
|
|
<div class="q-gutter-xs">
|
|
<q-icon
|
|
size="xs"
|
|
:name="selectedLanguage.icon"
|
|
class="padding-none"
|
|
/>
|
|
<span
|
|
class="cursor-pointer vertical-bottom q-mt-sm selected-lang-label"
|
|
>{{ selectedLanguage.label }}</span
|
|
>
|
|
</div>
|
|
</q-item-section>
|
|
<q-item-section side style="padding-left: 0px">
|
|
<q-icon
|
|
class="icon-ley-arrow-right"
|
|
name="keyboard_arrow_right"
|
|
/>
|
|
</q-item-section>
|
|
|
|
<!-- Language selection submenu -->
|
|
<q-menu
|
|
auto-close
|
|
anchor="top end"
|
|
self="top start"
|
|
data-test="language-dropdown-item"
|
|
class="header-menu-bar"
|
|
>
|
|
<q-list>
|
|
<q-item
|
|
v-for="lang in langList"
|
|
:key="lang.code"
|
|
v-bind="lang"
|
|
dense
|
|
clickable
|
|
@click="changeLanguage(lang)"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-icon
|
|
size="xs"
|
|
:name="lang.icon"
|
|
class="padding-none"
|
|
/>
|
|
</q-item-section>
|
|
|
|
<q-item-section
|
|
:data-test="`language-dropdown-item-${lang.code}`"
|
|
>
|
|
<q-item-label>{{ lang.label }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-item>
|
|
<q-separator />
|
|
|
|
<!-- Theme management -->
|
|
<q-item
|
|
data-test="menu-link-predefined-themes-item"
|
|
v-ripple="true"
|
|
v-close-popup="true"
|
|
clickable
|
|
@click="openPredefinedThemes"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-icon size="xs" name="color_lens" class="padding-none" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ t("common.manageTheme") }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-separator />
|
|
|
|
<!-- Logout -->
|
|
<q-item
|
|
data-test="menu-link-logout-item"
|
|
v-ripple="true"
|
|
v-close-popup="true"
|
|
clickable
|
|
@click="signout"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-icon size="xs" name="exit_to_app" class="padding-none" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ t("menu.signOut") }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
</div>
|
|
|
|
<!-- Enterprise Upgrade Dialog -->
|
|
<EnterpriseUpgradeDialog v-model="showEnterpriseDialog" />
|
|
</q-toolbar>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, PropType, computed, ref } from "vue";
|
|
import { useI18n } from "vue-i18n";
|
|
import ThemeSwitcher from "./ThemeSwitcher.vue";
|
|
import EnterpriseUpgradeDialog from "./EnterpriseUpgradeDialog.vue";
|
|
import { outlinedSettings } from "@quasar/extras/material-icons-outlined";
|
|
import { getImageURL } from "@/utils/zincutils";
|
|
|
|
export default defineComponent({
|
|
name: "HeaderComponent",
|
|
components: {
|
|
ThemeSwitcher,
|
|
EnterpriseUpgradeDialog,
|
|
},
|
|
props: {
|
|
// Store instance
|
|
store: {
|
|
type: Object as PropType<any>,
|
|
required: true,
|
|
},
|
|
// Router instance
|
|
router: {
|
|
type: Object as PropType<any>,
|
|
required: true,
|
|
},
|
|
// Configuration object
|
|
config: {
|
|
type: Object as PropType<any>,
|
|
required: true,
|
|
},
|
|
// Current user information
|
|
user: {
|
|
type: Object as PropType<any>,
|
|
required: true,
|
|
},
|
|
// Slack icon component
|
|
slackIcon: {
|
|
type: Object as PropType<any>,
|
|
required: true,
|
|
},
|
|
// Backend URL
|
|
zoBackendUrl: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
// List of available languages
|
|
langList: {
|
|
type: Array as PropType<any[]>,
|
|
required: true,
|
|
},
|
|
// Currently selected language
|
|
selectedLanguage: {
|
|
type: Object as PropType<any>,
|
|
required: true,
|
|
},
|
|
// Selected organization
|
|
selectedOrg: {
|
|
type: Object as PropType<any>,
|
|
required: true,
|
|
},
|
|
// User clicked organization (for UI sync)
|
|
userClickedOrg: {
|
|
type: Object as PropType<any>,
|
|
required: true,
|
|
},
|
|
// Filtered list of organizations based on search
|
|
filteredOrganizations: {
|
|
type: Array as PropType<any[]>,
|
|
required: true,
|
|
},
|
|
// Search query for organization filter
|
|
searchQuery: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
// Rows per page for organization table
|
|
rowsPerPage: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
// AI button hover state
|
|
isHovered: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
// AI button logo based on state
|
|
getBtnLogo: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
},
|
|
emits: [
|
|
"update:selectedOrg",
|
|
"update:searchQuery",
|
|
"update:isHovered",
|
|
"updateOrganization",
|
|
"goToHome",
|
|
"goToAbout",
|
|
"toggleAIChat",
|
|
"openSlack",
|
|
"navigateToOpenAPI",
|
|
"navigateToDocs",
|
|
"changeLanguage",
|
|
"openPredefinedThemes",
|
|
"signout",
|
|
],
|
|
setup(props, { emit }) {
|
|
const { t } = useI18n();
|
|
|
|
// Enterprise upgrade dialog state
|
|
const showEnterpriseDialog = ref(false);
|
|
|
|
// Computed property for enterprise button text based on deployment type
|
|
const enterpriseButtonText = computed(() => {
|
|
const isEnterprise = props.config.isEnterprise === 'true';
|
|
const isCloud = props.config.isCloud === 'true';
|
|
|
|
if (isCloud) {
|
|
return t('about.header_button.cloud_features');
|
|
} else if (isEnterprise) {
|
|
return t('about.header_button.enterprise_edition');
|
|
} else {
|
|
return t('about.header_button.get_enterprise_free');
|
|
}
|
|
});
|
|
|
|
// Computed property for ingestion quota percentage
|
|
const ingestionQuotaPercentage = computed(() => {
|
|
return Math.ceil(props.store.state.zoConfig.ingestion_quota_used * 100) / 100 || 0;
|
|
});
|
|
|
|
// Computed property for ingestion quota warning color
|
|
const ingestionQuotaColor = computed(() => {
|
|
return props.store.state.zoConfig.ingestion_quota_used >= 95 ? 'red' : 'orange';
|
|
});
|
|
|
|
// Event handlers that emit to parent component
|
|
const updateOrganization = () => {
|
|
emit("updateOrganization");
|
|
};
|
|
|
|
const goToHome = () => {
|
|
emit("goToHome");
|
|
};
|
|
|
|
const goToAbout = () => {
|
|
emit("goToAbout");
|
|
};
|
|
|
|
const toggleAIChat = () => {
|
|
emit("toggleAIChat");
|
|
};
|
|
|
|
const openSlack = () => {
|
|
emit("openSlack");
|
|
};
|
|
|
|
const navigateToOpenAPI = (url: string) => {
|
|
emit("navigateToOpenAPI", url);
|
|
};
|
|
|
|
const navigateToDocs = () => {
|
|
emit("navigateToDocs");
|
|
};
|
|
|
|
const changeLanguage = (lang: any) => {
|
|
emit("changeLanguage", lang);
|
|
};
|
|
|
|
const openPredefinedThemes = () => {
|
|
emit("openPredefinedThemes");
|
|
};
|
|
|
|
const signout = () => {
|
|
emit("signout");
|
|
};
|
|
|
|
// Handle mouse hover events for AI button
|
|
const handleMouseEnter = () => {
|
|
emit("update:isHovered", true);
|
|
};
|
|
|
|
const handleMouseLeave = () => {
|
|
emit("update:isHovered", false);
|
|
};
|
|
|
|
// Handle organization selection from dropdown
|
|
const handleOrgSelection = (org: any) => {
|
|
emit("update:selectedOrg", org);
|
|
emit("updateOrganization");
|
|
};
|
|
|
|
// Open enterprise upgrade dialog
|
|
const openEnterpriseDialog = () => {
|
|
showEnterpriseDialog.value = true;
|
|
};
|
|
|
|
return {
|
|
t,
|
|
outlinedSettings,
|
|
getImageURL,
|
|
enterpriseButtonText,
|
|
ingestionQuotaPercentage,
|
|
ingestionQuotaColor,
|
|
showEnterpriseDialog,
|
|
updateOrganization,
|
|
goToHome,
|
|
goToAbout,
|
|
toggleAIChat,
|
|
openSlack,
|
|
navigateToOpenAPI,
|
|
navigateToDocs,
|
|
changeLanguage,
|
|
openPredefinedThemes,
|
|
signout,
|
|
handleMouseEnter,
|
|
handleMouseLeave,
|
|
handleOrgSelection,
|
|
openEnterpriseDialog,
|
|
};
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.upgrade-enterprise-btn {
|
|
background: var(--q-primary) !important;
|
|
color: white !important;
|
|
border-radius: 4px !important;
|
|
padding: 0 10px !important;
|
|
transition: all 0.2s ease !important;
|
|
height: 28px !important;
|
|
min-height: 28px !important;
|
|
font-size: 12px !important;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
justify-content: center !important;
|
|
align-self: center !important;
|
|
vertical-align: middle !important;
|
|
margin-top: 0 !important;
|
|
margin-bottom: 0 !important;
|
|
|
|
&:hover {
|
|
opacity: 0.85;
|
|
filter: brightness(0.9);
|
|
}
|
|
|
|
.row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.q-icon {
|
|
color: white !important;
|
|
font-size: 14px !important;
|
|
margin-right: 4px;
|
|
}
|
|
|
|
span {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
line-height: 28px;
|
|
display: inline-block;
|
|
}
|
|
}
|
|
</style>
|