mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Streams 🌊] Move management page + update Streams template (#217487)
## 📓 Summary Closes https://github.com/elastic/streams-program/issues/233 This work applies changes as follows: - Move stream management section into a standalone page - Update routing config to support nested breadcrumbs and keep shared stream retrieval between detail <-> management - Replace custom panels with EuiTemplate for stream pages. Remove previous ad-hoc components - Improve stream detail view validation (add redirect) for unknown stream detail sections
This commit is contained in:
parent
99d6c85e02
commit
4302da3b6d
30 changed files with 416 additions and 626 deletions
|
@ -45044,8 +45044,6 @@
|
|||
"xpack.streams.filter.startsWith": "commence par",
|
||||
"xpack.streams.filter.value": "Valeur",
|
||||
"xpack.streams.filter.valuePlaceholder": "Valeur",
|
||||
"xpack.streams.notFound.calloutLabel": "La page actuelle est introuvable.",
|
||||
"xpack.streams.notFound.callOutTitle": "Page introuvable",
|
||||
"xpack.streams.resultPanel.euiDataGrid.previewLabel": "Aperçu",
|
||||
"xpack.streams.routingStreamEntry.euiPanel.dragHandleLabel": "Faire glisser la poignée",
|
||||
"xpack.streams.samplePreviewTable.errorTitle": "Une erreur a été rencontrée lors de la simulation de ces modifications de mappage avec un échantillon de documents",
|
||||
|
@ -45115,7 +45113,6 @@
|
|||
"xpack.streams.streamDetailView.enrichmentTab": "Extraire le champ",
|
||||
"xpack.streams.streamDetailView.indexTemplate": "Modèle d'index",
|
||||
"xpack.streams.streamDetailView.ingestPipeline": "Pipeline d'ingestion",
|
||||
"xpack.streams.streamDetailView.managementTab": "Gestion",
|
||||
"xpack.streams.streamDetailView.managementTab.bottomBar.cancel": "Annuler les modifications",
|
||||
"xpack.streams.streamDetailView.managementTab.bottomBar.confirm": "Enregistrer les modifications",
|
||||
"xpack.streams.streamDetailView.managementTab.enrichment.editProcessorAction": "Modifier le processeur {type}",
|
||||
|
|
|
@ -45011,8 +45011,6 @@
|
|||
"xpack.streams.filter.startsWith": "で始まる",
|
||||
"xpack.streams.filter.value": "値",
|
||||
"xpack.streams.filter.valuePlaceholder": "値",
|
||||
"xpack.streams.notFound.calloutLabel": "現在のページが見つかりません。",
|
||||
"xpack.streams.notFound.callOutTitle": "ページが見つかりません",
|
||||
"xpack.streams.resultPanel.euiDataGrid.previewLabel": "プレビュー",
|
||||
"xpack.streams.routingStreamEntry.euiPanel.dragHandleLabel": "ハンドルをドラッグ",
|
||||
"xpack.streams.samplePreviewTable.errorTitle": "ドキュメントのサンプルを使用してこれらのマッピング変更をシミュレーションしているときにエラーが発生しました",
|
||||
|
@ -45082,7 +45080,6 @@
|
|||
"xpack.streams.streamDetailView.enrichmentTab": "フィールドを抽出",
|
||||
"xpack.streams.streamDetailView.indexTemplate": "インデックステンプレート",
|
||||
"xpack.streams.streamDetailView.ingestPipeline": "パイプラインを投入",
|
||||
"xpack.streams.streamDetailView.managementTab": "管理",
|
||||
"xpack.streams.streamDetailView.managementTab.bottomBar.cancel": "変更をキャンセル",
|
||||
"xpack.streams.streamDetailView.managementTab.bottomBar.confirm": "変更を保存",
|
||||
"xpack.streams.streamDetailView.managementTab.enrichment.editProcessorAction": "{type}プロセッサーを編集",
|
||||
|
|
|
@ -45082,8 +45082,6 @@
|
|||
"xpack.streams.filter.startsWith": "开头为",
|
||||
"xpack.streams.filter.value": "值",
|
||||
"xpack.streams.filter.valuePlaceholder": "值",
|
||||
"xpack.streams.notFound.calloutLabel": "找不到当前页面。",
|
||||
"xpack.streams.notFound.callOutTitle": "未找到页面",
|
||||
"xpack.streams.resultPanel.euiDataGrid.previewLabel": "预览",
|
||||
"xpack.streams.routingStreamEntry.euiPanel.dragHandleLabel": "拖动手柄",
|
||||
"xpack.streams.samplePreviewTable.errorTitle": "通过文档样例模拟这些映射更改时出错",
|
||||
|
@ -45153,7 +45151,6 @@
|
|||
"xpack.streams.streamDetailView.enrichmentTab": "提取字段",
|
||||
"xpack.streams.streamDetailView.indexTemplate": "索引模板",
|
||||
"xpack.streams.streamDetailView.ingestPipeline": "采集管道",
|
||||
"xpack.streams.streamDetailView.managementTab": "管理",
|
||||
"xpack.streams.streamDetailView.managementTab.bottomBar.cancel": "取消更改",
|
||||
"xpack.streams.streamDetailView.managementTab.bottomBar.confirm": "保存更改",
|
||||
"xpack.streams.streamDetailView.managementTab.enrichment.editProcessorAction": "编辑 {type} 处理器",
|
||||
|
|
|
@ -21,11 +21,10 @@ export const FieldParent = ({
|
|||
<EuiBadge color="hollow">
|
||||
<EuiLink
|
||||
data-test-subj="streamsAppFieldParentLink"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
href={router.link('/{key}/management/{tab}', {
|
||||
path: {
|
||||
key: parent,
|
||||
tab: 'management',
|
||||
subtab: 'schemaEditor',
|
||||
tab: 'schemaEditor',
|
||||
},
|
||||
})}
|
||||
target="_blank"
|
||||
|
|
|
@ -102,11 +102,10 @@ export const FieldSummary = (props: FieldSummaryProps) => {
|
|||
size="s"
|
||||
color="primary"
|
||||
iconType="popout"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
href={router.link('/{key}/management/{tab}', {
|
||||
path: {
|
||||
key: field.parent,
|
||||
tab: 'management',
|
||||
subtab: 'schemaEditor',
|
||||
tab: 'schemaEditor',
|
||||
},
|
||||
})}
|
||||
>
|
||||
|
|
|
@ -15,10 +15,12 @@ import { ManagementTabs, Wrapper } from './wrapper';
|
|||
import { StreamDetailLifecycle } from '../stream_detail_lifecycle';
|
||||
import { UnmanagedElasticsearchAssets } from './unmanaged_elasticsearch_assets';
|
||||
|
||||
type ManagementSubTabs = 'enrich' | 'advanced' | 'lifecycle';
|
||||
const classicStreamManagementSubTabs = ['enrich', 'advanced', 'lifecycle'] as const;
|
||||
|
||||
function isValidManagementSubTab(value: string): value is ManagementSubTabs {
|
||||
return ['enrich', 'advanced', 'lifecycle'].includes(value);
|
||||
type ClassicStreamManagementSubTab = (typeof classicStreamManagementSubTabs)[number];
|
||||
|
||||
function isValidManagementSubTab(value: string): value is ClassicStreamManagementSubTab {
|
||||
return classicStreamManagementSubTabs.includes(value as ClassicStreamManagementSubTab);
|
||||
}
|
||||
|
||||
export function ClassicStreamDetailManagement({
|
||||
|
@ -29,8 +31,8 @@ export function ClassicStreamDetailManagement({
|
|||
refreshDefinition: () => void;
|
||||
}) {
|
||||
const {
|
||||
path: { key, subtab },
|
||||
} = useStreamsAppParams('/{key}/{tab}/{subtab}');
|
||||
path: { key, tab },
|
||||
} = useStreamsAppParams('/{key}/management/{tab}');
|
||||
|
||||
if (!definition.data_stream_exists) {
|
||||
return (
|
||||
|
@ -89,14 +91,9 @@ export function ClassicStreamDetailManagement({
|
|||
};
|
||||
}
|
||||
|
||||
if (!isValidManagementSubTab(subtab)) {
|
||||
return (
|
||||
<RedirectTo
|
||||
path="/{key}/{tab}/{subtab}"
|
||||
params={{ path: { key, tab: 'management', subtab: 'enrich' } }}
|
||||
/>
|
||||
);
|
||||
if (!isValidManagementSubTab(tab)) {
|
||||
return <RedirectTo path="/{key}/management/{tab}" params={{ path: { key, tab: 'enrich' } }} />;
|
||||
}
|
||||
|
||||
return <Wrapper tabs={tabs} streamId={key} subtab={subtab} />;
|
||||
return <Wrapper tabs={tabs} streamId={key} tab={tab} />;
|
||||
}
|
||||
|
|
|
@ -5,24 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { IngestStreamGetResponse, isWiredStreamGetResponse } from '@kbn/streams-schema';
|
||||
import { isWiredStreamGetResponse } from '@kbn/streams-schema';
|
||||
import { useStreamDetail } from '../../../hooks/use_stream_detail';
|
||||
import { WiredStreamDetailManagement } from './wired';
|
||||
import { ClassicStreamDetailManagement } from './classic';
|
||||
|
||||
export function StreamDetailManagement({
|
||||
definition,
|
||||
refreshDefinition,
|
||||
}: {
|
||||
definition: IngestStreamGetResponse;
|
||||
refreshDefinition: () => void;
|
||||
}) {
|
||||
export function StreamDetailManagement() {
|
||||
const { definition, refresh } = useStreamDetail();
|
||||
|
||||
if (isWiredStreamGetResponse(definition)) {
|
||||
return (
|
||||
<WiredStreamDetailManagement definition={definition} refreshDefinition={refreshDefinition} />
|
||||
);
|
||||
return <WiredStreamDetailManagement definition={definition} refreshDefinition={refresh} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClassicStreamDetailManagement definition={definition} refreshDefinition={refreshDefinition} />
|
||||
);
|
||||
return <ClassicStreamDetailManagement definition={definition} refreshDefinition={refresh} />;
|
||||
}
|
||||
|
|
|
@ -15,10 +15,12 @@ import { StreamDetailSchemaEditor } from '../stream_detail_schema_editor';
|
|||
import { StreamDetailLifecycle } from '../stream_detail_lifecycle';
|
||||
import { Wrapper } from './wrapper';
|
||||
|
||||
type ManagementSubTabs = 'route' | 'enrich' | 'schemaEditor' | 'lifecycle';
|
||||
const wiredStreamManagementSubTabs = ['route', 'enrich', 'schemaEditor', 'lifecycle'] as const;
|
||||
|
||||
function isValidManagementSubTab(value: string): value is ManagementSubTabs {
|
||||
return ['route', 'enrich', 'schemaEditor', 'lifecycle'].includes(value);
|
||||
type WiredStreamManagementSubTab = (typeof wiredStreamManagementSubTabs)[number];
|
||||
|
||||
function isValidManagementSubTab(value: string): value is WiredStreamManagementSubTab {
|
||||
return wiredStreamManagementSubTabs.includes(value as WiredStreamManagementSubTab);
|
||||
}
|
||||
|
||||
export function WiredStreamDetailManagement({
|
||||
|
@ -29,8 +31,8 @@ export function WiredStreamDetailManagement({
|
|||
refreshDefinition: () => void;
|
||||
}) {
|
||||
const {
|
||||
path: { key, subtab },
|
||||
} = useStreamsAppParams('/{key}/{tab}/{subtab}');
|
||||
path: { key, tab },
|
||||
} = useStreamsAppParams('/{key}/management/{tab}');
|
||||
|
||||
const tabs = {
|
||||
route: {
|
||||
|
@ -38,7 +40,7 @@ export function WiredStreamDetailManagement({
|
|||
<StreamDetailRouting definition={definition} refreshDefinition={refreshDefinition} />
|
||||
),
|
||||
label: i18n.translate('xpack.streams.streamDetailView.routingTab', {
|
||||
defaultMessage: 'Streams Partitioning',
|
||||
defaultMessage: 'Partitioning',
|
||||
}),
|
||||
},
|
||||
enrich: {
|
||||
|
@ -67,14 +69,9 @@ export function WiredStreamDetailManagement({
|
|||
},
|
||||
};
|
||||
|
||||
if (!isValidManagementSubTab(subtab)) {
|
||||
return (
|
||||
<RedirectTo
|
||||
path="/{key}/{tab}/{subtab}"
|
||||
params={{ path: { key, tab: 'management', subtab: 'route' } }}
|
||||
/>
|
||||
);
|
||||
if (!isValidManagementSubTab(tab)) {
|
||||
return <RedirectTo path="/{key}/management/{tab}" params={{ path: { key, tab: 'route' } }} />;
|
||||
}
|
||||
|
||||
return <Wrapper tabs={tabs} streamId={key} subtab={subtab} />;
|
||||
return <Wrapper tabs={tabs} streamId={key} tab={tab} />;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiBadgeGroup, EuiFlexGroup } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isUnwiredStreamDefinition } from '@kbn/streams-schema';
|
||||
import { useStreamDetail } from '../../../hooks/use_stream_detail';
|
||||
import { useStreamsAppRouter } from '../../../hooks/use_streams_app_router';
|
||||
import { StreamsAppPageTemplate } from '../../streams_app_page_template';
|
||||
import { ClassicStreamBadge, LifecycleBadge } from '../../stream_badges';
|
||||
|
||||
export type ManagementTabs = Record<
|
||||
string,
|
||||
|
@ -21,47 +25,55 @@ export type ManagementTabs = Record<
|
|||
export function Wrapper({
|
||||
tabs,
|
||||
streamId,
|
||||
subtab,
|
||||
tab,
|
||||
}: {
|
||||
tabs: ManagementTabs;
|
||||
streamId: string;
|
||||
subtab: string;
|
||||
tab: string;
|
||||
}) {
|
||||
const router = useStreamsAppRouter();
|
||||
const { definition } = useStreamDetail();
|
||||
|
||||
const tabMap = Object.fromEntries(
|
||||
Object.entries(tabs).map(([tabName, currentTab]) => {
|
||||
return [
|
||||
tabName,
|
||||
{
|
||||
href: router.link('/{key}/management/{tab}', {
|
||||
path: { key: streamId, tab: tabName },
|
||||
}),
|
||||
label: currentTab.label,
|
||||
content: currentTab.content,
|
||||
},
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
gutterSize="m"
|
||||
className={css`
|
||||
max-width: 100%;
|
||||
`}
|
||||
>
|
||||
{Object.keys(tabs).length > 1 && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonGroup
|
||||
legend="Management tabs"
|
||||
idSelected={subtab}
|
||||
onChange={(optionId) => {
|
||||
router.push('/{key}/{tab}/{subtab}', {
|
||||
path: { key: streamId, subtab: optionId, tab: 'management' },
|
||||
query: {},
|
||||
});
|
||||
}}
|
||||
options={Object.keys(tabs).map((id) => ({
|
||||
id,
|
||||
label: tabs[id].label,
|
||||
}))}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem
|
||||
className={css`
|
||||
overflow: auto;
|
||||
`}
|
||||
grow
|
||||
>
|
||||
{tabs[subtab].content}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<>
|
||||
<StreamsAppPageTemplate.Header
|
||||
bottomBorder="extended"
|
||||
pageTitle={
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.manageStreamTitle', {
|
||||
defaultMessage: 'Manage stream {streamId}',
|
||||
values: { streamId },
|
||||
})}
|
||||
<EuiBadgeGroup gutterSize="s">
|
||||
{isUnwiredStreamDefinition(definition.stream) && <ClassicStreamBadge />}
|
||||
<LifecycleBadge lifecycle={definition.effective_lifecycle} />
|
||||
</EuiBadgeGroup>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
tabs={Object.entries(tabMap).map(([tabKey, { label, href }]) => {
|
||||
return {
|
||||
label,
|
||||
href,
|
||||
isSelected: tab === tabKey,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<StreamsAppPageTemplate.Body>{tabs[tab].content}</StreamsAppPageTemplate.Body>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -130,11 +130,10 @@ export function ControlBar() {
|
|||
data-test-subj="streamsAppSaveOrUpdateChildrenOpenStreamInNewTabButton"
|
||||
size="s"
|
||||
target="_blank"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
href={router.link('/{key}/management/{tab}', {
|
||||
path: {
|
||||
key: routingAppState.childUnderEdit?.child.destination!,
|
||||
tab: 'management',
|
||||
subtab: 'route',
|
||||
tab: 'route',
|
||||
},
|
||||
})}
|
||||
>
|
||||
|
|
|
@ -21,11 +21,10 @@ export function CurrentStreamEntry({ definition }: { definition: WiredStreamGetR
|
|||
text: parentId,
|
||||
href: isBreadcrumbsTail
|
||||
? undefined
|
||||
: router.link('/{key}/{tab}/{subtab}', {
|
||||
: router.link('/{key}/management/{tab}', {
|
||||
path: {
|
||||
key: parentId,
|
||||
tab: 'management',
|
||||
subtab: 'route',
|
||||
tab: 'route',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -93,8 +93,8 @@ export function RoutingStreamEntry({
|
|||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
path: { key: child.destination, tab: 'management', subtab: 'route' },
|
||||
href={router.link('/{key}/management/{tab}', {
|
||||
path: { key: child.destination, tab: 'route' },
|
||||
})}
|
||||
data-test-subj="streamsAppRoutingStreamEntryButton"
|
||||
>
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiBadge } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { ILM_LOCATOR_ID, IlmLocatorParams } from '@kbn/index-lifecycle-management-common-shared';
|
||||
import {
|
||||
IngestStreamEffectiveLifecycle,
|
||||
IngestStreamGetResponse,
|
||||
isDslLifecycle,
|
||||
isErrorLifecycle,
|
||||
isIlmLifecycle,
|
||||
isUnwiredStreamDefinition,
|
||||
} from '@kbn/streams-schema';
|
||||
import { useStreamsAppBreadcrumbs } from '../../hooks/use_streams_app_breadcrumbs';
|
||||
import { useStreamsAppRouter } from '../../hooks/use_streams_app_router';
|
||||
import { EntityOverviewTabList } from '../entity_overview_tab_list';
|
||||
import { LoadingPanel } from '../loading_panel';
|
||||
import { StreamsAppPageBody } from '../streams_app_page_body';
|
||||
import { StreamsAppPageHeader } from '../streams_app_page_header';
|
||||
import { StreamsAppPageHeaderTitle } from '../streams_app_page_header/streams_app_page_header_title';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
|
||||
export interface EntityViewTab {
|
||||
name: string;
|
||||
label: string;
|
||||
content: React.ReactElement;
|
||||
background: boolean;
|
||||
}
|
||||
|
||||
export function EntityDetailViewWithoutParams({
|
||||
selectedTab,
|
||||
tabs,
|
||||
entity,
|
||||
definition,
|
||||
}: {
|
||||
selectedTab: string;
|
||||
tabs: EntityViewTab[];
|
||||
entity: {
|
||||
displayName?: string;
|
||||
id: string;
|
||||
};
|
||||
definition?: IngestStreamGetResponse;
|
||||
}) {
|
||||
const router = useStreamsAppRouter();
|
||||
useStreamsAppBreadcrumbs(() => {
|
||||
if (!entity.displayName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: entity.displayName,
|
||||
path: `/{key}`,
|
||||
params: { path: { key: entity.id } },
|
||||
} as const,
|
||||
];
|
||||
}, [entity.displayName, entity.id]);
|
||||
|
||||
if (!entity.displayName) {
|
||||
return <LoadingPanel />;
|
||||
}
|
||||
|
||||
const tabMap = Object.fromEntries(
|
||||
tabs.map((tab) => {
|
||||
return [
|
||||
tab.name,
|
||||
{
|
||||
href: router.link('/{key}/{tab}', {
|
||||
path: { key: entity.id, tab: tab.name },
|
||||
}),
|
||||
label: tab.label,
|
||||
content: tab.content,
|
||||
background: tab.background,
|
||||
},
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
const selectedTabObject = tabMap[selectedTab];
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
className={css`
|
||||
max-width: 100%;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<StreamsAppPageHeader
|
||||
title={
|
||||
<StreamsAppPageHeaderTitle
|
||||
title={
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
{entity.displayName}
|
||||
{definition && isUnwiredStreamDefinition(definition.stream) ? (
|
||||
<>
|
||||
{' '}
|
||||
<EuiBadge>
|
||||
{i18n.translate(
|
||||
'xpack.streams.entityDetailViewWithoutParams.unmanagedBadgeLabel',
|
||||
{ defaultMessage: 'Classic' }
|
||||
)}
|
||||
</EuiBadge>
|
||||
</>
|
||||
) : null}
|
||||
{definition && <LifecycleBadge lifecycle={definition.effective_lifecycle} />}
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EntityOverviewTabList
|
||||
tabs={Object.entries(tabMap).map(([tabKey, { label, href }]) => {
|
||||
return {
|
||||
name: tabKey,
|
||||
label,
|
||||
href,
|
||||
selected: selectedTab === tabKey,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</StreamsAppPageHeader>
|
||||
</EuiFlexItem>
|
||||
<StreamsAppPageBody background={selectedTabObject.background}>
|
||||
{selectedTabObject.content}
|
||||
</StreamsAppPageBody>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function LifecycleBadge({ lifecycle }: { lifecycle: IngestStreamEffectiveLifecycle }) {
|
||||
const {
|
||||
dependencies: {
|
||||
start: { share },
|
||||
},
|
||||
} = useKibana();
|
||||
const ilmLocator = share.url.locators.get<IlmLocatorParams>(ILM_LOCATOR_ID);
|
||||
|
||||
if (isIlmLifecycle(lifecycle)) {
|
||||
return (
|
||||
<EuiBadge color="hollow">
|
||||
<EuiLink
|
||||
data-test-subj="streamsAppLifecycleBadgeIlmPolicyNameLink"
|
||||
color="text"
|
||||
href={ilmLocator?.getRedirectUrl({
|
||||
page: 'policy_edit',
|
||||
policyName: lifecycle.ilm.policy,
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.ilmBadgeLabel', {
|
||||
defaultMessage: 'ILM Policy: {name}',
|
||||
values: { name: lifecycle.ilm.policy },
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorLifecycle(lifecycle)) {
|
||||
return (
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.errorBadgeLabel', {
|
||||
defaultMessage: 'Error: {message}',
|
||||
values: { message: lifecycle.error.message },
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
if (isDslLifecycle(lifecycle)) {
|
||||
return (
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.dslBadgeLabel', {
|
||||
defaultMessage: 'Retention: {retention}',
|
||||
values: { retention: lifecycle.dsl.data_retention || '∞' },
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.disabledLifecycleBadgeLabel', {
|
||||
defaultMessage: 'Retention: Disabled',
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiText } from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
export function EntityDetailViewHeaderSection({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiText
|
||||
className={css`
|
||||
font-weight: 600;
|
||||
`}
|
||||
>
|
||||
{title}
|
||||
</EuiText>
|
||||
{children}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiTab, EuiTabs, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
export function EntityOverviewTabList<
|
||||
T extends { name: string; label: string; href: string; selected: boolean }
|
||||
>({ tabs }: { tabs: T[] }) {
|
||||
const theme = useEuiTheme().euiTheme;
|
||||
|
||||
return (
|
||||
<EuiTabs
|
||||
size="m"
|
||||
className={css`
|
||||
padding: 0 ${theme.size.l};
|
||||
`}
|
||||
>
|
||||
{tabs.map((tab) => {
|
||||
return (
|
||||
<EuiTab key={tab.name} href={tab.href} isSelected={tab.selected}>
|
||||
{tab.label}
|
||||
</EuiTab>
|
||||
);
|
||||
})}
|
||||
</EuiTabs>
|
||||
);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export function NotFound() {
|
||||
return (
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
title={i18n.translate('xpack.streams.notFound.callOutTitle', {
|
||||
defaultMessage: 'Page not found',
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.streams.notFound.calloutLabel', {
|
||||
defaultMessage: 'The current page can not be found.',
|
||||
})}
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBadge, EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IlmLocatorParams, ILM_LOCATOR_ID } from '@kbn/index-lifecycle-management-common-shared';
|
||||
import {
|
||||
IngestStreamEffectiveLifecycle,
|
||||
isIlmLifecycle,
|
||||
isErrorLifecycle,
|
||||
isDslLifecycle,
|
||||
} from '@kbn/streams-schema';
|
||||
import React from 'react';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
|
||||
export function ClassicStreamBadge() {
|
||||
return (
|
||||
<EuiBadge>
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.unmanagedBadgeLabel', {
|
||||
defaultMessage: 'Classic',
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
export function LifecycleBadge({ lifecycle }: { lifecycle: IngestStreamEffectiveLifecycle }) {
|
||||
const {
|
||||
dependencies: {
|
||||
start: { share },
|
||||
},
|
||||
} = useKibana();
|
||||
const ilmLocator = share.url.locators.get<IlmLocatorParams>(ILM_LOCATOR_ID);
|
||||
|
||||
if (isIlmLifecycle(lifecycle)) {
|
||||
return (
|
||||
<EuiBadge color="hollow">
|
||||
<EuiLink
|
||||
data-test-subj="streamsAppLifecycleBadgeIlmPolicyNameLink"
|
||||
color="text"
|
||||
href={ilmLocator?.getRedirectUrl({
|
||||
page: 'policy_edit',
|
||||
policyName: lifecycle.ilm.policy,
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.ilmBadgeLabel', {
|
||||
defaultMessage: 'ILM Policy: {name}',
|
||||
values: { name: lifecycle.ilm.policy },
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorLifecycle(lifecycle)) {
|
||||
return (
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.errorBadgeLabel', {
|
||||
defaultMessage: 'Error: {message}',
|
||||
values: { message: lifecycle.error.message },
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
if (isDslLifecycle(lifecycle)) {
|
||||
return (
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.dslBadgeLabel', {
|
||||
defaultMessage: 'Retention: {retention}',
|
||||
values: { retention: lifecycle.dsl.data_retention || '∞' },
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.disabledLifecycleBadgeLabel', {
|
||||
defaultMessage: 'Retention: Disabled',
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
|
@ -83,11 +83,10 @@ export function StreamDeleteModal({
|
|||
<li key={stream}>
|
||||
<EuiListGroupItem
|
||||
target="_blank"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
href={router.link('/{key}/management/{tab}', {
|
||||
path: {
|
||||
key: stream,
|
||||
tab: 'management',
|
||||
subtab: 'route',
|
||||
tab: 'route',
|
||||
},
|
||||
})}
|
||||
label={stream}
|
||||
|
|
|
@ -55,11 +55,10 @@ export function ChildStreamList({ definition }: { definition?: IngestStreamGetRe
|
|||
<EuiButton
|
||||
data-test-subj="streamsAppChildStreamListCreateChildStreamButton"
|
||||
iconType="plusInCircle"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
href={router.link('/{key}/management/{tab}', {
|
||||
path: {
|
||||
key: definition.stream.name,
|
||||
tab: 'management',
|
||||
subtab: 'route',
|
||||
tab: 'route',
|
||||
},
|
||||
})}
|
||||
>
|
||||
|
|
|
@ -4,87 +4,107 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Outlet } from '@kbn/typed-react-router-config';
|
||||
import React from 'react';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
import { StreamDetailContextProvider, useStreamDetail } from '../../hooks/use_stream_detail';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiBadgeGroup, EuiButton } from '@elastic/eui';
|
||||
import { IngestStreamGetResponse, isUnwiredStreamDefinition } from '@kbn/streams-schema';
|
||||
import { useStreamsAppParams } from '../../hooks/use_streams_app_params';
|
||||
import { StreamDetailManagement } from '../data_management/stream_detail_management';
|
||||
import { EntityDetailViewWithoutParams, EntityViewTab } from '../entity_detail_view';
|
||||
import { RedirectTo } from '../redirect_to';
|
||||
import { StreamDetailDashboardsView } from '../stream_detail_dashboards_view';
|
||||
import { StreamDetailOverview } from '../stream_detail_overview';
|
||||
import { useStreamDetail } from '../../hooks/use_stream_detail';
|
||||
import { ClassicStreamBadge, LifecycleBadge } from '../stream_badges';
|
||||
import { StreamsAppPageTemplate } from '../streams_app_page_template';
|
||||
import { StatefulStreamsAppRouter, useStreamsAppRouter } from '../../hooks/use_streams_app_router';
|
||||
import { RedirectTo } from '../redirect_to';
|
||||
|
||||
export function StreamDetailView() {
|
||||
const { streamsRepositoryClient } = useKibana().dependencies.start.streams;
|
||||
|
||||
const {
|
||||
path: { key: name },
|
||||
} = useStreamsAppParams('/{key}/{tab}', true);
|
||||
|
||||
return (
|
||||
<StreamDetailContextProvider name={name} streamsRepositoryClient={streamsRepositoryClient}>
|
||||
<Outlet />
|
||||
</StreamDetailContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function StreamDetailViewContent() {
|
||||
const params1 = useStreamsAppParams('/{key}/{tab}', true);
|
||||
const params2 = useStreamsAppParams('/{key}/{tab}/{subtab}', true);
|
||||
|
||||
const name = params1?.path?.key || params2.path.key;
|
||||
const tab = params1?.path?.tab || 'management';
|
||||
const { definition, refresh } = useStreamDetail();
|
||||
|
||||
const entity = {
|
||||
id: name,
|
||||
displayName: name,
|
||||
};
|
||||
|
||||
if (params2?.path?.subtab && tab !== 'management') {
|
||||
// only management tab has subtabs
|
||||
return <RedirectTo path="/{key}/{tab}" params={{ path: { tab } }} />;
|
||||
}
|
||||
if (!params2?.path?.subtab && tab === 'management') {
|
||||
// management tab requires a subtab
|
||||
return <RedirectTo path="/{key}/{tab}/{subtab}" params={{ path: { tab, subtab: 'route' } }} />;
|
||||
}
|
||||
|
||||
const tabs: EntityViewTab[] = [
|
||||
{
|
||||
name: 'overview',
|
||||
const getStreamDetailTabs = ({
|
||||
definition,
|
||||
router,
|
||||
}: {
|
||||
definition: IngestStreamGetResponse;
|
||||
router: StatefulStreamsAppRouter;
|
||||
}) =>
|
||||
({
|
||||
overview: {
|
||||
href: router.link('/{key}/{tab}', {
|
||||
path: { key: definition.stream.name, tab: 'overview' },
|
||||
}),
|
||||
background: false,
|
||||
content: <StreamDetailOverview definition={definition} />,
|
||||
label: i18n.translate('xpack.streams.streamDetailView.overviewTab', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
background: false,
|
||||
},
|
||||
{
|
||||
name: 'dashboards',
|
||||
dashboards: {
|
||||
href: router.link('/{key}/{tab}', {
|
||||
path: { key: definition.stream.name, tab: 'dashboards' },
|
||||
}),
|
||||
background: true,
|
||||
content: <StreamDetailDashboardsView definition={definition} />,
|
||||
label: i18n.translate('xpack.streams.streamDetailView.dashboardsTab', {
|
||||
defaultMessage: 'Dashboards',
|
||||
}),
|
||||
background: true,
|
||||
},
|
||||
{
|
||||
name: 'management',
|
||||
content: <StreamDetailManagement definition={definition} refreshDefinition={refresh} />,
|
||||
label: i18n.translate('xpack.streams.streamDetailView.managementTab', {
|
||||
defaultMessage: 'Management',
|
||||
}),
|
||||
background: true,
|
||||
},
|
||||
];
|
||||
} as const);
|
||||
|
||||
export type StreamDetailTabs = ReturnType<typeof getStreamDetailTabs>;
|
||||
export type StreamDetailTabName = keyof StreamDetailTabs;
|
||||
|
||||
function isValidStreamDetailTab(value: string): value is StreamDetailTabName {
|
||||
return ['overview', 'dashboards'].includes(value as StreamDetailTabName);
|
||||
}
|
||||
|
||||
export function StreamDetailView() {
|
||||
const router = useStreamsAppRouter();
|
||||
const { path } = useStreamsAppParams('/{key}/{tab}', true);
|
||||
const { key, tab } = path;
|
||||
|
||||
const { definition } = useStreamDetail();
|
||||
|
||||
if (!isValidStreamDetailTab(tab)) {
|
||||
return <RedirectTo path="/{key}/{tab}" params={{ path: { key, tab: 'overview' } }} />;
|
||||
}
|
||||
|
||||
const tabs = getStreamDetailTabs({ definition, router });
|
||||
|
||||
const selectedTabObject = tabs[tab as StreamDetailTabName];
|
||||
|
||||
return (
|
||||
<EntityDetailViewWithoutParams
|
||||
tabs={tabs}
|
||||
entity={entity}
|
||||
definition={definition}
|
||||
selectedTab={tab}
|
||||
/>
|
||||
<>
|
||||
<StreamsAppPageTemplate.Header
|
||||
bottomBorder="extended"
|
||||
pageTitle={
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
{key}
|
||||
<EuiBadgeGroup gutterSize="s">
|
||||
{isUnwiredStreamDefinition(definition.stream) && <ClassicStreamBadge />}
|
||||
<LifecycleBadge lifecycle={definition.effective_lifecycle} />
|
||||
</EuiBadgeGroup>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
tabs={Object.entries(tabs).map(([tabName, { label, href }]) => {
|
||||
return {
|
||||
label,
|
||||
href,
|
||||
isSelected: tab === tabName,
|
||||
};
|
||||
})}
|
||||
rightSideItems={[
|
||||
<EuiButton
|
||||
iconType="gear"
|
||||
href={router.link('/{key}/management/{tab}', {
|
||||
path: { key, tab: 'route' },
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.streams.entityDetailViewWithoutParams.manageStreamLabel', {
|
||||
defaultMessage: 'Manage stream',
|
||||
})}
|
||||
</EuiButton>,
|
||||
]}
|
||||
/>
|
||||
<StreamsAppPageTemplate.Body color={selectedTabObject.background ? 'plain' : 'subdued'}>
|
||||
{selectedTabObject.content}
|
||||
</StreamsAppPageTemplate.Body>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiBetaBadge } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiBetaBadge } from '@elastic/eui';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch';
|
||||
import { StreamsAppPageHeader } from '../streams_app_page_header';
|
||||
import { StreamsAppPageHeaderTitle } from '../streams_app_page_header/streams_app_page_header_title';
|
||||
import { StreamsAppPageBody } from '../streams_app_page_body';
|
||||
import { StreamsTreeTable } from './tree_table';
|
||||
import { StreamsEmptyPrompt } from './empty_prompt';
|
||||
import { StreamsAppPageTemplate } from '../streams_app_page_template';
|
||||
|
||||
export function StreamListView() {
|
||||
const {
|
||||
|
@ -36,45 +34,35 @@ export function StreamListView() {
|
|||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<StreamsAppPageHeader
|
||||
title={
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<StreamsAppPageHeaderTitle
|
||||
title={i18n.translate('xpack.streams.streamsListView.pageHeaderTitle', {
|
||||
defaultMessage: 'Streams',
|
||||
})}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
label={i18n.translate('xpack.streams.streamsListView.betaBadgeLabel', {
|
||||
defaultMessage: 'Technical Preview',
|
||||
})}
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.streams.streamsListView.betaBadgeDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'This functionality is experimental and not supported. It may change or be removed at any time.',
|
||||
}
|
||||
)}
|
||||
alignment="middle"
|
||||
size="s"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<StreamsAppPageBody background>
|
||||
<>
|
||||
<StreamsAppPageTemplate.Header
|
||||
bottomBorder="extended"
|
||||
pageTitle={
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
{i18n.translate('xpack.streams.streamsListView.pageHeaderTitle', {
|
||||
defaultMessage: 'Streams',
|
||||
})}
|
||||
<EuiBetaBadge
|
||||
label={i18n.translate('xpack.streams.streamsListView.betaBadgeLabel', {
|
||||
defaultMessage: 'Technical Preview',
|
||||
})}
|
||||
tooltipContent={i18n.translate('xpack.streams.streamsListView.betaBadgeDescription', {
|
||||
defaultMessage:
|
||||
'This functionality is experimental and not supported. It may change or be removed at any time.',
|
||||
})}
|
||||
alignment="middle"
|
||||
size="s"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
<StreamsAppPageTemplate.Body grow>
|
||||
{!streamsListFetch.loading && !streamsListFetch.value?.length ? (
|
||||
<StreamsEmptyPrompt />
|
||||
) : (
|
||||
<StreamsTreeTable loading={streamsListFetch.loading} streams={streamsListFetch.value} />
|
||||
)}
|
||||
</StreamsAppPageBody>
|
||||
</EuiFlexGroup>
|
||||
</StreamsAppPageTemplate.Body>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useStreamsAppParams } from '../../hooks/use_streams_app_params';
|
||||
import { StreamDetailManagement } from '../data_management/stream_detail_management';
|
||||
import { useStreamsAppBreadcrumbs } from '../../hooks/use_streams_app_breadcrumbs';
|
||||
|
||||
export function StreamManagementView() {
|
||||
const {
|
||||
path: { key, tab },
|
||||
} = useStreamsAppParams('/{key}/management/{tab}', true);
|
||||
|
||||
useStreamsAppBreadcrumbs(() => {
|
||||
return [
|
||||
{
|
||||
title: i18n.translate('xpack.streams.streamManagementView.title', {
|
||||
defaultMessage: 'Manage stream',
|
||||
}),
|
||||
path: `/{key}/management/{tab}`,
|
||||
params: { path: { key, tab } },
|
||||
} as const,
|
||||
];
|
||||
}, [key, tab]);
|
||||
|
||||
return <StreamDetailManagement />;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { StreamDetailContextProvider } from '../../hooks/use_stream_detail';
|
||||
import { useStreamsAppBreadcrumbs } from '../../hooks/use_streams_app_breadcrumbs';
|
||||
import { useStreamsAppParams } from '../../hooks/use_streams_app_params';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
|
||||
export function StreamDetailRoot({ children }: { children: React.ReactNode }) {
|
||||
const { streamsRepositoryClient } = useKibana().dependencies.start.streams;
|
||||
|
||||
const {
|
||||
path: { key },
|
||||
} = useStreamsAppParams('/{key}', true);
|
||||
|
||||
useStreamsAppBreadcrumbs(() => {
|
||||
return [
|
||||
{
|
||||
title: key,
|
||||
path: `/{key}`,
|
||||
params: { path: { key } },
|
||||
},
|
||||
];
|
||||
}, [key]);
|
||||
|
||||
return (
|
||||
<StreamDetailContextProvider name={key} streamsRepositoryClient={streamsRepositoryClient}>
|
||||
{children}
|
||||
</StreamDetailContextProvider>
|
||||
);
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiPanel, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
export function StreamsAppPageBody({
|
||||
children,
|
||||
background,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
background: boolean;
|
||||
}) {
|
||||
const theme = useEuiTheme().euiTheme;
|
||||
return (
|
||||
<EuiPanel
|
||||
hasBorder={false}
|
||||
hasShadow={false}
|
||||
className={css`
|
||||
border-top: 1px solid ${theme.colors.lightShade};
|
||||
border-radius: 0px;
|
||||
display: flex;
|
||||
overflow-y: auto;
|
||||
padding-top: ${theme.size.base};
|
||||
${!background ? `background-color: transparent;` : ''}
|
||||
`}
|
||||
paddingSize="l"
|
||||
>
|
||||
{children}
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiPageHeader, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
export function StreamsAppPageHeader({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const theme = useEuiTheme().euiTheme;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiPageHeader
|
||||
className={css`
|
||||
padding: ${theme.size.l} ${theme.size.l} ${theme.size.m};
|
||||
`}
|
||||
>
|
||||
{title}
|
||||
</EuiPageHeader>
|
||||
{children}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
export function StreamsAppPageHeaderTitle({ title }: { title: React.ReactNode }) {
|
||||
return (
|
||||
<EuiTitle size="l">
|
||||
<h1>{title}</h1>
|
||||
</EuiTitle>
|
||||
);
|
||||
}
|
|
@ -4,9 +4,10 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import React from 'react';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import { EuiPageSectionProps, EuiPageTemplate } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
|
||||
export function StreamsAppPageTemplate({ children }: { children: React.ReactNode }) {
|
||||
|
@ -14,20 +15,29 @@ export function StreamsAppPageTemplate({ children }: { children: React.ReactNode
|
|||
services: { PageTemplate },
|
||||
} = useKibana();
|
||||
|
||||
return (
|
||||
<PageTemplate>
|
||||
<EuiPanel
|
||||
paddingSize="none"
|
||||
color="subdued"
|
||||
hasShadow={false}
|
||||
hasBorder={false}
|
||||
className={css`
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</EuiPanel>
|
||||
</PageTemplate>
|
||||
);
|
||||
/**
|
||||
* This template wrapper only serves the purpose of adding the o11y sidebar to the app.
|
||||
* Due to the dependency inversion used to get the template and the constrain on the dependencies imports,
|
||||
* we cannot get the right types for this template unless its definition gets moved into a more generic package.
|
||||
*/
|
||||
return <PageTemplate>{children}</PageTemplate>;
|
||||
}
|
||||
|
||||
StreamsAppPageTemplate.Header = EuiPageTemplate.Header;
|
||||
StreamsAppPageTemplate.EmptyPrompt = EuiPageTemplate.EmptyPrompt;
|
||||
StreamsAppPageTemplate.Body = (props: EuiPageSectionProps) => (
|
||||
<EuiPageTemplate.Section
|
||||
grow
|
||||
css={css`
|
||||
overflow-y: auto;
|
||||
`}
|
||||
contentProps={{
|
||||
css: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { StreamsAppRouter, StreamsAppRoutes } from '../routes/config';
|
|||
import { streamsAppRouter } from '../routes/config';
|
||||
import { useKibana } from './use_kibana';
|
||||
|
||||
interface StatefulStreamsAppRouter extends StreamsAppRouter {
|
||||
export interface StatefulStreamsAppRouter extends StreamsAppRouter {
|
||||
push<T extends PathsOf<StreamsAppRoutes>>(
|
||||
path: T,
|
||||
...params: TypeAsArgs<TypeOf<StreamsAppRoutes, T>>
|
||||
|
|
|
@ -8,11 +8,13 @@ import { i18n } from '@kbn/i18n';
|
|||
import { createRouter, Outlet, RouteMap } from '@kbn/typed-react-router-config';
|
||||
import * as t from 'io-ts';
|
||||
import React from 'react';
|
||||
import { StreamDetailView, StreamDetailViewContent } from '../components/stream_detail_view';
|
||||
import { StreamDetailView } from '../components/stream_detail_view';
|
||||
import { StreamsAppPageTemplate } from '../components/streams_app_page_template';
|
||||
import { StreamsAppRouterBreadcrumb } from '../components/streams_app_router_breadcrumb';
|
||||
import { RedirectTo } from '../components/redirect_to';
|
||||
import { StreamListView } from '../components/stream_list_view';
|
||||
import { StreamManagementView } from '../components/stream_management_view';
|
||||
import { StreamDetailRoot } from '../components/stream_root';
|
||||
|
||||
/**
|
||||
* The array of route definitions to be used when the application
|
||||
|
@ -33,8 +35,15 @@ const streamsAppRoutes = {
|
|||
</StreamsAppRouterBreadcrumb>
|
||||
),
|
||||
children: {
|
||||
'/': {
|
||||
element: <StreamListView />,
|
||||
},
|
||||
'/{key}': {
|
||||
element: <Outlet />,
|
||||
element: (
|
||||
<StreamDetailRoot>
|
||||
<Outlet />
|
||||
</StreamDetailRoot>
|
||||
),
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
key: t.string,
|
||||
|
@ -51,31 +60,22 @@ const streamsAppRoutes = {
|
|||
tab: t.string,
|
||||
}),
|
||||
}),
|
||||
children: {
|
||||
'/{key}/{tab}/{subtab}': {
|
||||
element: <StreamDetailViewContent />,
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
subtab: t.string,
|
||||
tab: t.string,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
'/{key}/{tab}': {
|
||||
element: <StreamDetailViewContent />,
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
tab: t.string,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
'/{key}/management': {
|
||||
element: (
|
||||
<RedirectTo path="/{key}/management/{tab}" params={{ path: { tab: 'route' } }} />
|
||||
),
|
||||
},
|
||||
'/{key}/management/{tab}': {
|
||||
element: <StreamManagementView />,
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
tab: t.string,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
'/': {
|
||||
element: <StreamListView />,
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies RouteMap;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { css } from '@emotion/css';
|
||||
import { css } from '@emotion/react';
|
||||
import React from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public';
|
||||
|
@ -26,21 +26,20 @@ export const createObservabilityStreamsAppPageTemplate =
|
|||
return (
|
||||
<PageTemplate
|
||||
pageSectionProps={{
|
||||
className: css`
|
||||
color: 'subdued',
|
||||
css: css`
|
||||
max-height: calc(
|
||||
100vh - var(--euiFixedHeadersOffset, 0)
|
||||
${isSolutionNavEnabled
|
||||
? `-
|
||||
var(--kbnProjectHeaderAppActionMenuHeight, 48px)`
|
||||
: ''}
|
||||
${isSolutionNavEnabled ? `- var(--kbnProjectHeaderAppActionMenuHeight, 48px)` : ''}
|
||||
);
|
||||
overflow: auto;
|
||||
padding-inline: 0px;
|
||||
`,
|
||||
contentProps: {
|
||||
className: css`
|
||||
css: css`
|
||||
padding-block: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`,
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue