Merge pull request #1 from harness/ui-tempalate-update
Sync UI template with latest updates
This commit is contained in:
commit
6c65018ccf
|
@ -35,6 +35,12 @@ settings:
|
|||
typescript:
|
||||
alwaysTryTypes: true
|
||||
rules:
|
||||
'@typescript-eslint/ban-types':
|
||||
- error
|
||||
- extendDefaults: true
|
||||
types:
|
||||
'{}': false
|
||||
|
||||
# custom rules
|
||||
no-document-body-snapshot: 2
|
||||
duplicate-data-tooltip-id: 'warn'
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
const packageJSON = require('../package.json')
|
||||
const { pick, omit, mapValues } = require('lodash')
|
||||
|
||||
/**
|
||||
* These packages must be stricly shared with exact versions
|
||||
*/
|
||||
const ExactSharedPackages = [
|
||||
'react',
|
||||
'react-dom',
|
||||
'react-router-dom',
|
||||
'@harness/use-modal',
|
||||
'@blueprintjs/core',
|
||||
'@blueprintjs/select',
|
||||
'@blueprintjs/datetime',
|
||||
'restful-react',
|
||||
'@harness/monaco-yaml',
|
||||
'monaco-editor',
|
||||
'monaco-editor-core',
|
||||
'monaco-languages',
|
||||
'monaco-plugin-helpers',
|
||||
'react-monaco-editor'
|
||||
]
|
||||
|
||||
/**
|
||||
* @type {import('webpack').ModuleFederationPluginOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
name: 'governance',
|
||||
filename: 'remoteEntry.js',
|
||||
library: {
|
||||
type: 'var',
|
||||
name: 'governance'
|
||||
},
|
||||
exposes: {
|
||||
'./App': './src/App.tsx'
|
||||
},
|
||||
shared: {
|
||||
formik: packageJSON.dependencies['formik'],
|
||||
...mapValues(pick(packageJSON.dependencies, ExactSharedPackages), version => ({
|
||||
singleton: true,
|
||||
requiredVersion: version
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
const path = require('path');
|
||||
|
||||
const webpack = require('webpack')
|
||||
const {
|
||||
container: { ModuleFederationPlugin },
|
||||
DefinePlugin
|
||||
} = require('webpack');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
const GenerateStringTypesPlugin = require('../scripts/webpack/GenerateStringTypesPlugin').GenerateStringTypesPlugin
|
||||
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')
|
||||
|
||||
const moduleFederationConfig = require('./moduleFederation.config');
|
||||
const CONTEXT = process.cwd();
|
||||
|
||||
const DEV = process.env.NODE_ENV === 'development'
|
||||
const ON_PREM = `${process.env.ON_PREM}` === 'true'
|
||||
|
||||
module.exports = {
|
||||
target: 'web',
|
||||
context: CONTEXT,
|
||||
stats: {
|
||||
modules: false,
|
||||
children: false
|
||||
},
|
||||
output: {
|
||||
publicPath: 'auto',
|
||||
filename: DEV ? 'static/[name].js' : 'static/[name].[contenthash:6].js',
|
||||
chunkFilename: DEV ? 'static/[name].[id].js' : 'static/[name].[id].[contenthash:6].js',
|
||||
pathinfo: false
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
include: /node_modules/,
|
||||
type: 'javascript/auto'
|
||||
},
|
||||
{
|
||||
test: /\.(j|t)sx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.module\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: '@harness/css-types-loader',
|
||||
options: {
|
||||
prettierConfig: CONTEXT
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
mode: 'local',
|
||||
localIdentName: DEV ? '[name]_[local]_[hash:base64:6]' : '[hash:base64:6]',
|
||||
exportLocalsConvention: 'camelCaseOnly'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sassOptions: {
|
||||
includePaths: [path.join(CONTEXT, 'src')]
|
||||
},
|
||||
sourceMap: false,
|
||||
implementation: require('sass')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /(?<!\.module)\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: false
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sassOptions: {
|
||||
includePaths: [path.join(CONTEXT, 'src')]
|
||||
},
|
||||
implementation: require('sass')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(jpg|jpeg|png|svg|gif)$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 2000,
|
||||
fallback: 'file-loader'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.ttf$/,
|
||||
loader: 'file-loader'
|
||||
},
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: [
|
||||
{
|
||||
loader: 'yaml-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.gql$/,
|
||||
type: 'asset/source'
|
||||
},
|
||||
{
|
||||
test: /\.(mp4)$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.mjs', '.js', '.ts', '.tsx', '.json', '.ttf', '.scss'],
|
||||
plugins: [
|
||||
new TsconfigPathsPlugin()]
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin(moduleFederationConfig),
|
||||
new DefinePlugin({
|
||||
'process.env': '{}', // required for @blueprintjs/core
|
||||
__DEV__: DEV,
|
||||
__ON_PREM__: ON_PREM
|
||||
}),
|
||||
new GenerateStringTypesPlugin(),
|
||||
new RetryChunkLoadPlugin({
|
||||
maxRetries: 2
|
||||
}),
|
||||
]
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
const path = require('path');
|
||||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const { merge } = require('webpack-merge');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HTMLWebpackPlugin = require('html-webpack-plugin');
|
||||
const { DefinePlugin, WatchIgnorePlugin, container: { ModuleFederationPlugin }} = require('webpack');
|
||||
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
|
||||
const commonConfig = require('./webpack.common');
|
||||
|
||||
const baseUrl = process.env.BASE_URL ?? 'https://qa.harness.io/gateway'
|
||||
const targetLocalHost = JSON.parse(process.env.TARGET_LOCALHOST || 'true')
|
||||
|
||||
const ON_PREM = `${process.env.ON_PREM}` === 'true'
|
||||
const DEV = process.env.NODE_ENV === 'development'
|
||||
|
||||
const devConfig = {
|
||||
mode: 'development',
|
||||
entry: './src/index.tsx',
|
||||
devtool: 'cheap-module-source-map',
|
||||
cache: { type: 'filesystem' },
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].[id].js'
|
||||
},
|
||||
devServer: {
|
||||
hot: true,
|
||||
host: "localhost",
|
||||
historyApiFallback: true,
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: targetLocalHost ? 'http://localhost:3001' : baseUrl,
|
||||
logLevel: 'debug',
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[name].[id].css'
|
||||
}),
|
||||
new HTMLWebpackPlugin({
|
||||
template: 'src/index.html',
|
||||
filename: 'index.html',
|
||||
minify: false,
|
||||
templateParameters: {
|
||||
__DEV__: DEV,
|
||||
__ON_PREM__: ON_PREM
|
||||
}
|
||||
}),
|
||||
new DefinePlugin({
|
||||
'process.env': '{}', // required for @blueprintjs/core
|
||||
__DEV__: DEV
|
||||
}),
|
||||
new MonacoWebpackPlugin({
|
||||
// available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options
|
||||
languages: ['yaml', 'json']
|
||||
}),
|
||||
// new ForkTsCheckerWebpackPlugin()
|
||||
// new WatchIgnorePlugin({
|
||||
// paths: [/node_modules(?!\/@wings-software)/, /\.d\.ts$/]
|
||||
// }),
|
||||
]
|
||||
};
|
||||
|
||||
console.table({ baseUrl, targetLocalHost })
|
||||
|
||||
module.exports = merge(commonConfig, devConfig);
|
|
@ -0,0 +1,52 @@
|
|||
const { merge } = require('webpack-merge');
|
||||
const HTMLWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CircularDependencyPlugin = require('circular-dependency-plugin');
|
||||
const JSONGeneratorPlugin = require('@harness/jarvis/lib/webpack/json-generator-plugin').default;
|
||||
const { DefinePlugin } = require('webpack');
|
||||
|
||||
const commonConfig = require('./webpack.common');
|
||||
|
||||
const ON_PREM = `${process.env.ON_PREM}` === 'true'
|
||||
|
||||
const prodConfig = {
|
||||
mode: 'production',
|
||||
devtool: 'hidden-source-map',
|
||||
output: {
|
||||
filename: '[name].[contenthash:6].js',
|
||||
chunkFilename: '[name].[id].[contenthash:6].js'
|
||||
},
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: 'all'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].[contenthash:6].css',
|
||||
chunkFilename: '[name].[id].[contenthash:6].css'
|
||||
}),
|
||||
new JSONGeneratorPlugin({
|
||||
content: {
|
||||
version: require('../package.json').version,
|
||||
gitCommit: process.env.GIT_COMMIT,
|
||||
gitBranch: process.env.GIT_BRANCH
|
||||
},
|
||||
filename: 'version.json'
|
||||
}),
|
||||
new CircularDependencyPlugin({
|
||||
exclude: /node_modules/,
|
||||
failOnError: true
|
||||
}),
|
||||
new HTMLWebpackPlugin({
|
||||
template: 'src/index.html',
|
||||
filename: 'index.html',
|
||||
minify: false,
|
||||
templateParameters: {
|
||||
__ON_PREM__: ON_PREM
|
||||
}
|
||||
}),
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = merge(commonConfig, prodConfig);
|
|
@ -0,0 +1,8 @@
|
|||
describe('dashboard', () => {
|
||||
it('load the dashboard', () => {
|
||||
// cy.visit('/')
|
||||
// cy.contains('In Effect')
|
||||
// cy.contains('Policy Evaluations')
|
||||
// cy.contains('Failures Recorded')
|
||||
})
|
||||
})
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
35
web/dist.go
35
web/dist.go
|
@ -10,40 +10,7 @@ package web
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
//go:embed dist/*
|
||||
var content embed.FS
|
||||
|
||||
// Handler returns an http.HandlerFunc that servers the
|
||||
// static content from the embedded file system.
|
||||
func Handler() http.HandlerFunc {
|
||||
// Load the files subdirectory
|
||||
fs, err := fs.Sub(content, "dist")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Create an http.FileServer to serve the
|
||||
// contents of the files subdiretory.
|
||||
handler := http.FileServer(http.FS(fs))
|
||||
|
||||
// Create an http.HandlerFunc that wraps the
|
||||
// http.FileServer to always load the index.html
|
||||
// file if a directory path is being requested.
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// because this is a single page application,
|
||||
// we need to always load the index.html file
|
||||
// in the root of the project, unless the path
|
||||
// points to a file with an extension (css, js, etc)
|
||||
if filepath.Ext(r.URL.Path) == "" {
|
||||
// HACK: alter the path to point to the
|
||||
// root of the project.
|
||||
r.URL.Path = "/"
|
||||
}
|
||||
// and finally server the file.
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
var UI embed.FS
|
||||
|
|
141
web/package.json
141
web/package.json
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "sample-module",
|
||||
"name": "ui-template",
|
||||
"description": "Harness Inc",
|
||||
"version": "0.0.1",
|
||||
"author": "Harness Inc",
|
||||
|
@ -8,14 +8,14 @@
|
|||
"homepage": "http://harness.io/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/drone/sample-module.git"
|
||||
"url": "https://github.com/wings-software/ui-template.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/sample-module/sample-module/issues"
|
||||
"url": "https://github.com/wings-software/ui-template/issues"
|
||||
},
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development webpack serve --progress",
|
||||
"dev": "webpack serve --config config/webpack.dev.js",
|
||||
"test": "jest src --silent",
|
||||
"test:watch": "jest --watch",
|
||||
"lint": "eslint --rulesdir ./scripts/eslint-rules --ext .ts --ext .tsx src",
|
||||
|
@ -24,60 +24,130 @@
|
|||
"services": "npm-run-all services:*",
|
||||
"services:pm": "restful-react import --config restful-react.config.js pm",
|
||||
"postservices": "prettier --write src/services/**/*.tsx",
|
||||
"build": "npm run clean; webpack --mode production",
|
||||
"build": "rm -rf dist && webpack --config config/webpack.prod.js",
|
||||
"coverage": "npm test --coverage",
|
||||
"setup-github-registry": "sh scripts/setup-github-registry.sh",
|
||||
"strings": "npm-run-all strings:*",
|
||||
"strings:genTypes": "node scripts/strings/generateTypesCli.mjs",
|
||||
"fmt": "prettier --write \"./src/**/*.{ts,tsx,css,scss}\"",
|
||||
"micro:watch": "nodemon --watch 'src/**/*' -e ts,tsx,html,scss,svg,yaml --exec 'npm-run-all' -- micro:build micro:serve",
|
||||
"micro:build": "webpack --mode production",
|
||||
"micro:serve": "serve ./dist -l 3000"
|
||||
"checks": "npm run typecheck; npm run lint; npm run test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "3.26.1",
|
||||
"@blueprintjs/datetime": "3.13.0",
|
||||
"@blueprintjs/select": "3.12.3",
|
||||
"@harness/uicore": "^1.23.0",
|
||||
"anser": "^2.1.0",
|
||||
"classnames": "^2.3.1",
|
||||
"@emotion/core": "^10.0.28",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@harness/design-system": "1.0.0",
|
||||
"@harness/icons": "^1.27.0",
|
||||
"@harness/monaco-yaml": ">=1.0.0",
|
||||
"@harness/ng-tooltip": ">=1.30.68",
|
||||
"@harness/telemetry": ">=1.0.37",
|
||||
"@harness/uicore": "3.70.0",
|
||||
"@harness/use-modal": ">=1.1.0",
|
||||
"@popperjs/core": "^2.4.2",
|
||||
"@projectstorm/react-diagrams-core": "^6.6.0",
|
||||
"@urql/exchange-request-policy": "^0.1.3",
|
||||
"anser": "^2.0.1",
|
||||
"classnames": "^2.2.6",
|
||||
"clipboard-copy": "^3.1.0",
|
||||
"formik": "1.5.8",
|
||||
"closest": "^0.0.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"cron-validator": "^1.2.1",
|
||||
"cronstrue": "^1.114.0",
|
||||
"event-source-polyfill": "^1.0.22",
|
||||
"formik": "2.2.9",
|
||||
"highcharts": "9.1.0",
|
||||
"highcharts-react-official": "3.0.0",
|
||||
"idb": "^5.0.4",
|
||||
"immer": "^9.0.6",
|
||||
"jsonc-parser": "^2.0.2",
|
||||
"lodash-es": "^4.17.15",
|
||||
"marked": "^3.0.8",
|
||||
"marked": "^4.0.12",
|
||||
"masonry-layout": "^4.2.2",
|
||||
"ml-matrix": "^6.5.0",
|
||||
"moment": "^2.25.3",
|
||||
"moment-range": "^4.0.2",
|
||||
"monaco-editor": "^0.19.2",
|
||||
"monaco-editor-core": "0.15.5",
|
||||
"monaco-languages": "1.6.0",
|
||||
"monaco-plugin-helpers": "^1.0.2",
|
||||
"p-debounce": "^3.0.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.0.0",
|
||||
"react-contenteditable": "^3.3.5",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-draggable": "^4.4.2",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-lottie-player": "^1.4.0",
|
||||
"react-monaco-editor": "^0.34.0",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-qr-code": "^1.1.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"react-table": "^7.1.0",
|
||||
"react-table-sticky": "^1.1.3",
|
||||
"react-timeago": "^4.4.0",
|
||||
"react-virtuoso": "^1.10.2",
|
||||
"restful-react": "15.6.0",
|
||||
"swr": "^0.5.4",
|
||||
"yaml": "^1.10.0"
|
||||
"secure-web-storage": "^1.0.2",
|
||||
"urql": "^2.0.3",
|
||||
"uuid": "^8.3.0",
|
||||
"vscode-languageserver-types": "3.15.1",
|
||||
"webpack-retry-chunk-load-plugin": "^3.1.0",
|
||||
"yaml": "^1.10.0",
|
||||
"yup": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@harness/css-types-loader": "^3.1.0",
|
||||
"@harness/jarvis": "0.12.0",
|
||||
"@babel/core": "^7.13.15",
|
||||
"@emotion/react": "^11.4.0",
|
||||
"@graphql-codegen/cli": "^1.21.2",
|
||||
"@graphql-codegen/typescript": "^1.21.1",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.15",
|
||||
"@graphql-codegen/typescript-urql": "^2.0.6",
|
||||
"@harness/css-types-loader": "2.0.2",
|
||||
"@harness/jarvis": "^0.12.0",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@stoplight/prism-cli": "^4.3.1",
|
||||
"@stoplight/prism-http": "^4.3.1",
|
||||
"@storybook/addon-actions": "^6.3.1",
|
||||
"@storybook/addon-docs": "^6.3.1",
|
||||
"@storybook/addon-essentials": "^6.3.1",
|
||||
"@storybook/addon-links": "^6.3.1",
|
||||
"@storybook/builder-webpack5": "^6.3.1",
|
||||
"@storybook/manager-webpack5": "^6.3.1",
|
||||
"@storybook/react": "^6.3.1",
|
||||
"@testing-library/jest-dom": "^5.12.0",
|
||||
"@testing-library/react": "^10.0.3",
|
||||
"@testing-library/react-hooks": "5",
|
||||
"@testing-library/user-event": "^10.3.1",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/lodash-es": "^4.17.3",
|
||||
"@types/masonry-layout": "^4.2.1",
|
||||
"@types/mustache": "^4.0.1",
|
||||
"@types/node": "^16.4.10",
|
||||
"@types/path-to-regexp": "^1.7.0",
|
||||
"@types/qs": "^6.9.4",
|
||||
"@types/query-string": "^6.3.0",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-monaco-editor": "^0.16.0",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"@types/react-table": "^7.0.18",
|
||||
"@types/react-timeago": "^4.1.1",
|
||||
"@types/testing-library__react-hooks": "^3.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"@types/testing-library__user-event": "^4.1.1",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@types/yup": "^0.29.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
"@typescript-eslint/parser": "^5.33.1",
|
||||
"@urql/devtools": "^2.0.3",
|
||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||
"assert": "^2.0.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"cache-loader": "^4.1.0",
|
||||
"case": "^1.6.3",
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
"css-loader": "^6.3.0",
|
||||
|
@ -89,12 +159,23 @@
|
|||
"eslint-plugin-jest": "^24.3.6",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"express": "^4.17.1",
|
||||
"external-remotes-plugin": "^1.0.0",
|
||||
"fake-indexeddb": "^3.1.2",
|
||||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^6.2.1",
|
||||
"glob": "^7.1.6",
|
||||
"graphql": "^15.5.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"https": "^1.0.0",
|
||||
"husky": "^6.0.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"istanbul-lib-coverage": "^3.0.0",
|
||||
"jest": "^26.2.0",
|
||||
"jest-canvas-mock": "^2.3.0",
|
||||
"jest-junit": "^12.0.0",
|
||||
"lighthouse": "^6.5.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"mini-css-extract-plugin": "^2.4.2",
|
||||
|
@ -102,22 +183,30 @@
|
|||
"mustache": "^4.0.1",
|
||||
"nodemon": "^2.0.15",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"null-loader": "^4.0.1",
|
||||
"nyc": "^15.1.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"path-to-regexp": "^6.1.0",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"sass": "^1.32.8",
|
||||
"sass-loader": "^12.1.0",
|
||||
"serve": "^13.0.2",
|
||||
"source-map-support": "^0.5.20",
|
||||
"style-loader": "^3.3.0",
|
||||
"ts-jest": "^26.5.5",
|
||||
"ts-loader": "^9.2.6",
|
||||
"ts-node": "^10.2.1",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||
"typescript": "^4.2.4",
|
||||
"typescript": "^4.7.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.58.0",
|
||||
"webpack-bugsnag-plugins": "^1.8.0",
|
||||
"webpack-bundle-analyzer": "^4.4.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.6.0",
|
||||
"webpack-cli": "^4.9.0",
|
||||
"webpack-dev-server": "^4.3.1",
|
||||
"worker-loader": "^3.0.8",
|
||||
"yaml-loader": "^0.6.0"
|
||||
},
|
||||
"resolutions": {
|
||||
|
@ -126,7 +215,11 @@
|
|||
"@types/testing-library__react": "^10.0.0",
|
||||
"@types/testing-library__dom": "^7.0.0",
|
||||
"anser": "2.0.1",
|
||||
"create-react-context": "0.3.0"
|
||||
"create-react-context": "0.3.0",
|
||||
"@blueprintjs/core": "3.26.1",
|
||||
"@blueprintjs/datetime": "3.13.0",
|
||||
"@blueprintjs/icons": "3.16.0",
|
||||
"@blueprintjs/select": "3.12.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16.0"
|
||||
|
|
|
@ -6,12 +6,11 @@ const customGenerator = require('./scripts/swagger-custom-generator.js')
|
|||
|
||||
module.exports = {
|
||||
pm: {
|
||||
output: 'src/services/pm/index.tsx',
|
||||
file: 'src/services/pm/swagger.json',
|
||||
transformer: 'scripts/swagger-transform.js',
|
||||
customImport: `import { getConfig } from "../config";`,
|
||||
output: 'src/services/policy-mgmt/index.tsx',
|
||||
file: '../design/gen/http/openapi3.json',
|
||||
customImport: `import { getConfigNew } from "../config";`,
|
||||
customProps: {
|
||||
base: `{getConfig("pm/api/v1")}`
|
||||
base: `{getConfigNew("pm")}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import React, { useEffect, useState, useCallback } from 'react'
|
||||
import { RestfulProvider } from 'restful-react'
|
||||
import { TooltipContextProvider, ModalProvider } from '@harness/uicore'
|
||||
import { TooltipContextProvider } from '@harness/uicore'
|
||||
import { ModalProvider } from '@harness/use-modal'
|
||||
import { FocusStyleManager } from '@blueprintjs/core'
|
||||
import { tooltipDictionary } from '@harness/ng-tooltip'
|
||||
import AppErrorBoundary from 'framework/AppErrorBoundary/AppErrorBoundary'
|
||||
import { useAPIToken } from 'hooks/useAPIToken'
|
||||
import { AppContextProvider } from 'AppContext'
|
||||
import { setBaseRouteInfo } from 'RouteUtils'
|
||||
import type { AppProps } from 'AppProps'
|
||||
import { buildResfulReactRequestOptions, handle401 } from 'AppUtils'
|
||||
import { RouteDestinations } from 'RouteDestinations'
|
||||
import { useAPIToken } from 'hooks/useAPIToken'
|
||||
import { languageLoader } from './framework/strings/languageLoader'
|
||||
import type { LanguageRecord } from './framework/strings/languageLoader'
|
||||
import { StringsContextProvider } from './framework/strings/StringsContextProvider'
|
||||
import './App.scss'
|
||||
|
||||
FocusStyleManager.onlyShowFocusOnTabs()
|
||||
|
||||
|
@ -31,8 +32,8 @@ const App: React.FC<AppProps> = props => {
|
|||
const [strings, setStrings] = useState<LanguageRecord>()
|
||||
const [token, setToken] = useAPIToken(apiToken)
|
||||
const getRequestOptions = useCallback((): Partial<RequestInit> => {
|
||||
return buildResfulReactRequestOptions(token)
|
||||
}, [token])
|
||||
return buildResfulReactRequestOptions(hooks.useGetToken?.() || apiToken || 'default')
|
||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
setBaseRouteInfo(accountId, baseRoutePath)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -48,7 +49,7 @@ const App: React.FC<AppProps> = props => {
|
|||
return strings ? (
|
||||
<StringsContextProvider initialStrings={strings}>
|
||||
<AppErrorBoundary>
|
||||
<AppContextProvider value={{ standalone, baseRoutePath, accountId, lang, apiToken, on401, hooks, components }}>
|
||||
<AppContextProvider value={{ standalone, baseRoutePath, accountId, lang, on401, hooks, components }}>
|
||||
<RestfulProvider
|
||||
base="/"
|
||||
requestOptions={getRequestOptions}
|
||||
|
@ -59,7 +60,7 @@ const App: React.FC<AppProps> = props => {
|
|||
on401()
|
||||
}
|
||||
}}>
|
||||
<TooltipContextProvider initialTooltipDictionary={{}}>
|
||||
<TooltipContextProvider initialTooltipDictionary={tooltipDictionary}>
|
||||
<ModalProvider>{children ? children : <RouteDestinations standalone={standalone} />}</ModalProvider>
|
||||
</TooltipContextProvider>
|
||||
</RestfulProvider>
|
||||
|
|
|
@ -13,7 +13,10 @@ const AppContext = React.createContext<AppContextProps>({
|
|||
components: {}
|
||||
})
|
||||
|
||||
export const AppContextProvider: React.FC<{ value: AppProps }> = ({ value: initialValue, children }) => {
|
||||
export const AppContextProvider: React.FC<{ value: AppProps }> = React.memo(function AppContextProvider({
|
||||
value: initialValue,
|
||||
children
|
||||
}) {
|
||||
const [appStates, setAppStates] = useState<AppProps>(initialValue)
|
||||
|
||||
return (
|
||||
|
@ -27,6 +30,6 @@ export const AppContextProvider: React.FC<{ value: AppProps }> = ({ value: initi
|
|||
{children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export const useAppContext: () => AppContextProps = () => useContext(AppContext)
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import type React from 'react'
|
||||
import type * as History from 'history'
|
||||
import type { PermissionOptionsMenuButtonProps } from 'components/Permissions/PermissionsOptionsMenuButton'
|
||||
import type { LangLocale } from './framework/strings/languageLoader'
|
||||
import type { FeatureFlagMap, GitFiltersProps } from './utils/GovernanceUtils'
|
||||
|
||||
/**
|
||||
* AppProps defines an interface for host (parent) and
|
||||
|
@ -7,7 +10,6 @@ import type { LangLocale } from './framework/strings/languageLoader'
|
|||
* of the child app to be customized from the parent app.
|
||||
*
|
||||
* Areas of customization:
|
||||
*
|
||||
* - API token
|
||||
* - Active user
|
||||
* - Active locale (i18n)
|
||||
|
@ -59,15 +61,27 @@ export interface AppPathProps {
|
|||
policyIdentifier?: string
|
||||
policySetIdentifier?: string
|
||||
evaluationId?: string
|
||||
pipeline?: string
|
||||
execution?: string
|
||||
repo?: string
|
||||
branch?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* AppPropsHook defines a collection of React Hooks that application receives from
|
||||
* Platform integration.
|
||||
*/
|
||||
export interface AppPropsHook {} // eslint-disable-line @typescript-eslint/no-empty-interface
|
||||
export interface AppPropsHook {
|
||||
usePermission(permissionRequest: any, deps?: Array<any>): Array<boolean>
|
||||
useGetSchemaYaml(params: any, deps?: Array<any>): Record<string, any>
|
||||
useFeatureFlags(): FeatureFlagMap
|
||||
useGetToken(): any
|
||||
useAppStore(): any
|
||||
useGitSyncStore(): any
|
||||
useSaveToGitDialog(props: { onSuccess: any; onClose: any; onProgessOverlayClose: any }): any
|
||||
useGetListOfBranchesWithStatus(props: any): any
|
||||
useAnyEnterpriseLicense(): boolean
|
||||
useCurrentEnterpriseLicense(): boolean
|
||||
useLicenseStore(): any
|
||||
} // eslint-disable-line @typescript-eslint/no-empty-interface
|
||||
|
||||
/**
|
||||
* AppPropsComponent defines a collection of React Components that application receives from
|
||||
|
@ -75,4 +89,20 @@ export interface AppPropsHook {} // eslint-disable-line @typescript-eslint/no-e
|
|||
*/
|
||||
export interface AppPropsComponent {
|
||||
NGBreadcrumbs: React.FC
|
||||
RbacButton: React.FC
|
||||
RbacOptionsMenuButton: React.FC<PermissionOptionsMenuButtonProps>
|
||||
GitFilters: React.FC<GitFiltersProps>
|
||||
GitSyncStoreProvider: React.FC
|
||||
GitContextForm: React.FC<any>
|
||||
NavigationCheck: React.FC<{
|
||||
when?: boolean
|
||||
textProps?: {
|
||||
contentText?: string
|
||||
titleText?: string
|
||||
confirmButtonText?: string
|
||||
cancelButtonText?: string
|
||||
}
|
||||
navigate: (path: string) => void
|
||||
shouldBlockNavigation?: (location: History.Location) => boolean
|
||||
}>
|
||||
}
|
||||
|
|
|
@ -3,37 +3,103 @@ import type { AppPathProps } from 'AppProps'
|
|||
|
||||
export enum RoutePath {
|
||||
SIGNIN = '/signin',
|
||||
TEST_PAGE1 = '/test-page1',
|
||||
TEST_PAGE2 = '/test-page2',
|
||||
|
||||
SIGNUP = '/signup',
|
||||
REGISTER = '/register',
|
||||
LOGIN = '/login',
|
||||
USERS = '/users',
|
||||
ACCOUNT = '/account',
|
||||
PIPELINES = '/pipelines',
|
||||
PIPELINE = '/pipelines/:pipeline',
|
||||
PIPELINE_SETTINGS = '/pipelines/:pipeline/settings',
|
||||
PIPELINE_EXECUTIONS = '/pipelines/:pipeline/executions',
|
||||
PIPELINE_EXECUTION = '/pipelines/:pipeline/executions/:execution',
|
||||
PIPELINE_EXECUTION_SETTINGS = '/pipelines/:pipeline/executions/:execution/settings'
|
||||
POLICY_DASHBOARD = '/dashboard',
|
||||
POLICY_LISTING = '/policies',
|
||||
POLICY_NEW = '/policies/new',
|
||||
POLICY_VIEW = '/policies/view/:policyIdentifier',
|
||||
//POLICY_EDIT = '/policies/edit/:policyIdentifier',
|
||||
POLICY_EDIT = '/policies/edit/:policyIdentifier/:repo?/:branch?',
|
||||
POLICY_SETS_LISTING = '/policy-sets',
|
||||
POLICY_SETS_DETAIL = '/policy-sets/:policySetIdentifier',
|
||||
POLICY_EVALUATIONS_LISTING = '/policy-evaluations',
|
||||
POLICY_EVALUATION_DETAIL = '/policy-evaluations/:evaluationId'
|
||||
}
|
||||
|
||||
export default {
|
||||
toLogin: (): string => toRouteURL(RoutePath.LOGIN),
|
||||
toSignIn: (): string => toRouteURL(RoutePath.SIGNIN),
|
||||
toSignUp: (): string => toRouteURL(RoutePath.SIGNUP),
|
||||
toRegister: (): string => toRouteURL(RoutePath.REGISTER),
|
||||
toAccount: (): string => toRouteURL(RoutePath.ACCOUNT),
|
||||
toPipelines: (): string => toRouteURL(RoutePath.PIPELINES),
|
||||
toPipeline: ({ pipeline }: Required<Pick<AppPathProps, 'pipeline'>>): string =>
|
||||
toRouteURL(RoutePath.PIPELINE, { pipeline }),
|
||||
toPipelineExecutions: ({ pipeline }: Required<Pick<AppPathProps, 'pipeline'>>): string =>
|
||||
toRouteURL(RoutePath.PIPELINE_EXECUTIONS, { pipeline }),
|
||||
toPipelineSettings: ({ pipeline }: Required<Pick<AppPathProps, 'pipeline'>>): string =>
|
||||
toRouteURL(RoutePath.PIPELINE_SETTINGS, { pipeline }),
|
||||
toPipelineExecution: ({ pipeline, execution }: AppPathProps): string =>
|
||||
toRouteURL(RoutePath.PIPELINE_EXECUTION, { pipeline, execution }),
|
||||
toPipelineExecutionSettings: ({ pipeline, execution }: AppPathProps): string =>
|
||||
toRouteURL(RoutePath.PIPELINE_EXECUTION_SETTINGS, { pipeline, execution })
|
||||
|
||||
// @see https://github.com/drone/policy-mgmt/blob/main/web/src/RouteDefinitions.ts
|
||||
// for more examples regarding to passing parameters to generate URLs
|
||||
toPolicyDashboard: (): string => toRouteURL(RoutePath.POLICY_DASHBOARD),
|
||||
toPolicyListing: (): string => toRouteURL(RoutePath.POLICY_LISTING),
|
||||
toPolicyNew: (): string => toRouteURL(RoutePath.POLICY_NEW),
|
||||
toPolicyView: ({ policyIdentifier }: Required<Pick<AppPathProps, 'policyIdentifier'>>): string =>
|
||||
toRouteURL(RoutePath.POLICY_VIEW, { policyIdentifier }),
|
||||
toPolicyEdit: ({ policyIdentifier }: Required<Pick<AppPathProps, 'policyIdentifier'>>): string =>
|
||||
toRouteURL(RoutePath.POLICY_EDIT, { policyIdentifier }),
|
||||
toPolicySets: (): string => toRouteURL(RoutePath.POLICY_SETS_LISTING),
|
||||
toPolicyEvaluations: (): string => toRouteURL(RoutePath.POLICY_EVALUATIONS_LISTING),
|
||||
toGovernancePolicyDashboard: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_DASHBOARD, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernancePolicyListing: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_LISTING, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernanceNewPolicy: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_NEW, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernanceEditPolicy: ({
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
policyIdentifier,
|
||||
module,
|
||||
repo,
|
||||
branch
|
||||
}: RequireField<AppPathProps, 'policyIdentifier'>) =>
|
||||
toRouteURL(RoutePath.POLICY_EDIT, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
policyIdentifier,
|
||||
module,
|
||||
repo,
|
||||
branch
|
||||
}),
|
||||
toGovernanceViewPolicy: ({
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
policyIdentifier,
|
||||
module
|
||||
}: RequireField<AppPathProps, 'policyIdentifier'>) =>
|
||||
toRouteURL(RoutePath.POLICY_VIEW, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
policyIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernancePolicySetsListing: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_SETS_LISTING, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernancePolicySetDetail: ({ orgIdentifier, projectIdentifier, policySetIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_SETS_DETAIL, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module,
|
||||
policySetIdentifier
|
||||
}),
|
||||
toGovernanceEvaluationsListing: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_EVALUATIONS_LISTING, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernanceEvaluationDetail: ({ orgIdentifier, projectIdentifier, evaluationId, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_EVALUATION_DETAIL, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module,
|
||||
evaluationId
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,68 +1,69 @@
|
|||
import React from 'react'
|
||||
import { HashRouter, Route, Switch } from 'react-router-dom'
|
||||
import type { AppProps } from 'AppProps'
|
||||
/* eslint-disable react/display-name */
|
||||
import React, { useCallback } from 'react'
|
||||
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'
|
||||
// import { SignInPage } from 'pages/signin/SignInPage'
|
||||
import { NotFoundPage } from 'pages/404/NotFoundPage'
|
||||
import { routePath } from 'RouteUtils'
|
||||
import { RoutePath } from 'RouteDefinitions'
|
||||
import { SignIn } from 'pages/SignIn/SignIn'
|
||||
import { Register } from 'pages/Register/Register'
|
||||
import { routePath, standaloneRoutePath } from './RouteUtils'
|
||||
import { RoutePath } from './RouteDefinitions'
|
||||
|
||||
import { Login } from './pages/Login/Login'
|
||||
import { Home } from './pages/Pipelines/Pipelines'
|
||||
import { Executions } from './pages/Executions/Executions'
|
||||
import { ExecutionSettings } from './pages/Execution/Settings'
|
||||
import { PipelineSettings } from './pages/Pipeline/Settings'
|
||||
import { Account } from './pages/Account/Account'
|
||||
import { SideNav } from './components/SideNav/SideNav'
|
||||
export const RouteDestinations: React.FC<{ standalone: boolean }> = React.memo(({ standalone }) => {
|
||||
const Destinations: React.FC = useCallback(
|
||||
() => (
|
||||
<Switch>
|
||||
{standalone && (
|
||||
<>
|
||||
<Route path={routePath(RoutePath.SIGNIN)}>
|
||||
<SignIn />
|
||||
</Route>
|
||||
<Route path={routePath(RoutePath.SIGNUP)}>
|
||||
<SignIn />
|
||||
</Route>
|
||||
<Route path={routePath(RoutePath.REGISTER)}>
|
||||
<Register />
|
||||
</Route>
|
||||
</>
|
||||
)}
|
||||
|
||||
export const RouteDestinations: React.FC<Pick<AppProps, 'standalone'>> = ({ standalone }) => {
|
||||
// TODO: Add a generic Auth Wrapper
|
||||
|
||||
const Destinations: React.FC = () => (
|
||||
<Switch>
|
||||
{standalone && (
|
||||
<Route path={routePath(RoutePath.REGISTER)}>
|
||||
<Login />
|
||||
<Route path={routePath(RoutePath.POLICY_DASHBOARD)}>
|
||||
<h1>Overview</h1>
|
||||
</Route>
|
||||
)}
|
||||
{standalone && (
|
||||
<Route path={routePath(RoutePath.LOGIN)}>
|
||||
<Login />
|
||||
|
||||
<Route path={routePath(RoutePath.POLICY_NEW)}>
|
||||
<h1>New</h1>
|
||||
</Route>
|
||||
)}
|
||||
|
||||
<Route exact path={routePath(RoutePath.PIPELINES)}>
|
||||
<SideNav>
|
||||
<Home />
|
||||
</SideNav>
|
||||
</Route>
|
||||
<Route path={routePath(RoutePath.POLICY_VIEW)}>
|
||||
<h1>View</h1>
|
||||
</Route>
|
||||
|
||||
<Route exact path={routePath(RoutePath.PIPELINE)}>
|
||||
<SideNav>
|
||||
<Executions />
|
||||
</SideNav>
|
||||
</Route>
|
||||
<Route exact path={routePath(RoutePath.POLICY_EDIT)}>
|
||||
<h1>Edit</h1>
|
||||
</Route>
|
||||
|
||||
<Route exact path={routePath(RoutePath.PIPELINE_SETTINGS)}>
|
||||
<SideNav>
|
||||
<PipelineSettings />
|
||||
</SideNav>
|
||||
</Route>
|
||||
<Route path={routePath(RoutePath.POLICY_LISTING)}>
|
||||
<h1>Listing</h1>
|
||||
</Route>
|
||||
|
||||
<Route exact path={routePath(RoutePath.PIPELINE_EXECUTION_SETTINGS)}>
|
||||
<SideNav>
|
||||
<ExecutionSettings />
|
||||
</SideNav>
|
||||
</Route>
|
||||
<Route exact path={routePath(RoutePath.POLICY_SETS_LISTING)}>
|
||||
<h1>Listing 2</h1>
|
||||
</Route>
|
||||
|
||||
<Route exact path={routePath(RoutePath.ACCOUNT)}>
|
||||
<SideNav>
|
||||
<Account />
|
||||
</SideNav>
|
||||
</Route>
|
||||
<Route path={routePath(RoutePath.POLICY_SETS_DETAIL)}>
|
||||
<h1>Detail 1</h1>
|
||||
</Route>
|
||||
|
||||
<Route path="/">
|
||||
<NotFoundPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
<Route path={routePath(RoutePath.POLICY_EVALUATION_DETAIL)}>
|
||||
<h1>Detail 2</h1>
|
||||
</Route>
|
||||
|
||||
<Route path="/">
|
||||
{standalone ? <Redirect to={standaloneRoutePath(RoutePath.POLICY_DASHBOARD)} /> : <NotFoundPage />}
|
||||
</Route>
|
||||
</Switch>
|
||||
),
|
||||
[standalone]
|
||||
)
|
||||
|
||||
return standalone ? (
|
||||
|
@ -72,4 +73,4 @@ export const RouteDestinations: React.FC<Pick<AppProps, 'standalone'>> = ({ stan
|
|||
) : (
|
||||
<Destinations />
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -14,40 +14,42 @@ type Scope = Pick<AppPathProps, 'orgIdentifier' | 'projectIdentifier' | 'module'
|
|||
//
|
||||
// Note: This function needs to be in sync with NextGen UI's routeUtils' getScopeBasedRoute. When
|
||||
// it's out of sync, the URL routing scheme could be broken.
|
||||
// @see https://github.com/harness/harness-core-ui/blob/master/src/modules/10-common/utils/routeUtils.ts#L171
|
||||
// @see https://github.com/wings-software/nextgenui/blob/master/src/modules/10-common/utils/routeUtils.ts#L171
|
||||
//
|
||||
const getScopeBasedRouteURL = ({ path, scope = {} }: { path: string; scope?: Scope }): string => {
|
||||
if (window.APP_RUN_IN_STANDALONE_MODE) {
|
||||
return path
|
||||
}
|
||||
|
||||
const { orgIdentifier, projectIdentifier, module } = scope
|
||||
|
||||
//
|
||||
// TODO: Change this scheme below to reflect your application when it's embedded into Harness NextGen UI
|
||||
//
|
||||
|
||||
// The Sample Module UI app is mounted in three places in Harness Platform
|
||||
// 1. Account Settings (account level)
|
||||
// 2. Org Details (org level)
|
||||
// 3. Project Settings (project level)
|
||||
// The Governance app is mounted in three places in Harness Platform
|
||||
// 1. Account Settings (account level governance)
|
||||
// 2. Org Details (org level governance)
|
||||
// 3. Project Settings (project level governance)
|
||||
if (module && orgIdentifier && projectIdentifier) {
|
||||
return `/account/${accountId}/${module}/orgs/${orgIdentifier}/projects/${projectIdentifier}/setup/sample-module${path}`
|
||||
return `/account/${accountId}/${module}/orgs/${orgIdentifier}/projects/${projectIdentifier}/setup/governance${path}`
|
||||
} else if (orgIdentifier && projectIdentifier) {
|
||||
return `/account/${accountId}/home/orgs/${orgIdentifier}/projects/${projectIdentifier}/setup/sample-module${path}`
|
||||
return `/account/${accountId}/home/orgs/${orgIdentifier}/projects/${projectIdentifier}/setup/governance${path}`
|
||||
} else if (orgIdentifier) {
|
||||
return `/account/${accountId}/settings/organizations/${orgIdentifier}/setup/sample-module${path}`
|
||||
return `/account/${accountId}/settings/organizations/${orgIdentifier}/setup/governance${path}`
|
||||
}
|
||||
|
||||
return `/account/${accountId}/settings/sample-module${path}`
|
||||
return `/account/${accountId}/settings/governance${path}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate route path to be used in RouteDefinitions.
|
||||
* Generate route paths to be used in RouteDefinitions.
|
||||
* @param path route path
|
||||
* @returns a proper route path that works in both standalone and embedded modes.
|
||||
* @returns an array of proper route paths that works in both standalone and embedded modes across all levels of governance.
|
||||
*/
|
||||
export const routePath = (path: string): string => `${baseRoutePath || ''}${path}`
|
||||
export const routePath = (path: string): string[] => [
|
||||
`/account/:accountId/settings/governance${path}`,
|
||||
`/account/:accountId/settings/organizations/:orgIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(cd)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(ci)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(cf)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(sto)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(cv)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
]
|
||||
|
||||
export const standaloneRoutePath = (path: string): string => `${baseRoutePath || ''}${path}`
|
||||
|
||||
/**
|
||||
* Generate route URL to be used RouteDefinitions' default export (aka actual react-router link href)
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
import './App.scss'
|
||||
|
||||
// This flag is used in services/config.ts to customize API path when app is run
|
||||
// in multiple modes (standalone vs. embedded).
|
||||
// Also being used in when generating proper URLs inside the app.
|
||||
window.APP_RUN_IN_STANDALONE_MODE = true
|
||||
window.STRIP_SCM_PREFIX = true
|
||||
|
||||
ReactDOM.render(<App standalone hooks={{}} components={{}} />, document.getElementById('react-root'))
|
||||
ReactDOM.render(
|
||||
<App
|
||||
standalone
|
||||
accountId="default"
|
||||
apiToken="default"
|
||||
baseRoutePath="/account/default/settings/governance"
|
||||
hooks={{}}
|
||||
components={{}}
|
||||
/>,
|
||||
document.getElementById('react-root')
|
||||
)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
.spinner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
position: relative !important;
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import React from 'react'
|
||||
import cx from 'classnames'
|
||||
import { Container, PageSpinner } from '@harness/uicore'
|
||||
import css from './ContainerSpinner.module.scss'
|
||||
|
||||
export const ContainerSpinner: React.FC<React.ComponentProps<typeof Container>> = ({ className, ...props }) => {
|
||||
return (
|
||||
<Container className={cx(css.spinner, className)} {...props}>
|
||||
<PageSpinner />
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
.status {
|
||||
--bg-color: var(--grey-350);
|
||||
white-space: nowrap !important;
|
||||
font-size: var(--font-size-xsmall) !important;
|
||||
color: var(--white) !important;
|
||||
border: none;
|
||||
background-color: var(--bg-color) !important;
|
||||
border-radius: var(--spacing-2);
|
||||
padding: var(--spacing-1) var(--spacing-3) !important;
|
||||
height: 18px;
|
||||
line-height: var(--font-size-normal) !important;
|
||||
font-weight: bold !important;
|
||||
display: inline-flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
letter-spacing: 0.2px;
|
||||
|
||||
&.danger {
|
||||
--bg-color: var(--red-600);
|
||||
}
|
||||
|
||||
&.none {
|
||||
--bg-color: var(--grey-800);
|
||||
}
|
||||
|
||||
&.success {
|
||||
--bg-color: var(--green-600);
|
||||
}
|
||||
|
||||
&.primary {
|
||||
--bg-color: var(--primary-7);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
--bg-color: var(--warning);
|
||||
}
|
||||
|
||||
> span {
|
||||
margin-right: var(--spacing-2) !important;
|
||||
color: var(--white) !important;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly status: string
|
||||
readonly danger: string
|
||||
readonly none: string
|
||||
readonly success: string
|
||||
readonly primary: string
|
||||
readonly warning: string
|
||||
}
|
||||
export default styles
|
|
@ -1,41 +0,0 @@
|
|||
import React from 'react'
|
||||
import cx from 'classnames'
|
||||
import { Intent, IconName, Text } from '@harness/uicore'
|
||||
import type { IconProps } from '@harness/uicore/dist/icons/Icon'
|
||||
import css from './EvaluationStatusLabel.module.scss'
|
||||
|
||||
export interface EvaluationStatusProps {
|
||||
intent: Intent
|
||||
label: string
|
||||
icon?: IconName
|
||||
iconProps?: IconProps
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const EvaluationStatusLabel: React.FC<EvaluationStatusProps> = ({
|
||||
intent,
|
||||
icon,
|
||||
iconProps,
|
||||
label,
|
||||
className
|
||||
}) => {
|
||||
let _icon: IconName | undefined = icon
|
||||
|
||||
if (!_icon) {
|
||||
switch (intent) {
|
||||
case Intent.DANGER:
|
||||
case Intent.WARNING:
|
||||
_icon = 'warning-sign'
|
||||
break
|
||||
case Intent.SUCCESS:
|
||||
_icon = 'tick-circle'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Text icon={_icon} iconProps={{ size: 9, ...iconProps }} className={cx(css.status, className, css[intent])}>
|
||||
{label}
|
||||
</Text>
|
||||
)
|
||||
}
|
|
@ -38,7 +38,7 @@ interface NameIdProps {
|
|||
|
||||
export const NameId = (props: NameIdProps): JSX.Element => {
|
||||
const { getString } = useStrings()
|
||||
const { identifierProps, nameLabel = getString('common.name'), inputGroupProps = {} } = props
|
||||
const { identifierProps, nameLabel = getString('name'), inputGroupProps = {} } = props
|
||||
const newInputGroupProps = { placeholder: getString('common.namePlaceholder'), ...inputGroupProps }
|
||||
return (
|
||||
<FormInput.InputWithIdentifier inputLabel={nameLabel} inputGroupProps={newInputGroupProps} {...identifierProps} />
|
||||
|
@ -55,9 +55,7 @@ export const Description = (props: DescriptionComponentProps): JSX.Element => {
|
|||
return (
|
||||
<Container style={{ marginBottom: isDescriptionOpen ? '0' : 'var(--spacing-medium)' }}>
|
||||
<Label className={cx(Classes.LABEL, css.descriptionLabel)} data-tooltip-id={props.dataTooltipId}>
|
||||
{isOptional
|
||||
? getString('common.optionalField', { name: getString('common.description') })
|
||||
: getString('common.description')}
|
||||
{isOptional ? getString('optionalField', { name: getString('description') }) : getString('description')}
|
||||
{props.dataTooltipId ? <HarnessDocTooltip useStandAlone={true} tooltipId={props.dataTooltipId} /> : null}
|
||||
{!isDescriptionOpen && (
|
||||
<Icon
|
||||
|
@ -79,7 +77,7 @@ export const Description = (props: DescriptionComponentProps): JSX.Element => {
|
|||
disabled={disabled}
|
||||
autoFocus={isDescriptionFocus}
|
||||
name="description"
|
||||
placeholder={getString('common.descriptionPlaceholder')}
|
||||
placeholder={getString('descriptionPlaceholder')}
|
||||
{...restDescriptionProps}
|
||||
/>
|
||||
)}
|
||||
|
@ -95,9 +93,7 @@ export const Tags = (props: TagsComponentProps): JSX.Element => {
|
|||
return (
|
||||
<Container>
|
||||
<Label className={cx(Classes.LABEL, css.descriptionLabel)} data-tooltip-id={props.dataTooltipId}>
|
||||
{isOptional
|
||||
? getString('common.optionalField', { name: getString('common.tagsLabel') })
|
||||
: getString('common.tagsLabel')}
|
||||
{isOptional ? getString('optionalField', { name: getString('tagsLabel') }) : getString('tagsLabel')}
|
||||
{props.dataTooltipId ? <HarnessDocTooltip useStandAlone={true} tooltipId={props.dataTooltipId} /> : null}
|
||||
{!isTagsOpen && (
|
||||
<Icon
|
||||
|
@ -125,7 +121,7 @@ function TagsDeprecated(props: TagsDeprecatedComponentProps): JSX.Element {
|
|||
return (
|
||||
<Container>
|
||||
<Label className={cx(Classes.LABEL, css.descriptionLabel)}>
|
||||
{getString('common.tagsLabel')}
|
||||
{getString('tagsLabel')}
|
||||
{!isTagsOpen && (
|
||||
<Icon
|
||||
className={css.editOpen}
|
||||
|
@ -159,15 +155,7 @@ function TagsDeprecated(props: TagsDeprecatedComponentProps): JSX.Element {
|
|||
|
||||
export function NameIdDescriptionTags(props: NameIdDescriptionTagsProps): JSX.Element {
|
||||
const { getString } = useStrings()
|
||||
const {
|
||||
className,
|
||||
identifierProps,
|
||||
descriptionProps,
|
||||
tagsProps,
|
||||
formikProps,
|
||||
inputGroupProps = {},
|
||||
tooltipProps
|
||||
} = props
|
||||
const { className, identifierProps, descriptionProps, formikProps, inputGroupProps = {}, tooltipProps } = props
|
||||
const newInputGroupProps = { placeholder: getString('common.namePlaceholder'), ...inputGroupProps }
|
||||
return (
|
||||
<Container className={cx(css.main, className)}>
|
||||
|
@ -177,12 +165,6 @@ export function NameIdDescriptionTags(props: NameIdDescriptionTagsProps): JSX.El
|
|||
hasValue={!!formikProps?.values.description}
|
||||
dataTooltipId={tooltipProps?.dataTooltipId ? `${tooltipProps.dataTooltipId}_description` : undefined}
|
||||
/>
|
||||
<Tags
|
||||
tagsProps={tagsProps}
|
||||
isOptional={tagsProps?.isOption}
|
||||
hasValue={!isEmpty(formikProps?.values.tags)}
|
||||
dataTooltipId={tooltipProps?.dataTooltipId ? `${tooltipProps.dataTooltipId}_tags` : undefined}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import { Classes, Menu } from '@blueprintjs/core'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { Classes, IMenuItemProps, Menu } from '@blueprintjs/core'
|
||||
import { Button, ButtonProps } from '@harness/uicore'
|
||||
import type { PopoverProps } from '@harness/uicore/dist/components/Popover/Popover'
|
||||
|
||||
|
@ -9,7 +9,7 @@ export interface OptionsMenuButtonProps extends ButtonProps {
|
|||
items: Array<React.ComponentProps<typeof Menu.Item> | '-'>
|
||||
}
|
||||
|
||||
export const OptionsMenuButton: React.FC<OptionsMenuButtonProps> = ({ items, ...props }) => {
|
||||
export const OptionsMenuButton = ({ items, ...props }: OptionsMenuButtonProps): ReactElement => {
|
||||
return (
|
||||
<Button
|
||||
minimal
|
||||
|
@ -23,7 +23,7 @@ export const OptionsMenuButton: React.FC<OptionsMenuButtonProps> = ({ items, ...
|
|||
<Menu.Item
|
||||
key={(item as React.ComponentProps<typeof Menu.Item>)?.text as string}
|
||||
className={Classes.POPOVER_DISMISS}
|
||||
{...item}
|
||||
{...(item as IMenuItemProps & React.AnchorHTMLAttributes<HTMLAnchorElement>)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react'
|
||||
import { Button, ButtonProps } from '@harness/uicore'
|
||||
import { useAppContext } from 'AppContext'
|
||||
|
||||
interface PermissionButtonProps extends ButtonProps {
|
||||
permission?: any
|
||||
}
|
||||
|
||||
export const PermissionsButton: React.FC<PermissionButtonProps> = (props: PermissionButtonProps) => {
|
||||
const {
|
||||
components: { RbacButton }
|
||||
} = useAppContext()
|
||||
const { permission, ...buttonProps } = props
|
||||
|
||||
return RbacButton ? <RbacButton permission={permission} {...props} /> : <Button {...buttonProps} />
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import React, { AnchorHTMLAttributes, ReactElement } from 'react'
|
||||
import type { IMenuItemProps } from '@blueprintjs/core'
|
||||
import { OptionsMenuButton, OptionsMenuButtonProps } from 'components/OptionsMenuButton/OptionsMenuButton'
|
||||
import { useAppContext } from 'AppContext'
|
||||
|
||||
type Item = ((IMenuItemProps | PermissionsMenuItemProps) & AnchorHTMLAttributes<HTMLAnchorElement>) | '-'
|
||||
|
||||
interface PermissionsMenuItemProps extends IMenuItemProps {
|
||||
permission?: any
|
||||
}
|
||||
|
||||
export interface PermissionOptionsMenuButtonProps extends OptionsMenuButtonProps {
|
||||
items: Item[]
|
||||
}
|
||||
|
||||
export const PermissionsOptionsMenuButton = (props: PermissionOptionsMenuButtonProps): ReactElement => {
|
||||
const {
|
||||
components: { RbacOptionsMenuButton }
|
||||
} = useAppContext()
|
||||
|
||||
return RbacOptionsMenuButton ? <RbacOptionsMenuButton {...props} /> : <OptionsMenuButton {...props} />
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
.minWidth {
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-bottom: unset !important;
|
||||
}
|
||||
|
||||
.pre {
|
||||
background: var(--theme-dark-canvas-dot);
|
||||
color: #fff;
|
||||
width: 300px;
|
||||
height: auto;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright 2021 Harness Inc. All rights reserved.
|
||||
* Use of this source code is governed by the PolyForm Shield 1.0.0 license
|
||||
* that can be found in the licenses directory at the root of this repository, also available at
|
||||
* https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
|
||||
**/
|
||||
// this is an auto-generated file, do not update this manually
|
||||
declare const styles: {
|
||||
readonly container: string
|
||||
readonly input: string
|
||||
readonly minWidth: string
|
||||
readonly pre: string
|
||||
readonly root: string
|
||||
}
|
||||
export default styles
|
|
@ -1,126 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
import {
|
||||
Container,
|
||||
Button,
|
||||
Formik,
|
||||
FormikForm,
|
||||
FormInput,
|
||||
Text,
|
||||
Color,
|
||||
Layout,
|
||||
ButtonVariation,
|
||||
Page,
|
||||
CodeBlock
|
||||
} from '@harness/uicore'
|
||||
import { useAPIToken } from 'hooks/useAPIToken'
|
||||
import { useStrings } from 'framework/strings'
|
||||
|
||||
import styles from './Settings.module.scss'
|
||||
|
||||
interface FormValues {
|
||||
name?: string
|
||||
desc?: string
|
||||
}
|
||||
|
||||
interface FormProps {
|
||||
name?: string
|
||||
desc?: string
|
||||
handleSubmit: (values: FormValues) => void
|
||||
loading: boolean | undefined
|
||||
refetch: () => void
|
||||
handleDelete: () => void
|
||||
error?: any
|
||||
title: string
|
||||
}
|
||||
|
||||
export const Settings = ({ name, desc, handleSubmit, handleDelete, loading, refetch, error, title }: FormProps) => {
|
||||
const [token] = useAPIToken()
|
||||
const { getString } = useStrings()
|
||||
const [showToken, setShowToken] = useState(false)
|
||||
const [editDetails, setEditDetails] = useState(false)
|
||||
|
||||
const onSubmit = (values: FormValues) => {
|
||||
handleSubmit(values)
|
||||
setEditDetails(false)
|
||||
}
|
||||
|
||||
const editForm = (
|
||||
<Formik initialValues={{ name, desc }} formName="newPipelineForm" onSubmit={values => onSubmit(values)}>
|
||||
<FormikForm>
|
||||
<Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'flex-start' }} margin={{ bottom: 'large' }}>
|
||||
<Text color={Color.GREY_600} className={styles.minWidth}>
|
||||
{getString('common.name')}
|
||||
</Text>
|
||||
<FormInput.Text name="name" className={styles.input} />
|
||||
</Layout.Horizontal>
|
||||
<Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'flex-start' }} margin={{ bottom: 'large' }}>
|
||||
<Text color={Color.GREY_600} className={styles.minWidth}>
|
||||
{getString('common.description')}
|
||||
</Text>
|
||||
<FormInput.Text name="desc" className={styles.input} />
|
||||
</Layout.Horizontal>
|
||||
<Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'flex-start' }} margin={{ bottom: 'large' }}>
|
||||
<Button variation={ButtonVariation.LINK} icon="updated" text={getString('common.save')} type="submit" />
|
||||
<Button variation={ButtonVariation.LINK} onClick={handleDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
)
|
||||
|
||||
return (
|
||||
<Container className={styles.root} height="inherit">
|
||||
<Page.Header title={getString('settings')} />
|
||||
<Page.Body
|
||||
loading={loading}
|
||||
retryOnError={() => refetch()}
|
||||
error={(error?.data as Error)?.message || error?.message}>
|
||||
<Container margin="xlarge" padding="xlarge" className={styles.container} background="white">
|
||||
<Text color={Color.BLACK} font={{ weight: 'semi-bold', size: 'medium' }} margin={{ bottom: 'xlarge' }}>
|
||||
{title}
|
||||
</Text>
|
||||
{editDetails ? (
|
||||
editForm
|
||||
) : (
|
||||
<>
|
||||
<Layout.Horizontal
|
||||
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}
|
||||
margin={{ bottom: 'large' }}>
|
||||
<Text color={Color.GREY_600} className={styles.minWidth}>
|
||||
{getString('common.name')}
|
||||
</Text>
|
||||
<Text color={Color.GREY_800}>{name}</Text>
|
||||
</Layout.Horizontal>
|
||||
<Layout.Horizontal
|
||||
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}
|
||||
margin={{ bottom: 'large' }}>
|
||||
<Text className={styles.minWidth}>{getString('common.description')}</Text>
|
||||
<Text color={Color.GREY_800}>{desc}</Text>
|
||||
</Layout.Horizontal>
|
||||
</>
|
||||
)}
|
||||
{!editDetails && (
|
||||
<Button
|
||||
variation={ButtonVariation.LINK}
|
||||
icon="Edit"
|
||||
text={getString('common.edit')}
|
||||
onClick={() => setEditDetails(true)}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
<Container margin="xlarge" padding="xlarge" className={styles.container} background="white">
|
||||
<Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'flex-start' }} margin={{ bottom: 'large' }}>
|
||||
<Text className={styles.minWidth}>{getString('common.token')}</Text>
|
||||
<Button variation={ButtonVariation.LINK} onClick={() => setShowToken(!showToken)}>
|
||||
Display/Hide Token
|
||||
</Button>
|
||||
</Layout.Horizontal>
|
||||
<Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'flex-start' }} margin={{ bottom: 'large' }}>
|
||||
{showToken && <CodeBlock allowCopy format="pre" snippet={token} />}
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
</Page.Body>
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
.root {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sideNav {
|
||||
width: 184px !important;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
background: #07182b !important;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: block;
|
||||
margin-left: var(--spacing-medium);
|
||||
padding: var(--spacing-small) var(--spacing-medium);
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
opacity: 1;
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
background-color: rgba(2, 120, 213, 0.5);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(2, 120, 213, 0.8);
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--white) !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright 2021 Harness Inc. All rights reserved.
|
||||
* Use of this source code is governed by the PolyForm Shield 1.0.0 license
|
||||
* that can be found in the licenses directory at the root of this repository, also available at
|
||||
* https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
|
||||
**/
|
||||
// this is an auto-generated file, do not update this manually
|
||||
declare const styles: {
|
||||
readonly link: string
|
||||
readonly root: string
|
||||
readonly selected: string
|
||||
readonly sideNav: string
|
||||
readonly text: string
|
||||
}
|
||||
export default styles
|
|
@ -1,38 +0,0 @@
|
|||
import React from 'react'
|
||||
import cx from 'classnames'
|
||||
import { NavLink as Link, NavLinkProps } from 'react-router-dom'
|
||||
import { Container, Text, Layout, IconName } from '@harness/uicore'
|
||||
import { useAPIToken } from 'hooks/useAPIToken'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import routes from 'RouteDefinitions'
|
||||
import css from './SideNav.module.scss'
|
||||
|
||||
interface SidebarLinkProps extends NavLinkProps {
|
||||
label: string
|
||||
icon?: IconName
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SidebarLink: React.FC<SidebarLinkProps> = ({ label, icon, className, ...others }) => (
|
||||
<Link className={cx(css.link, className)} activeClassName={css.selected} {...others}>
|
||||
<Text icon={icon} className={css.text}>
|
||||
{label}
|
||||
</Text>
|
||||
</Link>
|
||||
)
|
||||
|
||||
export const SideNav: React.FC = ({ children }) => {
|
||||
const { getString } = useStrings()
|
||||
const [, setToken] = useAPIToken()
|
||||
|
||||
return (
|
||||
<Container height="inherit" className={css.root}>
|
||||
<Layout.Vertical spacing="small" padding={{ top: 'xxxlarge' }} className={css.sideNav}>
|
||||
<SidebarLink exact icon="pipeline" label={getString('pipelines')} to={routes.toPipelines()} />
|
||||
<SidebarLink exact icon="advanced" label={getString('account')} to={routes.toAccount()} />
|
||||
<SidebarLink onClick={() => setToken('')} icon="log-out" label={getString('logout')} to={routes.toLogin()} />
|
||||
</Layout.Vertical>
|
||||
{children}
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
.loadingSpinnerWrapper {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
width: 0px
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly spinner: string
|
||||
readonly loadingSpinnerWrapper: string
|
||||
readonly hidden: string
|
||||
}
|
||||
export default styles
|
|
@ -0,0 +1,24 @@
|
|||
import React, { CSSProperties } from 'react'
|
||||
import { Layout } from '@harness/uicore'
|
||||
import { Spinner } from '@blueprintjs/core'
|
||||
import cx from 'classnames'
|
||||
import css from './SpinnerWrapper.module.scss'
|
||||
|
||||
export const SpinnerWrapper = ({
|
||||
loading,
|
||||
children,
|
||||
style
|
||||
}: {
|
||||
loading: boolean
|
||||
children: React.ReactNode | undefined
|
||||
style?: CSSProperties
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<Layout.Vertical style={style}>
|
||||
<Layout.Horizontal className={cx(css.loadingSpinnerWrapper, { [css.hidden]: !loading })}>
|
||||
<Spinner />
|
||||
</Layout.Horizontal>
|
||||
{!loading && children}
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
.table {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.layout {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.verticalCenter {
|
||||
justify-content: center;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright 2021 Harness Inc. All rights reserved.
|
||||
* Use of this source code is governed by the PolyForm Shield 1.0.0 license
|
||||
* that can be found in the licenses directory at the root of this repository, also available at
|
||||
* https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
|
||||
**/
|
||||
// this is an auto-generated file, do not update this manually
|
||||
declare const styles: {
|
||||
readonly layout: string
|
||||
readonly table: string
|
||||
readonly verticalCenter: string
|
||||
}
|
||||
export default styles
|
|
@ -1,172 +0,0 @@
|
|||
import React, { useMemo, useState } from 'react'
|
||||
import moment from 'moment'
|
||||
import {
|
||||
Text,
|
||||
Layout,
|
||||
Color,
|
||||
TableV2,
|
||||
Button,
|
||||
ButtonVariation,
|
||||
useConfirmationDialog,
|
||||
useToaster
|
||||
} from '@harness/uicore'
|
||||
import type { CellProps, Renderer, Column } from 'react-table'
|
||||
import { Menu, Position, Intent, Popover } from '@blueprintjs/core'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import type { Pipeline } from 'services/pm'
|
||||
|
||||
import styles from './Table.module.scss'
|
||||
|
||||
interface TableProps {
|
||||
data: Pipeline[] | null
|
||||
refetch: () => Promise<void>
|
||||
onDelete: (value: string) => Promise<void>
|
||||
onSettingsClick: (slug: string) => void
|
||||
onRowClick: (slug: string) => void
|
||||
}
|
||||
|
||||
type CustomColumn<T extends Record<string, any>> = Column<T> & {
|
||||
refetch?: () => Promise<void>
|
||||
}
|
||||
|
||||
const Table: React.FC<TableProps> = ({ data, refetch, onRowClick, onDelete, onSettingsClick }) => {
|
||||
const RenderColumn: Renderer<CellProps<Pipeline>> = ({
|
||||
cell: {
|
||||
column: { Header },
|
||||
row: { values }
|
||||
}
|
||||
}) => {
|
||||
let text
|
||||
switch (Header) {
|
||||
case 'ID':
|
||||
text = values.id
|
||||
break
|
||||
case 'Name':
|
||||
text = values.name
|
||||
break
|
||||
case 'Description':
|
||||
text = values.desc
|
||||
break
|
||||
case 'Slug':
|
||||
text = values.slug
|
||||
break
|
||||
case 'Created':
|
||||
text = moment(values.created).format('MM/DD/YYYY hh:mm:ss a')
|
||||
break
|
||||
}
|
||||
return (
|
||||
<Layout.Horizontal
|
||||
onClick={() => onRowClick(values.slug)}
|
||||
spacing="small"
|
||||
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}
|
||||
style={{ cursor: 'pointer' }}>
|
||||
<Layout.Vertical spacing="xsmall" padding={{ left: 'small' }} className={styles.verticalCenter}>
|
||||
<Layout.Horizontal spacing="small">
|
||||
<Text color={Color.BLACK} lineClamp={1}>
|
||||
{text}
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
</Layout.Vertical>
|
||||
</Layout.Horizontal>
|
||||
)
|
||||
}
|
||||
|
||||
const RenderColumnMenu: Renderer<CellProps<Pipeline>> = ({ row: { values } }) => {
|
||||
const { showSuccess, showError } = useToaster()
|
||||
const { getString } = useStrings()
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const { openDialog } = useConfirmationDialog({
|
||||
titleText: getString('common.delete'),
|
||||
contentText: <Text color={Color.GREY_800}>Are you sure you want to delete this?</Text>,
|
||||
confirmButtonText: getString('common.delete'),
|
||||
cancelButtonText: getString('common.cancel'),
|
||||
intent: Intent.DANGER,
|
||||
buttonIntent: Intent.DANGER,
|
||||
onCloseDialog: async (isConfirmed: boolean) => {
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
await onDelete(values.slug)
|
||||
showSuccess(getString('common.itemDeleted'))
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(`Error: ${err}`)
|
||||
console.error({ err })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Layout.Horizontal className={styles.layout}>
|
||||
<Popover
|
||||
isOpen={menuOpen}
|
||||
onInteraction={nextOpenState => setMenuOpen(nextOpenState)}
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
content={
|
||||
<Menu style={{ minWidth: 'unset' }}>
|
||||
<Menu.Item icon="trash" text={getString('common.delete')} onClick={openDialog} />
|
||||
<Menu.Item icon="settings" text={getString('settings')} onClick={() => onSettingsClick(values.slug)} />
|
||||
</Menu>
|
||||
}>
|
||||
<Button icon="Options" variation={ButtonVariation.ICON} />
|
||||
</Popover>
|
||||
</Layout.Horizontal>
|
||||
)
|
||||
}
|
||||
|
||||
const columns: CustomColumn<Pipeline>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'ID',
|
||||
id: 'id',
|
||||
accessor: row => row.id,
|
||||
width: '15%',
|
||||
Cell: RenderColumn
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
id: 'name',
|
||||
accessor: row => row.name,
|
||||
width: '20%',
|
||||
Cell: RenderColumn
|
||||
},
|
||||
{
|
||||
Header: 'Description',
|
||||
id: 'desc',
|
||||
accessor: row => row.desc,
|
||||
width: '30%',
|
||||
Cell: RenderColumn,
|
||||
disableSortBy: true
|
||||
},
|
||||
{
|
||||
Header: 'Slug',
|
||||
id: 'slug',
|
||||
accessor: row => row.slug,
|
||||
width: '15%',
|
||||
Cell: RenderColumn,
|
||||
disableSortBy: true
|
||||
},
|
||||
{
|
||||
Header: 'Created',
|
||||
id: 'created',
|
||||
accessor: row => row.created,
|
||||
width: '15%',
|
||||
Cell: RenderColumn,
|
||||
disableSortBy: true
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
id: 'menu',
|
||||
accessor: row => row.slug,
|
||||
width: '5%',
|
||||
Cell: RenderColumnMenu,
|
||||
disableSortBy: true,
|
||||
refetch: refetch
|
||||
}
|
||||
],
|
||||
[refetch]
|
||||
)
|
||||
return <TableV2<Pipeline> className={styles.table} columns={columns} name="basicTable" data={data || []} />
|
||||
}
|
||||
|
||||
export default Table
|
|
@ -0,0 +1,15 @@
|
|||
.banner {
|
||||
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
||||
|
||||
&.expiryCountdown {
|
||||
background: var(--orange-50) !important;
|
||||
}
|
||||
|
||||
&.expired {
|
||||
background: var(--red-50) !important;
|
||||
}
|
||||
|
||||
.bannerIcon {
|
||||
margin-right: var(--spacing-large) !important;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly banner: string
|
||||
readonly expiryCountdown: string
|
||||
readonly expired: string
|
||||
readonly bannerIcon: string
|
||||
}
|
||||
export default styles
|
|
@ -0,0 +1,53 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import cx from 'classnames'
|
||||
import moment from 'moment'
|
||||
import { Container, Icon, Text } from '@harness/uicore'
|
||||
import { Color } from '@harness/design-system'
|
||||
import { useGetTrialInfo } from 'utils/GovernanceUtils'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import css from './TrialBanner.module.scss'
|
||||
|
||||
const TrialBanner = (): ReactElement => {
|
||||
const trialInfo = useGetTrialInfo()
|
||||
const { getString } = useStrings()
|
||||
|
||||
if (!trialInfo) return <></>
|
||||
|
||||
const { expiryTime } = trialInfo
|
||||
|
||||
const time = moment(trialInfo.expiryTime)
|
||||
const days = Math.round(time.diff(moment.now(), 'days', true))
|
||||
const expiryDate = time.format('DD MMM YYYY')
|
||||
const isExpired = expiryTime !== -1 && days < 0
|
||||
const expiredDays = Math.abs(days)
|
||||
|
||||
const expiryMessage = isExpired
|
||||
? getString('banner.expired', {
|
||||
days: expiredDays
|
||||
})
|
||||
: getString('banner.expiryCountdown', {
|
||||
days
|
||||
})
|
||||
|
||||
const bannerMessage = `Harness Policy Engine trial ${expiryMessage} on ${expiryDate}`
|
||||
const bannerClassnames = cx(css.banner, isExpired ? css.expired : css.expiryCountdown)
|
||||
const color = isExpired ? Color.RED_700 : Color.ORANGE_700
|
||||
|
||||
return (
|
||||
<Container
|
||||
padding="small"
|
||||
intent="warning"
|
||||
flex={{
|
||||
justifyContent: 'start'
|
||||
}}
|
||||
className={bannerClassnames}
|
||||
font={{
|
||||
align: 'center'
|
||||
}}>
|
||||
<Icon name={'warning-sign'} size={15} className={css.bannerIcon} color={color} />
|
||||
<Text color={color}>{bannerMessage}</Text>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default TrialBanner
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react'
|
||||
import mustache from 'mustache'
|
||||
import { get } from 'lodash-es'
|
||||
|
||||
import { useStringsContext, StringKeys } from './StringsContext'
|
||||
|
||||
export interface UseStringsReturn {
|
||||
|
@ -46,9 +45,9 @@ export function String(props: StringProps): React.ReactElement | null {
|
|||
const text = getString(stringID, vars)
|
||||
|
||||
return useRichText ? (
|
||||
<Tag {...(rest as unknown)} dangerouslySetInnerHTML={{ __html: text }} />
|
||||
<Tag {...(rest as unknown as {})} dangerouslySetInnerHTML={{ __html: text }} />
|
||||
) : (
|
||||
<Tag {...(rest as unknown)}>{text}</Tag>
|
||||
<Tag {...(rest as unknown as {})}>{text}</Tag>
|
||||
)
|
||||
} catch (e) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
|
|
|
@ -3,37 +3,150 @@
|
|||
* Use the command `yarn strings` to regenerate this file.
|
||||
*/
|
||||
export interface StringsMap {
|
||||
account: string
|
||||
addExecution: string
|
||||
'common.accountDetails': string
|
||||
'common.accountOverview': string
|
||||
'common.cancel': string
|
||||
'common.delete': string
|
||||
'common.deleteConfirm': string
|
||||
'common.description': string
|
||||
'common.descriptionPlaceholder': string
|
||||
'common.edit': string
|
||||
'common.email': string
|
||||
'common.itemCreated': string
|
||||
'common.itemDeleted': string
|
||||
'common.itemUpdated': string
|
||||
'common.name': string
|
||||
AZ09: string
|
||||
ZA90: string
|
||||
action: string
|
||||
all: string
|
||||
apply: string
|
||||
back: string
|
||||
'banner.expired': string
|
||||
'banner.expiryCountdown': string
|
||||
cancel: string
|
||||
clearFilter: string
|
||||
'common.namePlaceholder': string
|
||||
'common.optionalField': string
|
||||
'common.save': string
|
||||
'common.tagsLabel': string
|
||||
'common.token': string
|
||||
created: string
|
||||
executions: string
|
||||
'common.policies': string
|
||||
'common.policiesSets.created': string
|
||||
'common.policiesSets.enforced': string
|
||||
'common.policiesSets.entity': string
|
||||
'common.policiesSets.evaluationCriteria': string
|
||||
'common.policiesSets.event': string
|
||||
'common.policiesSets.newPolicyset': string
|
||||
'common.policiesSets.noPolicySet': string
|
||||
'common.policiesSets.noPolicySetDescription': string
|
||||
'common.policiesSets.noPolicySetResult': string
|
||||
'common.policiesSets.noPolicySetTitle': string
|
||||
'common.policiesSets.noPolicySets': string
|
||||
'common.policiesSets.policySetSearch': string
|
||||
'common.policiesSets.scope': string
|
||||
'common.policiesSets.stepOne.validId': string
|
||||
'common.policiesSets.stepOne.validIdRegex': string
|
||||
'common.policiesSets.stepOne.validName': string
|
||||
'common.policiesSets.table.enforced': string
|
||||
'common.policiesSets.table.entityType': string
|
||||
'common.policiesSets.table.name': string
|
||||
'common.policiesSets.updated': string
|
||||
'common.policy.evaluations': string
|
||||
'common.policy.newPolicy': string
|
||||
'common.policy.noPolicy': string
|
||||
'common.policy.noPolicyEvalResult': string
|
||||
'common.policy.noPolicyEvalResultTitle': string
|
||||
'common.policy.noPolicyResult': string
|
||||
'common.policy.noPolicyTitle': string
|
||||
'common.policy.noSelectInput': string
|
||||
'common.policy.permission.noEdit': string
|
||||
'common.policy.policySearch': string
|
||||
'common.policy.policysets': string
|
||||
'common.policy.table.createdAt': string
|
||||
'common.policy.table.lastModified': string
|
||||
'common.policy.table.name': string
|
||||
confirm: string
|
||||
continue: string
|
||||
delete: string
|
||||
description: string
|
||||
descriptionPlaceholder: string
|
||||
details: string
|
||||
edit: string
|
||||
email: string
|
||||
entity: string
|
||||
'evaluation.evaluatedPoliciesCount': string
|
||||
'evaluation.onePolicyEvaluated': string
|
||||
executionsText: string
|
||||
existingAccount: string
|
||||
logout: string
|
||||
failed: string
|
||||
fileOverwrite: string
|
||||
finish: string
|
||||
'governance.clearOutput': string
|
||||
'governance.deleteConfirmation': string
|
||||
'governance.deleteDone': string
|
||||
'governance.deletePolicySetConfirmation': string
|
||||
'governance.deletePolicySetDone': string
|
||||
'governance.deletePolicySetTitle': string
|
||||
'governance.deleteTitle': string
|
||||
'governance.editPolicy': string
|
||||
'governance.editPolicyMetadataTitle': string
|
||||
'governance.emptyPolicySet': string
|
||||
'governance.evaluatedOn': string
|
||||
'governance.evaluatedTime': string
|
||||
'governance.evaluationEmpty': string
|
||||
'governance.evaluations': string
|
||||
'governance.event': string
|
||||
'governance.failureHeading': string
|
||||
'governance.failureHeadingEvaluationDetail': string
|
||||
'governance.failureModalTitle': string
|
||||
'governance.formatInput': string
|
||||
'governance.inputFailedEvaluation': string
|
||||
'governance.inputSuccededEvaluation': string
|
||||
'governance.noEvaluationForPipeline': string
|
||||
'governance.noPolicySetForPipeline': string
|
||||
'governance.onCreate': string
|
||||
'governance.onRun': string
|
||||
'governance.onSave': string
|
||||
'governance.onStep': string
|
||||
'governance.policyAccountCount': string
|
||||
'governance.policyDescription': string
|
||||
'governance.policyIdentifier': string
|
||||
'governance.policyName': string
|
||||
'governance.policyOrgCount': string
|
||||
'governance.policyProjectCount': string
|
||||
'governance.policySetGroup': string
|
||||
'governance.policySetGroupAccount': string
|
||||
'governance.policySetGroupOrg': string
|
||||
'governance.policySetGroupProject': string
|
||||
'governance.policySetName': string
|
||||
'governance.policySets': string
|
||||
'governance.policySetsApplied': string
|
||||
'governance.selectInput': string
|
||||
'governance.selectSamplePolicy': string
|
||||
'governance.successHeading': string
|
||||
'governance.viewPolicy': string
|
||||
'governance.warn': string
|
||||
'governance.warning': string
|
||||
'governance.warningHeading': string
|
||||
'governance.warningHeadingEvaluationDetail': string
|
||||
'governance.wizard.fieldArray': string
|
||||
'governance.wizard.policySelector.account': string
|
||||
'governance.wizard.policySelector.org': string
|
||||
'governance.wizard.policySelector.selectPolicy': string
|
||||
'governance.wizard.policyToEval': string
|
||||
input: string
|
||||
lastUpdated: string
|
||||
name: string
|
||||
navigationCheckText: string
|
||||
navigationCheckTitle: string
|
||||
no: string
|
||||
noAccount: string
|
||||
noSearchResultsFound: string
|
||||
optionalField: string
|
||||
outputLabel: string
|
||||
overview: string
|
||||
pageNotFound: string
|
||||
password: string
|
||||
pipelineSettings: string
|
||||
pipelines: string
|
||||
settings: string
|
||||
samplePolicies: string
|
||||
saveOverwrite: string
|
||||
search: string
|
||||
signIn: string
|
||||
signUp: string
|
||||
signin: string
|
||||
slug: string
|
||||
source: string
|
||||
status: string
|
||||
success: string
|
||||
tagsLabel: string
|
||||
type: string
|
||||
useSample: string
|
||||
'validation.identifierIsRequired': string
|
||||
'validation.identifierRequired': string
|
||||
'validation.nameRequired': string
|
||||
'validation.policySaveButtonMessage': string
|
||||
'validation.thisIsARequiredField': string
|
||||
'validation.validIdRegex': string
|
||||
yes: string
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
declare const __DEV__: boolean
|
||||
declare const __ON_PREM__: boolean
|
||||
declare const __ON_PREM__: booelan
|
||||
|
||||
declare module '*.png' {
|
||||
const value: string
|
||||
|
@ -45,7 +45,7 @@ declare module '*.gql' {
|
|||
declare interface Window {
|
||||
apiUrl: string
|
||||
bugsnagClient?: any
|
||||
APP_RUN_IN_STANDALONE_MODE?: boolean
|
||||
STRIP_SCM_PREFIX?: boolean
|
||||
}
|
||||
|
||||
declare const monaco: any
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import type { FeatureFlagMap } from '../utils/GovernanceUtils'
|
||||
|
||||
export function useStandaloneFeatureFlags(): FeatureFlagMap {
|
||||
return {
|
||||
OPA_PIPELINE_GOVERNANCE: true,
|
||||
OPA_FF_GOVERNANCE: false,
|
||||
CUSTOM_POLICY_STEP: false,
|
||||
OPA_GIT_GOVERNANCE: false,
|
||||
OPA_SECRET_GOVERNANCE: false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export function useStandalonePermission(_permissionsRequest?: any, _deps: Array<any> = []): Array<boolean> {
|
||||
return [true, true]
|
||||
}
|
|
@ -1,34 +1,159 @@
|
|||
signin: Sign In
|
||||
signUp: Sign Up
|
||||
logout: Logout
|
||||
password: Password
|
||||
pageNotFound: Page Not Found
|
||||
signIn: Sign In
|
||||
signUp: Sign Up
|
||||
email: Email
|
||||
password: Password
|
||||
noAccount: No Account
|
||||
existingAccount: Existing Account
|
||||
failed: Failed
|
||||
status: Status
|
||||
success: Success
|
||||
details: Details
|
||||
overview: Overview
|
||||
back: Back
|
||||
finish: Finish
|
||||
delete: Delete
|
||||
apply: Apply
|
||||
cancel: Cancel
|
||||
continue: Continue
|
||||
type: Type
|
||||
name: Name
|
||||
action: Action
|
||||
edit: Edit
|
||||
executionsText: Executions
|
||||
outputLabel: Output
|
||||
description: Description
|
||||
optionalField: '{{name}} (optional)'
|
||||
descriptionPlaceholder: Enter Description
|
||||
tagsLabel: Tags
|
||||
yes: Yes
|
||||
no: No
|
||||
source: Source
|
||||
common:
|
||||
save: Save
|
||||
edit: Edit
|
||||
name: Name
|
||||
email: Email
|
||||
namePlaceholder: Enter Name
|
||||
description: Description
|
||||
descriptionPlaceholder: Enter Description
|
||||
tagsLabel: Tags
|
||||
optionalField: '{{name}} (optional)'
|
||||
delete: Delete
|
||||
deleteConfirm: Are you sure you want to delete this?
|
||||
itemDeleted: Item Deleted
|
||||
itemUpdated: Item Updated
|
||||
itemCreated: Item Created
|
||||
cancel: Cancel
|
||||
accountDetails: Account Details
|
||||
accountOverview: Account Overview
|
||||
token: Token
|
||||
pipelines: Pipelines
|
||||
pipelineSettings: Pipeline Settings
|
||||
account: Account
|
||||
settings: Settings
|
||||
executions: Executions
|
||||
addExecution: Add New Execution
|
||||
slug: Slug
|
||||
created: Created
|
||||
noAccount: No account?
|
||||
existingAccount: Already have an account?
|
||||
policies: 'Policies'
|
||||
policy:
|
||||
policysets: Policy Sets
|
||||
newPolicy: New Policy
|
||||
evaluations: Evaluations
|
||||
policySearch: Search Policy by name
|
||||
noPolicy: A Harness policy is an OPA rule that can be enforced on your Harness software delivery processes to ensure governance and compliance.
|
||||
noPolicyTitle: You have no policies
|
||||
noPolicyResult: No policies found
|
||||
noPolicyEvalResultTitle: You have no policy evaluations
|
||||
noPolicyEvalResult: Policy evaluations are created when policy sets are enforced on your Harness entities.
|
||||
noSelectInput: Select appropriate options
|
||||
permission:
|
||||
noEdit: You do not have permission to edit a Policy
|
||||
table:
|
||||
name: Policy
|
||||
lastModified: Last Modified
|
||||
createdAt: Created At
|
||||
policiesSets:
|
||||
newPolicyset: New Policy Set
|
||||
noPolicySets: No Policy Sets evaluated
|
||||
evaluationCriteria: Policy evaluation criteria
|
||||
policySetSearch: Search Policy Set by name
|
||||
noPolicySetTitle: Create a Policy Set to apply Policies
|
||||
noPolicySet: A harness policy set allows you to group policies and configure where they will be enforced.
|
||||
noPolicySetResult: No Policy Sets found
|
||||
noPolicySetDescription: No Policy Set Description
|
||||
stepOne:
|
||||
validName: '{{$.validation.nameRequired}}'
|
||||
validId: '{{$.validation.identifierRequired}}'
|
||||
validIdRegex: '{{$.common.validation.formatMustBeAlphanumeric}}'
|
||||
table:
|
||||
name: Policy Set
|
||||
enforced: Enforced
|
||||
entityType: Entity Type {{name}}
|
||||
event: Event
|
||||
scope: Scope
|
||||
entity: Entity Type
|
||||
enforced: Enforced
|
||||
created: Created
|
||||
updated: Updated
|
||||
governance:
|
||||
policyAccountCount: Account ({{count}})
|
||||
policyOrgCount: Organization ({{count}})
|
||||
policyProjectCount: Project ({{count}})
|
||||
viewPolicy: View Policy
|
||||
editPolicy: Edit Policy
|
||||
editPolicyMetadataTitle: Policy Name
|
||||
formatInput: Format Input
|
||||
selectInput: Select Input
|
||||
clearOutput: Clear Output
|
||||
inputFailedEvaluation: Input failed Policy Evaluation
|
||||
inputSuccededEvaluation: Input succeeded Policy Evaluation
|
||||
warning: warning
|
||||
evaluatedTime: 'Evaluated {{time}}'
|
||||
failureHeading: Pipeline execution could not proceed due to the Policy Evaluation failures.
|
||||
warningHeading: Pipeline execution has Policy Evaluation warnings.
|
||||
failureHeadingEvaluationDetail: Policy Evaluation failed.
|
||||
warningHeadingEvaluationDetail: Policy Evaluation contains warnings.
|
||||
successHeading: All policies are passed.
|
||||
policySets: 'Policy Sets ({{count}})'
|
||||
evaluations: Evaluations {{count}}
|
||||
policySetName: 'Policy Set: {{name}}'
|
||||
emptyPolicySet: This Policy Set does not have any policies attached to it.
|
||||
failureModalTitle: Policy Set Evaluations
|
||||
policySetsApplied: '{{pipelineName}}: Policy Sets applied'
|
||||
warn: warning {{count}}
|
||||
event: Pipeline Event
|
||||
evaluatedOn: Evaluated On
|
||||
onRun: On Run
|
||||
onSave: On Save
|
||||
onCreate: On Create
|
||||
onStep: On Step
|
||||
policyName: 'Policy Name: {{name}}'
|
||||
policyIdentifier: 'Policy Identifier: {{policyIdentifier}}'
|
||||
policyDescription: 'Policy Desctiption: {{policyDescription}}'
|
||||
deleteTitle: Delete Policy
|
||||
deleteConfirmation: Are you sure you want to delete Policy "{{name}}"? This action cannot be undone.
|
||||
deleteDone: Policy "{{name}}" deleted.
|
||||
deletePolicySetTitle: Delete Policy Set
|
||||
deletePolicySetConfirmation: Are you sure you want to delete Policy Set "{{name}}"? This action cannot be undone.
|
||||
deletePolicySetDone: Policy Set "{{name}}" deleted.
|
||||
selectSamplePolicy: Select a Policy example
|
||||
evaluationEmpty: No Policy is linked for this evaluation.
|
||||
noPolicySetForPipeline: No Policy Set applied for this pipeline.
|
||||
noEvaluationForPipeline: No Evaluation found for this pipeline.
|
||||
wizard:
|
||||
policyToEval: Policy to Evaluate
|
||||
fieldArray: Applies to Pipeline on the following events
|
||||
policySelector:
|
||||
selectPolicy: Select Policy
|
||||
account: Account
|
||||
org: Org {{name}}
|
||||
policySetGroup: Policy Set Group
|
||||
policySetGroupAccount: Account {{name}}
|
||||
policySetGroupOrg: Organization {{name}}
|
||||
policySetGroupProject: Project {{name}}
|
||||
validation:
|
||||
identifierIsRequired: '{{$.validation.identifierRequired}}'
|
||||
validIdRegex: Identifier must start with an letter or _ and can then be followed by alphanumerics, _, or $
|
||||
thisIsARequiredField: This setting is required
|
||||
nameRequired: Name is required
|
||||
identifierRequired: Identifier is required
|
||||
policySaveButtonMessage: '{{type}} is required'
|
||||
lastUpdated: Last Updated
|
||||
AZ09: A - Z, 0 - 9
|
||||
ZA90: Z - A, 9 - 0
|
||||
evaluation:
|
||||
onePolicyEvaluated: 1 Policy Evaluated
|
||||
evaluatedPoliciesCount: '{{count}} Policies Evaluated'
|
||||
all: All
|
||||
entity: Entity
|
||||
fileOverwrite: File Overwrite
|
||||
saveOverwrite: Are you sure you want to overwrite this file? Your unsaved work will be lost.
|
||||
confirm: Confirm
|
||||
useSample: Use This Sample
|
||||
samplePolicies: Sample Policies
|
||||
search: Search
|
||||
input: Input
|
||||
navigationCheckText: 'You have unsaved changes. Are you sure you want to leave this page without saving?'
|
||||
navigationCheckTitle: 'Close without saving?'
|
||||
noSearchResultsFound: No search results found for '{{searchTerm}}'.
|
||||
clearFilter: Clear Filter
|
||||
banner:
|
||||
expired: expired {{ days }} days ago
|
||||
expiryCountdown: expires in {{ days }} days
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright 2021 Harness Inc. All rights reserved.
|
||||
* Use of this source code is governed by the PolyForm Shield 1.0.0 license
|
||||
* that can be found in the licenses directory at the root of this repository, also available at
|
||||
* https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
|
||||
**/
|
||||
// this is an auto-generated file, do not update this manually
|
||||
declare const styles: {
|
||||
readonly container: string
|
||||
readonly input: string
|
||||
readonly minWidth: string
|
||||
readonly root: string
|
||||
}
|
||||
export default styles
|
|
@ -1,153 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import {
|
||||
Container,
|
||||
Button,
|
||||
Formik,
|
||||
FormikForm,
|
||||
FormInput,
|
||||
Text,
|
||||
Color,
|
||||
Layout,
|
||||
ButtonVariation,
|
||||
Page,
|
||||
useToaster
|
||||
} from '@harness/uicore'
|
||||
import { useUpdateUser, useGetUser } from 'services/pm'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import routes from 'RouteDefinitions'
|
||||
|
||||
import styles from './Account.module.scss'
|
||||
|
||||
interface UserFormProps {
|
||||
name: string
|
||||
email: string
|
||||
password1: string
|
||||
password2: string
|
||||
}
|
||||
|
||||
export const Account = () => {
|
||||
const history = useHistory()
|
||||
const { getString } = useStrings()
|
||||
const { showSuccess, showError } = useToaster()
|
||||
const { data, loading, error, refetch } = useGetUser({})
|
||||
const { mutate } = useUpdateUser({})
|
||||
const [editDetails, setEditDetails] = useState(false)
|
||||
const [name, setName] = useState<string | undefined>('')
|
||||
const [email, setEmail] = useState<string | undefined>('')
|
||||
|
||||
useEffect(() => {
|
||||
setName(data?.name)
|
||||
setEmail(data?.email)
|
||||
}, [data])
|
||||
|
||||
if (error) {
|
||||
history.push(routes.toLogin())
|
||||
}
|
||||
|
||||
const updateUserDetails = async ({ email, name, password1 }: UserFormProps) => {
|
||||
try {
|
||||
await mutate({ email, name, password: password1 })
|
||||
showSuccess(getString('common.itemUpdated'))
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(`Error: ${err}`)
|
||||
console.error({ err })
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = (data: UserFormProps): void => {
|
||||
setEditDetails(false)
|
||||
updateUserDetails(data)
|
||||
}
|
||||
|
||||
const editUserForm = (
|
||||
<Formik<UserFormProps>
|
||||
initialValues={{ name: name as string, email: email as string, password1: '', password2: '' }}
|
||||
formName="newPipelineForm"
|
||||
onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'flex-start' }} margin={{ bottom: 'large' }}>
|
||||
<Text color={Color.GREY_600} className={styles.minWidth}>
|
||||
{getString('common.name')}
|
||||
</Text>
|
||||
<FormInput.Text name="name" className={styles.input} />
|
||||
</Layout.Horizontal>
|
||||
<Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'flex-start' }} margin={{ bottom: 'large' }}>
|
||||
<Text color={Color.GREY_600} className={styles.minWidth}>
|
||||
{getString('common.email')}
|
||||
</Text>
|
||||
<FormInput.Text name="email" className={styles.input} />
|
||||
</Layout.Horizontal>
|
||||
<Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'flex-start' }} margin={{ bottom: 'large' }}>
|
||||
<Text color={Color.GREY_600} className={styles.minWidth}>
|
||||
{getString('password')}
|
||||
</Text>
|
||||
<FormInput.Text
|
||||
name="password1"
|
||||
label="Password"
|
||||
inputGroup={{ type: 'password' }}
|
||||
className={styles.input}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name="password2"
|
||||
label="Re-type your Password"
|
||||
inputGroup={{ type: 'password' }}
|
||||
className={styles.input}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
<Button variation={ButtonVariation.LINK} icon="updated" text={getString('common.save')} type="submit" />
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
)
|
||||
|
||||
return (
|
||||
<Container className={styles.root} height="inherit">
|
||||
<Page.Header title={getString('common.accountOverview')} />
|
||||
<Page.Body
|
||||
loading={loading}
|
||||
retryOnError={() => refetch()}
|
||||
error={(error?.data as Error)?.message || error?.message}>
|
||||
<Container margin="xlarge" padding="xlarge" className={styles.container} background="white">
|
||||
<Text color={Color.BLACK} font={{ weight: 'semi-bold', size: 'medium' }} margin={{ bottom: 'xlarge' }}>
|
||||
{getString('common.accountDetails')}
|
||||
</Text>
|
||||
{editDetails ? (
|
||||
editUserForm
|
||||
) : (
|
||||
<>
|
||||
<Layout.Horizontal
|
||||
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}
|
||||
margin={{ bottom: 'large' }}>
|
||||
<Text color={Color.GREY_600} className={styles.minWidth}>
|
||||
{getString('common.name')}
|
||||
</Text>
|
||||
<Text color={Color.GREY_800}>{name}</Text>
|
||||
</Layout.Horizontal>
|
||||
<Layout.Horizontal
|
||||
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}
|
||||
margin={{ bottom: 'large' }}>
|
||||
<Text className={styles.minWidth}>{getString('common.email')}</Text>
|
||||
<Text color={Color.GREY_800}>{email}</Text>
|
||||
</Layout.Horizontal>
|
||||
<Layout.Horizontal
|
||||
flex={{ alignItems: 'center', justifyContent: 'flex-start' }}
|
||||
margin={{ bottom: 'large' }}>
|
||||
<Text className={styles.minWidth}>{getString('password')}</Text>
|
||||
<Text padding={{ right: 'small' }} color={Color.GREY_800}>
|
||||
*********
|
||||
</Text>
|
||||
</Layout.Horizontal>
|
||||
<Button
|
||||
variation={ButtonVariation.LINK}
|
||||
icon="Edit"
|
||||
text={getString('common.edit')}
|
||||
onClick={() => setEditDetails(true)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</Page.Body>
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
.minWidth {
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-bottom: unset !important;
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { useParams, useHistory } from 'react-router-dom'
|
||||
import { startCase, camelCase } from 'lodash'
|
||||
import { useToaster, useConfirmationDialog, Text, Color } from '@harness/uicore'
|
||||
import { Intent } from '@blueprintjs/core'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { useGetExecution, useDeleteExecution, useUpdateExecution } from 'services/pm'
|
||||
import { Settings } from '../../components/Settings/Settings'
|
||||
import routes from 'RouteDefinitions'
|
||||
|
||||
interface PathProps {
|
||||
pipeline: string
|
||||
execution: string
|
||||
}
|
||||
|
||||
interface ExecutionProps {
|
||||
name?: string
|
||||
desc?: string
|
||||
}
|
||||
|
||||
export const ExecutionSettings = () => {
|
||||
const history = useHistory()
|
||||
const { getString } = useStrings()
|
||||
const { showError, showSuccess } = useToaster()
|
||||
const { pipeline, execution } = useParams<PathProps>()
|
||||
|
||||
const [name, setName] = useState<string | undefined>('')
|
||||
const [desc, setDesc] = useState<string | undefined>('')
|
||||
|
||||
const { data, loading, error, refetch } = useGetExecution({ pipeline, execution })
|
||||
const { mutate: deleteExecution } = useDeleteExecution({ pipeline })
|
||||
const { mutate: updateExecution } = useUpdateExecution({ pipeline, execution })
|
||||
const title = `${startCase(camelCase(data?.name!.replace(/-/g, ' ')))} Settings`
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setName(data.name)
|
||||
setDesc(data.desc)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const handleUpdate = async ({ name, desc }: ExecutionProps) => {
|
||||
try {
|
||||
await updateExecution({ name, desc })
|
||||
showSuccess(getString('common.itemUpdated'))
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(`Error: ${err}`)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteExecution(execution)
|
||||
history.push(routes.toPipeline({ pipeline }))
|
||||
} catch (err) {
|
||||
showError(`Error: ${err}`)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const { openDialog } = useConfirmationDialog({
|
||||
titleText: getString('common.delete'),
|
||||
contentText: <Text color={Color.GREY_800}>Are you sure you want to delete this?</Text>,
|
||||
confirmButtonText: getString('common.delete'),
|
||||
cancelButtonText: getString('common.cancel'),
|
||||
intent: Intent.DANGER,
|
||||
buttonIntent: Intent.DANGER,
|
||||
onCloseDialog: async (isConfirmed: boolean) => {
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
await handleDelete()
|
||||
showSuccess(getString('common.itemDeleted'))
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(`Error: ${JSON.stringify(err)}`)
|
||||
console.error({ err })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleSubmit = (data: ExecutionProps): void => {
|
||||
handleUpdate(data)
|
||||
}
|
||||
|
||||
return (
|
||||
<Settings
|
||||
name={name}
|
||||
desc={desc}
|
||||
handleDelete={openDialog}
|
||||
loading={loading}
|
||||
handleSubmit={handleSubmit}
|
||||
refetch={refetch}
|
||||
title={title}
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 auto;
|
||||
|
||||
.filterTab {
|
||||
text-align: center;
|
||||
padding: 21px;
|
||||
border-bottom: 3px solid transparent;
|
||||
|
||||
&.selected {
|
||||
border-bottom-color: var(--primary-7);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: var(--spacing-large) var(--spacing-xlarge) !important;
|
||||
border-bottom: 1px solid var(--grey-200);
|
||||
background: var(--white) !important;
|
||||
|
||||
.headerLayout {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright 2021 Harness Inc. All rights reserved.
|
||||
* Use of this source code is governed by the PolyForm Shield 1.0.0 license
|
||||
* that can be found in the licenses directory at the root of this repository, also available at
|
||||
* https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
|
||||
**/
|
||||
// this is an auto-generated file, do not update this manually
|
||||
declare const styles: {
|
||||
readonly filterTab: string
|
||||
readonly header: string
|
||||
readonly headerLayout: string
|
||||
readonly root: string
|
||||
readonly selected: string
|
||||
}
|
||||
export default styles
|
|
@ -1,118 +0,0 @@
|
|||
import React from 'react'
|
||||
import { useParams, useHistory } from 'react-router-dom'
|
||||
import {
|
||||
Button,
|
||||
ButtonVariation,
|
||||
Container,
|
||||
Layout,
|
||||
Page,
|
||||
useModalHook,
|
||||
Formik,
|
||||
FormikForm,
|
||||
FormInput,
|
||||
useToaster
|
||||
} from '@harness/uicore'
|
||||
import { Dialog } from '@blueprintjs/core'
|
||||
import { useListExecutions, useCreateExecution, useDeleteExecution } from 'services/pm'
|
||||
import { startCase, camelCase } from 'lodash'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import Table from '../../components/Table/Table'
|
||||
import routes from 'RouteDefinitions'
|
||||
import styles from './Executions.module.scss'
|
||||
|
||||
export interface ExecutionsParams {
|
||||
pipeline: string
|
||||
}
|
||||
|
||||
interface ExecutionForm {
|
||||
name: string
|
||||
desc: string
|
||||
}
|
||||
|
||||
export const Executions: React.FC = () => {
|
||||
const history = useHistory()
|
||||
const { getString } = useStrings()
|
||||
const { showSuccess, showError } = useToaster()
|
||||
const { pipeline } = useParams<ExecutionsParams>()
|
||||
const { mutate: deleteExecution } = useDeleteExecution({ pipeline: pipeline })
|
||||
const { mutate: createExecution } = useCreateExecution({ pipeline })
|
||||
const { data: executionList, loading, error, refetch } = useListExecutions({ pipeline })
|
||||
|
||||
const title = `${startCase(camelCase(pipeline.replace(/-/g, ' ')))} ${getString('executions')}`
|
||||
|
||||
const handleCreate = async ({ name, desc }: ExecutionForm) => {
|
||||
try {
|
||||
await createExecution({ name, desc })
|
||||
showSuccess(getString('common.itemCreated'))
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(`Error: ${err}`)
|
||||
console.error({ error })
|
||||
}
|
||||
}
|
||||
|
||||
const modalProps = {
|
||||
isOpen: true,
|
||||
usePortal: true,
|
||||
autoFocus: true,
|
||||
canEscapeKeyClose: true,
|
||||
canOutsideClickClose: true,
|
||||
enforceFocus: true,
|
||||
title: getString('addExecution'),
|
||||
style: { width: 400, height: 300 }
|
||||
}
|
||||
|
||||
const handleSubmit = (data: ExecutionForm): void => {
|
||||
handleCreate(data)
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const onRowClick = (execution: string) => {
|
||||
history.push(routes.toPipelineExecutionSettings({ pipeline, execution }))
|
||||
}
|
||||
|
||||
const onSettingsClick = (execution: string) => {
|
||||
history.push(routes.toPipelineExecutionSettings({ pipeline, execution }))
|
||||
}
|
||||
|
||||
const [openModal, hideModal] = useModalHook(() => (
|
||||
<Dialog onClose={hideModal} {...modalProps}>
|
||||
<Container margin={{ top: 'large' }} flex={{ alignItems: 'center', justifyContent: 'space-around' }}>
|
||||
<Formik<ExecutionForm>
|
||||
initialValues={{ name: '', desc: '' }}
|
||||
formName="newExecutionForm"
|
||||
onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text name="name" label={getString('common.name')} />
|
||||
<FormInput.Text name="desc" label={getString('common.description')} />
|
||||
<Button type="submit" intent="primary" width="100%">
|
||||
Create
|
||||
</Button>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
</Container>
|
||||
</Dialog>
|
||||
))
|
||||
|
||||
return (
|
||||
<Container className={styles.root} height="inherit">
|
||||
<Page.Header title={title} />
|
||||
<Layout.Horizontal spacing="large" className={styles.header}>
|
||||
<Button variation={ButtonVariation.PRIMARY} text="New Execution" icon="plus" onClick={openModal} />
|
||||
<div style={{ flex: 1 }} />
|
||||
</Layout.Horizontal>
|
||||
<Page.Body
|
||||
loading={loading}
|
||||
retryOnError={() => refetch()}
|
||||
error={(error?.data as Error)?.message || error?.message}>
|
||||
<Table
|
||||
onRowClick={onRowClick}
|
||||
refetch={refetch}
|
||||
data={executionList}
|
||||
onDelete={deleteExecution}
|
||||
onSettingsClick={onSettingsClick}
|
||||
/>
|
||||
</Page.Body>
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -12,7 +12,7 @@ import {
|
|||
Layout,
|
||||
useToaster
|
||||
} from '@harness/uicore'
|
||||
import { get } from 'lodash'
|
||||
import { get } from 'lodash-es'
|
||||
import { useAPIToken } from 'hooks/useAPIToken'
|
||||
import { useOnLogin, useOnRegister } from 'services/pm'
|
||||
import { useStrings } from 'framework/strings'
|
||||
|
@ -43,23 +43,21 @@ export const Login: React.FC = () => {
|
|||
|
||||
if (pathname === '/login') {
|
||||
mutate(formData as unknown as void)
|
||||
.then(data => {
|
||||
setToken(get(data, 'access_token' as string))
|
||||
history.replace(routes.toPipelines())
|
||||
.then(_data => {
|
||||
setToken(get(_data, 'access_token' as string))
|
||||
history.replace(routes.toPolicyDashboard())
|
||||
})
|
||||
.catch(error => {
|
||||
showError(`Error: ${error}`)
|
||||
console.error({ error })
|
||||
})
|
||||
} else {
|
||||
mutateRegister(formData as unknown as void)
|
||||
.then(data => {
|
||||
setToken(get(data, 'access_token' as string))
|
||||
history.replace(routes.toPipelines())
|
||||
.then(_data => {
|
||||
setToken(get(_data, 'access_token' as string))
|
||||
history.replace(routes.toPolicyDashboard())
|
||||
})
|
||||
.catch(error => {
|
||||
showError(`Error: ${error}`)
|
||||
console.error({ error })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +74,7 @@ export const Login: React.FC = () => {
|
|||
<HarnessLogo height={25} />
|
||||
</Container>
|
||||
<Text font={{ size: 'large', weight: 'bold' }} color={Color.BLACK}>
|
||||
{pathname === '/login' ? getString('signin') : getString('signUp')}
|
||||
{pathname === '/login' ? getString('signIn') : getString('signUp')}
|
||||
</Text>
|
||||
<Text font={{ size: 'medium' }} color={Color.BLACK} margin={{ top: 'xsmall' }}>
|
||||
and get ship done.
|
||||
|
@ -88,10 +86,10 @@ export const Login: React.FC = () => {
|
|||
formName="loginPageForm"
|
||||
onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text name="email" label={getString('common.email')} />
|
||||
<FormInput.Text name="email" label={getString('email')} />
|
||||
<FormInput.Text name="password" label={getString('password')} inputGroup={{ type: 'password' }} />
|
||||
<Button type="submit" intent="primary" width="100%">
|
||||
{pathname === '/login' ? getString('signin') : getString('signUp')}
|
||||
{pathname === '/login' ? getString('signIn') : getString('signUp')}
|
||||
</Button>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
|
@ -99,8 +97,8 @@ export const Login: React.FC = () => {
|
|||
|
||||
<Layout.Horizontal margin={{ top: 'xxxlarge' }} spacing="xsmall">
|
||||
<Text>{pathname === '/login' ? getString('noAccount') : getString('existingAccount')}</Text>
|
||||
<Link to={pathname === '/login' ? routes.toRegister() : routes.toLogin()}>
|
||||
{pathname === '/login' ? getString('signUp') : getString('signin')}
|
||||
<Link to={pathname === '/login' ? routes.toRegister() : routes.toSignIn()}>
|
||||
{pathname === '/login' ? getString('signUp') : getString('signIn')}
|
||||
</Link>
|
||||
</Layout.Horizontal>
|
||||
</div>
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { useParams, useHistory } from 'react-router-dom'
|
||||
import { startCase, camelCase } from 'lodash'
|
||||
import { useToaster, useConfirmationDialog, Text, Color } from '@harness/uicore'
|
||||
import { Intent } from '@blueprintjs/core'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { useGetPipeline, useUpdatePipeline, useDeletePipeline } from 'services/pm'
|
||||
import { Settings } from '../../components/Settings/Settings'
|
||||
import routes from 'RouteDefinitions'
|
||||
|
||||
interface PathProps {
|
||||
pipeline: string
|
||||
}
|
||||
|
||||
interface PipelineProps {
|
||||
name?: string
|
||||
desc?: string
|
||||
}
|
||||
|
||||
export const PipelineSettings = () => {
|
||||
const history = useHistory()
|
||||
const { getString } = useStrings()
|
||||
const { showError, showSuccess } = useToaster()
|
||||
const { pipeline } = useParams<PathProps>()
|
||||
|
||||
const [name, setName] = useState<string | undefined>('')
|
||||
const [desc, setDesc] = useState<string | undefined>('')
|
||||
|
||||
const { data, loading, error, refetch } = useGetPipeline({ pipeline })
|
||||
const { mutate: updatePipeline } = useUpdatePipeline({ pipeline })
|
||||
const { mutate: deletePipeline } = useDeletePipeline({})
|
||||
const title = `${startCase(camelCase(data?.name!.replace(/-/g, ' ')))} Settings`
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setName(data.name)
|
||||
setDesc(data.desc)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const handleUpdate = async ({ name, desc }: PipelineProps) => {
|
||||
try {
|
||||
await updatePipeline({ name, desc })
|
||||
showSuccess(getString('common.itemUpdated'))
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(`Error: ${err}`)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deletePipeline(pipeline)
|
||||
history.push(routes.toPipelines())
|
||||
} catch (err) {
|
||||
showError(`Error: ${err}`)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const { openDialog } = useConfirmationDialog({
|
||||
titleText: getString('common.delete'),
|
||||
contentText: <Text color={Color.GREY_800}>Are you sure you want to delete this?</Text>,
|
||||
confirmButtonText: getString('common.delete'),
|
||||
cancelButtonText: getString('common.cancel'),
|
||||
intent: Intent.DANGER,
|
||||
buttonIntent: Intent.DANGER,
|
||||
onCloseDialog: async (isConfirmed: boolean) => {
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
await handleDelete()
|
||||
showSuccess(getString('common.itemDeleted'))
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(`Error: ${JSON.stringify(err)}`)
|
||||
console.error({ err })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleSubmit = (data: PipelineProps): void => {
|
||||
handleUpdate(data)
|
||||
}
|
||||
|
||||
return (
|
||||
<Settings
|
||||
name={name}
|
||||
desc={desc}
|
||||
handleDelete={openDialog}
|
||||
loading={loading}
|
||||
handleSubmit={handleSubmit}
|
||||
refetch={refetch}
|
||||
title={title}
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright 2021 Harness Inc. All rights reserved.
|
||||
* Use of this source code is governed by the PolyForm Shield 1.0.0 license
|
||||
* that can be found in the licenses directory at the root of this repository, also available at
|
||||
* https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
|
||||
**/
|
||||
// this is an auto-generated file, do not update this manually
|
||||
declare const styles: {
|
||||
readonly filterTab: string
|
||||
readonly header: string
|
||||
readonly headerLayout: string
|
||||
readonly root: string
|
||||
readonly selected: string
|
||||
}
|
||||
export default styles
|
|
@ -1,112 +0,0 @@
|
|||
import React from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import {
|
||||
Button,
|
||||
ButtonVariation,
|
||||
Container,
|
||||
Layout,
|
||||
Page,
|
||||
useModalHook,
|
||||
Formik,
|
||||
FormikForm,
|
||||
FormInput,
|
||||
useToaster
|
||||
} from '@harness/uicore'
|
||||
import { Dialog } from '@blueprintjs/core'
|
||||
import { useListPipelines, useCreatePipeline, useDeletePipeline } from 'services/pm'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import Table from '../../components/Table/Table'
|
||||
import routes from 'RouteDefinitions'
|
||||
|
||||
import styles from './Pipelines.module.scss'
|
||||
|
||||
interface PipelineForm {
|
||||
name: string
|
||||
desc: string
|
||||
}
|
||||
|
||||
export const Home: React.FC = () => {
|
||||
const { getString } = useStrings()
|
||||
const history = useHistory()
|
||||
const { showError, showSuccess } = useToaster()
|
||||
const { mutate: createPipeline } = useCreatePipeline({})
|
||||
const { mutate: deletePipeline } = useDeletePipeline({})
|
||||
const { data: pipelineList, loading, error, refetch } = useListPipelines({})
|
||||
const modalProps = {
|
||||
isOpen: true,
|
||||
usePortal: true,
|
||||
autoFocus: true,
|
||||
canEscapeKeyClose: true,
|
||||
canOutsideClickClose: true,
|
||||
enforceFocus: true,
|
||||
title: 'Add New Pipeline',
|
||||
style: { width: 400, height: 300 }
|
||||
}
|
||||
|
||||
const onRowClick = (pipeline: string) => {
|
||||
history.push(routes.toPipeline({ pipeline }))
|
||||
}
|
||||
|
||||
const onSettingsClick = (pipeline: string) => {
|
||||
history.push(routes.toPipelineSettings({ pipeline }))
|
||||
}
|
||||
|
||||
const [openModal, hideModal] = useModalHook(() => (
|
||||
<Dialog onClose={hideModal} {...modalProps}>
|
||||
<Container margin={{ top: 'large' }} flex={{ alignItems: 'center', justifyContent: 'space-around' }}>
|
||||
<Formik<PipelineForm> initialValues={{ name: '', desc: '' }} formName="newPipelineForm" onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text name="name" label={getString('common.name')} />
|
||||
<FormInput.Text name="desc" label={getString('common.description')} />
|
||||
<Button type="submit" intent="primary" width="100%">
|
||||
Create
|
||||
</Button>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
</Container>
|
||||
</Dialog>
|
||||
))
|
||||
|
||||
const handleCreate = async (data: PipelineForm) => {
|
||||
const { name, desc } = data
|
||||
try {
|
||||
await createPipeline({ name, desc })
|
||||
showSuccess(getString('common.itemCreated'))
|
||||
refetch()
|
||||
} catch (err) {
|
||||
showError(`Error: ${err}`)
|
||||
console.error({ err })
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = (data: PipelineForm): void => {
|
||||
handleCreate(data)
|
||||
hideModal()
|
||||
}
|
||||
|
||||
if (error) {
|
||||
history.push(routes.toLogin())
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className={styles.root} height="inherit">
|
||||
<Page.Header title={getString('pipelines')} />
|
||||
<Layout.Horizontal spacing="large" className={styles.header}>
|
||||
<Button variation={ButtonVariation.PRIMARY} text="New Pipeline" icon="plus" onClick={openModal} />
|
||||
<div style={{ flex: 1 }} />
|
||||
</Layout.Horizontal>
|
||||
<Page.Body
|
||||
loading={loading}
|
||||
retryOnError={() => refetch()}
|
||||
error={(error?.data as Error)?.message || error?.message}>
|
||||
<Table
|
||||
onRowClick={onRowClick}
|
||||
refetch={refetch}
|
||||
data={pipelineList}
|
||||
onDelete={deletePipeline}
|
||||
onSettingsClick={onSettingsClick}
|
||||
/>
|
||||
</Page.Body>
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 auto;
|
||||
|
||||
.filterTab {
|
||||
text-align: center;
|
||||
padding: 21px;
|
||||
border-bottom: 3px solid transparent;
|
||||
|
||||
&.selected {
|
||||
border-bottom-color: var(--primary-7);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: var(--spacing-large) var(--spacing-xlarge) !important;
|
||||
border-bottom: 1px solid var(--grey-200);
|
||||
background: var(--white) !important;
|
||||
|
||||
.headerLayout {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,94 +1,91 @@
|
|||
import React, { useRef, useState, useCallback } from "react";
|
||||
import React, { useRef, useState, useCallback } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import styles from "./Register.module.scss";
|
||||
import { useOnRegister } from 'services/pm'
|
||||
import routes from 'RouteDefinitions'
|
||||
|
||||
import Link from "../../components/Link/Link";
|
||||
import Input from "../../components/Input/input";
|
||||
import Button from "../../components/Button/button";
|
||||
import logo from "../../logo.svg"
|
||||
import Link from '../../components/Link/Link'
|
||||
import Input from '../../components/Input/input'
|
||||
import Button from '../../components/Button/button'
|
||||
import logo from '../../logo.svg'
|
||||
import styles from './Register.module.scss'
|
||||
|
||||
// Renders the Register page.
|
||||
export const Register = () => {
|
||||
const history = useHistory()
|
||||
const [error, setError] = useState(null);
|
||||
const [fullname, setFullname] = useState('')
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const { mutate } = useOnRegister({})
|
||||
const history = useHistory()
|
||||
const [error, setError] = useState(null)
|
||||
const [fullname, setFullname] = useState('')
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const { mutate } = useOnRegister({})
|
||||
|
||||
const handleRegister = useCallback(() => {
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append("fullname", fullname);
|
||||
formData.append("password", password);
|
||||
formData.append("username", username);
|
||||
|
||||
mutate(formData)
|
||||
.then(() => {
|
||||
history.replace(routes.toLogin())
|
||||
})
|
||||
.catch(error => {
|
||||
// TODO: Use toaster to show error
|
||||
// eslint-disable-next-line no-console
|
||||
console.error({ error })
|
||||
setError(error);
|
||||
})
|
||||
}, [mutate, username, password, fullname, history])
|
||||
const handleRegister = useCallback(() => {
|
||||
const formData = new FormData()
|
||||
|
||||
const alert =
|
||||
error && error.message ? (
|
||||
<div class="alert">{error.message}</div>
|
||||
) : undefined;
|
||||
formData.append('fullname', fullname)
|
||||
formData.append('password', password)
|
||||
formData.append('username', username)
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo} />
|
||||
</div>
|
||||
<h2>Sign up for a new account</h2>
|
||||
{alert}
|
||||
<div className={styles.field}>
|
||||
<label>Full Name</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="fullname"
|
||||
placeholder="Full Name"
|
||||
className={styles.input}
|
||||
onChange={e => setFullname(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label>Email</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="username"
|
||||
placeholder="Email"
|
||||
className={styles.input}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label>Password</label>
|
||||
<Input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
className={styles.input}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={handleRegister} className={styles.submit}>
|
||||
Sign Up
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<span>
|
||||
Already have an account? <Link href="/login">Sign In</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
mutate(formData)
|
||||
.then(() => {
|
||||
history.replace(routes.toLogin())
|
||||
})
|
||||
.catch(error => {
|
||||
// TODO: Use toaster to show error
|
||||
// eslint-disable-next-line no-console
|
||||
console.error({ error })
|
||||
setError(error)
|
||||
})
|
||||
}, [mutate, username, password, fullname, history])
|
||||
|
||||
const alert = error && error.message ? <div class="alert">{error.message}</div> : undefined
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo} />
|
||||
</div>
|
||||
<h2>Sign up for a new account</h2>
|
||||
{alert}
|
||||
<div className={styles.field}>
|
||||
<label>Full Name</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="fullname"
|
||||
placeholder="Full Name"
|
||||
className={styles.input}
|
||||
onChange={e => setFullname(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label>Email</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="username"
|
||||
placeholder="Email"
|
||||
className={styles.input}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label>Password</label>
|
||||
<Input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
className={styles.input}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={handleRegister} className={styles.submit}>
|
||||
Sign Up
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<span>
|
||||
Already have an account? <Link href="/login">Sign In</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ export const SignIn: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Layout.Vertical>
|
||||
<h1>{getString('signin')}</h1>
|
||||
<h1>{getString('signIn')}</h1>
|
||||
<Container>
|
||||
<Layout.Horizontal>
|
||||
<Text>Username</Text>
|
||||
|
@ -55,7 +55,7 @@ export const SignIn: React.FC = () => {
|
|||
</Layout.Horizontal>
|
||||
</Container>
|
||||
<Container>
|
||||
<Button text={getString('signin')} onClick={() => onLogin()} />
|
||||
<Button text={getString('signIn')} onClick={() => onLogin()} />
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
)
|
||||
|
|
|
@ -5,7 +5,7 @@ export const getConfig = (str: string): string => {
|
|||
// NOTE: Replace /^pm\// with your service prefixes when running in standalone mode
|
||||
// I.e: 'pm/api/v1' -> 'api/v1' (standalone)
|
||||
// -> 'pm/api/v1' (embedded inside Harness platform)
|
||||
if (window.APP_RUN_IN_STANDALONE_MODE) {
|
||||
if (window.STRIP_SCM_PREFIX) {
|
||||
str = str.replace(/^pm\//, '')
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
export enum Sort {
|
||||
DESC = 'DESC',
|
||||
ASC = 'ASC'
|
||||
}
|
||||
|
||||
export enum SortFields {
|
||||
LastUpdatedAt = 'updated',
|
||||
AZ09 = 'AZ09',
|
||||
ZA90 = 'ZA90',
|
||||
Name = 'name'
|
||||
}
|
|
@ -0,0 +1,442 @@
|
|||
import { Intent, IToaster, IToastProps, Position, Toaster } from '@blueprintjs/core'
|
||||
import type { editor as EDITOR } from 'monaco-editor/esm/vs/editor/editor.api'
|
||||
import { Color } from '@harness/uicore'
|
||||
import { get } from 'lodash-es'
|
||||
import moment from 'moment'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useEffect } from 'react'
|
||||
import type { StringsContextValue } from 'framework/strings/StringsContext'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { useStandaloneFeatureFlags } from '../hooks/useStandaloneFeatureFlags'
|
||||
|
||||
/** This utility shows a toaster without being bound to any component.
|
||||
* It's useful to show cross-page/component messages */
|
||||
export function showToaster(message: string, props?: Partial<IToastProps>): IToaster {
|
||||
const toaster = Toaster.create({ position: Position.TOP })
|
||||
toaster.show({ message, intent: Intent.SUCCESS, ...props })
|
||||
return toaster
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const getErrorMessage = (error: any): string =>
|
||||
get(error, 'data.error', get(error, 'data.message', error?.message))
|
||||
|
||||
export const MonacoEditorOptions = {
|
||||
ignoreTrimWhitespace: true,
|
||||
minimap: { enabled: false },
|
||||
codeLens: false,
|
||||
scrollBeyondLastLine: false,
|
||||
smartSelect: false,
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
overviewRulerBorder: false
|
||||
}
|
||||
|
||||
export const MonacoEditorJsonOptions = {
|
||||
...MonacoEditorOptions,
|
||||
tabSize: 2
|
||||
}
|
||||
|
||||
// Monaco editor has a bug where when its value is set, the value
|
||||
// is selected all by default.
|
||||
// Fix by set selection range to zero
|
||||
export const deselectAllMonacoEditor = (editor?: EDITOR.IStandaloneCodeEditor): void => {
|
||||
editor?.focus()
|
||||
setTimeout(() => {
|
||||
editor?.setSelection(new monaco.Selection(0, 0, 0, 0))
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export const ENTITIES = {
|
||||
pipeline: {
|
||||
label: 'Pipeline',
|
||||
value: 'pipeline',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'Pipeline Evaluation',
|
||||
value: 'evaluation'
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{ label: 'On Run', value: 'onrun' },
|
||||
{ label: 'On Save', value: 'onsave' }
|
||||
// {
|
||||
// label: 'On Step',
|
||||
// value: 'onstep',
|
||||
// enableAction: flags => {
|
||||
// return flags?.CUSTOM_POLICY_STEP
|
||||
// }
|
||||
// }
|
||||
],
|
||||
enabledFunc: flags => {
|
||||
return flags?.OPA_PIPELINE_GOVERNANCE
|
||||
}
|
||||
},
|
||||
flag: {
|
||||
label: 'Feature Flag',
|
||||
value: 'flag',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'Flag Evaluation',
|
||||
value: 'flag_evaluation'
|
||||
}
|
||||
],
|
||||
actions: [{ label: 'On Save', value: 'onsave' }],
|
||||
enabledFunc: flags => {
|
||||
return flags?.OPA_FF_GOVERNANCE
|
||||
}
|
||||
},
|
||||
connector: {
|
||||
label: 'Connector',
|
||||
value: 'connector',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'Connector Evaluation',
|
||||
value: 'connector_evaluation'
|
||||
}
|
||||
],
|
||||
actions: [{ label: 'On Save', value: 'onsave' }],
|
||||
enabledFunc: flags => {
|
||||
return flags?.OPA_CONNECTOR_GOVERNANCE
|
||||
}
|
||||
},
|
||||
secret: {
|
||||
label: 'Secret',
|
||||
value: 'secret',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'On Save',
|
||||
value: 'onsave'
|
||||
}
|
||||
],
|
||||
actions: [{ label: 'On Save', value: 'onsave' }],
|
||||
enabledFunc: flags => {
|
||||
return flags?.OPA_SECRET_GOVERNANCE
|
||||
}
|
||||
},
|
||||
custom: {
|
||||
label: 'Custom',
|
||||
value: 'custom',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'Custom Evaluation',
|
||||
value: 'custom_evaluation'
|
||||
}
|
||||
],
|
||||
actions: [{ label: 'On Step', value: 'onstep' }],
|
||||
enabledFunc: flags => {
|
||||
return flags?.CUSTOM_POLICY_STEP
|
||||
}
|
||||
}
|
||||
} as Entities
|
||||
|
||||
export const getEntityLabel = (entity: keyof Entities): string => {
|
||||
return ENTITIES[entity].label
|
||||
}
|
||||
|
||||
export function useEntities(): Entities {
|
||||
const {
|
||||
hooks: { useFeatureFlags = useStandaloneFeatureFlags }
|
||||
} = useAppContext()
|
||||
const flags = useFeatureFlags()
|
||||
const availableEntities = { ...ENTITIES }
|
||||
|
||||
for (const key in ENTITIES) {
|
||||
if (!ENTITIES[key as keyof Entities].enabledFunc(flags)) {
|
||||
delete availableEntities[key as keyof Entities]
|
||||
continue
|
||||
}
|
||||
|
||||
// temporary(?) feature flagging of actions
|
||||
availableEntities[key as keyof Entities].actions = availableEntities[key as keyof Entities].actions.filter(
|
||||
action => {
|
||||
return action.enableAction ? action.enableAction(flags) : true
|
||||
}
|
||||
)
|
||||
}
|
||||
return availableEntities
|
||||
}
|
||||
|
||||
export const getActionType = (type: string | undefined, action: string | undefined): string => {
|
||||
return ENTITIES[type as keyof Entities].actions.find(a => a.value === action)?.label || 'Unrecognised Action Type'
|
||||
}
|
||||
|
||||
export type FeatureFlagMap = Partial<Record<FeatureFlag, boolean>>
|
||||
|
||||
export enum FeatureFlag {
|
||||
OPA_PIPELINE_GOVERNANCE = 'OPA_PIPELINE_GOVERNANCE',
|
||||
OPA_FF_GOVERNANCE = 'OPA_FF_GOVERNANCE',
|
||||
CUSTOM_POLICY_STEP = 'CUSTOM_POLICY_STEP',
|
||||
OPA_CONNECTOR_GOVERNANCE = 'OPA_CONNECTOR_GOVERNANCE',
|
||||
OPA_GIT_GOVERNANCE = 'OPA_GIT_GOVERNANCE',
|
||||
OPA_SECRET_GOVERNANCE = 'OPA_SECRET_GOVERNANCE'
|
||||
}
|
||||
|
||||
export type Entity = {
|
||||
label: string
|
||||
value: string
|
||||
eventTypes: Event[]
|
||||
actions: Action[]
|
||||
enabledFunc: (flags: FeatureFlagMap) => boolean
|
||||
}
|
||||
|
||||
export type Event = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type Action = {
|
||||
label: string
|
||||
value: string
|
||||
enableAction?: (flags: FeatureFlagMap) => boolean
|
||||
}
|
||||
|
||||
export type Entities = {
|
||||
pipeline: Entity
|
||||
flag: Entity
|
||||
connector: Entity
|
||||
secret: Entity
|
||||
custom: Entity
|
||||
}
|
||||
|
||||
export enum EvaluationStatus {
|
||||
ERROR = 'error',
|
||||
PASS = 'pass',
|
||||
WARNING = 'warning'
|
||||
}
|
||||
|
||||
export const isEvaluationFailed = (status?: string): boolean =>
|
||||
status === EvaluationStatus.ERROR || status === EvaluationStatus.WARNING
|
||||
|
||||
export const LIST_FETCHING_PAGE_SIZE = 20
|
||||
|
||||
// TODO - we should try and drive all these from the ENTITIES const ^ as well
|
||||
// theres still a little duplication going on
|
||||
export enum PipeLineEvaluationEvent {
|
||||
ON_RUN = 'onrun',
|
||||
ON_SAVE = 'onsave',
|
||||
ON_CREATE = 'oncreate',
|
||||
ON_STEP = 'onstep'
|
||||
}
|
||||
|
||||
// TODO - we should try and drive all these from the ENTITIES const ^ as well
|
||||
// theres still a little duplication going on
|
||||
export enum PolicySetType {
|
||||
PIPELINE = 'pipeline',
|
||||
FEATURE_FLAGS = 'flag',
|
||||
CUSTOM = 'custom',
|
||||
CONNECTOR = 'connector'
|
||||
}
|
||||
|
||||
export const getEvaluationEventString = (
|
||||
getString: StringsContextValue['getString'],
|
||||
evaluation: PipeLineEvaluationEvent
|
||||
): string => {
|
||||
if (!getString) return ''
|
||||
|
||||
const evaluations = {
|
||||
onrun: getString('governance.onRun'),
|
||||
onsave: getString('governance.onSave'),
|
||||
oncreate: getString('governance.onCreate'),
|
||||
onstep: getString('governance.onStep')
|
||||
}
|
||||
|
||||
return evaluations[evaluation]
|
||||
}
|
||||
|
||||
export const getEvaluationNameString = (evaluationMetadata: string): string | undefined => {
|
||||
try {
|
||||
const entityMetadata = JSON.parse(decodeURIComponent(evaluationMetadata as string))
|
||||
if (entityMetadata.entityName) {
|
||||
return entityMetadata.entityName
|
||||
} else if (entityMetadata['pipelineName']) {
|
||||
return entityMetadata['pipelineName'] //temporary until pipelineName is not being used
|
||||
} else {
|
||||
return 'Unknown'
|
||||
}
|
||||
} catch {
|
||||
return 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
export const evaluationStatusToColor = (status: string): Color => {
|
||||
switch (status) {
|
||||
case EvaluationStatus.ERROR:
|
||||
return Color.ERROR
|
||||
case EvaluationStatus.WARNING:
|
||||
return Color.WARNING
|
||||
}
|
||||
|
||||
return Color.SUCCESS
|
||||
}
|
||||
|
||||
// @see https://github.com/drone/policy-mgmt/issues/270
|
||||
// export const QUERY_PARAM_VALUE_ALL = '*'
|
||||
|
||||
export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY hh:mm a'
|
||||
|
||||
interface SetPageNumberProps {
|
||||
setPage: (value: React.SetStateAction<number>) => void
|
||||
pageItemsCount?: number
|
||||
page: number
|
||||
}
|
||||
|
||||
export const setPageNumber = ({ setPage, pageItemsCount, page }: SetPageNumberProps): void => {
|
||||
if (pageItemsCount === 0 && page > 0) {
|
||||
setPage(page - 1)
|
||||
}
|
||||
}
|
||||
|
||||
export const ILLEGAL_IDENTIFIERS = [
|
||||
'or',
|
||||
'and',
|
||||
'eq',
|
||||
'ne',
|
||||
'lt',
|
||||
'gt',
|
||||
'le',
|
||||
'ge',
|
||||
'div',
|
||||
'mod',
|
||||
'not',
|
||||
'null',
|
||||
'true',
|
||||
'false',
|
||||
'new',
|
||||
'var',
|
||||
'return'
|
||||
]
|
||||
|
||||
export const REGO_MONACO_LANGUAGE_IDENTIFIER = 'rego'
|
||||
|
||||
export const omit = (originalObj = {}, keysToOmit: string[]) =>
|
||||
Object.fromEntries(Object.entries(originalObj).filter(([key]) => !keysToOmit.includes(key)))
|
||||
|
||||
export const displayDateTime = (value: number): string | null => {
|
||||
return value ? moment.unix(value / 1000).format(DEFAULT_DATE_FORMAT) : null
|
||||
}
|
||||
|
||||
export interface GitFilterScope {
|
||||
repo?: string
|
||||
branch?: GitBranchDTO['branchName']
|
||||
getDefaultFromOtherRepo?: boolean
|
||||
}
|
||||
|
||||
export interface GitFiltersProps {
|
||||
defaultValue?: GitFilterScope
|
||||
onChange: (value: GitFilterScope) => void
|
||||
className?: string
|
||||
branchSelectClassName?: string
|
||||
showRepoSelector?: boolean
|
||||
showBranchSelector?: boolean
|
||||
showBranchIcon?: boolean
|
||||
shouldAllowBranchSync?: boolean
|
||||
getDisabledOptionTitleText?: () => string
|
||||
}
|
||||
|
||||
export interface GitBranchDTO {
|
||||
branchName?: string
|
||||
branchSyncStatus?: 'SYNCED' | 'SYNCING' | 'UNSYNCED'
|
||||
}
|
||||
|
||||
type Module = 'cd' | 'cf' | 'ci' | undefined
|
||||
|
||||
export const useGetModuleQueryParam = (): Module => {
|
||||
const { projectIdentifier, module } = useParams<Record<string, string>>()
|
||||
return projectIdentifier ? (module as Module) : undefined
|
||||
}
|
||||
|
||||
export enum Editions {
|
||||
ENTERPRISE = 'ENTERPRISE',
|
||||
TEAM = 'TEAM',
|
||||
FREE = 'FREE',
|
||||
COMMUNITY = 'COMMUNITY'
|
||||
}
|
||||
|
||||
export interface License {
|
||||
accountIdentifier?: string
|
||||
createdAt?: number
|
||||
edition?: 'COMMUNITY' | 'FREE' | 'TEAM' | 'ENTERPRISE'
|
||||
expiryTime?: number
|
||||
id?: string
|
||||
lastModifiedAt?: number
|
||||
licenseType?: 'TRIAL' | 'PAID'
|
||||
moduleType?: 'CD' | 'CI' | 'CV' | 'CF' | 'CE' | 'STO' | 'CORE' | 'PMS' | 'TEMPLATESERVICE' | 'GOVERNANCE'
|
||||
premiumSupport?: boolean
|
||||
selfService?: boolean
|
||||
startTime?: number
|
||||
status?: 'ACTIVE' | 'DELETED' | 'EXPIRED'
|
||||
trialExtended?: boolean
|
||||
}
|
||||
|
||||
export interface LicenseInformation {
|
||||
[key: string]: License
|
||||
}
|
||||
|
||||
export const findEnterprisePaid = (licenseInformation: LicenseInformation): boolean => {
|
||||
return !!Object.values(licenseInformation).find(
|
||||
(license: License) => license.edition === Editions.ENTERPRISE && license.licenseType === 'PAID'
|
||||
)
|
||||
}
|
||||
|
||||
export const useAnyTrialLicense = (): boolean => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
|
||||
const hasEnterprisePaid = findEnterprisePaid(licenseInformation)
|
||||
if (hasEnterprisePaid) return false
|
||||
|
||||
const anyTrialEntitlements = Object.values(licenseInformation).find(
|
||||
(license: License) => license?.edition === Editions.ENTERPRISE && license?.licenseType === 'TRIAL'
|
||||
)
|
||||
|
||||
return !!anyTrialEntitlements
|
||||
}
|
||||
|
||||
export const useGetTrialInfo = (): any => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
|
||||
const hasEnterprisePaid = findEnterprisePaid(licenseInformation)
|
||||
if (hasEnterprisePaid) return
|
||||
|
||||
const allEntitlements = Object.keys(licenseInformation).map(module => {
|
||||
return licenseInformation[module]
|
||||
})
|
||||
|
||||
const trialEntitlement = allEntitlements
|
||||
.sort((a: License, b: License) => (b.expiryTime ?? 0) - (a.expiryTime ?? 0))
|
||||
.find((license: License) => license?.edition === Editions.ENTERPRISE && license?.licenseType === 'TRIAL')
|
||||
|
||||
return trialEntitlement
|
||||
}
|
||||
|
||||
export const useFindActiveEnterprise = (): boolean => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
return Object.values(licenseInformation).some(
|
||||
(license: License) => license.edition === Editions.ENTERPRISE && license.status === 'ACTIVE'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the target element to top when any dependency changes
|
||||
* @param {string} target Target element className selector
|
||||
* @param {array} dependencies Dependencies to watch
|
||||
* @returns {void}
|
||||
*/
|
||||
export const useScrollToTop = (target: string, dependencies: unknown[]): void => {
|
||||
useEffect(() => {
|
||||
const element = document.querySelector(`.${target}`)
|
||||
if (element) {
|
||||
element.scrollTop = 0
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dependencies])
|
||||
}
|
|
@ -0,0 +1,467 @@
|
|||
/* eslint-disable */
|
||||
export const REGO_FORMAT = {
|
||||
tokenPostfix: '.ruby',
|
||||
|
||||
keywords: [
|
||||
'__LINE__',
|
||||
'__ENCODING__',
|
||||
'__FILE__',
|
||||
'BEGIN',
|
||||
'END',
|
||||
'alias',
|
||||
'and',
|
||||
'begin',
|
||||
'break',
|
||||
'case',
|
||||
'class',
|
||||
'def',
|
||||
'defined?',
|
||||
'do',
|
||||
'else',
|
||||
'elsif',
|
||||
'end',
|
||||
'ensure',
|
||||
'for',
|
||||
'false',
|
||||
'if',
|
||||
'in',
|
||||
'module',
|
||||
'next',
|
||||
'nil',
|
||||
'not',
|
||||
'or',
|
||||
'redo',
|
||||
'rescue',
|
||||
'retry',
|
||||
'return',
|
||||
'self',
|
||||
'super',
|
||||
'then',
|
||||
'true',
|
||||
'undef',
|
||||
'unless',
|
||||
'until',
|
||||
'when',
|
||||
'while',
|
||||
'yield',
|
||||
'default',
|
||||
'not',
|
||||
'package',
|
||||
'import',
|
||||
'as',
|
||||
'with',
|
||||
'else',
|
||||
'some'
|
||||
],
|
||||
|
||||
keywordops: ['::', '..', '...', '?', ':', '=>'],
|
||||
|
||||
builtins: [
|
||||
'require',
|
||||
'public',
|
||||
'private',
|
||||
'include',
|
||||
'extend',
|
||||
'attr_reader',
|
||||
'protected',
|
||||
'private_class_method',
|
||||
'protected_class_method',
|
||||
'new'
|
||||
],
|
||||
|
||||
// these are closed by 'end' (if, while and until are handled separately)
|
||||
declarations: ['module', 'class', 'def', 'case', 'do', 'begin', 'for', 'if', 'while', 'until', 'unless'],
|
||||
|
||||
linedecls: ['def', 'case', 'do', 'begin', 'for', 'if', 'while', 'until', 'unless'],
|
||||
|
||||
operators: [
|
||||
'^',
|
||||
'&',
|
||||
'|',
|
||||
'<=>',
|
||||
'==',
|
||||
'===',
|
||||
'!~',
|
||||
'=~',
|
||||
'>',
|
||||
'>=',
|
||||
'<',
|
||||
'<=',
|
||||
'<<',
|
||||
'>>',
|
||||
'+',
|
||||
'-',
|
||||
'*',
|
||||
'/',
|
||||
'%',
|
||||
'**',
|
||||
'~',
|
||||
'+@',
|
||||
'-@',
|
||||
'[]',
|
||||
'[]=',
|
||||
'`',
|
||||
'+=',
|
||||
'-=',
|
||||
'*=',
|
||||
'**=',
|
||||
'/=',
|
||||
'^=',
|
||||
'%=',
|
||||
'<<=',
|
||||
'>>=',
|
||||
'&=',
|
||||
'&&=',
|
||||
'||=',
|
||||
'|='
|
||||
],
|
||||
|
||||
brackets: [
|
||||
{ open: '(', close: ')', token: 'delimiter.parenthesis' },
|
||||
{ open: '{', close: '}', token: 'delimiter.curly' },
|
||||
{ open: '[', close: ']', token: 'delimiter.square' }
|
||||
],
|
||||
|
||||
// we include these common regular expressions
|
||||
symbols: /[=><!~?:&|+\-*\/\^%\.]+/,
|
||||
|
||||
// escape sequences
|
||||
escape: /(?:[abefnrstv\\"'\n\r]|[0-7]{1,3}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4})/,
|
||||
escapes: /\\(?:C\-(@escape|.)|c(@escape|.)|@escape)/,
|
||||
|
||||
decpart: /\d(_?\d)*/,
|
||||
decimal: /0|@decpart/,
|
||||
|
||||
delim: /[^a-zA-Z0-9\s\n\r]/,
|
||||
heredelim: /(?:\w+|'[^']*'|"[^"]*"|`[^`]*`)/,
|
||||
|
||||
regexpctl: /[(){}\[\]\$\^|\-*+?\.]/,
|
||||
regexpesc: /\\(?:[AzZbBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})?/,
|
||||
|
||||
// The main tokenizer for our languages
|
||||
tokenizer: {
|
||||
// Main entry.
|
||||
// root.<decl> where decl is the current opening declaration (like 'class')
|
||||
root: [
|
||||
// identifiers and keywords
|
||||
// most complexity here is due to matching 'end' correctly with declarations.
|
||||
// We distinguish a declaration that comes first on a line, versus declarations further on a line (which are most likey modifiers)
|
||||
[
|
||||
/^(\s*)([a-z_]\w*[!?=]?)/,
|
||||
[
|
||||
'white',
|
||||
{
|
||||
cases: {
|
||||
'for|until|while': { token: 'keyword.$2', next: '@dodecl.$2' },
|
||||
'@declarations': { token: 'keyword.$2', next: '@root.$2' },
|
||||
end: { token: 'keyword.$S2', next: '@pop' },
|
||||
'@keywords': 'keyword',
|
||||
'@builtins': 'predefined',
|
||||
'@default': 'identifier'
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
/[a-z_]\w*[!?=]?/,
|
||||
{
|
||||
cases: {
|
||||
'if|unless|while|until': { token: 'keyword.$0x', next: '@modifier.$0x' },
|
||||
for: { token: 'keyword.$2', next: '@dodecl.$2' },
|
||||
'@linedecls': { token: 'keyword.$0', next: '@root.$0' },
|
||||
end: { token: 'keyword.$S2', next: '@pop' },
|
||||
'@keywords': 'keyword',
|
||||
'@builtins': 'predefined',
|
||||
'@default': 'identifier'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
[/[A-Z][\w]*[!?=]?/, 'constructor.identifier'], // constant
|
||||
[/\$[\w]*/, 'global.constant'], // global
|
||||
[/@[\w]*/, 'namespace.instance.identifier'], // instance
|
||||
[/@@[\w]*/, 'namespace.class.identifier'], // class
|
||||
|
||||
// here document
|
||||
[/<<[-~](@heredelim).*/, { token: 'string.heredoc.delimiter', next: '@heredoc.$1' }],
|
||||
[/[ \t\r\n]+<<(@heredelim).*/, { token: 'string.heredoc.delimiter', next: '@heredoc.$1' }],
|
||||
[/^<<(@heredelim).*/, { token: 'string.heredoc.delimiter', next: '@heredoc.$1' }],
|
||||
|
||||
// whitespace
|
||||
{ include: '@whitespace' },
|
||||
|
||||
// strings
|
||||
[/"/, { token: 'string.d.delim', next: '@dstring.d."' }],
|
||||
[/'/, { token: 'string.sq.delim', next: '@sstring.sq' }],
|
||||
|
||||
// % literals. For efficiency, rematch in the 'pstring' state
|
||||
[/%([rsqxwW]|Q?)/, { token: '@rematch', next: 'pstring' }],
|
||||
|
||||
// commands and symbols
|
||||
[/`/, { token: 'string.x.delim', next: '@dstring.x.`' }],
|
||||
[/:(\w|[$@])\w*[!?=]?/, 'string.s'],
|
||||
[/:"/, { token: 'string.s.delim', next: '@dstring.s."' }],
|
||||
[/:'/, { token: 'string.s.delim', next: '@sstring.s' }],
|
||||
|
||||
// regular expressions. Lookahead for a (not escaped) closing forwardslash on the same line
|
||||
[/\/(?=(\\\/|[^\/\n])+\/)/, { token: 'regexp.delim', next: '@regexp' }],
|
||||
|
||||
// delimiters and operators
|
||||
[/[{}()\[\]]/, '@brackets'],
|
||||
[
|
||||
/@symbols/,
|
||||
{
|
||||
cases: {
|
||||
'@keywordops': 'keyword',
|
||||
'@operators': 'operator',
|
||||
'@default': ''
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
[/[;,]/, 'delimiter'],
|
||||
|
||||
// numbers
|
||||
[/0[xX][0-9a-fA-F](_?[0-9a-fA-F])*/, 'number.hex'],
|
||||
[/0[_oO][0-7](_?[0-7])*/, 'number.octal'],
|
||||
[/0[bB][01](_?[01])*/, 'number.binary'],
|
||||
[/0[dD]@decpart/, 'number'],
|
||||
[
|
||||
/@decimal((\.@decpart)?([eE][\-+]?@decpart)?)/,
|
||||
{
|
||||
cases: {
|
||||
$1: 'number.float',
|
||||
'@default': 'number'
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
// used to not treat a 'do' as a block opener if it occurs on the same
|
||||
// line as a 'do' statement: 'while|until|for'
|
||||
// dodecl.<decl> where decl is the declarations started, like 'while'
|
||||
dodecl: [
|
||||
[/^/, { token: '', switchTo: '@root.$S2' }], // get out of do-skipping mode on a new line
|
||||
[
|
||||
/[a-z_]\w*[!?=]?/,
|
||||
{
|
||||
cases: {
|
||||
end: { token: 'keyword.$S2', next: '@pop' }, // end on same line
|
||||
do: { token: 'keyword', switchTo: '@root.$S2' }, // do on same line: not an open bracket here
|
||||
'@linedecls': { token: '@rematch', switchTo: '@root.$S2' }, // other declaration on same line: rematch
|
||||
'@keywords': 'keyword',
|
||||
'@builtins': 'predefined',
|
||||
'@default': 'identifier'
|
||||
}
|
||||
}
|
||||
],
|
||||
{ include: '@root' }
|
||||
],
|
||||
|
||||
// used to prevent potential modifiers ('if|until|while|unless') to match
|
||||
// with 'end' keywords.
|
||||
// modifier.<decl>x where decl is the declaration starter, like 'if'
|
||||
modifier: [
|
||||
[/^/, '', '@pop'], // it was a modifier: get out of modifier mode on a new line
|
||||
[
|
||||
/[a-z_]\w*[!?=]?/,
|
||||
{
|
||||
cases: {
|
||||
end: { token: 'keyword.$S2', next: '@pop' }, // end on same line
|
||||
'then|else|elsif|do': { token: 'keyword', switchTo: '@root.$S2' }, // real declaration and not a modifier
|
||||
'@linedecls': { token: '@rematch', switchTo: '@root.$S2' }, // other declaration => not a modifier
|
||||
'@keywords': 'keyword',
|
||||
'@builtins': 'predefined',
|
||||
'@default': 'identifier'
|
||||
}
|
||||
}
|
||||
],
|
||||
{ include: '@root' }
|
||||
],
|
||||
|
||||
// single quote strings (also used for symbols)
|
||||
// sstring.<kind> where kind is 'sq' (single quote) or 's' (symbol)
|
||||
sstring: [
|
||||
[/[^\\']+/, 'string.$S2'],
|
||||
[/\\\\|\\'|\\$/, 'string.$S2.escape'],
|
||||
[/\\./, 'string.$S2.invalid'],
|
||||
[/'/, { token: 'string.$S2.delim', next: '@pop' }]
|
||||
],
|
||||
|
||||
// double quoted "string".
|
||||
// dstring.<kind>.<delim> where kind is 'd' (double quoted), 'x' (command), or 's' (symbol)
|
||||
// and delim is the ending delimiter (" or `)
|
||||
dstring: [
|
||||
[/[^\\`"#]+/, 'string.$S2'],
|
||||
[/#/, 'string.$S2.escape', '@interpolated'],
|
||||
[/\\$/, 'string.$S2.escape'],
|
||||
[/@escapes/, 'string.$S2.escape'],
|
||||
[/\\./, 'string.$S2.escape.invalid'],
|
||||
[
|
||||
/[`"]/,
|
||||
{
|
||||
cases: {
|
||||
'$#==$S3': { token: 'string.$S2.delim', next: '@pop' },
|
||||
'@default': 'string.$S2'
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
// literal documents
|
||||
// heredoc.<close> where close is the closing delimiter
|
||||
heredoc: [
|
||||
[
|
||||
/^(\s*)(@heredelim)$/,
|
||||
{
|
||||
cases: {
|
||||
'$2==$S2': ['string.heredoc', { token: 'string.heredoc.delimiter', next: '@pop' }],
|
||||
'@default': ['string.heredoc', 'string.heredoc']
|
||||
}
|
||||
}
|
||||
],
|
||||
[/.*/, 'string.heredoc']
|
||||
],
|
||||
|
||||
// interpolated sequence
|
||||
interpolated: [
|
||||
[/\$\w*/, 'global.constant', '@pop'],
|
||||
[/@\w*/, 'namespace.class.identifier', '@pop'],
|
||||
[/@@\w*/, 'namespace.instance.identifier', '@pop'],
|
||||
[/[{]/, { token: 'string.escape.curly', switchTo: '@interpolated_compound' }],
|
||||
['', '', '@pop'] // just a # is interpreted as a #
|
||||
],
|
||||
|
||||
// any code
|
||||
interpolated_compound: [[/[}]/, { token: 'string.escape.curly', next: '@pop' }], { include: '@root' }],
|
||||
|
||||
// %r quoted regexp
|
||||
// pregexp.<open>.<close> where open/close are the open/close delimiter
|
||||
pregexp: [
|
||||
{ include: '@whitespace' },
|
||||
// turns out that you can quote using regex control characters, aargh!
|
||||
// for example; %r|kgjgaj| is ok (even though | is used for alternation)
|
||||
// so, we need to match those first
|
||||
[
|
||||
/[^\(\{\[\\]/,
|
||||
{
|
||||
cases: {
|
||||
'$#==$S3': { token: 'regexp.delim', next: '@pop' },
|
||||
'$#==$S2': { token: 'regexp.delim', next: '@push' }, // nested delimiters are allowed..
|
||||
'~[)}\\]]': '@brackets.regexp.escape.control',
|
||||
'~@regexpctl': 'regexp.escape.control',
|
||||
'@default': 'regexp'
|
||||
}
|
||||
}
|
||||
],
|
||||
{ include: '@regexcontrol' }
|
||||
],
|
||||
|
||||
// We match regular expression quite precisely
|
||||
regexp: [{ include: '@regexcontrol' }, [/[^\\\/]/, 'regexp'], ['/[ixmp]*', { token: 'regexp.delim' }, '@pop']],
|
||||
|
||||
regexcontrol: [
|
||||
[
|
||||
/(\{)(\d+(?:,\d*)?)(\})/,
|
||||
['@brackets.regexp.escape.control', 'regexp.escape.control', '@brackets.regexp.escape.control']
|
||||
],
|
||||
[/(\[)(\^?)/, ['@brackets.regexp.escape.control', { token: 'regexp.escape.control', next: '@regexrange' }]],
|
||||
[/(\()(\?[:=!])/, ['@brackets.regexp.escape.control', 'regexp.escape.control']],
|
||||
[/\(\?#/, { token: 'regexp.escape.control', next: '@regexpcomment' }],
|
||||
[/[()]/, '@brackets.regexp.escape.control'],
|
||||
[/@regexpctl/, 'regexp.escape.control'],
|
||||
[/\\$/, 'regexp.escape'],
|
||||
[/@regexpesc/, 'regexp.escape'],
|
||||
[/\\\./, 'regexp.invalid'],
|
||||
[/#/, 'regexp.escape', '@interpolated']
|
||||
],
|
||||
|
||||
regexrange: [
|
||||
[/-/, 'regexp.escape.control'],
|
||||
[/\^/, 'regexp.invalid'],
|
||||
[/\\$/, 'regexp.escape'],
|
||||
[/@regexpesc/, 'regexp.escape'],
|
||||
[/[^\]]/, 'regexp'],
|
||||
[/\]/, '@brackets.regexp.escape.control', '@pop']
|
||||
],
|
||||
|
||||
regexpcomment: [
|
||||
[/[^)]+/, 'comment'],
|
||||
[/\)/, { token: 'regexp.escape.control', next: '@pop' }]
|
||||
],
|
||||
|
||||
// % quoted strings
|
||||
// A bit repetitive since we need to often special case the kind of ending delimiter
|
||||
pstring: [
|
||||
[/%([qws])\(/, { token: 'string.$1.delim', switchTo: '@qstring.$1.(.)' }],
|
||||
[/%([qws])\[/, { token: 'string.$1.delim', switchTo: '@qstring.$1.[.]' }],
|
||||
[/%([qws])\{/, { token: 'string.$1.delim', switchTo: '@qstring.$1.{.}' }],
|
||||
[/%([qws])</, { token: 'string.$1.delim', switchTo: '@qstring.$1.<.>' }],
|
||||
[/%([qws])(@delim)/, { token: 'string.$1.delim', switchTo: '@qstring.$1.$2.$2' }],
|
||||
|
||||
[/%r\(/, { token: 'regexp.delim', switchTo: '@pregexp.(.)' }],
|
||||
[/%r\[/, { token: 'regexp.delim', switchTo: '@pregexp.[.]' }],
|
||||
[/%r\{/, { token: 'regexp.delim', switchTo: '@pregexp.{.}' }],
|
||||
[/%r</, { token: 'regexp.delim', switchTo: '@pregexp.<.>' }],
|
||||
[/%r(@delim)/, { token: 'regexp.delim', switchTo: '@pregexp.$1.$1' }],
|
||||
|
||||
[/%(x|W|Q?)\(/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.(.)' }],
|
||||
[/%(x|W|Q?)\[/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.[.]' }],
|
||||
[/%(x|W|Q?)\{/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.{.}' }],
|
||||
[/%(x|W|Q?)</, { token: 'string.$1.delim', switchTo: '@qqstring.$1.<.>' }],
|
||||
[/%(x|W|Q?)(@delim)/, { token: 'string.$1.delim', switchTo: '@qqstring.$1.$2.$2' }],
|
||||
|
||||
[/%([rqwsxW]|Q?)./, { token: 'invalid', next: '@pop' }], // recover
|
||||
[/./, { token: 'invalid', next: '@pop' }] // recover
|
||||
],
|
||||
|
||||
// non-expanded quoted string.
|
||||
// qstring.<kind>.<open>.<close>
|
||||
// kind = q|w|s (single quote, array, symbol)
|
||||
// open = open delimiter
|
||||
// close = close delimiter
|
||||
qstring: [
|
||||
[/\\$/, 'string.$S2.escape'],
|
||||
[/\\./, 'string.$S2.escape'],
|
||||
[
|
||||
/./,
|
||||
{
|
||||
cases: {
|
||||
'$#==$S4': { token: 'string.$S2.delim', next: '@pop' },
|
||||
'$#==$S3': { token: 'string.$S2.delim', next: '@push' }, // nested delimiters are allowed..
|
||||
'@default': 'string.$S2'
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
// expanded quoted string.
|
||||
// qqstring.<kind>.<open>.<close>
|
||||
// kind = Q|W|x (double quote, array, command)
|
||||
// open = open delimiter
|
||||
// close = close delimiter
|
||||
qqstring: [[/#/, 'string.$S2.escape', '@interpolated'], { include: '@qstring' }],
|
||||
|
||||
// whitespace & comments
|
||||
whitespace: [
|
||||
[/[ \t\r\n]+/, ''],
|
||||
[/^\s*=begin\b/, 'comment', '@comment'],
|
||||
[/#.*$/, 'comment']
|
||||
],
|
||||
|
||||
comment: [
|
||||
[/[^=]+/, 'comment'],
|
||||
[/^\s*=begin\b/, 'comment.invalid'], // nested comment
|
||||
[/^\s*=end\b.*/, 'comment', '@pop'],
|
||||
[/[=]/, 'comment']
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export const REGO_THEME = {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
colors: {
|
||||
'editor.background': '#4F5162'
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import { queryByAttribute } from '@testing-library/react'
|
|||
import { compile } from 'path-to-regexp'
|
||||
import { createMemoryHistory } from 'history'
|
||||
import { Router, Route, Switch, useLocation, useHistory } from 'react-router-dom'
|
||||
import { ModalProvider } from '@harness/uicore'
|
||||
import { ModalProvider } from '@harness/use-modal'
|
||||
import qs from 'qs'
|
||||
import { enableMapSet } from 'immer'
|
||||
import { StringsContext } from 'framework/strings'
|
||||
|
|
11152
web/yarn.lock
11152
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue