diff --git a/.gitignore b/.gitignore index 421b03c5822..a4d1d77c9c1 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ test/functional/cypress/screenshots/**/* # vite local files **/.env.local +settings-my.js diff --git a/Dockerfile.slim b/Dockerfile.slim index 939870813f6..8504ad2ce0f 100644 --- a/Dockerfile.slim +++ b/Dockerfile.slim @@ -2,11 +2,11 @@ FROM debian:stable-slim LABEL maintainer="sig-platform@spinnaker.io" WORKDIR /opt/deck -COPY docker /opt/deck/docker +RUN chown www-data:www-data . +COPY --chown=www-data:www-data docker /opt/deck/docker RUN docker/setup-apache2.sh -COPY build/webpack /opt/deck/html -RUN chown -R www-data:www-data /opt/deck +COPY --chown=www-data:www-data build/webpack /opt/deck/html USER www-data CMD docker/run-apache2.sh diff --git a/packages/core/src/artifact/artifactTab.less b/packages/core/src/artifact/artifactTab.less index 112f67646f9..6278a2532ce 100644 --- a/packages/core/src/artifact/artifactTab.less +++ b/packages/core/src/artifact/artifactTab.less @@ -14,6 +14,12 @@ } } +.artifact-list-row { + display: flex; + flex-direction: row; + align-items: baseline; +} + .artifact-list-item { height: 28px; position: relative; diff --git a/packages/core/src/artifact/react/ArtifactIconList.tsx b/packages/core/src/artifact/react/ArtifactIconList.tsx index 0bbc3e6e71f..76f18981a9f 100644 --- a/packages/core/src/artifact/react/ArtifactIconList.tsx +++ b/packages/core/src/artifact/react/ArtifactIconList.tsx @@ -4,33 +4,45 @@ import React from 'react'; import { ArtifactIconService } from '../ArtifactIconService'; import { ArtifactTypePatterns } from '../ArtifactTypes'; import type { IArtifact } from '../../domain'; +import { CopyToClipboard } from '../../utils'; export interface IArtifactIconListProps { artifacts: IArtifact[]; } +function artifactDelimiter(artifact: IArtifact): string { + switch (artifact.type) { + case 'docker/image': + return ':'; + default: + return ' - '; + } +} export const ArtifactIconList = (props: IArtifactIconListProps): any => { return props.artifacts.map((artifact, i) => { const { location, reference, type } = artifact; let { name } = artifact; const iconPath = ArtifactIconService.getPath(type); const key = `${location || ''}${type || ''}${reference || ''}` || String(i); - let title = type; + let artifactName = `${name || reference}${artifact.version ? artifactDelimiter(artifact) + artifact.version : ''}`; + let title = artifactName; + const copyToClipboardText = artifactDelimiter(artifact) === ':' ? artifactName : ''; if (isString(type) && type.length > 0) { const k8sKindMatch = type.match(ArtifactTypePatterns.KUBERNETES); if (k8sKindMatch != null && k8sKindMatch[1].length > 0) { const kind = k8sKindMatch[1].substr(0, 1).toUpperCase() + k8sKindMatch[1].substr(1); name = `${kind} ${name}`; title = name; + artifactName = `${name || reference}${artifact.version ? artifactDelimiter(artifact) + artifact.version : ''}`; } } return ( -
- {iconPath && } - - {name} - {artifact.version && - {artifact.version}} - +
+
+ {iconPath && } + {artifactName} +
+ {copyToClipboardText && }
); }); diff --git a/packages/core/src/cluster/rollups.less b/packages/core/src/cluster/rollups.less index ac65549fe0f..dc6fdcf5d7a 100644 --- a/packages/core/src/cluster/rollups.less +++ b/packages/core/src/cluster/rollups.less @@ -563,6 +563,10 @@ running-tasks-tag { margin-left: 3px; } + .padded-images-span { + padding-left: 35px; + } + .server-group-title, .subgroup-title { padding: 5px 10px 2px; diff --git a/packages/core/src/pipeline/config/actions/delete/DeletePipelineModal.tsx b/packages/core/src/pipeline/config/actions/delete/DeletePipelineModal.tsx index 24de7f7f9cc..194e2a606b1 100644 --- a/packages/core/src/pipeline/config/actions/delete/DeletePipelineModal.tsx +++ b/packages/core/src/pipeline/config/actions/delete/DeletePipelineModal.tsx @@ -1,4 +1,4 @@ -import { get, isEmpty, set } from 'lodash'; +import { get } from 'lodash'; import { $log } from 'ngimport'; import React from 'react'; import { Modal } from 'react-bootstrap'; @@ -27,22 +27,13 @@ export function DeletePipelineModal(props: IDeletePipelineModalProps) { PipelineConfigService.deletePipeline(application.name, pipeline, pipeline.name).then( () => { - const idsToUpdatedIndices = {}; + // const idsToUpdatedIndices = {}; const isPipelineStrategy = pipeline.strategy === true; const data = isPipelineStrategy ? application.strategyConfigs.data : application.pipelineConfigs.data; data.splice( data.findIndex((p: any) => p.id === pipeline.id), 1, ); - data.forEach((p: IPipeline, index: number) => { - if (p.index !== index) { - p.index = index; - set(idsToUpdatedIndices, p.id, index); - } - }); - if (!isEmpty(idsToUpdatedIndices)) { - PipelineConfigService.reorderPipelines(application.name, idsToUpdatedIndices, isPipelineStrategy); - } ReactInjector.$state.go('^.executions', null, { location: 'replace' }); closeModal(); }, diff --git a/packages/core/src/pipeline/config/services/PipelineConfigService.ts b/packages/core/src/pipeline/config/services/PipelineConfigService.ts index 0d32a9943ae..7bbfb814951 100644 --- a/packages/core/src/pipeline/config/services/PipelineConfigService.ts +++ b/packages/core/src/pipeline/config/services/PipelineConfigService.ts @@ -67,7 +67,9 @@ export class PipelineConfigService { } const endpoint = pipeline.strategy ? 'strategies' : 'pipelines'; - return REST(endpoint).query({ staleCheck: true }).post(pipeline); + // return REST(endpoint).query({ staleCheck: true }).post(pipeline); + // temp turn off stale check as it causes gate to throw 400 for unknown reason + return REST(endpoint).post(pipeline); } public static reorderPipelines( @@ -170,22 +172,8 @@ export class PipelineConfigService { return uniq(upstreamStages); } - private static sortPipelines(pipelines: IPipeline[]): PromiseLike { + static sortPipelines(pipelines: IPipeline[]): PromiseLike { const sorted = sortBy(pipelines, ['index', 'name']); - - // if there are pipelines with a bad index, fix that - const toReindex: Array> = []; - if (sorted && sorted.length) { - sorted.forEach((pipeline, index) => { - if (pipeline.index !== index) { - pipeline.index = index; - toReindex.push(this.savePipeline(pipeline)); - } - }); - if (toReindex.length) { - return $q.all(toReindex).then(() => sorted); - } - } return $q.resolve(sorted); } } diff --git a/packages/core/src/pipeline/config/validation/PipelineConfigValidator.ts b/packages/core/src/pipeline/config/validation/PipelineConfigValidator.ts index 19c2f7f3dcc..7da6c597894 100644 --- a/packages/core/src/pipeline/config/validation/PipelineConfigValidator.ts +++ b/packages/core/src/pipeline/config/validation/PipelineConfigValidator.ts @@ -191,7 +191,7 @@ export class PipelineConfigValidator { if (pipeline.strategy && !pipeline.stages.some((stage) => stage.type === 'deploy')) { messages.push('To be able to create new server groups, a custom strategy should contain a Deploy stage.'); } - if ((pipeline.expectedArtifacts || []).some((a) => !a.matchArtifact || (a.matchArtifact as any) === {})) { + if ((pipeline.expectedArtifacts || []).some((a) => !a.matchArtifact || (a.matchArtifact as any) == {})) { messages.push('Every expected artifact must specify an artifact to match against.'); } return messages; diff --git a/packages/core/src/pipeline/filter/ExecutionFilters.tsx b/packages/core/src/pipeline/filter/ExecutionFilters.tsx index 89e9e53a5b0..358e5ba8974 100644 --- a/packages/core/src/pipeline/filter/ExecutionFilters.tsx +++ b/packages/core/src/pipeline/filter/ExecutionFilters.tsx @@ -208,17 +208,67 @@ export class ExecutionFilters extends React.Component { + if (sortEnd.oldIndex === sortEnd.newIndex) { + return; + } const pipelineNames = arrayMove(this.state.pipelineNames, sortEnd.oldIndex, sortEnd.newIndex); - this.applyNewPipelineSortOrder(pipelineNames); + this.setState({ pipelineNames: pipelineNames }); + this.sortAsVisuallySeen(pipelineNames); }; private sortAlphabetically = () => { const pipelineNames = this.state.pipelineNames.slice().sort(); - this.applyNewPipelineSortOrder(pipelineNames); + this.setState({ pipelineNames: pipelineNames }); + this.sortAsVisuallySeen(pipelineNames); }; + private sortAsVisuallySeen(pipelineNames: string[]) { + const idsToUpdatedIndices: { [key: string]: number } = {}; + let i = 0; + const indices: number[] = []; + this.props.application.pipelineConfigs.data.forEach((pipeline: IPipeline) => { + indices.push(pipeline.index); + }); + for (const pName of pipelineNames) { + const pipeline = this.getPipelineByName(pName); + const toIndex = indices[i]; + if (pipeline.index !== toIndex) { + idsToUpdatedIndices[pipeline.id] = toIndex; + this.setPipelineIndex(pipeline.id, toIndex); + } + i = i + 1; + } + if (!isEmpty(idsToUpdatedIndices)) { + this.updatePipelines(idsToUpdatedIndices).then(() => { + this.refreshPipelines(); + }); + } + } + + // @ts-ignore + // noinspection JSUnusedLocalSymbols private applyNewPipelineSortOrder = (pipelineNames: string[]): void => { const { application } = this.props; logger.log({ category: 'Pipelines', action: 'Reordered pipeline' }); @@ -232,7 +282,7 @@ export class ExecutionFilters extends React.Component {}); this.refreshPipelines(); } }; diff --git a/packages/core/src/pipeline/status/Artifact.tsx b/packages/core/src/pipeline/status/Artifact.tsx index 0051fe4c1c0..a337293b181 100644 --- a/packages/core/src/pipeline/status/Artifact.tsx +++ b/packages/core/src/pipeline/status/Artifact.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { ArtifactIconService } from '../../artifact'; import type { IArtifact } from '../../domain'; +import { CopyToClipboard } from '../../utils'; import './artifact.less'; @@ -32,9 +33,20 @@ export class Artifact extends React.Component { return tooltipEntries.join('\n'); } + private artifactDelimiter(artifact: IArtifact): string { + switch (artifact.type) { + case 'docker/image': + return ':'; + default: + return ' - '; + } + } + public render() { const { artifact, isDefault } = this.props; const { name, reference, version, type } = artifact; + const artifactName = `${name || reference}${this.artifactDelimiter(artifact)}${version || 'latest'}`; + const copyToClipboardText = this.artifactDelimiter(artifact) === ':' ? artifactName : ''; return (
@@ -42,14 +54,22 @@ export class Artifact extends React.Component {
{ArtifactIconService.getPath(type) ? ( - + {type} ) : ( [{type}] )}
-
{name || reference}
- {version &&
- {version}
} +
+                {artifactName}
+                {copyToClipboardText && }
+              
diff --git a/packages/core/src/pipeline/status/artifact.less b/packages/core/src/pipeline/status/artifact.less index a0d951c21b2..ee3546ef82b 100644 --- a/packages/core/src/pipeline/status/artifact.less +++ b/packages/core/src/pipeline/status/artifact.less @@ -12,6 +12,7 @@ .artifact-detail { display: inline-flex; padding: 0.1px; + align-items: baseline; .artifact-icon { padding-right: 4px; diff --git a/packages/core/src/presentation/main.less b/packages/core/src/presentation/main.less index 68a3c2ec1f3..0f0a458ee65 100644 --- a/packages/core/src/presentation/main.less +++ b/packages/core/src/presentation/main.less @@ -1464,6 +1464,8 @@ ul.checkmap { .break-word { overflow-wrap: break-word; + text-wrap: avoid; + white-space: nowrap; } .horizontal-rule { diff --git a/packages/core/src/serverGroup/ServerGroupHeader.tsx b/packages/core/src/serverGroup/ServerGroupHeader.tsx index 3322f3510b0..8dfafc971cd 100644 --- a/packages/core/src/serverGroup/ServerGroupHeader.tsx +++ b/packages/core/src/serverGroup/ServerGroupHeader.tsx @@ -13,6 +13,7 @@ import { LoadBalancersTagWrapper } from '../loadBalancer'; import { NameUtils } from '../naming'; import { Overridable } from '../overrideRegistry'; import { RunningTasksTag } from './pod/RunningTasksTag'; +import { CopyToClipboard } from '../utils'; export interface IServerGroupHeaderProps { application: Application; @@ -83,7 +84,10 @@ export class ImageList extends React.Component {collapsed && ( <> - {images[0]} + + {images[0]} + +   {images.length > 1 && (