feat: [CDE-107]: Gitspace in standalone gitness (#2207)

unified-ui
Deepesh Kumar 2024-07-13 02:18:25 +00:00 committed by Harness
parent 31ae886d00
commit 6f7d1079d9
54 changed files with 7635 additions and 483 deletions

View File

@ -30,6 +30,7 @@ const moduleFederationConfig = require('./moduleFederation.config')
const moduleFederationConfigCDE = require('./cde/moduleFederation.config')
const CONTEXT = process.cwd()
const DEV = process.env.NODE_ENV === 'development'
const ENABLE_GITSPACE = process.env.ENABLE_GITSPACE === 'true'
const getModuleFields = () => {
if (process.env.MODULE === 'cde') {
@ -223,7 +224,8 @@ module.exports = {
moduleFederationPlugin,
new DefinePlugin({
'process.env': '{}', // required for @blueprintjs/core
__DEV__: DEV
__DEV__: DEV,
__ENABLE_GITSPACE__: ENABLE_GITSPACE
}),
new GenerateStringTypesPlugin(),
new RetryChunkLoadPlugin({

View File

@ -34,12 +34,13 @@ const API_URL = process.env.API_URL ?? 'http://localhost:3000'
const HOST = 'localhost'
const PORT = getPortByModule()
const STANDALONE = process.env.STANDALONE === 'true'
const ENABLE_GITSPACE = process.env.ENABLE_GITSPACE === 'true'
const CONTEXT = process.cwd()
const prodConfig = require('./webpack.prod')
console.info(`Starting development build... http://${HOST}:${PORT}`)
console.info('Environment variables:')
console.table({ STANDALONE, HOST, PORT, API_URL })
console.table({ STANDALONE, ENABLE_GITSPACE, HOST, PORT, API_URL })
const devConfig = {
mode: 'development',

View File

@ -19,12 +19,12 @@
"webpack:cde": "NODE_ENV=development MODULE=cde webpack serve --config config/webpack.dev.js",
"typed-scss": "typed-scss-modules src --watch",
"dev": "run-p webpack typed-scss",
"dev:cde": "MODULE=cde run-p webpack:cde typed-scss",
"dev:cde": "MODULE=cde ENABLE_GITSPACE=true run-p webpack:cde typed-scss",
"test": "jest src --silent",
"test:watch": "jest --watch",
"build": "rm -rf dist && webpack --config config/webpack.prod.js",
"build:cde": "rm -rf dist && MODULE=cde webpack --config config/webpack.prod.js",
"lint": "eslint --rulesdir ./scripts/eslint-rules --ext .ts --ext .tsx src",
"build:cde": "rm -rf dist && MODULE=cde ENABLE_GITSPACE=true webpack --config config/webpack.prod.js",
"lint": "eslint --fix --rulesdir ./scripts/eslint-rules --ext .ts --ext .tsx src",
"prettier": "prettier --check \"./src/**/*.{ts,tsx,css,scss}\"",
"coverage": "npm test --coverage",
"typecheck": "tsc",
@ -47,7 +47,7 @@
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.9.6",
"@harnessio/design-system": "^2.1.1",
"@harnessio/icons": "^2.1.3",
"@harnessio/icons": "^2.1.5",
"@harnessio/uicore": "^4.1.2",
"@types/dompurify": "^3.0.2",
"@types/react-monaco-editor": "^0.16.0",

View File

@ -51,7 +51,7 @@ import CodeSearchPage from 'pages/Search/CodeSearchPage'
import AddUpdatePipeline from 'pages/AddUpdatePipeline/AddUpdatePipeline'
import { useAppContext } from 'AppContext'
import PipelineSettings from 'components/PipelineSettings/PipelineSettings'
import GitspaceDetail from 'cde/pages/GitspaceDetail/GitspaceDetail'
import { GitspaceDetails } from 'cde-gitness/pages/GitspaceDetails/GitspaceDetails'
import { GitspaceListing } from 'cde-gitness/pages/GitspaceListing/GitspaceListing'
import { GitspaceCreate } from 'cde-gitness/pages/GitspaceCreate/GitspaceCreate'
@ -283,7 +283,7 @@ export const RouteDestinations: React.FC = React.memo(function RouteDestinations
{standalone && (
<Route path={routes.toCDEGitspaceDetail({ space: pathProps.space, gitspaceId: pathProps.gitspaceId })}>
<LayoutWithSideNav title={getString('cde.gitspaces')}>
<GitspaceDetail />
<GitspaceDetails />
</LayoutWithSideNav>
</Route>
)}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M3.429 6.32a.8.8 0 0 1 .793-.907h7.099a.8.8 0 0 1 .796.88l-.693 6.805a.8.8 0 0 1-.795.72H5.145a.8.8 0 0 1-.793-.693L3.43 6.32Zm7.892-.107h-7.1l.924 6.805h5.484l.692-6.805ZM6.61 1.729a.4.4 0 0 1 .48-.3l2.816.642a.4.4 0 0 1 .301.48l-.206.902 2.573.588a.4.4 0 1 1-.178.78L3.69 2.832a.4.4 0 0 1 .178-.78l2.537.58.206-.903Zm.574 1.08 2.037.466.117-.513-2.036-.465-.118.513Z" fill="#E43326"/><path opacity=".5" fill-rule="evenodd" clip-rule="evenodd" d="M8 7.872c.184 0 .334.15.334.333v3.334a.333.333 0 1 1-.667 0V8.205c0-.184.15-.333.333-.333ZM6.16 7.88a.333.333 0 0 1 .36.303l.29 3.32a.333.333 0 1 1-.663.059l-.29-3.321a.333.333 0 0 1 .302-.361ZM9.841 7.88a.333.333 0 0 1 .303.36l-.29 3.322a.333.333 0 1 1-.664-.059l.29-3.32a.333.333 0 0 1 .361-.303Z" fill="#E43326"/></svg>

After

Width:  |  Height:  |  Size: 891 B

View File

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 395 B

View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70 70"><path fill-rule="evenodd" clip-rule="evenodd" d="M54.704 60H13.508C6.048 60 0 53.844 0 46.25c0-6.662 4.654-12.217 10.834-13.48a.275.275 0 0 0 .218-.27c0-12.427 9.896-22.5 22.104-22.5 10.62 0 19.49 7.622 21.619 17.785.117.559.589.971 1.148 1.018C63.805 29.463 70 36.183 70 44.375c0 8.419-6.541 15.283-14.733 15.612a.006.006 0 0 0-.006.007.006.006 0 0 1-.006.006h-.551Zm-27.886-7.613a2.817 2.817 0 0 1 2.817-2.818h12.963a2.817 2.817 0 1 1 0 5.636H29.635a2.818 2.818 0 0 1-2.817-2.818Zm4.734-29.431a2.818 2.818 0 0 0-3.834 4.129l7.859 7.297-7.859 7.298a2.817 2.817 0 1 0 3.834 4.13l10.083-9.363a2.817 2.817 0 0 0 0-4.13l-10.083-9.361Z" fill="#004BA4" opacity=".8"/></svg>

After

Width:  |  Height:  |  Size: 740 B

View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 17"><path fill="#fff" fill-opacity=".01" d="M0 .5h16v16H0z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M7.551.737a.64.64 0 0 1 .898 0l7.2 7.083a.64.64 0 0 1-.898.912l-.884-.87v5.971a.533.533 0 0 1-.534.534H2.668a.533.533 0 0 1-.534-.534V7.862l-.884.87a.64.64 0 1 1-.898-.912l7.2-7.083Zm.45 1.354 4.8 4.722V13.3h-2.134V9.567a.533.533 0 0 0-.534-.534h-3.2a.533.533 0 0 0-.533.534V13.3H3.2V6.813L8 2.09ZM7.466 13.3H9.6v-3.2H7.467v3.2Z" fill="#030010" fill-opacity=".612"/></svg>

After

Width:  |  Height:  |  Size: 550 B

View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92 87"><path d="m8.64 62.856-1.671-.967 5.803-9.233 1.846.88-5.979 9.32ZM31.5 63.472l-1.582.615-6.33-8.617h2.637l5.276 8.002Z" fill="#642F08"/><path d="M10.509 5.266C7.9 7.22 8.245 26.724 8.743 36.23c.815 1.54 2.092 5.84.68 10.73-1.413 4.888 4.21 6.654 7.197 6.925l6.519-6.926.815-16.569L30.2 7.71c-.996-1.675-3.64-5.242-6.247-6.111C20.694.512 13.768 2.82 10.509 5.266ZM5.142 65.362c-1.208-1.542.504-2.9 1.51-3.385 2.46.666 2.657 2.118 2.449 2.76-.816.851-2.75 2.167-3.959.625ZM29.624 66.925c-2.084-1.334-.59-2.778.417-3.334 1.254-1.146 3.281 0 3.542 1.771.26 1.771-1.355 3.23-3.96 1.563Z" fill="#00ADE4"/><path d="M1.287 44.358v-3.922l42.136-18.728 18.601-4.049 28.977 14.299v4.049L56.457 50.81 34.44 60.175l-10.503-5.188v21.385l12.02 5.947v2.15l-3.289 1.266L4.324 71.816v-1.771l3.163-1.392 8.858 3.923V51.824L1.287 44.358Z" fill="#A64F0E"/><path d="M67.97 51.57v-5.567l6.833-3.543v12.274l12.021 5.694v2.404l-2.784 1.012-27.458-13.16 4.935-2.024 6.453 2.91Z" fill="#A64F0E"/><path d="M33.775 56.084v4.143" stroke="#000" stroke-width=".627" stroke-linecap="round" stroke-linejoin="round"/><path d="M90.85 32.03v4.138M1 39.953v4.138" stroke="#000" stroke-width=".627" stroke-linecap="round"/><path d="m33.746 56.185 56.699-24.054M34.15 60.222l56.7-24.054M1 39.953l33.15 16.07M57.7 16.06l33.149 16.07M1 44.153l33.15 16.07M16.205 51.638V75.08M22.072 54.717v23.441M23.516 55.294v23.442M32.74 83.65l3.009-1.277M7.173 68.515l-3.009 1.276v2.052M32.74 85.601l3.027-1.284M4.182 71.88 32.78 85.743M4.232 69.772l28.65 13.888M35.62 84.206v-1.84l-11.84-5.74m-7.613-3.69-9.001-4.363m25.725 15.102v1.839M67.545 45.929v7.706M73.412 56.713V43.598M74.856 57.29V43.069M84.08 62.205l3.009-1.276M84.08 64.157l3.027-1.284M84.12 64.298 56.41 50.865M84.222 62.216l-25.474-12.35M86.962 62.76v-1.838l-11.84-5.74m-7.614-3.691-5.84-2.831m22.564 13.57v1.84" stroke="#000" stroke-width=".627" stroke-linecap="round" stroke-linejoin="round"/><path d="M8.58 36.714c-.262-6.654-.651-12.75 0-19.392.354-3.611.207-7.548 1.235-11.05.54-1.839 4.72-3.688 7.82-4.648C23.57-.213 27.4 2.192 30.053 7.94M8.683 48.342c-.16 1.852 2.131 3.954 3.59 4.716 1.067.558 2.705.943 3.819.943M12.707 53.505l-5.82 8.293M14.58 53.984l-5.74 8.491M9.218 64.67c-.614 1.842-3.199 2.258-4.34.723-.834-1.155-.413-3.057 1.167-3.456 1.488-.375 3.766.953 3.173 2.733ZM31.21 63.009l-4.32-6.154m-3.374-1.66 5.74 8.491m3.963 2.918c-1.142 1.535-3.727 1.119-4.34-.723-.594-1.78 1.684-3.108 3.172-2.733 1.58.399 2.002 2.301 1.168 3.456Z" stroke="#000" stroke-width=".627" stroke-linecap="round"/><path d="m33.51 29.878-11.613 5.02L34.1 41.1l28.347-11.91V9.111L34.1 21.217l-.59 8.661Z" fill="#6B6D85"/><path d="M21.395 35.097 33.88 29.8" stroke="#000" stroke-width=".627" stroke-linecap="round" stroke-linejoin="round"/><path d="m24.095 29.985-7.172 3.306c-1.953-1.831 1.797-7.884 3.917-10.68l1.474-3.765C17.758 10.584 25.74 8.012 30.3 7.76c8.137 1.22 7.256 6.849 5.798 9.51l2.746 1.577-4.984 2.187v4.883l-5.85 2.594c-.243 2.889 2.85 6.154 4.426 7.425-1.22.163-6.07-3.899-8.341-5.95Z" fill="#fff"/><path d="m33.824 21 28.35-12.026M62.174 9.301v19.795l-28.35 12.027v-20.06" stroke="#000" stroke-width=".627" stroke-linecap="round" stroke-linejoin="round"/><path d="m33.826 41.123-8.288-4.017-4.143-2.01" stroke="#000" stroke-width=".627" stroke-linecap="round" stroke-linejoin="round"/><path d="M22.273 18.912c-4.277 5.477 2.631 13.572 10.276 17.256" stroke="#000" stroke-width=".627" stroke-linecap="round"/><path d="M32.041 35.787s-4.23-3.827-4.23-7.511c0-3.004 3.304-5.248 5.877-3.705m5.206-5.579-2.74-1.768M20.723 22.307c-1.74 3.544-5.866 9.365-3.91 11.114M36.97 12.943c.485 1.923-.575 3.712-1.841 5.1-3.42 3.749-14.14 3.336-14.4-3.064-.333-8.147 14.154-10.304 16.24-2.036Z" stroke="#000" stroke-width=".627" stroke-linecap="round"/><path d="M28.016 15.516c.823 2.778 3.602 3.601 6.998 2.572" stroke="#000" stroke-width=".627" stroke-linecap="round"/><path d="M1 39.952 24.226 30.1m3.652-1.55 6.092-2.584" stroke="#000" stroke-width=".627" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 17"><path d="M4 12.833a1 1 0 0 0 1 1h.125a1 1 0 0 0 1-1V4.167a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v8.666Zm6.323-9.666a1 1 0 0 0-1 1v8.666a1 1 0 0 0 1 1h.145a1 1 0 0 0 1-1V4.167a1 1 0 0 0-1-1h-.145Z" fill="#6B6D85"/></svg>

After

Width:  |  Height:  |  Size: 282 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 548 KiB

View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#fff" fill-opacity=".01" d="M0 0h16v16H0z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M8 1.92a6.08 6.08 0 1 0 0 12.16A6.08 6.08 0 0 0 8 1.92ZM.96 8a7.04 7.04 0 1 1 14.08 0A7.04 7.04 0 0 1 .96 8Z" fill="#6B6D85"/><path fill-rule="evenodd" clip-rule="evenodd" d="M14.4 8.427H1.6v-.854h12.8v.854Z" fill="#6B6D85"/><path fill-rule="evenodd" clip-rule="evenodd" d="M7.573 14.4V1.6h.853v12.8h-.853ZM11.066 8c0-2.317-.833-4.61-2.467-6.126l.508-.548C10.922 3.01 11.813 5.518 11.813 8s-.891 4.99-2.706 6.674l-.508-.548c1.634-1.515 2.467-3.808 2.467-6.126Zm-6.8 0c0-2.479.862-4.985 2.622-6.67l.516.54C5.822 3.385 5.013 5.679 5.013 8c0 2.321.809 4.615 2.391 6.13l-.516.54C5.128 12.985 4.266 10.479 4.266 8Z" fill="#6B6D85"/><path fill-rule="evenodd" clip-rule="evenodd" d="M8 4.222c2.314 0 4.667.428 6.262 1.319a.373.373 0 1 1-.364.652c-1.44-.805-3.65-1.224-5.898-1.224-2.249 0-4.458.42-5.898 1.224a.373.373 0 0 1-.364-.652C3.333 4.65 5.686 4.22 8 4.22Zm0 7.351c2.314 0 4.667-.428 6.262-1.319a.373.373 0 1 0-.364-.651c-1.44.804-3.65 1.224-5.898 1.224-2.249 0-4.458-.42-5.898-1.224a.373.373 0 1 0-.364.651c1.595.891 3.948 1.32 6.262 1.32Z" fill="#6B6D85"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,92 @@
/*
* Copyright 2023 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import 'src/utils/utils';
.consoleContainer {
min-height: 500px !important;
max-height: 70vh !important;
overflow: scroll;
align-items: start !important;
padding-top: var(--spacing-large) !important;
padding-left: var(--spacing-xlarge) !important;
background-color: var(--black) !important;
color: var(--white) !important;
pre {
background-color: var(--black) !important;
color: var(--white) !important;
text-align: start !important;
font-size: 12px;
font-family: var(--font-family-mono) !important;
}
}
.logTitle {
color: var(--white) !important;
background: var(--black) !important;
border-top-left-radius: 50px;
border-top-right-radius: 50px;
padding-top: var(--spacing-medium) !important;
text-align: left;
padding-left: var(--spacing-xxlarge) !important;
padding-bottom: var(--spacing-large) !important;
font-size: 16px !important;
}
.mainLog {
flex-shrink: 0;
.line {
margin: 0;
padding: 0;
cursor: text;
min-height: 20px;
display: block;
@include mono-font;
color: var(--white);
word-wrap: break-word !important;
white-space: pre-wrap !important;
}
}
.stepLogContainer {
padding: var(--spacing-small) !important;
flex-shrink: 0;
.consoleLine {
color: var(--white);
@include mono-font;
word-wrap: break-word !important;
white-space: pre-wrap !important;
cursor: text;
margin: 0;
padding: 0;
&:empty {
display: inline-block;
min-height: 20px;
}
}
}
.logContainer {
width: 60%;
max-width: 800px !important;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2023 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable */
// This is an auto-generated file
export declare const consoleContainer: string
export declare const consoleLine: string
export declare const line: string
export declare const logContainer: string
export declare const logTitle: string
export declare const mainLog: string
export declare const stepLogContainer: string

View File

@ -0,0 +1,52 @@
import { Container } from '@harnessio/uicore'
import cx from 'classnames'
import React, { useEffect, useRef } from 'react'
import { lineElement } from 'components/LogViewer/LogViewer'
import type { LogData } from '../../hooks/useGetLogStream'
import css from './ContainerLogs.module.scss'
const ContainerLogs = ({ data }: { data: LogData[] }) => {
const localRef = useRef<HTMLDivElement | null>()
useEffect(() => {
try {
if (data) {
const fragment = new DocumentFragment()
const logContainer = localRef.current as HTMLDivElement
// Clear the container first
if (localRef.current) {
localRef.current.innerHTML = ''
}
if (data) {
data?.forEach((line: any) => {
const linePos = line.pos + 1
const localDate = new Date(line.time)
// Format date to a more readable format (local time)
const formattedDate = localDate.toLocaleString()
fragment.appendChild(lineElement(`${linePos} ${formattedDate.replace(',', '')} ${line.out}`))
})
logContainer.appendChild(fragment)
}
const scrollParent = logContainer.parentElement as HTMLDivElement
const autoScroll =
scrollParent && scrollParent.scrollTop === scrollParent.scrollHeight - scrollParent.offsetHeight
if (autoScroll || scrollParent.scrollTop === 0) {
scrollParent.scrollTop = scrollParent.scrollHeight
}
}
} catch (_err) {
//
}
}, [data])
return (
<Container className={css.consoleContainer}>
<Container key={`harnesslog`} ref={localRef} className={cx(css.mainLog, css.stepLogContainer)} />
</Container>
)
}
export default ContainerLogs

View File

@ -0,0 +1,89 @@
import { Color } from '@harnessio/design-system'
import { Layout, Text } from '@harnessio/uicore'
import React from 'react'
import ReactTimeago from 'react-timeago'
import { Circle } from 'iconoir-react'
import type { IconName } from '@harnessio/icons'
import { useStrings } from 'framework/strings'
import { getIconByRepoType } from 'cde/components/CreateGitspace/components/SelectRepository/SelectRepository.utils'
import type { TypesGitspaceConfig } from 'cde-gitness/services'
import { GitspaceStatus } from 'cde/constants'
import { getStatusColor, getStatusText } from '../GitspaceListing/ListGitspaces'
export const DetailsCard = ({ data }: { data: TypesGitspaceConfig | null; loading?: boolean }) => {
const { getString } = useStrings()
const { branch, state, name, code_repo_url, code_repo_type, instance } = data || {}
const color = getStatusColor(state)
const customProps =
state === GitspaceStatus.STARTING
? {
icon: 'loading' as IconName,
iconProps: { color: Color.PRIMARY_4 }
}
: { icon: undefined }
return (
<>
<Layout.Horizontal
width={'80%'}
flex={{ justifyContent: 'space-between' }}
padding={{ bottom: 'xlarge', top: 'xlarge' }}>
<Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}>
<Text>{getString('cde.status')}</Text>
<Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'start' }}>
{state !== GitspaceStatus.STARTING && <Circle height={10} width={10} color={color} fill={color} />}
<Text
{...customProps}
color={Color.BLACK}
title={name}
font={{ align: 'left', size: 'normal', weight: 'semi-bold' }}>
{getStatusText(getString, state)}
</Text>
</Layout.Horizontal>
</Layout.Vertical>
<Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}>
<Text>{getString('cde.repository.repo')}</Text>
<Layout.Horizontal
spacing="small"
flex={{ alignItems: 'center', justifyContent: 'start' }}
onClick={e => {
e.preventDefault()
e.stopPropagation()
}}>
{getIconByRepoType({ repoType: code_repo_type, height: 20 })}
<Text
title={'RepoName'}
color={Color.PRIMARY_7}
margin={{ left: 'small' }}
style={{ cursor: 'pointer' }}
font={{ align: 'left', size: 'normal' }}
onClick={() => window.open(code_repo_url, '_blank')}>
{name}
</Text>
</Layout.Horizontal>
</Layout.Vertical>
<Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}>
<Text>{getString('branch')}</Text>
<Text
iconProps={{ size: 10 }}
color={Color.PRIMARY_7}
icon="git-branch"
style={{ cursor: 'pointer' }}
onClick={() => window.open(code_repo_url, '_blank')}>
{branch}
</Text>
</Layout.Vertical>
<Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}>
<Text>{getString('cde.lastUsed')}</Text>
{instance?.last_used ? (
<ReactTimeago date={instance?.last_used || 0} />
) : (
<Text color={Color.GREY_500}>{getString('cde.na')}</Text>
)}
</Layout.Vertical>
</Layout.Horizontal>
</>
)
}

View File

@ -0,0 +1,23 @@
.marker {
align-self: center;
position: relative;
left: 5.5px;
height: 12px;
width: 12px;
background: var(--grey-400) !important;
border-radius: 6px;
padding: 2px;
border: 2px solid #fff;
}
.main {
overflow: scroll;
max-height: 350px;
}
.lightBackground {
background: #dee8f9 !important;
}
.darkBackground {
background: #bbd1f6 !important;
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2023 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable */
// This is an auto-generated file
export declare const darkBackground: string
export declare const lightBackground: string
export declare const main: string
export declare const marker: string

View File

@ -0,0 +1,80 @@
import React, { useEffect, useRef, useState } from 'react'
import { Color } from '@harnessio/design-system'
import { Container, Text, Layout } from '@harnessio/uicore'
import { isArray, isEqual } from 'lodash-es'
import type { TypesGitspaceEventResponse } from 'cde-gitness/services'
import { useStrings } from 'framework/strings'
import { formatTimestamp } from './EventTimeline.utils'
import css from './EventTimeline.module.scss'
const EventTimeline = ({ data }: { data?: TypesGitspaceEventResponse[] | null; polling?: boolean }) => {
const localRef = useRef<HTMLDivElement | null>(null)
const scrollContainerRef = useRef<HTMLDivElement | null>(null)
const { getString } = useStrings()
const [cache, setCache] = useState(data)
useEffect(() => {
if (!isEqual(data, cache)) {
setCache(data)
}
}, [data])
useEffect(() => {
if (scrollContainerRef.current) {
const scrollParent = scrollContainerRef.current
const autoScroll = scrollParent.scrollTop <= scrollParent.scrollHeight - scrollParent.clientHeight
if (autoScroll || scrollParent.scrollTop === 0) {
scrollParent.scrollTop = scrollParent.scrollHeight
}
}
}, [cache])
return (
<Container className={css.main} ref={scrollContainerRef}>
{!data?.length && isArray(data) && (
<Container
width={'100%'}
padding={{ left: 'large' }}
flex={{ alignItems: 'center' }}
height={'64px'}
border={{ left: true, color: Color.GREY_200 }}
background={Color.PRIMARY_2}>
<Text iconProps={{ color: Color.GREEN_450 }} icon="no-deployments">
{getString('cde.details.fetchingDetails')}
</Text>
</Container>
)}
<Container ref={localRef}>
{data?.map((item, index) => {
return (
<Layout.Horizontal background={Color.GREY_50} key={item.query_key}>
<Container
background={Color.GREY_50}
width={'8%'}
flex={{ alignItems: 'center', justifyContent: 'center' }}>
{formatTimestamp(item.timestamp || 0)}
</Container>
<Text className={css.marker} />
<Container
width={'92%'}
padding={{ left: 'large' }}
flex={{ alignItems: 'center' }}
height={'64px'}
border={{ left: true, color: Color.GREY_200 }}
className={index % 2 ? css.lightBackground : css.darkBackground}>
<Text iconProps={{ color: Color.GREEN_450 }} icon="tick-circle">
{`${item.message}`}
</Text>
</Container>
</Layout.Horizontal>
)
})}
</Container>
</Container>
)
}
export default EventTimeline

View File

@ -0,0 +1,28 @@
import React from 'react'
import moment from 'moment'
import { Text, Layout } from '@harnessio/uicore'
import { Color } from '@harnessio/design-system'
export const formatTimestamp = (timestamp: number) => {
const inputDate = moment(timestamp)
const currentDate = moment()
if (inputDate.isSame(currentDate, 'day')) {
return (
<Text width="70%" color={Color.GREY_500}>
{inputDate.format('HH:mm:ss')}
</Text>
)
} else {
return (
<Layout.Vertical width="70%" spacing="small">
<Text font={{ size: 'small' }} color={Color.GREY_500}>
{inputDate.format('YYYY-MM-DD')}
</Text>
<Text font={{ size: 'small' }} color={Color.GREY_500}>
{inputDate.format('HH:mm:ss')}
</Text>
</Layout.Vertical>
)
}
}

View File

@ -0,0 +1,25 @@
import React from 'react'
import { defaultTo } from 'lodash-es'
import { Accordion } from '@harnessio/uicore'
import type { TypesGitspaceEventResponse } from 'cde-gitness/services'
import EventTimelineSummary from '../EventTimelineSummary/EventTimelineSummary'
import EventTimeline from '../EventTimeline/EventTimeline'
import parentCss from 'cde-gitness/pages/GitspaceDetails/GitspaceDetails.module.scss'
const EventTimelineAccordion = ({ data }: { data: TypesGitspaceEventResponse[] | null; polling?: boolean }) => {
const sortedData = data?.sort((a, b) => defaultTo(a?.timestamp, 0) - defaultTo(b?.timestamp, 0))
const latestEvent = sortedData?.[sortedData?.length - 1] || { message: '', timestamp: 0 }
return (
<Accordion activeId="eventsCard">
<Accordion.Panel
shouldRender
id="eventsCard"
details={<EventTimeline data={sortedData} />}
summary={<EventTimelineSummary message={latestEvent.message} timestamp={latestEvent.timestamp} />}
className={parentCss.accordionnCustomSummary}
/>
</Accordion>
)
}
export default EventTimelineAccordion

View File

@ -0,0 +1,28 @@
import React from 'react'
import moment from 'moment'
import { Container, Text, Layout } from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
import { useStrings } from 'framework/strings'
const EventTimelineSummary = ({ timestamp, message }: { timestamp?: number; message?: string }) => {
const { getString } = useStrings()
return (
<Container width="100%" flex={{ alignItems: 'center', justifyContent: 'space-between' }}>
<Text font={{ variation: FontVariation.CARD_TITLE }} margin={{ left: 'large' }}>
{getString('cde.details.gitspaceActivity')}
</Text>
{Boolean(message) && (
<Layout.Horizontal spacing="large" flex={{ alignItems: 'center' }}>
<Text iconProps={{ color: Color.GREEN_450 }} icon="tick-circle">
{message}
</Text>
<Text margin={{ left: 'large' }} font={{ size: 'small' }}>
{moment(timestamp).format('DD MMM, YYYY hh:mma')}
</Text>
</Layout.Horizontal>
)}
</Container>
)
}
export default EventTimelineSummary

View File

@ -1,3 +1,8 @@
.repoAndBranch {
margin-bottom: 0 !important;
}
.noReposContainer {
padding: var(--spacing-large) !important;
background-color: var(--grey-50) !important;
}

View File

@ -16,4 +16,5 @@
/* eslint-disable */
// This is an auto-generated file
export declare const noReposContainer: string
export declare const repoAndBranch: string

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import { useGet } from 'restful-react'
import { Container, ExpandingSearchInput, Layout, Text } from '@harnessio/uicore'
import { Button, ButtonVariation, Container, ExpandingSearchInput, Layout, Text } from '@harnessio/uicore'
import { Menu, MenuItem } from '@blueprintjs/core'
import { Color } from '@harnessio/design-system'
import { Icon } from '@harnessio/icons'
@ -10,8 +10,10 @@ import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import { String, useStrings } from 'framework/strings'
import { LIST_FETCHING_LIMIT } from 'utils/Utils'
import NewRepoModalButton from 'components/NewRepoModalButton/NewRepoModalButton'
import noRepo from 'cde-gitness/assests/noRepo.svg?url'
import { RepoCreationType } from 'utils/GitUtils'
import gitnessRepoLogo from 'cde-gitness/assests/gitness.svg?url'
import { GitspaceSelect } from '../../../cde/components/GitspaceSelect/GitspaceSelect'
import gitnessRepoLogo from './gitness.svg?url'
import css from './GitnessRepoImportForm.module.scss'
const RepositoryText = ({ repoList, value }: { repoList: TypesRepository[] | null; value?: string }) => {
@ -64,6 +66,7 @@ export const GitnessRepoImportForm = () => {
const space = useGetSpaceParam()
const [branchSearch, setBranchSearch] = useState('')
const [repoSearch, setRepoSearch] = useState('')
const [hadReops, setHadRepos] = useState(false)
const [repoRef, setReporef] = useState('')
const {
@ -76,6 +79,12 @@ export const GitnessRepoImportForm = () => {
debounce: 500
})
useEffect(() => {
if (!hadReops && repositories?.length) {
setHadRepos(true)
}
}, [repositories])
const {
data: branches,
refetch,
@ -100,6 +109,7 @@ export const GitnessRepoImportForm = () => {
}, [repoRef, branchSearch])
const repoListOptions = repositories || []
const hideInitialMenu = Boolean(repoSearch) || Boolean(repositories)
const formik = useFormikContext<any>()
@ -124,15 +134,17 @@ export const GitnessRepoImportForm = () => {
}}
renderMenu={
<Menu>
<Container margin={'small'}>
<ExpandingSearchInput
placeholder={getString('cde.create.searchRepositoryPlaceholder')}
alwaysExpanded
autoFocus={false}
defaultValue={repoSearch}
onChange={setRepoSearch}
/>
</Container>
{hideInitialMenu && (
<Container margin={'small'}>
<ExpandingSearchInput
placeholder={getString('cde.create.searchRepositoryPlaceholder')}
alwaysExpanded
autoFocus={false}
defaultValue={repoSearch}
onChange={setRepoSearch}
/>
</Container>
)}
{loading ? (
<MenuItem disabled text={getString('loading')} />
) : repoListOptions?.length ? (
@ -147,11 +159,12 @@ export const GitnessRepoImportForm = () => {
}
active={repo.git_url === values.code_repo_url}
onClick={() => {
const repoParams = repo?.path?.split('/') || []
formik.setValues((prvValues: any) => {
return {
...prvValues,
code_repo_url: repo.git_url,
id: repo.path,
identifier: repoParams?.[repoParams.length - 1],
name: repo.path
}
})
@ -159,18 +172,69 @@ export const GitnessRepoImportForm = () => {
}}
/>
))
) : (
) : hideInitialMenu ? (
<Container>
<NewRepoModalButton
space={space}
newRepoModalOnly
notFoundRepoName={repoSearch}
repoCreationType={RepoCreationType.CREATE}
customRenderer={fn => (
<MenuItem
icon="plus"
text={<String stringID="cde.create.repoNotFound" vars={{ repo: repoSearch }} useRichText />}
onClick={fn}
/>
)}
modalTitle={getString('createRepo')}
onSubmit={() => {
refetchRepos()
}}
/>
</Container>
) : !hadReops ? (
<Container>
<Layout.Vertical
spacing="medium"
className={css.noReposContainer}
flex={{ justifyContent: 'center' }}>
<img src={noRepo} height={90} width={90} />
<Layout.Vertical spacing="small" flex={{ alignItems: 'center' }}>
<Text color={Color.PRIMARY_10} font={{ size: 'normal', weight: 'bold' }}>
{getString('cde.getStarted')}
</Text>
<Text color={Color.PRIMARY_10} font={{ size: 'normal', weight: 'bold' }}>
{getString('cde.createImport')}
</Text>
</Layout.Vertical>
<NewRepoModalButton
space={space}
repoCreationType={RepoCreationType.CREATE}
customRenderer={fn => (
<Button width={'80%'} variation={ButtonVariation.PRIMARY} onClick={fn}>
{getString('createNewRepo')}
</Button>
)}
modalTitle={getString('newRepo')}
onSubmit={() => {
refetchRepos()
}}
/>
<NewRepoModalButton
space={space}
repoCreationType={RepoCreationType.IMPORT}
customRenderer={fn => (
<Button width={'80%'} variation={ButtonVariation.SECONDARY} onClick={fn}>
{getString('cde.importInto')}
</Button>
)}
modalTitle={getString('importGitRepo')}
onSubmit={() => {
refetchRepos()
}}
/>
</Layout.Vertical>
</Container>
) : (
<MenuItem disabled text={getString('loading')} />
)}
</Menu>
}

View File

@ -34,6 +34,7 @@
}
.listContainer {
border-radius: 20px !important;
:global {
a.bp3-menu-item:hover,
.bp3-active {
@ -52,4 +53,18 @@
.repositoryCell {
width: fit-content !important;
margin-right: var(--spacing-xxlarge) !important;
}
.stopModal {
width: 650px !important;
[class*='ConfirmationDialog--body'] {
margin-bottom: 0px !important;
}
span[data-icon='info-messaging'] {
display: none;
}
svg {
fill: #004ba4;
}
}

View File

@ -20,4 +20,5 @@ export declare const gitspaceUrl: string
export declare const listContainer: string
export declare const popover: string
export declare const repositoryCell: string
export declare const stopModal: string
export declare const table: string

View File

@ -14,45 +14,55 @@
* limitations under the License.
*/
import { Container, Layout, TableV2, Text, useToaster } from '@harnessio/uicore'
import React from 'react'
import {
ConfirmationDialog,
Container,
Layout,
TableV2,
Text,
useToaster,
Button,
ButtonVariation
} from '@harnessio/uicore'
import React, { useEffect, useState } from 'react'
import { Color } from '@harnessio/design-system'
import type { Renderer, CellProps } from 'react-table'
import ReactTimeago from 'react-timeago'
import {
Circle,
GitBranch,
Cpu,
Clock,
Play,
Square,
Db,
ModernTv,
OpenInBrowser,
DeleteCircle,
EditPencil,
ViewColumns2,
GithubCircle,
GitLabFull,
Code,
Bitbucket as BitbucketIcon
} from 'iconoir-react'
import { Menu, MenuItem, PopoverInteractionKind, Position } from '@blueprintjs/core'
import { Intent, Menu, MenuItem, PopoverInteractionKind, Position } from '@blueprintjs/core'
import { useHistory } from 'react-router-dom'
import { isNil } from 'lodash-es'
import { useMutate } from 'restful-react'
import type { IconName } from '@harnessio/icons'
import { UseStringsReturn, useStrings } from 'framework/strings'
import { useAppContext } from 'AppContext'
import { getErrorMessage } from 'utils/Utils'
import { useConfirmAct } from 'hooks/useConfirmAction'
import VSCode from 'cde/icons/VSCode.svg?url'
import { GitspaceStatus } from 'cde/constants'
import {
import { GitspaceActionType, GitspaceStatus } from 'cde/constants'
import type {
EnumGitspaceStateType,
EnumIDEType,
useDeleteGitspace,
type TypesGitspaceConfig,
type EnumCodeRepoType
TypesGitspaceConfig,
EnumGitspaceCodeRepoType
} from 'cde-gitness/services'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import gitspaceIcon from 'cde-gitness/assests/gitspace.svg?url'
import { useModalHook } from 'hooks/useModalHook'
import pause from 'cde-gitness/assests/pause.svg?url'
import web from 'cde-gitness/assests/web.svg?url'
import deleteIcon from 'cde-gitness/assests/delete.svg?url'
import css from './ListGitspaces.module.scss'
enum CodeRepoType {
@ -63,18 +73,18 @@ enum CodeRepoType {
Unknown = 'unknown'
}
const getIconByRepoType = ({ repoType }: { repoType?: EnumCodeRepoType }): React.ReactNode => {
const getIconByRepoType = ({ repoType }: { repoType?: EnumGitspaceCodeRepoType }): React.ReactNode => {
switch (repoType) {
case CodeRepoType.Github:
return <GithubCircle height={40} />
return <GithubCircle height={24} width={24} />
case CodeRepoType.Gitlab:
return <GitLabFull height={40} />
return <GitLabFull height={24} width={24} />
case CodeRepoType.Bitbucket:
return <BitbucketIcon height={40} />
return <BitbucketIcon height={24} width={24} />
default:
case CodeRepoType.Unknown:
case CodeRepoType.HarnessCode:
return <Code height={40} />
return <Code height={24} width={24} />
}
}
@ -82,8 +92,11 @@ export const getStatusColor = (status?: EnumGitspaceStateType) => {
switch (status) {
case GitspaceStatus.RUNNING:
return '#42AB45'
case GitspaceStatus.STOPPING:
return '#FF832B'
case GitspaceStatus.STOPPED:
return '#F3F3FA'
case GitspaceStatus.UNINITIALIZED:
return '#D0D0D9'
case GitspaceStatus.ERROR:
return '#FF0000'
default:
@ -99,6 +112,10 @@ export const getStatusText = (getString: UseStringsReturn['getString'], status?:
return getString('cde.listing.offline')
case GitspaceStatus.ERROR:
return getString('cde.listing.error')
case GitspaceStatus.STARTING:
return getString('cde.listing.starting')
case GitspaceStatus.STOPPING:
return getString('cde.listing.stopping')
default:
return getString('cde.listing.offline')
}
@ -148,7 +165,7 @@ export const RenderRepository: Renderer<CellProps<TypesGitspaceConfig>> = ({ row
const { name, branch, code_repo_url, code_repo_type, instance } = details || {}
return (
<Layout.Vertical>
<Layout.Vertical spacing={'small'}>
<Layout.Horizontal
spacing={'small'}
className={css.repositoryCell}
@ -158,13 +175,20 @@ export const RenderRepository: Renderer<CellProps<TypesGitspaceConfig>> = ({ row
e.stopPropagation()
window.open(code_repo_url, '_blank')
}}>
{getIconByRepoType({ repoType: code_repo_type })}
<Text className={css.gitspaceUrl} color={Color.PRIMARY_7} title={name} font={{ align: 'left', size: 'normal' }}>
<Container height={24} width={24}>
{getIconByRepoType({ repoType: code_repo_type })}
</Container>
<Text lineClamp={1} color={Color.PRIMARY_7} title={name} font={{ align: 'left', size: 'normal' }}>
{name}
</Text>
<Text color={Color.PRIMARY_7}>:</Text>
<GitBranch />
<Text color={Color.PRIMARY_7} title={name} font={{ align: 'left', size: 'normal' }}>
<Text
lineClamp={1}
icon="git-branch"
iconProps={{ size: 12 }}
color={Color.PRIMARY_7}
title={name}
font={{ align: 'left', size: 'normal' }}>
{branch}
</Text>
</Layout.Horizontal>
@ -182,7 +206,7 @@ export const RenderCPUUsage: Renderer<CellProps<TypesGitspaceConfig>> = ({ row }
const instance = row.original.instance
const { resource_usage, total_time_used } = instance || {}
return getUsageTemplate(getString, <Cpu />, resource_usage, total_time_used)
return getUsageTemplate(getString, <Cpu />, resource_usage as string, total_time_used)
}
export const RenderStorageUsage: Renderer<CellProps<TypesGitspaceConfig>> = ({ row }) => {
@ -190,7 +214,7 @@ export const RenderStorageUsage: Renderer<CellProps<TypesGitspaceConfig>> = ({ r
const instance = row.original.instance
const { resource_usage, total_time_used } = instance || {}
return getUsageTemplate(getString, <Db />, resource_usage, total_time_used)
return getUsageTemplate(getString, <Db />, resource_usage as string, total_time_used)
}
export const RenderLastActivity: Renderer<CellProps<TypesGitspaceConfig>> = ({ row }) => {
@ -214,13 +238,23 @@ export const RenderLastActivity: Renderer<CellProps<TypesGitspaceConfig>> = ({ r
export const RenderGitspaceStatus: Renderer<CellProps<TypesGitspaceConfig>> = ({ row }) => {
const { getString } = useStrings()
const details = row.original
const { instance, name } = details
const { state } = instance || {}
const { name, state } = details
const color = getStatusColor(state)
const customProps =
state === GitspaceStatus.STARTING
? {
icon: 'loading' as IconName,
iconProps: { color: Color.PRIMARY_4 }
}
: { icon: undefined }
return (
<Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'start' }}>
<Circle height={10} width={10} color={color} fill={color} />
<Text color={Color.BLACK} title={name} font={{ align: 'left', size: 'normal', weight: 'semi-bold' }}>
{state !== GitspaceStatus.STARTING && <Circle height={10} width={10} color={color} fill={color} />}
<Text
{...customProps}
color={Color.BLACK}
title={name}
font={{ align: 'left', size: 'normal', weight: 'semi-bold' }}>
{getStatusText(getString, state)}
</Text>
</Layout.Horizontal>
@ -231,10 +265,14 @@ export const StartStopButton = ({ state, loading }: { state?: EnumGitspaceStateT
const { getString } = useStrings()
return (
<Layout.Horizontal spacing="small" flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
{loading ? <></> : state === GitspaceStatus.RUNNING ? <Square /> : <Play />}
{loading ? <></> : state === GitspaceStatus.RUNNING ? <img src={pause} height={16} width={16} /> : <Play />}
<Text icon={loading ? 'loading' : undefined}>
{state === GitspaceStatus.RUNNING
? getString('cde.details.stopGitspace')
? loading
? getString('cde.stopingGitspace')
: getString('cde.details.stopGitspace')
: loading
? getString('cde.startingGitspace')
: getString('cde.details.startGitspace')}
</Text>
</Layout.Horizontal>
@ -246,7 +284,7 @@ export const OpenGitspaceButton = ({ ide }: { ide?: EnumIDEType }) => {
return (
<Layout.Horizontal spacing="small" flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
{ide === IDEType.VSCODE ? <ModernTv /> : <OpenInBrowser />}
{ide === IDEType.VSCODE ? <ModernTv /> : <img src={web} height={16} width={16} />}
<Text>{ide === IDEType.VSCODE ? getString('cde.ide.openVSCode') : getString('cde.ide.openBrowser')}</Text>
</Layout.Horizontal>
)
@ -254,8 +292,8 @@ export const OpenGitspaceButton = ({ ide }: { ide?: EnumIDEType }) => {
interface ActionMenuProps {
data: TypesGitspaceConfig
refreshList: () => void
handleStartStop?: () => Promise<void>
handleStartGitspace?: () => void
handleStopGitspace?: () => void
loading?: boolean
actionLoading?: boolean
deleteLoading?: boolean
@ -265,19 +303,21 @@ interface ActionMenuProps {
const ActionMenu = ({
data,
deleteGitspace,
refreshList,
handleStartStop,
handleStartGitspace,
handleStopGitspace,
actionLoading,
deleteLoading
}: ActionMenuProps) => {
const { getString } = useStrings()
const { showError } = useToaster()
const { instance, ide } = data
const { id, state, url = ' ' } = instance || {}
const { instance, ide, identifier = '', space_path = '', state } = data
const { identifier: id, url = '' } = instance || {}
const history = useHistory()
const { routes } = useAppContext()
const pathparamsList = instance?.space_path?.split('/') || []
const projectIdentifier = pathparamsList[pathparamsList.length - 1] || ''
const topBorder = state === GitspaceStatus.RUNNING && !actionLoading ? { top: true } : {}
const disabledActionButtons = [GitspaceStatus.STARTING, GitspaceStatus.STOPPING].includes(state as GitspaceStatus)
return (
<Container
@ -287,58 +327,7 @@ const ActionMenu = ({
e.stopPropagation()
}}>
<Menu>
<MenuItem
onClick={() => {
history.push(
routes.toCDEGitspaceDetail({
space: instance?.space_path || '',
gitspaceId: instance?.id || ''
})
)
}}
text={
<Layout.Horizontal spacing="small" flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
<ViewColumns2 />
<Text>{getString('cde.viewGitspace')}</Text>
</Layout.Horizontal>
}
/>
<MenuItem
onClick={() => {
history.push(
routes.toCDEGitspacesEdit({
space: instance?.space_path || '',
gitspaceId: instance?.id || ''
})
)
}}
text={
<Layout.Horizontal spacing="small" flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
<EditPencil />
<Text>{getString('cde.editGitspace')}</Text>
</Layout.Horizontal>
}
/>
<MenuItem
onClick={async e => {
try {
if (!actionLoading) {
e.preventDefault()
e.stopPropagation()
await handleStartStop?.()
await refreshList()
}
} catch (error) {
showError(getErrorMessage(error))
}
}}
text={
<Layout.Horizontal spacing="small">
<StartStopButton state={state} loading={actionLoading} />
</Layout.Horizontal>
}
/>
{ide && state == GitspaceStatus.RUNNING && (
{ide && state == GitspaceStatus.RUNNING && !actionLoading && (
<MenuItem
onClick={e => {
e.preventDefault()
@ -346,7 +335,7 @@ const ActionMenu = ({
if (ide === IDEType.VSCODE) {
window.open(`vscode://harness-inc.gitspaces/${projectIdentifier}/${id}`, '_blank')
} else {
window.open(url, '_blank')
window.open(url || '', '_blank')
}
}}
text={
@ -356,12 +345,55 @@ const ActionMenu = ({
}
/>
)}
<Container border={{ bottom: true, ...topBorder }}>
{!disabledActionButtons && (
<MenuItem
onClick={async e => {
try {
if (!actionLoading) {
e.preventDefault()
e.stopPropagation()
if (state === GitspaceStatus.RUNNING) {
handleStopGitspace?.()
} else {
handleStartGitspace?.()
}
}
} catch (error) {
showError(getErrorMessage(error))
}
}}
disabled={disabledActionButtons}
text={
<Layout.Horizontal spacing="small">
<StartStopButton state={state} loading={actionLoading} />
</Layout.Horizontal>
}
/>
)}
<MenuItem
onClick={() => {
history.push(
routes.toCDEGitspaceDetail({
space: space_path,
gitspaceId: identifier
})
)
}}
text={<Text icon="gitspace">{getString('cde.viewGitspace')}</Text>}
/>
</Container>
<MenuItem
onClick={deleteGitspace as Unknown as () => void}
text={
<Layout.Horizontal spacing="small" flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
{deleteLoading ? <></> : <DeleteCircle />}
<Text icon={deleteLoading ? 'loading' : undefined}>{getString('cde.deleteGitspace')}</Text>
{deleteLoading ? <></> : <img src={deleteIcon} height={16} width={16} />}
<Text color={Color.RED_450} icon={deleteLoading ? 'loading' : undefined}>
{getString('cde.deleteGitspace')}
</Text>
</Layout.Horizontal>
}
/>
@ -376,24 +408,132 @@ interface RenderActionsProps extends CellProps<TypesGitspaceConfig> {
export const RenderActions = ({ row, refreshList }: RenderActionsProps) => {
const { getString } = useStrings()
const space = useGetSpaceParam()
const history = useHistory()
const { routes } = useAppContext()
const { showError, showSuccess } = useToaster()
const details = row.original
const { instance, name } = details
const { mutate: deleteGitspace, loading: deleteLoading } = useDeleteGitspace({})
const { identifier, name, space_path } = details
// const { mutate: deleteGitspace, loading: deleteLoading } = useDeleteGitspace({})
// To be added in BE later.
// const { mutate: actionGitspace, loading: actionLoading } = useGitspaceAction({
// accountIdentifier,
// projectIdentifier,
// orgIdentifier,
// gitspaceIdentifier: instance?.id || ''
// })
const { mutate: deleteGitspace, loading: deleteLoading } = useMutate<any>({
verb: 'DELETE',
path: `/api/v1/gitspaces/${space}/${identifier}/+`
})
// const handleStartStop = async () => {
// return await actionGitspace({
// action: instance?.state === GitspaceStatus.RUNNING ? GitspaceActionType.STOP : GitspaceActionType.START
// })
// }
const { mutate: actionGitspace, loading: actionLoading } = useMutate({
verb: 'POST',
path: `/api/v1/gitspaces/${space}/${identifier}/+/actions`
})
const [handleStopGitspace, hideModal] = useModalHook(() => {
return (
<ConfirmationDialog
isOpen
className={css.stopModal}
titleText={
<Layout.Vertical flex={{ alignItems: 'self-start' }}>
<img src={gitspaceIcon} height={44} />
<Text color={Color.BLACK} font="medium">{`Do you want to stop the Gitspace “${name}” ?`}</Text>
</Layout.Vertical>
}
contentText={
<Container>
<Text margin={{ bottom: 'xxlarge' }}>
By clicking on Stop Gitspace, the gitspace will start de-provisioning.
</Text>
<Layout.Horizontal width="100%" flex={{ justifyContent: 'space-between', alignItems: 'self-start' }}>
<Layout.Horizontal spacing="medium">
<Button
onClick={async () => {
await actionGitspace({
action: GitspaceActionType.STOP
})
await refreshList()
hideModal()
}}
intent={Intent.PRIMARY}>
{getString('cde.details.stopGitspace')}
</Button>
<Button
onClick={() => {
history.push(
routes.toCDEGitspaceDetail({
space: space_path as string,
gitspaceId: identifier as string
})
)
}}
icon="gitspace"
variation={ButtonVariation.SECONDARY}>
{getString('cde.viewGitspace')}
</Button>
</Layout.Horizontal>
<Button variation={ButtonVariation.TERTIARY} onClick={hideModal}>
{getString('cancel')}
</Button>
</Layout.Horizontal>
</Container>
}
onClose={hideModal}
/>
)
}, [details, actionGitspace, history, routes])
const [handleStartGitspace, hideStartModal] = useModalHook(() => {
return (
<ConfirmationDialog
isOpen
className={css.stopModal}
titleText={
<Layout.Vertical flex={{ alignItems: 'self-start' }}>
<img src={gitspaceIcon} height={44} />
<Text color={Color.BLACK} font="medium">{`Do you want to start the Gitspace “${name}” ?`}</Text>
</Layout.Vertical>
}
contentText={
<Container>
<Text margin={{ bottom: 'xxlarge' }}>
By clicking on Start Gitspace, the gitspace will start provisioning.
</Text>
<Layout.Horizontal width="100%" flex={{ justifyContent: 'space-between', alignItems: 'self-start' }}>
<Layout.Horizontal spacing="medium">
<Button
onClick={() => {
history.push(
`${routes.toCDEGitspaceDetail({
space: space_path as string,
gitspaceId: identifier as string
})}?redirectFrom=login`
)
}}
intent={Intent.PRIMARY}>
{getString('cde.details.startGitspace')}
</Button>
<Button
onClick={() => {
history.push(
routes.toCDEGitspaceDetail({
space: space_path as string,
gitspaceId: identifier as string
})
)
}}
icon="gitspace"
variation={ButtonVariation.SECONDARY}>
{getString('cde.viewGitspace')}
</Button>
</Layout.Horizontal>
<Button variation={ButtonVariation.TERTIARY} onClick={hideStartModal}>
{getString('cancel')}
</Button>
</Layout.Horizontal>
</Container>
}
onClose={hideStartModal}
/>
)
}, [details, actionGitspace, history, routes])
const confirmDelete = useConfirmAct()
@ -405,7 +545,7 @@ export const RenderActions = ({ row, refreshList }: RenderActionsProps) => {
try {
e.preventDefault()
e.stopPropagation()
await deleteGitspace(instance?.id || '')
await deleteGitspace({})
showSuccess(getString('cde.deleteSuccess'))
await refreshList()
} catch (exception) {
@ -426,10 +566,11 @@ export const RenderActions = ({ row, refreshList }: RenderActionsProps) => {
tooltip={
<ActionMenu
data={details}
actionLoading={false}
actionLoading={actionLoading}
deleteLoading={deleteLoading}
deleteGitspace={handleDelete}
refreshList={refreshList}
handleStartGitspace={handleStartGitspace}
handleStopGitspace={handleStopGitspace}
/>
}
tooltipProps={{
@ -447,6 +588,77 @@ export const ListGitspaces = ({ data, refreshList }: { data: TypesGitspaceConfig
const { getString } = useStrings()
const { routes } = useAppContext()
const [currentRow, setCurrentRow] = useState<TypesGitspaceConfig>()
const [handleStartGitspace, hideStartModal] = useModalHook(() => {
return (
<ConfirmationDialog
isOpen
className={css.stopModal}
onClosed={() => setCurrentRow(undefined)}
titleText={
<Layout.Vertical flex={{ alignItems: 'self-start' }}>
<img src={gitspaceIcon} height={44} />
<Text color={Color.BLACK} font="medium">{`Do you want to start the Gitspace “${currentRow?.name}” ?`}</Text>
</Layout.Vertical>
}
contentText={
<Container>
<Text margin={{ bottom: 'xxlarge' }}>
By clicking on Start Gitspace, the gitspace will start provisioning.
</Text>
<Layout.Horizontal width="100%" flex={{ justifyContent: 'space-between', alignItems: 'self-start' }}>
<Layout.Horizontal spacing="medium">
<Button
onClick={() => {
history.push(
`${routes.toCDEGitspaceDetail({
space: currentRow?.space_path as string,
gitspaceId: currentRow?.identifier as string
})}?redirectFrom=login`
)
}}
intent={Intent.PRIMARY}>
{getString('cde.details.startGitspace')}
</Button>
<Button
onClick={() => {
history.push(
routes.toCDEGitspaceDetail({
space: currentRow?.space_path as string,
gitspaceId: currentRow?.identifier as string
})
)
}}
icon="gitspace"
variation={ButtonVariation.SECONDARY}>
{getString('cde.viewGitspace')}
</Button>
</Layout.Horizontal>
<Button
variation={ButtonVariation.TERTIARY}
onClick={() => {
hideStartModal()
setCurrentRow(undefined)
}}>
{getString('cancel')}
</Button>
</Layout.Horizontal>
</Container>
}
onClose={hideStartModal}
/>
)
}, [currentRow, history, routes])
useEffect(() => {
if (currentRow) {
setTimeout(() => {
handleStartGitspace()
}, 100)
}
}, [currentRow])
return (
<Container>
{data && (
@ -456,17 +668,22 @@ export const ListGitspaces = ({ data, refreshList }: { data: TypesGitspaceConfig
const pathparamsList = row?.instance?.space_path?.split('/') || []
const projectIdentifier = pathparamsList[pathparamsList.length - 1] || ''
if (row?.instance?.state === GitspaceStatus.RUNNING) {
if (row?.state === GitspaceStatus.RUNNING) {
if (row?.ide === IDEType.VSCODE) {
window.open(`vscode://harness-inc.gitspaces/${projectIdentifier}/${row?.instance?.id}`, '_blank')
window.open(
`vscode://harness-inc.gitspaces/${projectIdentifier}/${row?.instance?.identifier}`,
'_blank'
)
} else {
window.open(row?.instance.url, '_blank')
window.open(row?.instance?.url || '', '_blank')
}
} else if (row?.state === GitspaceStatus.STOPPED) {
setCurrentRow(row)
} else {
history.push(
routes.toCDEGitspaceDetail({
space: row?.instance?.space_path as string,
gitspaceId: row?.instance?.id as string
space: row?.space_path as string,
gitspaceId: row?.identifier as string
})
)
}
@ -489,7 +706,7 @@ export const ListGitspaces = ({ data, refreshList }: { data: TypesGitspaceConfig
},
{
id: 'lastactivity',
Header: getString('cde.sessionDuration'),
Header: getString('cde.lastActivated'),
Cell: RenderLastActivity
},
{

View File

@ -1,12 +1,37 @@
.hideContainer {
:global {
--bp3-intent-color: unset !important;
.bp3-form-helper-text {
margin-top: unset !important;
}
.formFields {
[class*='repoInput'],
[class*='branchDropdown'] {
width: 100% !important;
}
}
.repoAndBranch {
width: 47% !important;
.importForm {
cursor: pointer;
display: contents;
font-size: var(--font-size-small) !important;
}
.repoInput {
span[icon='git-repo'],
span[icon='git-branch'] {
top: 20%;
left: 2% !important;
color: var(--grey-500) !important;
}
input {
height: 50px !important;
border-radius: 7px !important;
border: 1px solid var(--grey-200) !important;
box-shadow: none !important;
background: var(--grey-100);
padding-left: 10% !important;
&:active {
background: white;
}
&:focus {
background: white;
}
}
}

View File

@ -16,5 +16,6 @@
/* eslint-disable */
// This is an auto-generated file
export declare const hideContainer: string
export declare const repoAndBranch: string
export declare const formFields: string
export declare const importForm: string
export declare const repoInput: string

View File

@ -1,125 +1,145 @@
import { FormInput, FormikForm, Layout } from '@harnessio/uicore'
import React, { useState } from 'react'
import React, { useCallback, useState } from 'react'
import { get, debounce } from 'lodash-es'
import cx from 'classnames'
import { FormikForm, Layout, FormInput, Container, Text } from '@harnessio/uicore'
import { useFormikContext } from 'formik'
import { Color } from '@harnessio/design-system'
import { useHistory } from 'react-router-dom'
import { Icon } from '@harnessio/icons'
import { useStrings } from 'framework/strings'
import { GitProviders, ImportFormData, getOrgLabel, getOrgPlaceholder, getProviders } from 'utils/GitUtils'
import {
getRepoIdFromURL,
getRepoNameFromURL,
isValidUrl
} from 'cde/components/CreateGitspace/components/SelectRepository/SelectRepository.utils'
import { BranchInput } from 'cde/components/CreateGitspace/components/BranchInput/BranchInput'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import NewRepoModalButton from 'components/NewRepoModalButton/NewRepoModalButton'
import { RepoCreationType } from 'utils/GitUtils'
import { useAppContext } from 'AppContext'
import { OpenapiCreateGitspaceRequest, useGitspacelookup } from 'cde-gitness/services'
import css from './ThirdPartyRepoImportForm.module.scss'
export interface ThirdPartyRepoImportFormProps extends ImportFormData {
branch: string
ide: string
id: string
enum RepoCheckStatus {
Valid = 'valid',
InValid = 'InValid'
}
export const ThirdPartyRepoImportForm = () => {
const [auth, setAuth] = useState(false)
const { getString } = useStrings()
const { values, setFieldValue, validateField } = useFormikContext<ThirdPartyRepoImportFormProps>()
const history = useHistory()
const space = useGetSpaceParam()
const { routes } = useAppContext()
const { setValues, setFieldError } = useFormikContext<OpenapiCreateGitspaceRequest>()
const { mutate, loading } = useGitspacelookup({})
const [repoCheckState, setRepoCheckState] = useState<RepoCheckStatus | undefined>()
const onChange = useCallback(
debounce(async (url: string) => {
let errorMessage = ''
try {
if (isValidUrl(url)) {
const response = (await mutate({ space_ref: space, url })) as {
is_private?: boolean
branch: string
url: string
}
if (response?.is_private) {
errorMessage = getString('cde.repository.privateRepoWarning')
setRepoCheckState(RepoCheckStatus.InValid)
} else {
setValues((prvValues: any) => {
return {
...prvValues,
code_repo_url: response.url,
branch: response.branch,
identifier: getRepoIdFromURL(response.url),
name: getRepoNameFromURL(response.url)
}
})
setRepoCheckState(RepoCheckStatus.Valid)
}
} else {
if (url?.trim()?.length) {
errorMessage = 'Invalid URL Format'
setRepoCheckState(RepoCheckStatus.InValid)
} else {
if (repoCheckState) {
setRepoCheckState(undefined)
}
}
}
} catch (err) {
errorMessage = get(err, 'message') || ''
}
setFieldError('code_repo_url', errorMessage)
}, 1000),
[repoCheckState]
)
return (
<FormikForm>
<FormInput.Select name={'gitProvider'} label={getString('importSpace.gitProvider')} items={getProviders()} />
{![GitProviders.GITHUB, GitProviders.GITLAB, GitProviders.BITBUCKET, GitProviders.AZURE].includes(
values.gitProvider
) && (
<FormInput.Text
className={css.hideContainer}
name="hostUrl"
label={getString('importRepo.url')}
placeholder={getString('importRepo.urlPlaceholder')}
tooltipProps={{
dataTooltipId: 'repositoryURLTextField'
}}
/>
)}
<FormInput.Text
className={css.hideContainer}
name="org"
label={getString(getOrgLabel(values.gitProvider))}
placeholder={getString(getOrgPlaceholder(values.gitProvider))}
/>
{values.gitProvider === GitProviders.AZURE && (
<FormInput.Text
className={css.hideContainer}
name="project"
label={getString('importRepo.project')}
placeholder={getString('importRepo.projectPlaceholder')}
/>
)}
<Layout.Horizontal spacing="medium" flex={{ justifyContent: 'space-between', alignItems: 'baseline' }}>
<Layout.Vertical className={css.repoAndBranch} spacing="small">
<FormInput.Text
className={css.hideContainer}
name="repo"
label={getString('importRepo.repo')}
placeholder={getString('importRepo.repoPlaceholder')}
onChange={event => {
const target = event.target as HTMLInputElement
setFieldValue('repo', target.value)
if (target.value) {
setFieldValue('name', target.value)
validateField('repo')
}
}}
/>
</Layout.Vertical>
<Layout.Vertical className={css.repoAndBranch} spacing="small">
<FormInput.Text
className={css.hideContainer}
name="branch"
label={getString('branch')}
placeholder={getString('cde.create.branchPlaceholder')}
onChange={event => {
const target = event.target as HTMLInputElement
setFieldValue('branch', target.value)
}}
/>
</Layout.Vertical>
</Layout.Horizontal>
<Layout.Horizontal spacing="medium">
<FormInput.CheckBox
name="authorization"
label={getString('importRepo.reqAuth')}
tooltipProps={{
dataTooltipId: 'authorization'
}}
onClick={() => {
setAuth(!auth)
}}
style={auth ? {} : { margin: 0 }}
/>
</Layout.Horizontal>
{auth ? (
<>
{[GitProviders.BITBUCKET, GitProviders.AZURE].includes(values.gitProvider) && (
<FormInput.Text
name="username"
label={getString('userName')}
placeholder={getString('importRepo.userPlaceholder')}
tooltipProps={{
dataTooltipId: 'repositoryUserTextField'
<Layout.Horizontal spacing="small">
<Text
icon="warning-icon"
font={{ size: 'small' }}
margin={{ bottom: 'medium' }}
iconProps={{ size: 20, color: Color.ORANGE_500 }}
background={Color.ORANGE_50}
padding="small">
{getString('cde.create.importWarning')}
{
<NewRepoModalButton
space={space}
repoCreationType={RepoCreationType.IMPORT}
customRenderer={fn => (
<Text className={css.importForm} color={Color.PRIMARY_7} onClick={fn}>
{getString('cde.importInto')}
</Text>
)}
modalTitle={getString('importGitRepo')}
onSubmit={() => {
history.push(routes.toCDEGitspacesCreate({ space }))
}}
/>
)}
}
</Text>
</Layout.Horizontal>
<Layout.Horizontal spacing="medium">
<Container width="63%" className={css.formFields}>
<FormInput.Text
inputGroup={{ type: 'password' }}
name="password"
label={
[GitProviders.BITBUCKET, GitProviders.AZURE].includes(values.gitProvider)
? getString('importRepo.appPassword')
: getString('importRepo.passToken')
}
placeholder={
[GitProviders.BITBUCKET, GitProviders.AZURE].includes(values.gitProvider)
? getString('importRepo.appPasswordPlaceholder')
: getString('importRepo.passTokenPlaceholder')
}
tooltipProps={{
dataTooltipId: 'repositoryPasswordTextField'
name="code_repo_url"
inputGroup={{
leftIcon: 'git-repo',
color: Color.GREY_500,
rightElement: (
<Container height={50} width={25} flex={{ alignItems: 'center' }}>
{loading ? (
<Icon name="loading" />
) : repoCheckState ? (
repoCheckState === RepoCheckStatus.Valid ? (
<Icon name="tick-circle" color={Color.GREEN_450} />
) : (
<Icon name="warning-sign" color={Color.ERROR} />
)
) : undefined}
</Container>
)
}}
placeholder={getString('cde.repository.repositoryURL')}
className={cx(css.repoInput)}
onChange={async event => {
const target = event.target as HTMLInputElement
await onChange(target.value)
}}
/>
</>
) : null}
</Container>
<Container width="35%" className={css.formFields}>
<BranchInput />
</Container>
</Layout.Horizontal>
</FormikForm>
)
}

View File

@ -0,0 +1,4 @@
export enum StandaloneIDEType {
VSCODE = 'vs_code',
VSCODEWEB = 'vs_code_web'
}

View File

@ -0,0 +1,54 @@
import { useEffect, useState } from 'react'
export interface LogData {
pos: number
out: string
time: number
}
export function parseLog(log: string): LogData[] {
const logLines = log.trim().split('\n\n')
const parsedData: LogData[] = []
logLines.forEach(line => {
const dataMatch = line.match(/data: (.+)/)
if (dataMatch && dataMatch[1] !== 'eof') {
const eventData: LogData = JSON.parse(dataMatch[1])
parsedData.push(eventData)
}
})
return parsedData
}
export const useGetLogStream = ({ response }: { response: any }) => {
const [data, setData] = useState('')
useEffect(() => {
const fetchStreamData = async () => {
const reader = response?.body?.getReader()
const decoder = new TextDecoder()
let done = false
while (!done) {
/* eslint-disable no-await-in-loop */
const { value, done: streamDone } = (await reader?.read()) || {}
done = streamDone
const chunk = decoder.decode(value)
setData(prevData => prevData + chunk)
}
}
try {
if (response && !response.body.locked) {
fetchStreamData()
}
} catch (error) {
//
}
}, [response])
return { data: parseLog(data) }
}

View File

@ -1,28 +1,10 @@
import type { ThirdPartyRepoImportFormProps } from 'cde-gitness/components/ThirdPartyRepoImportForm/ThirdPartyRepoImportForm'
import type { EnumIDEType, OpenapiCreateGitspaceRequest } from 'cde-gitness/services'
import { GitProviders } from 'utils/GitUtils'
export const gitnessFormInitialValues: OpenapiCreateGitspaceRequest = {
branch: '',
code_repo_url: '',
devcontainer_path: '',
id: '',
ide: 'vsCode' as EnumIDEType,
infra_provider_resource_id: 'default',
identifier: '',
ide: 'vs_code' as EnumIDEType,
resource_identifier: 'default',
name: ''
}
export const thirdPartyformInitialValues: ThirdPartyRepoImportFormProps = {
gitProvider: GitProviders.GITHUB,
hostUrl: '',
org: '',
project: '',
repo: '',
username: '',
password: '',
name: '',
description: '',
branch: '',
ide: 'vsCode' as EnumIDEType,
id: '',
importPipelineLabel: false
}

View File

@ -1,7 +1,21 @@
.main {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: 10% !important;
.titleContainer {
width: 50%;
margin-bottom: var(--spacing-medium) !important;
h2 {
font-size: 24px !important;
}
p {
font-size: 16px !important;
color: #383946;
}
}
.cardMain {
width: 50%;

View File

@ -24,3 +24,4 @@ export declare const formOuterContainer: string
export declare const formTitleContainer: string
export declare const main: string
export declare const subContainers: string
export declare const titleContainer: string

View File

@ -1,23 +1,22 @@
import React, { useState } from 'react'
import {
Breadcrumbs,
Button,
ButtonVariation,
Card,
Container,
Formik,
FormikForm,
Heading,
Layout,
Page,
Text,
useToaster
} from '@harnessio/uicore'
import { FontVariation } from '@harnessio/design-system'
import { Color, FontVariation } from '@harnessio/design-system'
import { useHistory } from 'react-router-dom'
import { useStrings } from 'framework/strings'
import {
ThirdPartyRepoImportForm,
ThirdPartyRepoImportFormProps
} from 'cde-gitness/components/ThirdPartyRepoImportForm/ThirdPartyRepoImportForm'
import { ThirdPartyRepoImportForm } from 'cde-gitness/components/ThirdPartyRepoImportForm/ThirdPartyRepoImportForm'
import { GitnessRepoImportForm } from 'cde-gitness/components/GitnessRepoImportForm/GitnessRepoImportForm'
import { SelectIDE } from 'cde/components/CreateGitspace/components/SelectIDE/SelectIDE'
import { useCreateGitspace, type OpenapiCreateGitspaceRequest } from 'cde-gitness/services'
@ -25,8 +24,8 @@ import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import { getErrorMessage } from 'utils/Utils'
import { useAppContext } from 'AppContext'
import RepositoryTypeButton, { RepositoryType } from '../../components/RepositoryTypeButton/RepositoryTypeButton'
import { gitnessFormInitialValues, thirdPartyformInitialValues } from './GitspaceCreate.constants'
import { handleImportSubmit, validateGitnessForm, validationSchemaStepOne } from './GitspaceCreate.utils'
import { gitnessFormInitialValues } from './GitspaceCreate.constants'
import { validateGitnessForm } from './GitspaceCreate.utils'
import css from './GitspaceCreate.module.scss'
export const GitspaceCreate = () => {
@ -40,38 +39,49 @@ export const GitspaceCreate = () => {
return (
<>
<Page.Header title={getString('cde.gitspaces')} />
<Page.Header
title={getString('cde.createGitspace')}
breadcrumbs={
<Breadcrumbs
links={[
{ url: routes.toCDEGitspaces({ space }), label: getString('cde.gitspaces') },
{ url: routes.toCDEGitspacesCreate({ space }), label: getString('cde.createGitspace') }
]}
/>
}
/>
<Page.Body className={css.main}>
<Container className={css.titleContainer}>
<Layout.Vertical spacing="small" margin={{ bottom: 'medium' }}>
<Heading font={{ weight: 'bold' }} color={Color.BLACK} level={2}>
{getString('cde.createGitspace')}
</Heading>
<Text font={{ size: 'medium' }}>{getString('cde.create.subtext')}</Text>
</Layout.Vertical>
</Container>
<Card className={css.cardMain}>
<Text className={css.cardTitle} font={{ variation: FontVariation.CARD_TITLE }}>
{getString('cde.createGitspace')}
</Text>
<Container className={css.subContainers}>
<Formik
onSubmit={async data => {
try {
const payload =
activeButton === RepositoryType.GITNESS
? data
: handleImportSubmit(data as ThirdPartyRepoImportFormProps)
await mutate({ ...payload, space_ref: space } as OpenapiCreateGitspaceRequest & {
const payload = data
const response = await mutate({ ...payload, space_ref: space } as OpenapiCreateGitspaceRequest & {
space_ref?: string
})
showSuccess(getString('cde.create.gitspaceCreateSuccess'))
history.push(routes.toCDEGitspaces({ space }))
history.push(
`${routes.toCDEGitspaceDetail({
space,
gitspaceId: response.identifier || ''
})}?redirectFrom=login`
)
} catch (error) {
showError(getString('cde.create.gitspaceCreateFailed'))
showError(getErrorMessage(error))
}
}}
initialValues={
activeButton === RepositoryType.GITNESS ? gitnessFormInitialValues : thirdPartyformInitialValues
}
validationSchema={
activeButton === RepositoryType.GITNESS
? validateGitnessForm(getString)
: validationSchemaStepOne(getString)
}
initialValues={gitnessFormInitialValues}
validationSchema={validateGitnessForm(getString)}
formName="importRepoForm"
enableReinitialize>
{formik => {
@ -105,7 +115,7 @@ export const GitspaceCreate = () => {
)}
</Container>
<Container className={css.formOuterContainer}>
<SelectIDE />
<SelectIDE standalone />
<Button width={'100%'} variation={ButtonVariation.PRIMARY} height={50} type="submit">
{getString('cde.createGitspace')}
</Button>

View File

@ -1,90 +1,12 @@
import * as yup from 'yup'
import { compact } from 'lodash-es'
import type { UseStringsReturn } from 'framework/strings'
import { GitProviders, getProviderTypeMapping } from 'utils/GitUtils'
import type { ThirdPartyRepoImportFormProps } from 'cde-gitness/components/ThirdPartyRepoImportForm/ThirdPartyRepoImportForm'
export const validateGitnessForm = (getString: UseStringsReturn['getString']) =>
yup.object().shape({
branch: yup.string().trim().required(getString('cde.branchValidationMessage')),
code_repo_url: yup.string().trim().required(getString('cde.repoValidationMessage')),
id: yup.string().trim().required(),
identifier: yup.string().trim().required(),
ide: yup.string().trim().required(),
infra_provider_resource_id: yup.string().trim().required(getString('cde.machineValidationMessage')),
resource_identifier: yup.string().trim().required(getString('cde.machineValidationMessage')),
name: yup.string().trim().required()
})
export const validationSchemaStepOne = (getString: UseStringsReturn['getString']) =>
yup.object().shape({
gitProvider: yup.string().required(),
repo: yup
.string()
.trim()
.when('gitProvider', {
is: gitProvider => [GitProviders.GITHUB, GitProviders.GITLAB, GitProviders.BITBUCKET].includes(gitProvider),
then: yup.string().required(getString('importSpace.orgRequired'))
}),
branch: yup.string().trim().required(getString('cde.branchValidationMessage')),
hostUrl: yup
.string()
// .matches(MATCH_REPOURL_REGEX, getString('importSpace.invalidUrl'))
.when('gitProvider', {
is: gitProvider =>
![GitProviders.GITHUB, GitProviders.GITLAB, GitProviders.BITBUCKET, GitProviders.AZURE].includes(gitProvider),
then: yup.string().required(getString('importRepo.required')),
otherwise: yup.string().notRequired() // Optional based on your needs
}),
org: yup
.string()
.trim()
.when('gitProvider', {
is: GitProviders.AZURE,
then: yup.string().required(getString('importSpace.orgRequired'))
}),
project: yup
.string()
.trim()
.when('gitProvider', {
is: GitProviders.AZURE,
then: yup.string().required(getString('importSpace.spaceNameRequired'))
}),
name: yup.string().trim().required(getString('validation.nameIsRequired'))
})
export const handleImportSubmit = (formData: ThirdPartyRepoImportFormProps) => {
const type = getProviderTypeMapping(formData.gitProvider)
const provider = {
type,
username: formData.username,
password: formData.password,
host: ''
}
if (
![GitProviders.GITHUB, GitProviders.GITLAB, GitProviders.BITBUCKET, GitProviders.AZURE].includes(
formData.gitProvider
)
) {
provider.host = formData.hostUrl
}
const importPayload = {
name: formData.repo || formData.name,
description: formData.description || '',
id: formData.repo,
provider,
ide: formData.ide,
branch: formData.branch,
infra_provider_resource_id: 'default',
provider_repo: compact([
formData.org,
formData.gitProvider === GitProviders.AZURE ? formData.project : '',
formData.repo
])
.join('/')
.replace(/\.git$/, '')
}
return importPayload
}

View File

@ -0,0 +1,69 @@
.customSubheader {
height: 10vh;
}
.pageMain {
margin: var(--spacing-xxlarge) !important;
}
@media (min-width: 2000px) {
.titleContainer {
width: 80% !important;
margin: 0 10% !important;
}
}
.titleContainer {
width: 95% !important;
margin: 0 5% !important;
}
.cardContainer {
margin-top: var(--spacing-xxlarge) !important;
width: 100% !important;
[data-testid='eventsCard-panel'] {
padding: 0px !important;
}
&:first-child {
margin-top: 0px !important;
}
}
.popover {
> div[class*='popover-arrow'] {
display: none;
}
:global {
a.bp3-menu-item:hover,
.bp3-active {
background: var(--primary-1) !important;
color: var(--grey-1000) !important;
}
}
}
.accordionnCustomSummary {
:global(.Accordion--chevron) {
margin-left: auto;
color: var(--grey-700);
}
:global([class*='Accordion--panel']) {
padding: 0px !important;
}
div[data-testid='eventsCard-summary'],
div[data-testid='logsCard-summary'] {
flex-direction: row-reverse;
justify-content: flex-end;
margin: 0 var(--spacing-medium);
width: 100%;
div:first-child {
width: 97%;
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2023 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable */
// This is an auto-generated file
export declare const accordionnCustomSummary: string
export declare const cardContainer: string
export declare const customSubheader: string
export declare const pageMain: string
export declare const popover: string
export declare const titleContainer: string

View File

@ -0,0 +1,347 @@
import React, { useEffect, useState } from 'react'
import {
Breadcrumbs,
Button,
ButtonVariation,
Card,
Container,
Layout,
Accordion,
Page,
Text,
useToaster
} from '@harnessio/uicore'
import { Play } from 'iconoir-react'
import { useHistory, useParams } from 'react-router-dom'
import { Color, FontVariation, PopoverProps } from '@harnessio/design-system'
import { Menu, MenuItem, PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core'
import { useGet, useMutate } from 'restful-react'
import { defaultTo } from 'lodash-es'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import { useAppContext } from 'AppContext'
import { useStrings } from 'framework/strings'
import EventTimelineAccordion from 'cde-gitness/components/EventTimelineAccordion/EventTimelineAccordion'
import { DetailsCard } from 'cde-gitness/components/DetailsCard/DetailsCard'
import type { TypesGitspaceConfig, TypesGitspaceEventResponse } from 'cde-gitness/services'
import { GitspaceActionType, GitspaceStatus } from 'cde/constants'
import { useQueryParams } from 'hooks/useQueryParams'
import { useUpdateQueryParams } from 'hooks/useUpdateQueryParams'
import { getErrorMessage } from 'utils/Utils'
import { usePolling } from 'cde/components/GitspaceDetails/usePolling'
import deleteIcon from 'cde-gitness/assests/delete.svg?url'
import vscodeIcon from 'cde/icons/VSCode.svg?url'
import pauseIcon from 'cde-gitness/assests/pause.svg?url'
import { StandaloneIDEType } from 'cde-gitness/constants'
import homeIcon from 'cde-gitness/assests/home.svg?url'
import ContainerLogs from '../../components/ContainerLogs/ContainerLogs'
import { useGetLogStream } from '../../hooks/useGetLogStream'
import css from './GitspaceDetails.module.scss'
export const GitspaceDetails = () => {
const space = useGetSpaceParam()
const { getString } = useStrings()
const { routes } = useAppContext()
const { showError } = useToaster()
const history = useHistory()
const [startTriggred, setStartTriggred] = useState<boolean>(false)
const { gitspaceId = '' } = useParams<{ gitspaceId?: string }>()
const [isStreamingLogs, setIsStreamingLogs] = useState(false)
const [startPolling, setStartPolling] = useState<GitspaceActionType | undefined>(undefined)
const { loading, data, refetch, error } = useGet<TypesGitspaceConfig>({
path: `/api/v1/gitspaces/${space}/${gitspaceId}/+`,
debounce: 500
})
const { data: eventData, refetch: refetchEventData } = useGet<TypesGitspaceEventResponse[]>({
path: `/api/v1/gitspaces/${space}/${gitspaceId}/+/events`,
debounce: 500
})
const { refetch: refetchLogsData, response } = useGet<any>({
path: `api/v1/gitspaces/${space}/${gitspaceId}/+/logs/stream`,
debounce: 500,
lazy: true
})
const { mutate: actionMutate, loading: mutateLoading } = useMutate({
verb: 'POST',
path: `/api/v1/gitspaces/${space}/${gitspaceId}/+/actions`
})
const { mutate: deleteGitspace, loading: deleteLoading } = useMutate<any>({
verb: 'DELETE',
path: `/api/v1/gitspaces/${space}/${gitspaceId}/+`
})
const { updateQueryParams } = useUpdateQueryParams<{ redirectFrom?: string }>()
const { redirectFrom = '' } = useQueryParams<{ redirectFrom?: string }>()
const pollingCondition = [
GitspaceStatus.RUNNING,
GitspaceStatus.STOPPED,
GitspaceStatus.ERROR,
GitspaceStatus.UNINITIALIZED
].includes(data?.state as GitspaceStatus)
const disabledActionButtons = [GitspaceStatus.STARTING, GitspaceStatus.STOPPING].includes(
data?.state as GitspaceStatus
)
useEffect(() => {
const filteredEvent = eventData?.filter(
item =>
item.event === 'agent_gitspace_creation_start' &&
defaultTo(item?.timestamp, 0) >= defaultTo(data?.instance?.updated, 0)
)
if (disabledActionButtons && filteredEvent?.length && !isStreamingLogs) {
refetchLogsData()
setIsStreamingLogs(true)
} else if (filteredEvent?.length && !disabledActionButtons && isStreamingLogs) {
setIsStreamingLogs(false)
}
}, [eventData, data?.instance?.updated, disabledActionButtons])
usePolling(
async () => {
await refetchEventData()
await refetch()
},
{
pollingInterval: 10000,
startCondition: Boolean(startPolling) || !pollingCondition
}
)
useEffect(() => {
const startTrigger = async () => {
if (redirectFrom && !startTriggred && !mutateLoading) {
try {
setStartTriggred(true)
await actionMutate({ action: GitspaceActionType.START })
await refetch()
updateQueryParams({ redirectFrom: undefined })
} catch (err) {
showError(getErrorMessage(err))
}
}
}
if (data?.state && data?.state !== GitspaceStatus.RUNNING && redirectFrom) {
startTrigger()
}
}, [data?.state, redirectFrom, mutateLoading, startTriggred])
const formattedlogsdata = useGetLogStream({ response })
return (
<>
<Page.Header
title=""
breadcrumbs={
<Breadcrumbs
links={[
{ url: routes.toCDEGitspaces({ space }), label: getString('cde.gitspaces') },
{ url: routes.toCDEGitspaceDetail({ gitspaceId, space }), label: data?.name || 'Gitspace Name' }
]}
/>
}
/>
<Page.SubHeader className={css.customSubheader}>
<Layout.Horizontal width="100%" flex={{ alignItems: 'center', justifyContent: 'space-between' }}>
<Container>
<Layout.Horizontal spacing="small">
<img src={vscodeIcon} height={32} width={32} />
<Text font={{ variation: FontVariation.H3 }}>{data?.name}</Text>
</Layout.Horizontal>
</Container>
<Container>
<Layout.Horizontal spacing="large">
<Button
variation={ButtonVariation.SECONDARY}
rightIcon="chevron-down"
tooltipProps={
{
interactionKind: PopoverInteractionKind.CLICK,
position: PopoverPosition.BOTTOM_LEFT,
popoverClassName: css.popover
} as PopoverProps
}
tooltip={
<Menu>
{!disabledActionButtons && (
<MenuItem
text={
<Layout.Horizontal
spacing="small"
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
{data?.state === GitspaceStatus.RUNNING ? (
<img src={pauseIcon} height={16} width={16} />
) : (
<Play />
)}
<Text>
{data?.state === GitspaceStatus.RUNNING
? mutateLoading
? getString('cde.stopingGitspace')
: getString('cde.details.stopGitspace')
: mutateLoading
? getString('cde.startingGitspace')
: getString('cde.details.startGitspace')}
</Text>
</Layout.Horizontal>
}
disabled={loading || mutateLoading || disabledActionButtons}
onClick={async () => {
try {
setStartPolling(GitspaceActionType.START)
await actionMutate({ action: data?.state === GitspaceStatus.RUNNING ? 'stop' : 'start' })
await refetch()
setStartPolling(undefined)
updateQueryParams({ redirectFrom: undefined })
} catch (err) {
showError(getErrorMessage(err))
setStartPolling(undefined)
}
}}
/>
)}
<MenuItem
text={
<Layout.Horizontal
spacing="small"
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
<img src={homeIcon} height={16} width={16} />
<Text>{getString('cde.details.goToDashboard')}</Text>
</Layout.Horizontal>
}
onClick={() => {
history.push(routes.toCDEGitspaces({ space }))
}}
/>
<MenuItem
onClick={deleteGitspace as Unknown as () => void}
text={
<Layout.Horizontal
spacing="small"
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
{deleteLoading ? <></> : <img src={deleteIcon} height={16} width={16} />}
<Text color={Color.RED_450} icon={deleteLoading ? 'loading' : undefined}>
{getString('cde.deleteGitspace')}
</Text>
</Layout.Horizontal>
}
disabled={disabledActionButtons}
/>
</Menu>
}>
{getString('cde.details.actions')}
</Button>
{(data?.state === GitspaceStatus.RUNNING ||
data?.state === GitspaceStatus.STARTING ||
data?.state === GitspaceStatus.STOPPING) &&
data?.ide ? (
<Button
disabled={disabledActionButtons}
variation={ButtonVariation.PRIMARY}
tooltip={
disabledActionButtons ? (
<Container width={300} padding="medium">
<Layout.Vertical spacing="small">
<Text color={Color.WHITE} font="small">
We are provisioning the Gitspace
</Text>
<Text color={Color.WHITE} font="small">
Please wait for a few minutes before the VS Code Desktop can be launched
</Text>
</Layout.Vertical>
</Container>
) : undefined
}
tooltipProps={{ isDark: true, position: PopoverPosition.BOTTOM_LEFT }}
onClick={e => {
e.preventDefault()
e.stopPropagation()
if (data?.ide === StandaloneIDEType.VSCODE) {
window.open(`vscode://harness-inc.gitspaces/${data?.identifier}`, '_blank')
} else {
window.open(data?.instance?.url || '', '_blank')
}
}}>
{data?.ide === StandaloneIDEType.VSCODE && getString('cde.details.openEditor')}
{data?.ide === StandaloneIDEType.VSCODEWEB && getString('cde.details.openBrowser')}
</Button>
) : (
<Button
loading={mutateLoading}
disabled={mutateLoading || disabledActionButtons}
icon="run-pipeline"
variation={ButtonVariation.PRIMARY}
intent="success"
onClick={async () => {
try {
setStartPolling(GitspaceActionType.START)
await actionMutate({ action: GitspaceActionType.START })
await refetch()
setStartPolling(undefined)
updateQueryParams({ redirectFrom: undefined })
} catch (err) {
showError(getErrorMessage(err))
setStartPolling(undefined)
}
}}>
{getString('cde.details.startGitspace')}
</Button>
)}
</Layout.Horizontal>
</Container>
</Layout.Horizontal>
</Page.SubHeader>
<Page.Body
loading={loading}
error={getErrorMessage(error)}
noData={{
when: () => !data?.identifier,
message: getString('cde.details.noData')
}}
className={css.pageMain}>
<Container>
<Card className={css.cardContainer}>
<Text font={{ variation: FontVariation.CARD_TITLE }}>{getString('cde.gitspaceDetail')}</Text>
<DetailsCard data={data} loading={mutateLoading} />
</Card>
<Card className={css.cardContainer}>
<EventTimelineAccordion data={eventData} />
</Card>
<Card className={css.cardContainer}>
<Accordion activeId="logsCard">
<Accordion.Panel
shouldRender
className={css.accordionnCustomSummary}
summary={
<Layout.Vertical spacing="small">
<Text font={{ variation: FontVariation.CARD_TITLE }} margin={{ left: 'large' }}>
{getString('cde.details.containerLogs')}
</Text>
<Text margin={{ left: 'large' }}>{getString('cde.details.containerLogsSubText')} </Text>
</Layout.Vertical>
}
id="logsCard"
details={
<Container>
<ContainerLogs data={formattedlogsdata.data} />
</Container>
}
/>
</Accordion>
</Card>
</Container>
</Page.Body>
</>
)
}

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@
*/
.branchInput {
span[icon='git-repo'],
span[icon='git-branch'] {
top: 20%;
left: 2% !important;

View File

@ -22,18 +22,22 @@ import { GitspaceSelect } from 'cde/components/GitspaceSelect/GitspaceSelect'
import { useStrings, type UseStringsReturn } from 'framework/strings'
import { IDEType } from 'cde/constants'
import type { OpenapiCreateGitspaceRequest } from 'services/cde'
import { StandaloneIDEType } from 'cde-gitness/constants'
import VSCode from '../../../../icons/VSCode.svg?url'
export const getIDESelectItems = (getString: UseStringsReturn['getString']) => [
{ label: getString('cde.ide.desktop'), value: IDEType.VSCODE },
{ label: getString('cde.ide.browser'), value: IDEType.VSCODEWEB }
]
export const getIDESelectItems = (getString: UseStringsReturn['getString'], standalone = false) => {
const ideEnum = standalone ? StandaloneIDEType : IDEType
return [
{ label: getString('cde.ide.desktop'), value: ideEnum.VSCODE },
{ label: getString('cde.ide.browser'), value: ideEnum.VSCODEWEB }
]
}
export const SelectIDE = () => {
export const SelectIDE = ({ standalone = false }: { standalone?: boolean }) => {
const { values, errors, setFieldValue: onChange } = useFormikContext<OpenapiCreateGitspaceRequest>()
const { ide } = values
const { getString } = useStrings()
const IDESelectItems = getIDESelectItems(getString)
const IDESelectItems = getIDESelectItems(getString, standalone)
const IDELabel = IDESelectItems.find(item => item.value === ide)?.label
return (
<GitspaceSelect

View File

@ -1,6 +1,7 @@
import React from 'react'
import { Bitbucket as BitbucketIcon, Code, GitLabFull, GithubCircle } from 'iconoir-react'
import type { EnumCodeRepoType } from 'services/cde'
import type { EnumGitspaceCodeRepoType } from 'cde-gitness/services'
export const isValidUrl = (url: string) => {
const urlPattern = new RegExp(
@ -37,17 +38,24 @@ export enum CodeRepoType {
Unknown = 'unknown'
}
export const getIconByRepoType = ({ repoType }: { repoType?: EnumCodeRepoType }): React.ReactNode => {
export const getIconByRepoType = ({
repoType,
height = 40
}: {
repoType?: EnumCodeRepoType | EnumGitspaceCodeRepoType
height?: number
}): React.ReactNode => {
switch (repoType) {
case CodeRepoType.Github:
return <GithubCircle height={40} />
return <GithubCircle height={height} />
case CodeRepoType.Gitlab:
return <GitLabFull height={40} />
return <GitLabFull height={height} />
case CodeRepoType.Bitbucket:
return <BitbucketIcon height={40} />
return <BitbucketIcon height={height} />
default:
case CodeRepoType.Unknown:
case CodeRepoType.HarnessCode:
return <Code height={40} />
case 'harness_code':
return <Code height={height} />
}
}

View File

@ -34,7 +34,10 @@ export enum GitspaceStatus {
RUNNING = 'running',
STOPPED = 'stopped',
UNKNOWN = 'unknown',
ERROR = 'error'
ERROR = 'error',
STARTING = 'starting',
STOPPING = 'stopping',
UNINITIALIZED = 'uninitialized'
}
export enum IDEType {

View File

@ -49,7 +49,7 @@ import { useGet, useMutate } from 'restful-react'
import { Render } from 'react-jsx-match'
import { compact, get } from 'lodash-es'
import { useModalHook } from 'hooks/useModalHook'
import { String, useStrings } from 'framework/strings'
import { useStrings } from 'framework/strings'
import {
DEFAULT_BRANCH_NAME,
getErrorMessage,
@ -75,6 +75,7 @@ import type {
OpenapiCreateRepositoryRequest
} from 'services/code'
import { useAppContext } from 'AppContext'
import type { TypesRepository } from 'cde-gitness/services'
import ImportForm from './ImportForm/ImportForm'
import ImportReposForm from './ImportReposForm/ImportReposForm'
import Private from '../../icons/private.svg?url'
@ -95,9 +96,9 @@ export interface NewRepoModalButtonProps extends Omit<ButtonProps, 'onClick' | '
modalTitle: string
submitButtonTitle?: string
cancelButtonTitle?: string
onSubmit: (data: RepoRepositoryOutput & SpaceImportRepositoriesOutput) => void
newRepoModalOnly?: boolean
notFoundRepoName?: string
onSubmit: (data: TypesRepository & RepoRepositoryOutput & SpaceImportRepositoriesOutput) => void
repoCreationType?: RepoCreationType
customRenderer?: (onChange: (event: any) => void) => React.ReactNode
}
export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
@ -481,17 +482,15 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
[space]
)
return props?.newRepoModalOnly ? (
<MenuItem
icon="plus"
text={<String stringID="cde.create.repoNotFound" vars={{ repo: props?.notFoundRepoName }} useRichText />}
onClick={e => {
return props?.repoCreationType ? (
<>
{props?.customRenderer?.(e => {
e.preventDefault()
e.stopPropagation()
setRepoOption(repoCreateOptions[0])
setRepoOption(repoCreateOptions.find(option => option.type === props?.repoCreationType) || repoCreateOptions[0])
setTimeout(() => openModal(), 0)
}}
/>
})}
</>
) : (
<SplitButton
{...props}

View File

@ -147,24 +147,30 @@ export interface StringsMap {
'cde.create.gitnessRepositories': string
'cde.create.gitspaceCreateFailed': string
'cde.create.gitspaceCreateSuccess': string
'cde.create.importWarning': string
'cde.create.repoNotFound': string
'cde.create.repositoryDetails': string
'cde.create.searchBranchPlaceholder': string
'cde.create.searchRepositoryPlaceholder': string
'cde.create.selectBranchPlaceholder': string
'cde.create.subtext': string
'cde.create.thirdPartyGitRepositories': string
'cde.create.unsaved.message': string
'cde.create.unsaved.title': string
'cde.createGitspace': string
'cde.createImport': string
'cde.createRepo': string
'cde.deleteGitspace': string
'cde.deleteGitspaceText': string
'cde.deleteGitspaceTitle': string
'cde.deleteSuccess': string
'cde.details.actions': string
'cde.details.containerLogs': string
'cde.details.containerLogsSubText': string
'cde.details.fetchingDetails': string
'cde.details.fetchingGitspace': string
'cde.details.fetchingLogs': string
'cde.details.gitspaceActivity': string
'cde.details.gitspaceRunning': string
'cde.details.gitspaceStopped': string
'cde.details.goToDashboard': string
@ -181,6 +187,7 @@ export interface StringsMap {
'cde.disk': string
'cde.editGitspace': string
'cde.eventTimeline': string
'cde.getStarted': string
'cde.gitspaceDetail': string
'cde.gitspaceUpdateSuccess': string
'cde.gitspaces': string
@ -192,12 +199,17 @@ export interface StringsMap {
'cde.ide.selectIDE': string
'cde.ide.title': string
'cde.ide.vsCode': string
'cde.importInto': string
'cde.introText1': string
'cde.introText2': string
'cde.introText3': string
'cde.lastActivated': string
'cde.lastUsed': string
'cde.listing.error': string
'cde.listing.offline': string
'cde.listing.online': string
'cde.listing.starting': string
'cde.listing.stopping': string
'cde.logs': string
'cde.machine': string
'cde.machineValidationMessage': string

3
web/src/global.d.ts vendored
View File

@ -17,6 +17,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare const __DEV__: boolean
/* eslint-disable @typescript-eslint/no-explicit-any */
declare const __ENABLE_GITSPACE__: boolean
declare module '*.png' {
const value: string
export default value

View File

@ -1119,6 +1119,7 @@ checklist: Check list
code: Code
cde:
branchPlaceholder: Branch
lastUsed: Last Used
introText1: Get ready to code in seconds
introText2: Isolated cloud development environments with your favorite editor
introText3: Connect in seconds with a repository
@ -1126,7 +1127,7 @@ cde:
createGitspace: Create Gitspace
deleteGitspace: Delete Gitspace
editGitspace: Edit Gitspace
viewGitspace: View Gitspace Detail
viewGitspace: View Gitspace Details
updateGitspace: Update Gitspace
gitspaces: Gitspaces
noRepo: Dont have a Git repo?
@ -1158,19 +1159,25 @@ cde:
startingGitspace: Starting Gitspace
stopingGitspace: Stopping Gitspace
sessionDuration: Last Started
lastActivated: LAST ACTIVATED
deleteGitspaceTitle: Delete Gitspace
deleteGitspaceText: "Are you sure to delete the gitspace, '{{name}}'?"
deleteSuccess: Gitspace deleted succesfully
repositoryAndBranch: Repository & Branch
importInto: Import Repository into Gitness
status: Status
getStarted: Get started with Gitspaces by
createImport: creating or importing repositories
listing:
online: Online
offline: Offline
online: Active
offline: Stopped
error: Error
starting: Starting...
stopping: Stopping...
create:
repositoryDetails: Repository Details
gitnessRepositories: Gitness Repositories
thirdPartyGitRepositories: Third Party Git Repositories
thirdPartyGitRepositories: Other Public Git Repositories
branchPlaceholder: Enter the Branch name
selectBranchPlaceholder: Select branch
searchRepositoryPlaceholder: Search for a repository
@ -1178,6 +1185,10 @@ cde:
repoNotFound: 'Create a repo <strong>{{repo}}</strong> in Gitness'
gitspaceCreateSuccess: Gitspace created successfully
gitspaceCreateFailed: Gitspace creation failed
subtext: Start Coding, no setup required. Dedicated cloud development environments with your favorite editor
importWarning:
Currently we support only third party public repositories. In order to use a private git repository, please
import the repository into Gitness.
unsaved:
title: Unsaved Changes
message: 'You have unsaved changes, On switching all unsaved changes will be lost'
@ -1189,6 +1200,7 @@ cde:
startGitspace: Start Gitspace
stopProvising: Stop Provising
noData: No Data for Gitspace
gitspaceActivity: Gitspace Activity
provisioningGitspace: Provisioning the Gitspace...
gitspaceStopped: Gitspace is stopped
gitspaceRunning: Gitspace is running
@ -1198,7 +1210,9 @@ cde:
logsFailed: Gitspace logs api failed
noLogsFound: No gitspace logs found
wrongIdentifier: Invalid gitspace id or project params
fetchingDetails: Fetching Gitspace Details ....
fetchingDetails: Fetching Gitspace Events ....
containerLogs: Container Logs
containerLogsSubText: Logs showing activity inside container
repository:
repo: Repository
selectRepository: Select Repository

View File

@ -172,6 +172,17 @@ export const DefaultMenu: React.FC = () => {
</Container>
</Render>
{__ENABLE_GITSPACE__ && (
<Render when={selectedSpace}>
<NavMenuItem
className=""
label={getString('cde.gitspaces')}
to={routes.toCDEGitspaces({ space: selectedSpace?.path as string })}
icon="gitspace"
/>
</Render>
)}
<Render when={!standalone && selectedSpace}>
<NavMenuItem
icon="thinner-search"

View File

@ -1649,10 +1649,10 @@
resolved "https://registry.npmjs.org/@harnessio/design-system/-/design-system-2.1.1.tgz#2da3036602ed9b9446d8139c72009e6dc1e25642"
integrity sha512-ZwAGM1srOZ49/6YkwyjkczUv4v91CN0rCecRYnV3/g+xdSV5ycrUvkJjl9nHub6jw2eCGC0GdyNgAtAJnLmGfQ==
"@harnessio/icons@^2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@harnessio/icons/-/icons-2.1.3.tgz#abe777ff94491dd1647693fc18e74f956c42fd52"
integrity sha512-hlYwjlS8sv46SvXyydnykNu0ZM89+xyGp3MAuOfdKLgxOn1wMMu/88VwP7tPhHmCRhkGhn7xAPY/JLkYT24pgg==
"@harnessio/icons@^2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@harnessio/icons/-/icons-2.1.5.tgz#07ad9417e79a2fef6ccc79df7f21758c2a85feb9"
integrity sha512-fFVXoPXnEXAM6lwfT8r7qhR265Pn79u99i/0EijbA0CgxPvtrHaIldcDoKBCk963VFXNIdjqJps405TzHwJSeA==
"@harnessio/uicore@^4.1.2":
version "4.1.2"