mirror of
https://github.com/harness/drone.git
synced 2025-05-04 20:23:25 +08:00
Sync UI template with latest updates
This commit is contained in:
parent
b5e426a177
commit
66fea2a730
48
web/config/moduleFederation.config.js
Normal file
48
web/config/moduleFederation.config.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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',
|
||||||
|
'./EvaluationModal': './src/modals/EvaluationModal/EvaluationModal.tsx',
|
||||||
|
'./PipelineGovernanceView': './src/views/PipelineGovernanceView/PipelineGovernanceView.tsx',
|
||||||
|
'./EvaluationView': './src/views/EvaluationView/EvaluationView.tsx',
|
||||||
|
'./PolicySetWizard': './src/pages/PolicySets/components/PolicySetWizard.tsx'
|
||||||
|
},
|
||||||
|
shared: {
|
||||||
|
formik: packageJSON.dependencies['formik'],
|
||||||
|
...mapValues(pick(packageJSON.dependencies, ExactSharedPackages), version => ({
|
||||||
|
singleton: true,
|
||||||
|
requiredVersion: version
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
168
web/config/webpack.common.js
Normal file
168
web/config/webpack.common.js
Normal file
@ -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
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
};
|
75
web/config/webpack.dev.js
Normal file
75
web/config/webpack.dev.js
Normal file
@ -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);
|
52
web/config/webpack.prod.js
Normal file
52
web/config/webpack.prod.js
Normal file
@ -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);
|
8
web/cypress/integration/dashboard.spec.js
Normal file
8
web/cypress/integration/dashboard.spec.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
describe('dashboard', () => {
|
||||||
|
it('load the dashboard', () => {
|
||||||
|
cy.visit('/')
|
||||||
|
cy.contains('In Effect')
|
||||||
|
cy.contains('Policy Evaluations')
|
||||||
|
cy.contains('Failures Recorded')
|
||||||
|
})
|
||||||
|
})
|
9
web/cypress/integration/evaluations.spec.js
Normal file
9
web/cypress/integration/evaluations.spec.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// disabling at the moment because of logic around Evaluations tab being removed in standalone mode (account in NG equivalent)
|
||||||
|
|
||||||
|
// describe('evaluations', () => {
|
||||||
|
// it('load the table', () => {
|
||||||
|
// cy.visit('/')
|
||||||
|
// cy.contains('Evaluations').click()
|
||||||
|
// cy.contains('Policy evaluations are created when policy sets are enforced on your Harness entities.')
|
||||||
|
// })
|
||||||
|
// })
|
7
web/cypress/integration/policy.spec.js
Normal file
7
web/cypress/integration/policy.spec.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
describe('policies', () => {
|
||||||
|
it('load the table', () => {
|
||||||
|
cy.visit('/')
|
||||||
|
cy.contains('Policies').click()
|
||||||
|
cy.get('[class="TableV2--row TableV2--card TableV2--clickable"]').should('have.length', 12)
|
||||||
|
})
|
||||||
|
})
|
7
web/cypress/integration/policyset.spec.js
Normal file
7
web/cypress/integration/policyset.spec.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
describe('policy sets', () => {
|
||||||
|
it('load the table', () => {
|
||||||
|
cy.visit('/')
|
||||||
|
cy.contains('Policy Set').click()
|
||||||
|
cy.contains('A harness policy set allows you to group policies and configure where they will be enforced.')
|
||||||
|
})
|
||||||
|
})
|
BIN
web/cypress/videos/dashboard.spec.js.mp4
Normal file
BIN
web/cypress/videos/dashboard.spec.js.mp4
Normal file
Binary file not shown.
BIN
web/cypress/videos/evaluations.spec.js.mp4
Normal file
BIN
web/cypress/videos/evaluations.spec.js.mp4
Normal file
Binary file not shown.
BIN
web/cypress/videos/policy.spec.js.mp4
Normal file
BIN
web/cypress/videos/policy.spec.js.mp4
Normal file
Binary file not shown.
BIN
web/cypress/videos/policyset.spec.js.mp4
Normal file
BIN
web/cypress/videos/policyset.spec.js.mp4
Normal file
Binary file not shown.
35
web/dist.go
35
web/dist.go
@ -10,40 +10,7 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed dist/*
|
//go:embed dist/*
|
||||||
var content embed.FS
|
var UI 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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
135
web/package.json
135
web/package.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "sample-module",
|
"name": "ui-template",
|
||||||
"description": "Harness Inc",
|
"description": "Harness Inc",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"author": "Harness Inc",
|
"author": "Harness Inc",
|
||||||
@ -8,14 +8,14 @@
|
|||||||
"homepage": "http://harness.io/",
|
"homepage": "http://harness.io/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/drone/sample-module.git"
|
"url": "https://github.com/wings-software/ui-template.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/sample-module/sample-module/issues"
|
"url": "https://github.com/wings-software/ui-template/issues"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "NODE_ENV=development webpack serve --progress",
|
"dev": "webpack serve --config config/webpack.dev.js",
|
||||||
"test": "jest src --silent",
|
"test": "jest src --silent",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"lint": "eslint --rulesdir ./scripts/eslint-rules --ext .ts --ext .tsx src",
|
"lint": "eslint --rulesdir ./scripts/eslint-rules --ext .ts --ext .tsx src",
|
||||||
@ -24,60 +24,130 @@
|
|||||||
"services": "npm-run-all services:*",
|
"services": "npm-run-all services:*",
|
||||||
"services:pm": "restful-react import --config restful-react.config.js pm",
|
"services:pm": "restful-react import --config restful-react.config.js pm",
|
||||||
"postservices": "prettier --write src/services/**/*.tsx",
|
"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",
|
"coverage": "npm test --coverage",
|
||||||
"setup-github-registry": "sh scripts/setup-github-registry.sh",
|
"setup-github-registry": "sh scripts/setup-github-registry.sh",
|
||||||
"strings": "npm-run-all strings:*",
|
"strings": "npm-run-all strings:*",
|
||||||
"strings:genTypes": "node scripts/strings/generateTypesCli.mjs",
|
"strings:genTypes": "node scripts/strings/generateTypesCli.mjs",
|
||||||
"fmt": "prettier --write \"./src/**/*.{ts,tsx,css,scss}\"",
|
"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",
|
"checks": "npm run typecheck; npm run lint; npm run test"
|
||||||
"micro:build": "webpack --mode production",
|
|
||||||
"micro:serve": "serve ./dist -l 3000"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blueprintjs/core": "3.26.1",
|
"@blueprintjs/core": "3.26.1",
|
||||||
"@blueprintjs/datetime": "3.13.0",
|
"@blueprintjs/datetime": "3.13.0",
|
||||||
"@blueprintjs/select": "3.12.3",
|
"@blueprintjs/select": "3.12.3",
|
||||||
"@harness/uicore": "^1.23.0",
|
"@emotion/core": "^10.0.28",
|
||||||
"anser": "^2.1.0",
|
"@emotion/styled": "^10.0.27",
|
||||||
"classnames": "^2.3.1",
|
"@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",
|
"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",
|
"immer": "^9.0.6",
|
||||||
|
"jsonc-parser": "^2.0.2",
|
||||||
"lodash-es": "^4.17.15",
|
"lodash-es": "^4.17.15",
|
||||||
"marked": "^3.0.8",
|
"marked": "^4.0.12",
|
||||||
"masonry-layout": "^4.2.2",
|
"masonry-layout": "^4.2.2",
|
||||||
|
"ml-matrix": "^6.5.0",
|
||||||
"moment": "^2.25.3",
|
"moment": "^2.25.3",
|
||||||
|
"moment-range": "^4.0.2",
|
||||||
"monaco-editor": "^0.19.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",
|
"qs": "^6.9.4",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
"react-beautiful-dnd": "^13.0.0",
|
||||||
|
"react-contenteditable": "^3.3.5",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-draggable": "^4.4.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": "^7.1.0",
|
||||||
|
"react-table-sticky": "^1.1.3",
|
||||||
|
"react-timeago": "^4.4.0",
|
||||||
|
"react-virtuoso": "^1.10.2",
|
||||||
"restful-react": "15.6.0",
|
"restful-react": "15.6.0",
|
||||||
"swr": "^0.5.4",
|
"secure-web-storage": "^1.0.2",
|
||||||
"yaml": "^1.10.0"
|
"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": {
|
"devDependencies": {
|
||||||
"@harness/css-types-loader": "^3.1.0",
|
"@babel/core": "^7.13.15",
|
||||||
"@harness/jarvis": "0.12.0",
|
"@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/jest-dom": "^5.12.0",
|
||||||
"@testing-library/react": "^10.0.3",
|
"@testing-library/react": "^10.0.3",
|
||||||
"@testing-library/react-hooks": "5",
|
"@testing-library/react-hooks": "5",
|
||||||
"@testing-library/user-event": "^10.3.1",
|
"@testing-library/user-event": "^10.3.1",
|
||||||
"@types/classnames": "^2.2.10",
|
"@types/classnames": "^2.2.10",
|
||||||
|
"@types/jest": "^26.0.15",
|
||||||
"@types/lodash-es": "^4.17.3",
|
"@types/lodash-es": "^4.17.3",
|
||||||
|
"@types/masonry-layout": "^4.2.1",
|
||||||
"@types/mustache": "^4.0.1",
|
"@types/mustache": "^4.0.1",
|
||||||
|
"@types/node": "^16.4.10",
|
||||||
"@types/path-to-regexp": "^1.7.0",
|
"@types/path-to-regexp": "^1.7.0",
|
||||||
"@types/qs": "^6.9.4",
|
"@types/qs": "^6.9.4",
|
||||||
|
"@types/query-string": "^6.3.0",
|
||||||
"@types/react": "^17.0.3",
|
"@types/react": "^17.0.3",
|
||||||
|
"@types/react-beautiful-dnd": "^13.0.0",
|
||||||
"@types/react-dom": "^17.0.3",
|
"@types/react-dom": "^17.0.3",
|
||||||
|
"@types/react-monaco-editor": "^0.16.0",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"@types/react-table": "^7.0.18",
|
"@types/react-table": "^7.0.18",
|
||||||
|
"@types/react-timeago": "^4.1.1",
|
||||||
"@types/testing-library__react-hooks": "^3.2.0",
|
"@types/testing-library__react-hooks": "^3.2.0",
|
||||||
|
"@types/testing-library__user-event": "^4.1.1",
|
||||||
|
"@types/uuid": "^8.3.0",
|
||||||
|
"@types/yup": "^0.29.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||||
"@typescript-eslint/parser": "^4.22.0",
|
"@typescript-eslint/parser": "^4.22.0",
|
||||||
|
"@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",
|
"case": "^1.6.3",
|
||||||
"circular-dependency-plugin": "^5.2.2",
|
"circular-dependency-plugin": "^5.2.2",
|
||||||
"css-loader": "^6.3.0",
|
"css-loader": "^6.3.0",
|
||||||
@ -89,12 +159,23 @@
|
|||||||
"eslint-plugin-jest": "^24.3.6",
|
"eslint-plugin-jest": "^24.3.6",
|
||||||
"eslint-plugin-react": "^7.23.2",
|
"eslint-plugin-react": "^7.23.2",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"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",
|
"fast-json-stable-stringify": "^2.1.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.2.1",
|
"fork-ts-checker-webpack-plugin": "^6.2.1",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
|
"graphql": "^15.5.0",
|
||||||
"html-webpack-plugin": "^5.3.1",
|
"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": "^26.2.0",
|
||||||
|
"jest-canvas-mock": "^2.3.0",
|
||||||
|
"jest-junit": "^12.0.0",
|
||||||
"lighthouse": "^6.5.0",
|
"lighthouse": "^6.5.0",
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
"mini-css-extract-plugin": "^2.4.2",
|
"mini-css-extract-plugin": "^2.4.2",
|
||||||
@ -102,22 +183,30 @@
|
|||||||
"mustache": "^4.0.1",
|
"mustache": "^4.0.1",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"npm-run-all": "^4.1.5",
|
"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",
|
"path-to-regexp": "^6.1.0",
|
||||||
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"react-test-renderer": "^17.0.2",
|
"react-test-renderer": "^17.0.2",
|
||||||
"sass": "^1.32.8",
|
"sass": "^1.32.8",
|
||||||
"sass-loader": "^12.1.0",
|
"sass-loader": "^12.1.0",
|
||||||
"serve": "^13.0.2",
|
"serve": "^13.0.2",
|
||||||
|
"source-map-support": "^0.5.20",
|
||||||
"style-loader": "^3.3.0",
|
"style-loader": "^3.3.0",
|
||||||
"ts-jest": "^26.5.5",
|
"ts-jest": "^26.5.5",
|
||||||
"ts-loader": "^9.2.6",
|
"ts-loader": "^9.2.6",
|
||||||
|
"ts-node": "^10.2.1",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||||
"typescript": "^4.2.4",
|
"typescript": "^4.2.4",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"webpack": "^5.58.0",
|
"webpack": "^5.58.0",
|
||||||
|
"webpack-bugsnag-plugins": "^1.8.0",
|
||||||
"webpack-bundle-analyzer": "^4.4.0",
|
"webpack-bundle-analyzer": "^4.4.0",
|
||||||
"webpack-cli": "^4.9.1",
|
"webpack-cli": "^4.9.0",
|
||||||
"webpack-dev-server": "^4.6.0",
|
"webpack-dev-server": "^4.3.1",
|
||||||
|
"worker-loader": "^3.0.8",
|
||||||
"yaml-loader": "^0.6.0"
|
"yaml-loader": "^0.6.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
@ -126,7 +215,11 @@
|
|||||||
"@types/testing-library__react": "^10.0.0",
|
"@types/testing-library__react": "^10.0.0",
|
||||||
"@types/testing-library__dom": "^7.0.0",
|
"@types/testing-library__dom": "^7.0.0",
|
||||||
"anser": "2.0.1",
|
"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": {
|
"engines": {
|
||||||
"node": ">=14.16.0"
|
"node": ">=14.16.0"
|
||||||
|
@ -6,12 +6,11 @@ const customGenerator = require('./scripts/swagger-custom-generator.js')
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
pm: {
|
pm: {
|
||||||
output: 'src/services/pm/index.tsx',
|
output: 'src/services/policy-mgmt/index.tsx',
|
||||||
file: 'src/services/pm/swagger.json',
|
file: '../design/gen/http/openapi3.json',
|
||||||
transformer: 'scripts/swagger-transform.js',
|
customImport: `import { getConfigNew } from "../config";`,
|
||||||
customImport: `import { getConfig } from "../config";`,
|
|
||||||
customProps: {
|
customProps: {
|
||||||
base: `{getConfig("pm/api/v1")}`
|
base: `{getConfigNew("pm")}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react'
|
import React, { useEffect, useState, useCallback } from 'react'
|
||||||
import { RestfulProvider } from 'restful-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 { FocusStyleManager } from '@blueprintjs/core'
|
||||||
|
import { tooltipDictionary } from '@harness/ng-tooltip'
|
||||||
import AppErrorBoundary from 'framework/AppErrorBoundary/AppErrorBoundary'
|
import AppErrorBoundary from 'framework/AppErrorBoundary/AppErrorBoundary'
|
||||||
import { useAPIToken } from 'hooks/useAPIToken'
|
|
||||||
import { AppContextProvider } from 'AppContext'
|
import { AppContextProvider } from 'AppContext'
|
||||||
import { setBaseRouteInfo } from 'RouteUtils'
|
import { setBaseRouteInfo } from 'RouteUtils'
|
||||||
import type { AppProps } from 'AppProps'
|
import type { AppProps } from 'AppProps'
|
||||||
import { buildResfulReactRequestOptions, handle401 } from 'AppUtils'
|
import { buildResfulReactRequestOptions, handle401 } from 'AppUtils'
|
||||||
import { RouteDestinations } from 'RouteDestinations'
|
import { RouteDestinations } from 'RouteDestinations'
|
||||||
|
import { useAPIToken } from 'hooks/useAPIToken'
|
||||||
import { languageLoader } from './framework/strings/languageLoader'
|
import { languageLoader } from './framework/strings/languageLoader'
|
||||||
import type { LanguageRecord } from './framework/strings/languageLoader'
|
import type { LanguageRecord } from './framework/strings/languageLoader'
|
||||||
import { StringsContextProvider } from './framework/strings/StringsContextProvider'
|
import { StringsContextProvider } from './framework/strings/StringsContextProvider'
|
||||||
import './App.scss'
|
|
||||||
|
|
||||||
FocusStyleManager.onlyShowFocusOnTabs()
|
FocusStyleManager.onlyShowFocusOnTabs()
|
||||||
|
|
||||||
@ -31,8 +32,8 @@ const App: React.FC<AppProps> = props => {
|
|||||||
const [strings, setStrings] = useState<LanguageRecord>()
|
const [strings, setStrings] = useState<LanguageRecord>()
|
||||||
const [token, setToken] = useAPIToken(apiToken)
|
const [token, setToken] = useAPIToken(apiToken)
|
||||||
const getRequestOptions = useCallback((): Partial<RequestInit> => {
|
const getRequestOptions = useCallback((): Partial<RequestInit> => {
|
||||||
return buildResfulReactRequestOptions(token)
|
return buildResfulReactRequestOptions(hooks.useGetToken?.() || apiToken || 'default')
|
||||||
}, [token])
|
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
setBaseRouteInfo(accountId, baseRoutePath)
|
setBaseRouteInfo(accountId, baseRoutePath)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -48,7 +49,7 @@ const App: React.FC<AppProps> = props => {
|
|||||||
return strings ? (
|
return strings ? (
|
||||||
<StringsContextProvider initialStrings={strings}>
|
<StringsContextProvider initialStrings={strings}>
|
||||||
<AppErrorBoundary>
|
<AppErrorBoundary>
|
||||||
<AppContextProvider value={{ standalone, baseRoutePath, accountId, lang, apiToken, on401, hooks, components }}>
|
<AppContextProvider value={{ standalone, baseRoutePath, accountId, lang, on401, hooks, components }}>
|
||||||
<RestfulProvider
|
<RestfulProvider
|
||||||
base="/"
|
base="/"
|
||||||
requestOptions={getRequestOptions}
|
requestOptions={getRequestOptions}
|
||||||
@ -59,7 +60,7 @@ const App: React.FC<AppProps> = props => {
|
|||||||
on401()
|
on401()
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<TooltipContextProvider initialTooltipDictionary={{}}>
|
<TooltipContextProvider initialTooltipDictionary={tooltipDictionary}>
|
||||||
<ModalProvider>{children ? children : <RouteDestinations standalone={standalone} />}</ModalProvider>
|
<ModalProvider>{children ? children : <RouteDestinations standalone={standalone} />}</ModalProvider>
|
||||||
</TooltipContextProvider>
|
</TooltipContextProvider>
|
||||||
</RestfulProvider>
|
</RestfulProvider>
|
||||||
|
@ -13,7 +13,7 @@ const AppContext = React.createContext<AppContextProps>({
|
|||||||
components: {}
|
components: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const AppContextProvider: React.FC<{ value: AppProps }> = ({ value: initialValue, children }) => {
|
export const AppContextProvider: React.FC<{ value: AppProps }> = React.memo(({ value: initialValue, children }) => {
|
||||||
const [appStates, setAppStates] = useState<AppProps>(initialValue)
|
const [appStates, setAppStates] = useState<AppProps>(initialValue)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -27,6 +27,6 @@ export const AppContextProvider: React.FC<{ value: AppProps }> = ({ value: initi
|
|||||||
{children}
|
{children}
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export const useAppContext: () => AppContextProps = () => useContext(AppContext)
|
export const useAppContext: () => AppContextProps = () => useContext(AppContext)
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
|
import type * as History from 'history'
|
||||||
|
import type { PermissionOptionsMenuButtonProps } from 'components/Permissions/PermissionsOptionsMenuButton'
|
||||||
|
import type { OverviewChartsWithToggleProps } from 'components/OverviewChartsWithToggle/OverviewChartsWithToggle'
|
||||||
import type { LangLocale } from './framework/strings/languageLoader'
|
import type { LangLocale } from './framework/strings/languageLoader'
|
||||||
|
import type { FeatureFlagMap, GitFiltersProps } from './utils/GovernanceUtils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppProps defines an interface for host (parent) and
|
* AppProps defines an interface for host (parent) and
|
||||||
@ -7,7 +11,6 @@ import type { LangLocale } from './framework/strings/languageLoader'
|
|||||||
* of the child app to be customized from the parent app.
|
* of the child app to be customized from the parent app.
|
||||||
*
|
*
|
||||||
* Areas of customization:
|
* Areas of customization:
|
||||||
*
|
|
||||||
* - API token
|
* - API token
|
||||||
* - Active user
|
* - Active user
|
||||||
* - Active locale (i18n)
|
* - Active locale (i18n)
|
||||||
@ -59,15 +62,27 @@ export interface AppPathProps {
|
|||||||
policyIdentifier?: string
|
policyIdentifier?: string
|
||||||
policySetIdentifier?: string
|
policySetIdentifier?: string
|
||||||
evaluationId?: string
|
evaluationId?: string
|
||||||
pipeline?: string
|
repo?: string
|
||||||
execution?: string
|
branch?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppPropsHook defines a collection of React Hooks that application receives from
|
* AppPropsHook defines a collection of React Hooks that application receives from
|
||||||
* Platform integration.
|
* 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
|
* AppPropsComponent defines a collection of React Components that application receives from
|
||||||
@ -75,4 +90,21 @@ export interface AppPropsHook {} // eslint-disable-line @typescript-eslint/no-e
|
|||||||
*/
|
*/
|
||||||
export interface AppPropsComponent {
|
export interface AppPropsComponent {
|
||||||
NGBreadcrumbs: React.FC
|
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
|
||||||
|
}>
|
||||||
|
OverviewChartsWithToggle: React.FC<OverviewChartsWithToggleProps>
|
||||||
}
|
}
|
||||||
|
@ -3,37 +3,99 @@ import type { AppPathProps } from 'AppProps'
|
|||||||
|
|
||||||
export enum RoutePath {
|
export enum RoutePath {
|
||||||
SIGNIN = '/signin',
|
SIGNIN = '/signin',
|
||||||
TEST_PAGE1 = '/test-page1',
|
POLICY_DASHBOARD = '/dashboard',
|
||||||
TEST_PAGE2 = '/test-page2',
|
POLICY_LISTING = '/policies',
|
||||||
|
POLICY_NEW = '/policies/new',
|
||||||
REGISTER = '/register',
|
POLICY_VIEW = '/policies/view/:policyIdentifier',
|
||||||
LOGIN = '/login',
|
//POLICY_EDIT = '/policies/edit/:policyIdentifier',
|
||||||
USERS = '/users',
|
POLICY_EDIT= '/policies/edit/:policyIdentifier/:repo?/:branch?',
|
||||||
ACCOUNT = '/account',
|
POLICY_SETS_LISTING = '/policy-sets',
|
||||||
PIPELINES = '/pipelines',
|
POLICY_SETS_DETAIL = '/policy-sets/:policySetIdentifier',
|
||||||
PIPELINE = '/pipelines/:pipeline',
|
POLICY_EVALUATIONS_LISTING = '/policy-evaluations',
|
||||||
PIPELINE_SETTINGS = '/pipelines/:pipeline/settings',
|
POLICY_EVALUATION_DETAIL = '/policy-evaluations/:evaluationId'
|
||||||
PIPELINE_EXECUTIONS = '/pipelines/:pipeline/executions',
|
|
||||||
PIPELINE_EXECUTION = '/pipelines/:pipeline/executions/:execution',
|
|
||||||
PIPELINE_EXECUTION_SETTINGS = '/pipelines/:pipeline/executions/:execution/settings'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
toLogin: (): string => toRouteURL(RoutePath.LOGIN),
|
toSignIn: (): string => toRouteURL(RoutePath.SIGNIN),
|
||||||
toRegister: (): string => toRouteURL(RoutePath.REGISTER),
|
toPolicyDashboard: (): string => toRouteURL(RoutePath.POLICY_DASHBOARD),
|
||||||
toAccount: (): string => toRouteURL(RoutePath.ACCOUNT),
|
toPolicyListing: (): string => toRouteURL(RoutePath.POLICY_LISTING),
|
||||||
toPipelines: (): string => toRouteURL(RoutePath.PIPELINES),
|
toPolicyNew: (): string => toRouteURL(RoutePath.POLICY_NEW),
|
||||||
toPipeline: ({ pipeline }: Required<Pick<AppPathProps, 'pipeline'>>): string =>
|
toPolicyView: ({ policyIdentifier }: Required<Pick<AppPathProps, 'policyIdentifier'>>): string =>
|
||||||
toRouteURL(RoutePath.PIPELINE, { pipeline }),
|
toRouteURL(RoutePath.POLICY_VIEW, { policyIdentifier }),
|
||||||
toPipelineExecutions: ({ pipeline }: Required<Pick<AppPathProps, 'pipeline'>>): string =>
|
toPolicyEdit: ({ policyIdentifier }: Required<Pick<AppPathProps, 'policyIdentifier'>>): string =>
|
||||||
toRouteURL(RoutePath.PIPELINE_EXECUTIONS, { pipeline }),
|
toRouteURL(RoutePath.POLICY_EDIT, { policyIdentifier }),
|
||||||
toPipelineSettings: ({ pipeline }: Required<Pick<AppPathProps, 'pipeline'>>): string =>
|
toPolicySets: (): string => toRouteURL(RoutePath.POLICY_SETS_LISTING),
|
||||||
toRouteURL(RoutePath.PIPELINE_SETTINGS, { pipeline }),
|
toPolicyEvaluations: (): string => toRouteURL(RoutePath.POLICY_EVALUATIONS_LISTING),
|
||||||
toPipelineExecution: ({ pipeline, execution }: AppPathProps): string =>
|
toGovernancePolicyDashboard: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||||
toRouteURL(RoutePath.PIPELINE_EXECUTION, { pipeline, execution }),
|
toRouteURL(RoutePath.POLICY_DASHBOARD, {
|
||||||
toPipelineExecutionSettings: ({ pipeline, execution }: AppPathProps): string =>
|
orgIdentifier,
|
||||||
toRouteURL(RoutePath.PIPELINE_EXECUTION_SETTINGS, { pipeline, execution })
|
projectIdentifier,
|
||||||
|
module
|
||||||
// @see https://github.com/drone/policy-mgmt/blob/main/web/src/RouteDefinitions.ts
|
}),
|
||||||
// for more examples regarding to passing parameters to generate URLs
|
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,75 +1,105 @@
|
|||||||
import React from 'react'
|
/* eslint-disable react/display-name */
|
||||||
import { HashRouter, Route, Switch } from 'react-router-dom'
|
import React, { useCallback } from 'react'
|
||||||
import type { AppProps } from 'AppProps'
|
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'
|
||||||
|
import { SignInPage } from 'pages/signin/SignInPage'
|
||||||
import { NotFoundPage } from 'pages/404/NotFoundPage'
|
import { NotFoundPage } from 'pages/404/NotFoundPage'
|
||||||
import { routePath } from 'RouteUtils'
|
import { routePath, standaloneRoutePath } from './RouteUtils'
|
||||||
import { RoutePath } from 'RouteDefinitions'
|
import { RoutePath } from './RouteDefinitions'
|
||||||
|
import PolicyControlPage from './pages/PolicyControl/PolicyControlPage'
|
||||||
|
import Policies from './pages/Policies/Policies'
|
||||||
|
import PolicyDashboard from './pages/PolicyDashboard/PolicyDashboard'
|
||||||
|
import PolicySets from './pages/PolicySets/PolicySets'
|
||||||
|
import PolicyEvaluations from './pages/PolicyEvaluations/PolicyEvaluations'
|
||||||
|
import { EditPolicy } from './pages/EditPolicy/EditPolicy'
|
||||||
|
import { ViewPolicy } from './pages/ViewPolicy/ViewPolicy'
|
||||||
|
import { PolicySetDetail } from './pages/PolicySetDetail/PolicySetDetail'
|
||||||
|
import { EvaluationDetail } from './pages/EvaluationDetail/EvaluationDetail'
|
||||||
|
|
||||||
import { Login } from './pages/Login/Login'
|
export const RouteDestinations: React.FC<{ standalone: boolean }> = React.memo(
|
||||||
import { Home } from './pages/Pipelines/Pipelines'
|
({ standalone }) => {
|
||||||
import { Executions } from './pages/Executions/Executions'
|
// TODO: Add Auth wrapper
|
||||||
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<Pick<AppProps, 'standalone'>> = ({ standalone }) => {
|
const Destinations: React.FC = useCallback(
|
||||||
// TODO: Add a generic Auth Wrapper
|
() => (
|
||||||
|
<Switch>
|
||||||
|
{standalone && (
|
||||||
|
<Route path={routePath(RoutePath.SIGNIN)}>
|
||||||
|
<SignInPage />
|
||||||
|
</Route>
|
||||||
|
)}
|
||||||
|
|
||||||
const Destinations: React.FC = () => (
|
<Route path={routePath(RoutePath.POLICY_DASHBOARD)}>
|
||||||
<Switch>
|
<PolicyControlPage titleKey="overview">
|
||||||
{standalone && (
|
<PolicyDashboard />
|
||||||
<Route path={routePath(RoutePath.REGISTER)}>
|
</PolicyControlPage>
|
||||||
<Login />
|
</Route>
|
||||||
</Route>
|
|
||||||
)}
|
|
||||||
{standalone && (
|
|
||||||
<Route path={routePath(RoutePath.LOGIN)}>
|
|
||||||
<Login />
|
|
||||||
</Route>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Route exact path={routePath(RoutePath.PIPELINES)}>
|
<Route path={routePath(RoutePath.POLICY_NEW)}>
|
||||||
<SideNav>
|
<PolicyControlPage titleKey="common.policy.newPolicy">
|
||||||
<Home />
|
<EditPolicy />
|
||||||
</SideNav>
|
</PolicyControlPage>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route exact path={routePath(RoutePath.PIPELINE)}>
|
<Route path={routePath(RoutePath.POLICY_VIEW)}>
|
||||||
<SideNav>
|
<PolicyControlPage titleKey="governance.viewPolicy">
|
||||||
<Executions />
|
<ViewPolicy />
|
||||||
</SideNav>
|
</PolicyControlPage>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route exact path={routePath(RoutePath.PIPELINE_SETTINGS)}>
|
<Route exact path={routePath(RoutePath.POLICY_EDIT)}>
|
||||||
<SideNav>
|
<PolicyControlPage titleKey="governance.editPolicy">
|
||||||
<PipelineSettings />
|
<EditPolicy />
|
||||||
</SideNav>
|
</PolicyControlPage>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route exact path={routePath(RoutePath.PIPELINE_EXECUTION_SETTINGS)}>
|
<Route path={routePath(RoutePath.POLICY_LISTING)}>
|
||||||
<SideNav>
|
<PolicyControlPage titleKey="common.policies">
|
||||||
<ExecutionSettings />
|
<Policies />
|
||||||
</SideNav>
|
</PolicyControlPage>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route exact path={routePath(RoutePath.ACCOUNT)}>
|
<Route exact path={routePath(RoutePath.POLICY_SETS_LISTING)}>
|
||||||
<SideNav>
|
<PolicyControlPage titleKey="common.policy.policysets">
|
||||||
<Account />
|
<PolicySets />
|
||||||
</SideNav>
|
</PolicyControlPage>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/">
|
<Route path={routePath(RoutePath.POLICY_SETS_DETAIL)}>
|
||||||
<NotFoundPage />
|
<PolicyControlPage titleKey="common.policy.policysets">
|
||||||
</Route>
|
<PolicySetDetail />
|
||||||
</Switch>
|
</PolicyControlPage>
|
||||||
)
|
</Route>
|
||||||
|
|
||||||
return standalone ? (
|
<Route path={routePath(RoutePath.POLICY_EVALUATION_DETAIL)}>
|
||||||
<HashRouter>
|
<PolicyControlPage titleKey="governance.evaluations">
|
||||||
|
<EvaluationDetail />
|
||||||
|
</PolicyControlPage>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path={routePath(RoutePath.POLICY_EVALUATIONS_LISTING)}>
|
||||||
|
<PolicyControlPage titleKey="governance.evaluations">
|
||||||
|
<PolicyEvaluations />
|
||||||
|
</PolicyControlPage>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/">
|
||||||
|
{standalone ? (
|
||||||
|
<Redirect to={standaloneRoutePath(RoutePath.POLICY_DASHBOARD)} />
|
||||||
|
) : (
|
||||||
|
<NotFoundPage />
|
||||||
|
)}
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
),
|
||||||
|
[standalone]
|
||||||
|
)
|
||||||
|
|
||||||
|
return standalone ? (
|
||||||
|
<HashRouter>
|
||||||
|
<Destinations />
|
||||||
|
</HashRouter>
|
||||||
|
) : (
|
||||||
<Destinations />
|
<Destinations />
|
||||||
</HashRouter>
|
)
|
||||||
) : (
|
}
|
||||||
<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
|
// 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.
|
// 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 => {
|
const getScopeBasedRouteURL = ({ path, scope = {} }: { path: string; scope?: Scope }): string => {
|
||||||
if (window.APP_RUN_IN_STANDALONE_MODE) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
const { orgIdentifier, projectIdentifier, module } = scope
|
const { orgIdentifier, projectIdentifier, module } = scope
|
||||||
|
|
||||||
//
|
// The Governance app is mounted in three places in Harness Platform
|
||||||
// TODO: Change this scheme below to reflect your application when it's embedded into Harness NextGen UI
|
// 1. Account Settings (account level governance)
|
||||||
//
|
// 2. Org Details (org level governance)
|
||||||
|
// 3. Project Settings (project level governance)
|
||||||
// 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)
|
|
||||||
if (module && orgIdentifier && projectIdentifier) {
|
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) {
|
} 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) {
|
} 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
|
* @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)
|
* Generate route URL to be used RouteDefinitions' default export (aka actual react-router link href)
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
import './App.scss'
|
||||||
|
|
||||||
// This flag is used in services/config.ts to customize API path when app is run
|
// This flag is used in services/config.ts to customize API path when app is run
|
||||||
// in multiple modes (standalone vs. embedded).
|
// in multiple modes (standalone vs. embedded).
|
||||||
// Also being used in when generating proper URLs inside the app.
|
// Also being used in when generating proper URLs inside the app.
|
||||||
window.APP_RUN_IN_STANDALONE_MODE = true
|
window.STRIP_PM_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 => {
|
export const NameId = (props: NameIdProps): JSX.Element => {
|
||||||
const { getString } = useStrings()
|
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 }
|
const newInputGroupProps = { placeholder: getString('common.namePlaceholder'), ...inputGroupProps }
|
||||||
return (
|
return (
|
||||||
<FormInput.InputWithIdentifier inputLabel={nameLabel} inputGroupProps={newInputGroupProps} {...identifierProps} />
|
<FormInput.InputWithIdentifier inputLabel={nameLabel} inputGroupProps={newInputGroupProps} {...identifierProps} />
|
||||||
@ -55,9 +55,7 @@ export const Description = (props: DescriptionComponentProps): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<Container style={{ marginBottom: isDescriptionOpen ? '0' : 'var(--spacing-medium)' }}>
|
<Container style={{ marginBottom: isDescriptionOpen ? '0' : 'var(--spacing-medium)' }}>
|
||||||
<Label className={cx(Classes.LABEL, css.descriptionLabel)} data-tooltip-id={props.dataTooltipId}>
|
<Label className={cx(Classes.LABEL, css.descriptionLabel)} data-tooltip-id={props.dataTooltipId}>
|
||||||
{isOptional
|
{isOptional ? getString('optionalField', { name: getString('description') }) : getString('description')}
|
||||||
? getString('common.optionalField', { name: getString('common.description') })
|
|
||||||
: getString('common.description')}
|
|
||||||
{props.dataTooltipId ? <HarnessDocTooltip useStandAlone={true} tooltipId={props.dataTooltipId} /> : null}
|
{props.dataTooltipId ? <HarnessDocTooltip useStandAlone={true} tooltipId={props.dataTooltipId} /> : null}
|
||||||
{!isDescriptionOpen && (
|
{!isDescriptionOpen && (
|
||||||
<Icon
|
<Icon
|
||||||
@ -79,7 +77,7 @@ export const Description = (props: DescriptionComponentProps): JSX.Element => {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
autoFocus={isDescriptionFocus}
|
autoFocus={isDescriptionFocus}
|
||||||
name="description"
|
name="description"
|
||||||
placeholder={getString('common.descriptionPlaceholder')}
|
placeholder={getString('descriptionPlaceholder')}
|
||||||
{...restDescriptionProps}
|
{...restDescriptionProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -95,9 +93,7 @@ export const Tags = (props: TagsComponentProps): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Label className={cx(Classes.LABEL, css.descriptionLabel)} data-tooltip-id={props.dataTooltipId}>
|
<Label className={cx(Classes.LABEL, css.descriptionLabel)} data-tooltip-id={props.dataTooltipId}>
|
||||||
{isOptional
|
{isOptional ? getString('optionalField', { name: getString('tagsLabel') }) : getString('tagsLabel')}
|
||||||
? getString('common.optionalField', { name: getString('common.tagsLabel') })
|
|
||||||
: getString('common.tagsLabel')}
|
|
||||||
{props.dataTooltipId ? <HarnessDocTooltip useStandAlone={true} tooltipId={props.dataTooltipId} /> : null}
|
{props.dataTooltipId ? <HarnessDocTooltip useStandAlone={true} tooltipId={props.dataTooltipId} /> : null}
|
||||||
{!isTagsOpen && (
|
{!isTagsOpen && (
|
||||||
<Icon
|
<Icon
|
||||||
@ -125,7 +121,7 @@ function TagsDeprecated(props: TagsDeprecatedComponentProps): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Label className={cx(Classes.LABEL, css.descriptionLabel)}>
|
<Label className={cx(Classes.LABEL, css.descriptionLabel)}>
|
||||||
{getString('common.tagsLabel')}
|
{getString('tagsLabel')}
|
||||||
{!isTagsOpen && (
|
{!isTagsOpen && (
|
||||||
<Icon
|
<Icon
|
||||||
className={css.editOpen}
|
className={css.editOpen}
|
||||||
@ -159,15 +155,7 @@ function TagsDeprecated(props: TagsDeprecatedComponentProps): JSX.Element {
|
|||||||
|
|
||||||
export function NameIdDescriptionTags(props: NameIdDescriptionTagsProps): JSX.Element {
|
export function NameIdDescriptionTags(props: NameIdDescriptionTagsProps): JSX.Element {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const {
|
const { className, identifierProps, descriptionProps, formikProps, inputGroupProps = {}, tooltipProps } = props
|
||||||
className,
|
|
||||||
identifierProps,
|
|
||||||
descriptionProps,
|
|
||||||
tagsProps,
|
|
||||||
formikProps,
|
|
||||||
inputGroupProps = {},
|
|
||||||
tooltipProps
|
|
||||||
} = props
|
|
||||||
const newInputGroupProps = { placeholder: getString('common.namePlaceholder'), ...inputGroupProps }
|
const newInputGroupProps = { placeholder: getString('common.namePlaceholder'), ...inputGroupProps }
|
||||||
return (
|
return (
|
||||||
<Container className={cx(css.main, className)}>
|
<Container className={cx(css.main, className)}>
|
||||||
@ -177,12 +165,6 @@ export function NameIdDescriptionTags(props: NameIdDescriptionTagsProps): JSX.El
|
|||||||
hasValue={!!formikProps?.values.description}
|
hasValue={!!formikProps?.values.description}
|
||||||
dataTooltipId={tooltipProps?.dataTooltipId ? `${tooltipProps.dataTooltipId}_description` : undefined}
|
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>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Classes, Menu } from '@blueprintjs/core'
|
import { Classes, Menu } from '@blueprintjs/core'
|
||||||
import { Button, ButtonProps } from '@harness/uicore'
|
import { Button, ButtonProps } from '@harness/uicore'
|
||||||
import type { PopoverProps } from '@harness/uicore/dist/components/Popover/Popover'
|
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> | '-'>
|
items: Array<React.ComponentProps<typeof Menu.Item> | '-'>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OptionsMenuButton: React.FC<OptionsMenuButtonProps> = ({ items, ...props }) => {
|
export const OptionsMenuButton = ({ items, ...props }: OptionsMenuButtonProps): ReactElement => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
minimal
|
minimal
|
||||||
|
16
web/src/components/Permissions/PermissionsButton.tsx
Normal file
16
web/src/components/Permissions/PermissionsButton.tsx
Normal file
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
11
web/src/components/SpinnerWrapper/SpinnerWrapper.module.scss
Normal file
11
web/src/components/SpinnerWrapper/SpinnerWrapper.module.scss
Normal file
@ -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 */
|
/* eslint-disable */
|
||||||
// this is an auto-generated file
|
// this is an auto-generated file
|
||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly spinner: string
|
readonly loadingSpinnerWrapper: string
|
||||||
|
readonly hidden: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
24
web/src/components/SpinnerWrapper/SpinnerWrapper.tsx
Normal file
24
web/src/components/SpinnerWrapper/SpinnerWrapper.tsx
Normal file
@ -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;
|
|
||||||
}
|
|
14
web/src/components/Table/Table.module.scss.d.ts
vendored
14
web/src/components/Table/Table.module.scss.d.ts
vendored
@ -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
|
|
15
web/src/components/TrialBanner/TrialBanner.module.scss
Normal file
15
web/src/components/TrialBanner/TrialBanner.module.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
9
web/src/components/TrialBanner/TrialBanner.module.scss.d.ts
vendored
Normal file
9
web/src/components/TrialBanner/TrialBanner.module.scss.d.ts
vendored
Normal file
@ -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
|
53
web/src/components/TrialBanner/TrialBanner.tsx
Normal file
53
web/src/components/TrialBanner/TrialBanner.tsx
Normal file
@ -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
|
@ -3,37 +3,143 @@
|
|||||||
* Use the command `yarn strings` to regenerate this file.
|
* Use the command `yarn strings` to regenerate this file.
|
||||||
*/
|
*/
|
||||||
export interface StringsMap {
|
export interface StringsMap {
|
||||||
account: string
|
AZ09: string
|
||||||
addExecution: string
|
ZA90: string
|
||||||
'common.accountDetails': string
|
action: string
|
||||||
'common.accountOverview': string
|
all: string
|
||||||
'common.cancel': string
|
apply: string
|
||||||
'common.delete': string
|
back: string
|
||||||
'common.deleteConfirm': string
|
'banner.expired': string
|
||||||
'common.description': string
|
'banner.expiryCountdown': string
|
||||||
'common.descriptionPlaceholder': string
|
cancel: string
|
||||||
'common.edit': string
|
clearFilter: string
|
||||||
'common.email': string
|
|
||||||
'common.itemCreated': string
|
|
||||||
'common.itemDeleted': string
|
|
||||||
'common.itemUpdated': string
|
|
||||||
'common.name': string
|
|
||||||
'common.namePlaceholder': string
|
'common.namePlaceholder': string
|
||||||
'common.optionalField': string
|
'common.policies': string
|
||||||
'common.save': string
|
'common.policiesSets.created': string
|
||||||
'common.tagsLabel': string
|
'common.policiesSets.enforced': string
|
||||||
'common.token': string
|
'common.policiesSets.entity': string
|
||||||
created: string
|
'common.policiesSets.evaluationCriteria': string
|
||||||
executions: string
|
'common.policiesSets.event': string
|
||||||
existingAccount: string
|
'common.policiesSets.newPolicyset': string
|
||||||
logout: string
|
'common.policiesSets.noPolicySet': string
|
||||||
noAccount: string
|
'common.policiesSets.noPolicySetDescription': string
|
||||||
pageNotFound: string
|
'common.policiesSets.noPolicySetResult': string
|
||||||
password: string
|
'common.policiesSets.noPolicySetTitle': string
|
||||||
pipelineSettings: string
|
'common.policiesSets.noPolicySets': string
|
||||||
pipelines: string
|
'common.policiesSets.policySetSearch': string
|
||||||
settings: string
|
'common.policiesSets.scope': string
|
||||||
signUp: string
|
'common.policiesSets.stepOne.validId': string
|
||||||
signin: string
|
'common.policiesSets.stepOne.validIdRegex': string
|
||||||
slug: 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
|
||||||
|
entity: string
|
||||||
|
'evaluation.evaluatedPoliciesCount': string
|
||||||
|
'evaluation.onePolicyEvaluated': string
|
||||||
|
executionsText: 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
|
||||||
|
noSearchResultsFound: string
|
||||||
|
optionalField: string
|
||||||
|
outputLabel: string
|
||||||
|
overview: string
|
||||||
|
samplePolicies: string
|
||||||
|
saveOverwrite: string
|
||||||
|
search: 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
|
||||||
}
|
}
|
||||||
|
4
web/src/global.d.ts
vendored
4
web/src/global.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
declare const __DEV__: boolean
|
declare const __DEV__: boolean
|
||||||
declare const __ON_PREM__: boolean
|
declare const __ON_PREM__: booelan
|
||||||
|
|
||||||
declare module '*.png' {
|
declare module '*.png' {
|
||||||
const value: string
|
const value: string
|
||||||
@ -45,7 +45,7 @@ declare module '*.gql' {
|
|||||||
declare interface Window {
|
declare interface Window {
|
||||||
apiUrl: string
|
apiUrl: string
|
||||||
bugsnagClient?: any
|
bugsnagClient?: any
|
||||||
APP_RUN_IN_STANDALONE_MODE?: boolean
|
STRIP_PM_PREFIX?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const monaco: any
|
declare const monaco: any
|
||||||
|
11
web/src/hooks/useStandaloneFeatureFlags.ts
Normal file
11
web/src/hooks/useStandaloneFeatureFlags.ts
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
3
web/src/hooks/useStandalonePermission.ts
Normal file
3
web/src/hooks/useStandalonePermission.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function useStandalonePermission(_permissionsRequest?: any, _deps: Array<any> = []): Array<boolean> {
|
||||||
|
return [true, true]
|
||||||
|
}
|
@ -1,34 +1,152 @@
|
|||||||
signin: Sign In
|
failed: Failed
|
||||||
signUp: Sign Up
|
status: Status
|
||||||
logout: Logout
|
success: Success
|
||||||
password: Password
|
details: Details
|
||||||
pageNotFound: Page Not Found
|
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:
|
common:
|
||||||
save: Save
|
|
||||||
edit: Edit
|
|
||||||
name: Name
|
|
||||||
email: Email
|
|
||||||
namePlaceholder: Enter Name
|
namePlaceholder: Enter Name
|
||||||
description: Description
|
policies: 'Policies'
|
||||||
descriptionPlaceholder: Enter Description
|
policy:
|
||||||
tagsLabel: Tags
|
policysets: Policy Sets
|
||||||
optionalField: '{{name}} (optional)'
|
newPolicy: New Policy
|
||||||
delete: Delete
|
evaluations: Evaluations
|
||||||
deleteConfirm: Are you sure you want to delete this?
|
policySearch: Search Policy by name
|
||||||
itemDeleted: Item Deleted
|
noPolicy: A Harness policy is an OPA rule that can be enforced on your Harness software delivery processes to ensure governance and compliance.
|
||||||
itemUpdated: Item Updated
|
noPolicyTitle: You have no policies
|
||||||
itemCreated: Item Created
|
noPolicyResult: No policies found
|
||||||
cancel: Cancel
|
noPolicyEvalResultTitle: You have no policy evaluations
|
||||||
accountDetails: Account Details
|
noPolicyEvalResult: Policy evaluations are created when policy sets are enforced on your Harness entities.
|
||||||
accountOverview: Account Overview
|
noSelectInput: Select appropriate options
|
||||||
token: Token
|
permission:
|
||||||
pipelines: Pipelines
|
noEdit: You do not have permission to edit a Policy
|
||||||
pipelineSettings: Pipeline Settings
|
table:
|
||||||
account: Account
|
name: Policy
|
||||||
settings: Settings
|
lastModified: Last Modified
|
||||||
executions: Executions
|
createdAt: Created At
|
||||||
addExecution: Add New Execution
|
policiesSets:
|
||||||
slug: Slug
|
newPolicyset: New Policy Set
|
||||||
created: Created
|
noPolicySets: No Policy Sets evaluated
|
||||||
noAccount: No account?
|
evaluationCriteria: Policy evaluation criteria
|
||||||
existingAccount: Already have an account?
|
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
|
||||||
|
11
web/src/utils/Enums.ts
Normal file
11
web/src/utils/Enums.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export enum Sort {
|
||||||
|
DESC = 'DESC',
|
||||||
|
ASC = 'ASC'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SortFields {
|
||||||
|
LastUpdatedAt = 'updated',
|
||||||
|
AZ09 = 'AZ09',
|
||||||
|
ZA90 = 'ZA90',
|
||||||
|
Name = 'name'
|
||||||
|
}
|
442
web/src/utils/GovernanceUtils.ts
Normal file
442
web/src/utils/GovernanceUtils.ts
Normal file
@ -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])
|
||||||
|
}
|
467
web/src/utils/rego.ts
Normal file
467
web/src/utils/rego.ts
Normal file
@ -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'
|
||||||
|
}
|
||||||
|
}
|
11049
web/yarn.lock
11049
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user