Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .swn
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ export const ingestStream = {
name: 'logs.nginx',
elasticsearch_assets: [],
stream: ingestStreamConfig,
privileges: {
manage: true,
monitor: true,
lifecycle: true,
simulate: true,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,33 @@ const ingestUpsertRequestSchema: z.Schema<IngestUpsertRequest> = z.union([
unwiredIngestUpsertRequestSchema,
]);

interface IngestStreamPrivileges {
// User can change everything about the stream
manage: boolean;
// User can read stats (like size in bytes) about the stream
monitor: boolean;
// User can change the retention policy of the stream
lifecycle: boolean;
// User can simulate changes to the processing or the mapping of the stream
simulate: boolean;
}

/**
* Stream get response
*/
interface WiredStreamGetResponse extends StreamGetResponseBase {
stream: WiredStreamDefinition;
inherited_fields: InheritedFieldDefinition;
effective_lifecycle: WiredIngestStreamEffectiveLifecycle;
privileges: IngestStreamPrivileges;
}

interface UnwiredStreamGetResponse extends StreamGetResponseBase {
stream: UnwiredStreamDefinition;
elasticsearch_assets?: ElasticsearchAssets;
data_stream_exists: boolean;
effective_lifecycle: UnwiredIngestStreamEffectiveLifecycle;
privileges: IngestStreamPrivileges;
}

type IngestStreamGetResponse = WiredStreamGetResponse | UnwiredStreamGetResponse;
Expand Down Expand Up @@ -121,12 +134,20 @@ const ingestStreamUpsertRequestSchema: z.Schema<IngestStreamUpsertRequest> = z.u
unwiredStreamUpsertRequestSchema,
]);

const ingestStreamPrivilegesSchema: z.Schema<IngestStreamPrivileges> = z.object({
manage: z.boolean(),
monitor: z.boolean(),
lifecycle: z.boolean(),
simulate: z.boolean(),
});

const wiredStreamGetResponseSchema: z.Schema<WiredStreamGetResponse> = z.intersection(
streamGetResponseSchemaBase,
z.object({
stream: wiredStreamDefinitionSchema,
inherited_fields: inheritedFieldDefinitionSchema,
effective_lifecycle: wiredIngestStreamEffectiveLifecycleSchema,
privileges: ingestStreamPrivilegesSchema,
})
);

Expand All @@ -137,6 +158,7 @@ const unwiredStreamGetResponseSchema: z.Schema<UnwiredStreamGetResponse> = z.int
elasticsearch_assets: z.optional(elasticsearchAssetsSchema),
data_stream_exists: z.boolean(),
effective_lifecycle: unwiredIngestStreamEffectiveLifecycleSchema,
privileges: ingestStreamPrivilegesSchema,
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,47 @@ export class StreamsClient {
});
}

/**
* Checks whether the user has the required privileges to manage the stream.
* Managing a stream means updating the stream properties. It does not
* include the dashboard links.
*/
async getPrivileges(name: string) {
const privileges =
await this.dependencies.scopedClusterClient.asCurrentUser.security.hasPrivileges({
cluster: [
'manage_index_templates',
'manage_ingest_pipelines',
'manage_pipeline',
'read_pipeline',
],
index: [
{
names: [name],
privileges: [
'read',
'write',
'create',
'manage',
'monitor',
'manage_data_stream_lifecycle',
'manage_ilm',
],
},
],
});

return {
manage:
Object.values(privileges.cluster).every((privilege) => privilege === true) &&
Object.values(privileges.index[name]).every((privilege) => privilege === true),
monitor: privileges.index[name].monitor,
lifecycle:
privileges.index[name].manage_data_stream_lifecycle && privileges.index[name].manage_ilm,
simulate: privileges.cluster.read_pipeline && privileges.index[name].create,
};
}

/**
* Creates an on-the-fly ingest stream definition
* from a concrete data stream.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,28 @@ export async function readStream({
}

// These queries are only relavant for IngestStreams
const [ancestors, dataStream] = await Promise.all([
const [ancestors, dataStream, privileges] = await Promise.all([
streamsClient.getAncestors(name),
streamsClient.getDataStream(name).catch((e) => {
if (e.statusCode === 404) {
return null;
}
throw e;
}),
streamsClient.getPrivileges(name),
]);

if (isUnwiredStreamDefinition(streamDefinition)) {
return {
stream: streamDefinition,
elasticsearch_assets: dataStream
? await getUnmanagedElasticsearchAssets({
dataStream,
scopedClusterClient,
})
: undefined,
privileges,
elasticsearch_assets:
dataStream && privileges.manage
? await getUnmanagedElasticsearchAssets({
dataStream,
scopedClusterClient,
})
: undefined,
data_stream_exists: !!dataStream,
effective_lifecycle: getDataStreamLifecycle(dataStream),
dashboards,
Expand All @@ -89,6 +92,7 @@ export async function readStream({
const body: WiredStreamGetResponse = {
stream: streamDefinition,
dashboards,
privileges,
queries,
effective_lifecycle: findInheritedLifecycle(streamDefinition, ancestors),
inherited_fields: getInheritedFieldsFromAncestors(ancestors),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ import {
RouteRenderer,
RouterProvider,
} from '@kbn/typed-react-router-config';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { StreamsAppContextProvider } from '../streams_app_context_provider';
import { streamsAppRouter } from '../../routes/config';
import { StreamsAppStartDependencies } from '../../types';
import { StreamsAppServices } from '../../services/types';
import { HeaderMenuPortal } from '../header_menu';
import { TimeFilterProvider } from '../../hooks/use_timefilter';

export function AppRoot({
Expand Down Expand Up @@ -53,28 +51,9 @@ export function AppRoot({
<BreadcrumbsContextProvider>
<RouteRenderer />
</BreadcrumbsContextProvider>
<StreamsAppHeaderActionMenu appMountParameters={appMountParameters} />
</RouterProvider>
</TimeFilterProvider>
</RedirectAppLinks>
</StreamsAppContextProvider>
);
}

export function StreamsAppHeaderActionMenu({
appMountParameters,
}: {
appMountParameters: AppMountParameters;
}) {
const { setHeaderActionMenu, theme$ } = appMountParameters;

return (
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu} theme$={theme$}>
<EuiFlexGroup responsive={false} gutterSize="s">
<EuiFlexItem>
<></>
</EuiFlexItem>
</EuiFlexGroup>
</HeaderMenuPortal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
*/

import React from 'react';
import { EuiButton, EuiButtonEmpty, EuiFlexGroup } from '@elastic/eui';
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useDiscardConfirm } from '../../../hooks/use_discard_confirm';

interface ManagementBottomBarProps {
confirmButtonText?: string;
disabled?: boolean;
insufficientPrivileges?: boolean;
isLoading?: boolean;
onCancel: () => void;
onConfirm: () => void;
Expand All @@ -22,6 +23,7 @@ export function ManagementBottomBar({
confirmButtonText = defaultConfirmButtonText,
disabled = false,
isLoading = false,
insufficientPrivileges = false,
onCancel,
onConfirm,
}: ManagementBottomBarProps) {
Expand All @@ -46,18 +48,31 @@ export function ManagementBottomBar({
defaultMessage: 'Cancel changes',
})}
</EuiButtonEmpty>
<EuiButton
data-test-subj="streamsAppManagementBottomBarButton"
disabled={disabled}
color="primary"
fill
size="s"
iconType="check"
onClick={onConfirm}
isLoading={isLoading}
<EuiToolTip
content={
insufficientPrivileges
? i18n.translate(
'xpack.streams.streamDetailView.managementTab.bottomBar.onlySimulate',
{
defaultMessage: "You don't have sufficient privileges to save changes.",
}
)
: undefined
}
>
{confirmButtonText}
</EuiButton>
<EuiButton
data-test-subj="streamsAppManagementBottomBarButton"
disabled={disabled || insufficientPrivileges}
color="primary"
fill
size="s"
iconType="check"
onClick={onConfirm}
isLoading={isLoading}
>
{confirmButtonText}
</EuiButton>
</EuiToolTip>
</EuiFlexGroup>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export function StreamDetailEnrichmentContentImpl() {
const { resetChanges, saveChanges } = useStreamEnrichmentEvents();

const hasChanges = useStreamsEnrichmentSelector((state) => state.can({ type: 'stream.update' }));
const canManage = useStreamsEnrichmentSelector(
(state) => state.context.definition.privileges.manage
);
const isSavingChanges = useStreamsEnrichmentSelector((state) =>
state.matches({ ready: { stream: 'updating' } })
);
Expand Down Expand Up @@ -124,6 +127,7 @@ export function StreamDetailEnrichmentContentImpl() {
onConfirm={saveChanges}
isLoading={isSavingChanges}
disabled={!hasChanges}
insufficientPrivileges={!canManage}
/>
</EuiSplitPanel.Inner>
</EuiSplitPanel.Outer>
Expand All @@ -134,6 +138,7 @@ const ProcessorsEditor = React.memo(() => {
const { euiTheme } = useEuiTheme();

const { reorderProcessors } = useStreamEnrichmentEvents();
const definition = useStreamsEnrichmentSelector((state) => state.context.definition);

const processorsRefs = useStreamsEnrichmentSelector((state) =>
state.context.processorsRefs.filter((processorRef) =>
Expand Down Expand Up @@ -222,6 +227,7 @@ const ProcessorsEditor = React.memo(() => {
<SortableList onDragItem={handlerItemDrag}>
{processorsRefs.map((processorRef, idx) => (
<DraggableProcessorListItem
disableDrag={!definition.privileges.manage}
key={processorRef.id}
idx={idx}
processorRef={processorRef}
Expand All @@ -230,7 +236,7 @@ const ProcessorsEditor = React.memo(() => {
))}
</SortableList>
)}
<AddProcessorPanel />
{definition.privileges.simulate && <AddProcessorPanel />}
</EuiPanel>
<EuiPanel paddingSize="m" hasShadow={false} grow={false}>
{!isEmpty(errors.ignoredFields) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export interface EditProcessorPanelProps {
export function EditProcessorPanel({ processorRef, processorMetrics }: EditProcessorPanelProps) {
const { euiTheme } = useEuiTheme();
const state = useSelector(processorRef, (s) => s);
const canEdit = useStreamsEnrichmentSelector((s) => s.context.definition.privileges.manage);
const previousProcessor = state.context.previousProcessor;
const processor = state.context.processor;

Expand Down Expand Up @@ -343,6 +344,7 @@ export function EditProcessorPanel({ processorRef, processorMetrics }: EditProce
data-test-subj="streamsAppEditProcessorPanelButton"
onClick={handleOpen}
iconType="pencil"
disabled={!canEdit}
color="text"
size="xs"
aria-label={i18n.translate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import { EditProcessorPanel, type EditProcessorPanelProps } from './processors';

export const DraggableProcessorListItem = ({
idx,
disableDrag,
...props
}: EditProcessorPanelProps & { idx: number }) => (
}: EditProcessorPanelProps & { idx: number; disableDrag: boolean }) => (
<EuiDraggable
index={idx}
spacing="m"
draggableId={props.processorRef.id}
hasInteractiveChildren
isDragDisabled={disableDrag}
css={{
paddingLeft: 0,
paddingRight: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,18 +195,20 @@ export function StreamDetailLifecycle({

<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={2}>
<EuiPanel grow={true} hasShadow={false} hasBorder paddingSize="s">
<IngestionRate
definition={definition}
refreshStats={refreshStats}
isLoadingStats={isLoadingStats}
stats={stats}
/>
</EuiPanel>
</EuiFlexItem>
{definition.privileges.monitor && (
<EuiFlexItem grow={2}>
<EuiPanel grow={true} hasShadow={false} hasBorder paddingSize="s">
<IngestionRate
definition={definition}
refreshStats={refreshStats}
isLoadingStats={isLoadingStats}
stats={stats}
/>
</EuiPanel>
</EuiFlexItem>
)}

{isIlmLifecycle(definition.effective_lifecycle) ? (
{definition.privileges.lifecycle && isIlmLifecycle(definition.effective_lifecycle) ? (
<EuiFlexItem grow={3}>
<EuiPanel grow={true} hasShadow={false} hasBorder paddingSize="s">
<IlmSummary definition={definition} lifecycle={definition.effective_lifecycle} />
Expand Down
Loading