From ca688764a3d332fa8f017d3d5c1922ee6e30047d Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 1 Feb 2023 11:56:38 +0700 Subject: [PATCH 01/52] Add login/logout logs --- backend/src/app.ts | 2 +- backend/src/controllers/v1/authController.ts | 34 +++- .../src/controllers/v1/passwordController.ts | 3 +- .../src/controllers/v2/secretsController.ts | 36 ++-- backend/src/ee/helpers/action.ts | 181 +++++++++++++++--- backend/src/ee/helpers/log.ts | 18 +- backend/src/ee/models/action.ts | 20 +- backend/src/ee/models/log.ts | 4 + backend/src/ee/services/EELogService.ts | 26 ++- backend/src/helpers/secret.ts | 32 ++-- backend/src/models/LoginSRPDetail.ts | 14 +- backend/src/models/index.ts | 5 +- backend/src/models/user.ts | 4 +- backend/src/variables/action.ts | 4 + backend/src/variables/index.ts | 4 + 15 files changed, 297 insertions(+), 90 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 79561d00c..066da16d4 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -73,7 +73,7 @@ app.use( }) ); -app.use(requestIp.mw()) +// app.use(requestIp.mw()) if (NODE_ENV === 'production') { // enable app-wide rate-limiting + helmet security diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index 19850680e..6e2de53b3 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -4,16 +4,21 @@ import jwt from 'jsonwebtoken'; import * as Sentry from '@sentry/node'; import * as bigintConversion from 'bigint-conversion'; const jsrp = require('jsrp'); -import { User } from '../../models'; +import { User, LoginSRPDetail } from '../../models'; import { createToken, issueTokens, clearTokens } from '../../helpers/auth'; +import { + ACTION_LOGIN, + ACTION_LOGOUT +} from '../../variables'; import { NODE_ENV, JWT_AUTH_LIFETIME, JWT_AUTH_SECRET, JWT_REFRESH_SECRET } from '../../config'; -import LoginSRPDetail from '../../models/LoginSRPDetail'; import { BadRequestError } from '../../utils/errors'; +import { EELogService } from '../../ee/services'; +import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -116,6 +121,18 @@ export const login2 = async (req: Request, res: Response) => { secure: NODE_ENV === 'production' ? true : false }); + const loginAction = await EELogService.createAction({ + name: ACTION_LOGIN, + userId: user._id + }); + + loginAction && await EELogService.createLog({ + userId: user._id, + actions: [loginAction], + channel: getChannelFromUserAgent(req.headers['user-agent']), + ipAddress: req.ip + }); + // return (access) token in response return res.status(200).send({ token: tokens.token, @@ -159,6 +176,19 @@ export const logout = async (req: Request, res: Response) => { sameSite: 'strict', secure: NODE_ENV === 'production' ? true : false }); + + const logoutAction = await EELogService.createAction({ + name: ACTION_LOGOUT, + userId: req.user._id + }); + + logoutAction && await EELogService.createLog({ + userId: req.user._id, + actions: [logoutAction], + channel: getChannelFromUserAgent(req.headers['user-agent']), + ipAddress: req.ip + }); + } catch (err) { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); diff --git a/backend/src/controllers/v1/passwordController.ts b/backend/src/controllers/v1/passwordController.ts index d429cbf41..0c5530f58 100644 --- a/backend/src/controllers/v1/passwordController.ts +++ b/backend/src/controllers/v1/passwordController.ts @@ -4,12 +4,11 @@ import crypto from 'crypto'; // eslint-disable-next-line @typescript-eslint/no-var-requires const jsrp = require('jsrp'); import * as bigintConversion from 'bigint-conversion'; -import { User, Token, BackupPrivateKey } from '../../models'; +import { User, Token, BackupPrivateKey, LoginSRPDetail } from '../../models'; import { checkEmailVerification } from '../../helpers/signup'; import { createToken } from '../../helpers/auth'; import { sendMail } from '../../helpers/nodemailer'; import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config'; -import LoginSRPDetail from '../../models/LoginSRPDetail'; import { BadRequestError } from '../../utils/errors'; /** diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 90eb8e48d..5c6f5898e 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -79,7 +79,7 @@ export const createSecrets = async (req: Request, res: Response) => { */ const channel = getChannelFromUserAgent(req.headers['user-agent']) - const { workspaceId, environment } = req.body; + const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body; const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_WRITE) if (!hasAccess) { @@ -175,17 +175,17 @@ export const createSecrets = async (req: Request, res: Response) => { })) }); - const addAction = await EELogService.createActionSecret({ + const addAction = await EELogService.createAction({ name: ACTION_ADD_SECRETS, - userId: req.user._id.toString(), - workspaceId, + userId: req.user._id, + workspaceId: new Types.ObjectId(workspaceId), secretIds: newSecrets.map((n) => n._id) }); // (EE) create (audit) log addAction && await EELogService.createLog({ userId: req.user._id.toString(), - workspaceId, + workspaceId: new Types.ObjectId(workspaceId), actions: [addAction], channel, ipAddress: req.ip @@ -300,16 +300,16 @@ export const getSecrets = async (req: Request, res: Response) => { const channel = getChannelFromUserAgent(req.headers['user-agent']) - const readAction = await EELogService.createActionSecret({ + const readAction = await EELogService.createAction({ name: ACTION_READ_SECRETS, - userId: userId, - workspaceId: workspaceId as string, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId as string), secretIds: secrets.map((n: any) => n._id) }); readAction && await EELogService.createLog({ - userId: userId, - workspaceId: workspaceId as string, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId as string), actions: [readAction], channel, ipAddress: req.ip @@ -505,17 +505,17 @@ export const updateSecrets = async (req: Request, res: Response) => { }); }, 10000); - const updateAction = await EELogService.createActionSecret({ + const updateAction = await EELogService.createAction({ name: ACTION_UPDATE_SECRETS, - userId: req.user._id.toString(), - workspaceId: key, + userId: req.user._id, + workspaceId: new Types.ObjectId(key), secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id) }); // (EE) create (audit) log updateAction && await EELogService.createLog({ userId: req.user._id.toString(), - workspaceId: key, + workspaceId: new Types.ObjectId(key), actions: [updateAction], channel, ipAddress: req.ip @@ -631,17 +631,17 @@ export const deleteSecrets = async (req: Request, res: Response) => { workspaceId: key }) }); - const deleteAction = await EELogService.createActionSecret({ + const deleteAction = await EELogService.createAction({ name: ACTION_DELETE_SECRETS, - userId: req.user._id.toString(), - workspaceId: key, + userId: req.user._id, + workspaceId: new Types.ObjectId(key), secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id) }); // (EE) create (audit) log deleteAction && await EELogService.createLog({ userId: req.user._id.toString(), - workspaceId: key, + workspaceId: new Types.ObjectId(key), actions: [deleteAction], channel, ipAddress: req.ip diff --git a/backend/src/ee/helpers/action.ts b/backend/src/ee/helpers/action.ts index 389cfedbf..eb94810f4 100644 --- a/backend/src/ee/helpers/action.ts +++ b/backend/src/ee/helpers/action.ts @@ -1,39 +1,40 @@ import * as Sentry from '@sentry/node'; import { Types } from 'mongoose'; -import { SecretVersion, Action } from '../models'; +import { Action } from '../models'; import { getLatestSecretVersionIds, getLatestNSecretSecretVersionIds } from '../helpers/secretVersion'; -import { ACTION_UPDATE_SECRETS } from '../../variables'; +import { + ACTION_LOGIN, + ACTION_LOGOUT, + ACTION_ADD_SECRETS, + ACTION_READ_SECRETS, + ACTION_DELETE_SECRETS, + ACTION_UPDATE_SECRETS, +} from '../../variables'; /** - * Create an (audit) action for secrets including - * add, delete, update, and read actions. + * Create an (audit) action for updating secrets * @param {Object} obj * @param {String} obj.name - name of action - * @param {ObjectId[]} obj.secretIds - ids of relevant secrets + * @param {Types.ObjectId} obj.secretIds - ids of relevant secrets * @returns {Action} action - new action */ -const createActionSecretHelper = async ({ +const createActionUpdateSecret = async ({ name, userId, workspaceId, secretIds }: { name: string; - userId: string; - workspaceId: string; + userId: Types.ObjectId; + workspaceId: Types.ObjectId; secretIds: Types.ObjectId[]; }) => { - let action; - let latestSecretVersions; try { - if (name === ACTION_UPDATE_SECRETS) { - // case: action is updating secrets - // -> add old and new secret versions - latestSecretVersions = (await getLatestNSecretSecretVersionIds({ + const latestSecretVersions = (await getLatestNSecretSecretVersionIds({ secretIds, n: 2 })) @@ -41,17 +42,7 @@ const createActionSecretHelper = async ({ oldSecretVersion: s.versions[0]._id, newSecretVersion: s.versions[1]._id })); - } else { - // case: action is adding, deleting, or reading secrets - // -> add new secret versions - latestSecretVersions = (await getLatestSecretVersionIds({ - secretIds - })) - .map((s) => ({ - newSecretVersion: s.versionId - })); - } - + action = await new Action({ name, user: userId, @@ -64,10 +55,148 @@ const createActionSecretHelper = async ({ } catch (err) { Sentry.setUser(null); Sentry.captureException(err); + throw new Error('Failed to create update secret action'); + } + + return action; +} + +/** + * Create an (audit) action for creating, reading, and deleting + * secrets + * @param {Object} obj + * @param {String} obj.name - name of action + * @param {Types.ObjectId} obj.secretIds - ids of relevant secrets + * @returns {Action} action - new action + */ +const createActionSecret = async ({ + name, + userId, + workspaceId, + secretIds +}: { + name: string; + userId: Types.ObjectId; + workspaceId: Types.ObjectId; + secretIds: Types.ObjectId[]; +}) => { + let action; + try { + // case: action is adding, deleting, or reading secrets + // -> add new secret versions + const latestSecretVersions = (await getLatestSecretVersionIds({ + secretIds + })) + .map((s) => ({ + newSecretVersion: s.versionId + })); + + action = await new Action({ + name, + user: userId, + workspace: workspaceId, + payload: { + secretVersions: latestSecretVersions + } + }).save(); + + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to create action create/read/delete secret action'); + } + + return action; +} + +/** + * Create an (audit) action for user with id [userId] + * @param {Object} obj + * @param {String} obj.name - name of action + * @param {String} obj.userId - id of user associated with action + * @returns + */ +const createActionUser = ({ + name, + userId +}: { + name: string; + userId: Types.ObjectId; +}) => { + let action; + try { + action = new Action({ + name, + user: userId + }).save(); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to create user action'); + } + + return action; +} + +/** + * Create an (audit) action. + * @param {Object} obj + * @param {Object} obj.name - name of action + * @param {Types.ObjectId} obj.userId - id of user associated with action + * @param {Types.ObjectId} obj.workspaceId - id of workspace associated with action + * @param {Types.ObjectId[]} obj.secretIds - ids of secrets associated with action +*/ +const createActionHelper = async ({ + name, + userId, + workspaceId, + secretIds, +}: { + name: string; + userId: Types.ObjectId; + workspaceId?: Types.ObjectId; + secretIds?: Types.ObjectId[]; +}) => { + let action; + try { + switch (name) { + case ACTION_LOGIN: + case ACTION_LOGOUT: + action = await createActionUser({ + name, + userId + }); + break; + case ACTION_ADD_SECRETS: + case ACTION_READ_SECRETS: + case ACTION_DELETE_SECRETS: + if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret'); + action = await createActionSecret({ + name, + userId, + workspaceId, + secretIds + }); + break; + case ACTION_UPDATE_SECRETS: + if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret'); + action = await createActionUpdateSecret({ + name, + userId, + workspaceId, + secretIds + }); + break; + } + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); throw new Error('Failed to create action'); } return action; } -export { createActionSecretHelper }; \ No newline at end of file +export { + createActionHelper +}; \ No newline at end of file diff --git a/backend/src/ee/helpers/log.ts b/backend/src/ee/helpers/log.ts index c357c9818..bdb0a2380 100644 --- a/backend/src/ee/helpers/log.ts +++ b/backend/src/ee/helpers/log.ts @@ -1,9 +1,19 @@ import * as Sentry from '@sentry/node'; +import { Types } from 'mongoose'; import { Log, IAction } from '../models'; - +/** + * Create an (audit) log + * @param {Object} obj + * @param {Types.ObjectId} obj.userId - id of user associated with the log + * @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the log + * @param {IAction[]} obj.actions - actions to include in log + * @param {String} obj.channel - channel (web/cli/auto) associated with the log + * @param {String} obj.ipAddress - ip address associated with the log + * @returns {Log} log - new audit log + */ const createLogHelper = async ({ userId, workspaceId, @@ -11,8 +21,8 @@ const createLogHelper = async ({ channel, ipAddress }: { - userId: string; - workspaceId: string; + userId: Types.ObjectId; + workspaceId?: Types.ObjectId; actions: IAction[]; channel: string; ipAddress: string; @@ -21,7 +31,7 @@ const createLogHelper = async ({ try { log = await new Log({ user: userId, - workspace: workspaceId, + workspace: workspaceId ?? undefined, actionNames: actions.map((a) => a.name), actions, channel, diff --git a/backend/src/ee/models/action.ts b/backend/src/ee/models/action.ts index 3d48aa04d..7cdd7a355 100644 --- a/backend/src/ee/models/action.ts +++ b/backend/src/ee/models/action.ts @@ -1,10 +1,18 @@ import { Schema, model, Types } from 'mongoose'; +import { + ACTION_LOGIN, + ACTION_LOGOUT, + ACTION_ADD_SECRETS, + ACTION_UPDATE_SECRETS, + ACTION_READ_SECRETS, + ACTION_DELETE_SECRETS +} from '../../variables'; export interface IAction { name: string; user?: Types.ObjectId, workspace?: Types.ObjectId, - payload: { + payload?: { secretVersions?: Types.ObjectId[] } } @@ -13,7 +21,15 @@ const actionSchema = new Schema( { name: { type: String, - required: true + required: true, + enum: [ + ACTION_LOGIN, + ACTION_LOGOUT, + ACTION_ADD_SECRETS, + ACTION_UPDATE_SECRETS, + ACTION_READ_SECRETS, + ACTION_DELETE_SECRETS + ] }, user: { type: Schema.Types.ObjectId, diff --git a/backend/src/ee/models/log.ts b/backend/src/ee/models/log.ts index b9eadc016..47be2e58f 100644 --- a/backend/src/ee/models/log.ts +++ b/backend/src/ee/models/log.ts @@ -1,5 +1,7 @@ import { Schema, model, Types } from 'mongoose'; import { + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_UPDATE_SECRETS, ACTION_READ_SECRETS, @@ -29,6 +31,8 @@ const logSchema = new Schema( actionNames: { type: [String], enum: [ + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_UPDATE_SECRETS, ACTION_READ_SECRETS, diff --git a/backend/src/ee/services/EELogService.ts b/backend/src/ee/services/EELogService.ts index c1b2da6fb..bbe03e09e 100644 --- a/backend/src/ee/services/EELogService.ts +++ b/backend/src/ee/services/EELogService.ts @@ -1,14 +1,12 @@ import { Types } from 'mongoose'; import { - Log, - Action, IAction } from '../models'; import { createLogHelper } from '../helpers/log'; import { - createActionSecretHelper + createActionHelper } from '../helpers/action'; import EELicenseService from './EELicenseService'; @@ -33,8 +31,8 @@ class EELogService { channel, ipAddress }: { - userId: string; - workspaceId: string; + userId: Types.ObjectId; + workspaceId?: Types.ObjectId; actions: IAction[]; channel: string; ipAddress: string; @@ -50,26 +48,26 @@ class EELogService { } /** - * Create an (audit) action for secrets including - * add, delete, update, and read actions. + * Create an (audit) action * @param {Object} obj * @param {String} obj.name - name of action - * @param {ObjectId[]} obj.secretIds - secret ids + * @param {Types.ObjectId} obj.userId - id of user associated with the action + * @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the action + * @param {ObjectId[]} obj.secretIds - ids of secrets associated with the action * @returns {Action} action - new action */ - static async createActionSecret({ + static async createAction({ name, userId, workspaceId, secretIds }: { name: string; - userId: string; - workspaceId: string; - secretIds: Types.ObjectId[]; + userId: Types.ObjectId; + workspaceId?: Types.ObjectId; + secretIds?: Types.ObjectId[]; }) { - if (!EELicenseService.isLicenseValid) return null; - return await createActionSecretHelper({ + return await createActionHelper({ name, userId, workspaceId, diff --git a/backend/src/helpers/secret.ts b/backend/src/helpers/secret.ts index a16d114c6..9466cf7aa 100644 --- a/backend/src/helpers/secret.ts +++ b/backend/src/helpers/secret.ts @@ -406,10 +406,10 @@ const v2PushSecrets = async ({ secretIds: toDelete }); - const deleteAction = await EELogService.createActionSecret({ + const deleteAction = await EELogService.createAction({ name: ACTION_DELETE_SECRETS, - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(userId), secretIds: toDelete }); @@ -499,10 +499,10 @@ const v2PushSecrets = async ({ }) }); - const updateAction = await EELogService.createActionSecret({ + const updateAction = await EELogService.createAction({ name: ACTION_UPDATE_SECRETS, - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), secretIds: toUpdate.map((u) => u._id) }); @@ -536,10 +536,10 @@ const v2PushSecrets = async ({ }) }); - const addAction = await EELogService.createActionSecret({ + const addAction = await EELogService.createAction({ name: ACTION_ADD_SECRETS, - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), secretIds: newSecrets.map((n) => n._id) }); addAction && actions.push(addAction); @@ -553,8 +553,8 @@ const v2PushSecrets = async ({ // (EE) create (audit) log if (actions.length > 0) { await EELogService.createLog({ - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), actions, channel, ipAddress @@ -645,16 +645,16 @@ const pullSecrets = async ({ environment }) - const readAction = await EELogService.createActionSecret({ + const readAction = await EELogService.createAction({ name: ACTION_READ_SECRETS, - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), secretIds: secrets.map((n: any) => n._id) }); readAction && await EELogService.createLog({ - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), actions: [readAction], channel, ipAddress diff --git a/backend/src/models/LoginSRPDetail.ts b/backend/src/models/LoginSRPDetail.ts index f65e779c3..6b615db97 100644 --- a/backend/src/models/LoginSRPDetail.ts +++ b/backend/src/models/LoginSRPDetail.ts @@ -1,6 +1,14 @@ -import mongoose, { Schema, model } from 'mongoose'; +import mongoose, { Schema, model, Types } from 'mongoose'; -const LoginSRPDetailSchema = new Schema( +export interface ILoginSRPDetail { + _id: Types.ObjectId; + clientPublicKey: string; + email: string; + serverBInt: mongoose.Schema.Types.Buffer; + expireAt: Date; +} + +const loginSRPDetailSchema = new Schema( { clientPublicKey: { type: String, @@ -16,7 +24,7 @@ const LoginSRPDetailSchema = new Schema( } ); -const LoginSRPDetail = model('LoginSRPDetail', LoginSRPDetailSchema); +const LoginSRPDetail = model('LoginSRPDetail', loginSRPDetailSchema); // LoginSRPDetailSchema.index({ "expireAt": 1 }, { expireAfterSeconds: 0 }); diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index 72ffca607..ac5f102c0 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -16,6 +16,7 @@ import UserAction, { IUserAction } from './userAction'; import Workspace, { IWorkspace } from './workspace'; import ServiceTokenData, { IServiceTokenData } from './serviceTokenData'; import APIKeyData, { IAPIKeyData } from './apiKeyData'; +import LoginSRPDetail, { ILoginSRPDetail } from './loginSRPDetail'; export { BackupPrivateKey, @@ -53,5 +54,7 @@ export { ServiceTokenData, IServiceTokenData, APIKeyData, - IAPIKeyData + IAPIKeyData, + LoginSRPDetail, + ILoginSRPDetail }; diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts index 7ea988c9d..ba9a59bc5 100644 --- a/backend/src/models/user.ts +++ b/backend/src/models/user.ts @@ -12,6 +12,7 @@ export interface IUser { salt?: string; verifier?: string; refreshVersion?: number; + seenIps: [string]; } const userSchema = new Schema( @@ -54,7 +55,8 @@ const userSchema = new Schema( type: Number, default: 0, select: false - } + }, + seenIps: [String] }, { timestamps: true diff --git a/backend/src/variables/action.ts b/backend/src/variables/action.ts index 512eb8e8d..682b357a7 100644 --- a/backend/src/variables/action.ts +++ b/backend/src/variables/action.ts @@ -1,9 +1,13 @@ +const ACTION_LOGIN = 'login'; +const ACTION_LOGOUT = 'logout'; const ACTION_ADD_SECRETS = 'addSecrets'; const ACTION_DELETE_SECRETS = 'deleteSecrets'; const ACTION_UPDATE_SECRETS = 'updateSecrets'; const ACTION_READ_SECRETS = 'readSecrets'; export { + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_DELETE_SECRETS, ACTION_UPDATE_SECRETS, diff --git a/backend/src/variables/index.ts b/backend/src/variables/index.ts index dc1ce6f78..fafab6939 100644 --- a/backend/src/variables/index.ts +++ b/backend/src/variables/index.ts @@ -35,6 +35,8 @@ import { import { SECRET_SHARED, SECRET_PERSONAL } from './secret'; import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from './event'; import { + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_UPDATE_SECRETS, ACTION_DELETE_SECRETS, @@ -75,6 +77,8 @@ export { INTEGRATION_FLYIO_API_URL, EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS, + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_UPDATE_SECRETS, ACTION_DELETE_SECRETS, From 4722bb8fcdac05dfba6a5d7589bb01a4228bb42e Mon Sep 17 00:00:00 2001 From: Hasan Mansoor <68682354+HasanMansoor4@users.noreply.github.com> Date: Thu, 2 Feb 2023 05:27:07 +0300 Subject: [PATCH 02/52] add auto capitalization api controllers and routes with mongo schema updated --- .../src/controllers/v2/workspaceController.ts | 40 ++++++++++++++++++- backend/src/models/workspace.ts | 5 +++ backend/src/routes/v2/workspace.ts | 15 +++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 90781b58c..ce3ecba31 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -467,4 +467,42 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) => return res.status(200).send({ membership }); -} \ No newline at end of file +} + +/** + * Change autoCapitilzation Rule of workspace + * @param req + * @param res + * @returns + */ +export const toggleAutoCapitilzation = async (req: Request, res: Response) => { + let workspace; + try { + const { workspaceId } = req.params; + const { autoCapitalization } = req.body; + + workspace = await Workspace.findOneAndUpdate( + { + _id: workspaceId + }, + { + autoCapitalization + }, + { + new: true + } + ); + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to change autoCapitalization setting' + }); + } + + return res.status(200).send({ + message: 'Successfully changed autoCapitalization setting', + workspace + }); +}; + diff --git a/backend/src/models/workspace.ts b/backend/src/models/workspace.ts index a70b64340..d68ae9416 100644 --- a/backend/src/models/workspace.ts +++ b/backend/src/models/workspace.ts @@ -8,6 +8,7 @@ export interface IWorkspace { name: string; slug: string; }>; + autoCapitalization: boolean; } const workspaceSchema = new Schema({ @@ -15,6 +16,10 @@ const workspaceSchema = new Schema({ type: String, required: true }, + autoCapitalization: { + type: Boolean, + default: true, + }, organization: { type: Schema.Types.ObjectId, ref: 'Organization', diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index 55299e575..4e48c9301 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -118,4 +118,19 @@ router.delete( // TODO - rewire dashboard to this route workspaceController.deleteWorkspaceMembership ); + +router.post( + '/:workspaceId/settings', + requireAuth({ + acceptedAuthModes: ['jwt'] + }), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER] + }), + param('workspaceId').exists().trim(), + body('autoCapitalization').exists().trim().notEmpty(), + validateRequest, + workspaceController.toggleAutoCapitilzation +); + export default router; From 75cd7a0f1580a8a09dd00509d991bed72fed1fc8 Mon Sep 17 00:00:00 2001 From: Hasan Mansoor <68682354+HasanMansoor4@users.noreply.github.com> Date: Thu, 2 Feb 2023 05:30:22 +0300 Subject: [PATCH 03/52] integrate frontend with backend for auto capitalization setting --- .../public/locales/en/settings-project.json | 4 +- .../dashboard/DashboardInputField.tsx | 9 +++-- frontend/src/components/dashboard/KeyPair.tsx | 5 ++- .../src/pages/api/workspace/getWorkspaces.ts | 1 + .../api/workspace/toggleAutoCapitalization.ts | 26 +++++++++++++ frontend/src/pages/dashboard/[id].tsx | 4 ++ frontend/src/pages/settings/project/[id].tsx | 38 +++++++++++++++++++ 7 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 frontend/src/pages/api/workspace/toggleAutoCapitalization.ts diff --git a/frontend/public/locales/en/settings-project.json b/frontend/public/locales/en/settings-project.json index 03e4b1459..3f3401f2e 100644 --- a/frontend/public/locales/en/settings-project.json +++ b/frontend/public/locales/en/settings-project.json @@ -9,5 +9,7 @@ "project-id-description": "To integrate Infisical into your code base and get automatic injection of environmental variables, you should use the following Project ID.", "project-id-description2": "For more guidance, including code snipets for various languages and frameworks, see ", "auto-generated": "This is your project's auto-generated unique identifier. It can't be changed.", - "docs": "Infisical Docs" + "docs": "Infisical Docs", + "auto-capitalization": "Auto Capitalization", + "auto-capitalization-description": "According to standards, Infisical will automatically capitalize your keys. If you want to disable this feature, you can do so here." } diff --git a/frontend/src/components/dashboard/DashboardInputField.tsx b/frontend/src/components/dashboard/DashboardInputField.tsx index 0e292d65d..6969b04d8 100644 --- a/frontend/src/components/dashboard/DashboardInputField.tsx +++ b/frontend/src/components/dashboard/DashboardInputField.tsx @@ -14,6 +14,7 @@ interface DashboardInputFieldProps { blurred?: boolean; isDuplicate?: boolean; override?: boolean; + isCapitalized?: boolean; } /** @@ -36,7 +37,8 @@ const DashboardInputField = ({ value, blurred, isDuplicate, - override + override, + isCapitalized }: DashboardInputFieldProps) => { const ref = useRef(null); const syncScroll = (e: SyntheticEvent) => { @@ -58,7 +60,7 @@ const DashboardInputField = ({ } rounded-md`} > onChangeHandler(e.target.value.toUpperCase(), position)} + onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, position)} type={type} value={value} className={`z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 ${ @@ -164,7 +166,8 @@ function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInput prev.position === next.position && prev.blurred === next.blurred && prev.override === next.override && - prev.isDuplicate === next.isDuplicate + prev.isDuplicate === next.isDuplicate && + prev.isCapitalized === next.isCapitalized ); } diff --git a/frontend/src/components/dashboard/KeyPair.tsx b/frontend/src/components/dashboard/KeyPair.tsx index 7de4edf82..c7aece86f 100644 --- a/frontend/src/components/dashboard/KeyPair.tsx +++ b/frontend/src/components/dashboard/KeyPair.tsx @@ -14,6 +14,7 @@ interface KeyPairProps { toggleSidebar: (id: string) => void; sidebarSecretId: string; isSnapshot: boolean; + isCapitalized: boolean; } /** @@ -39,7 +40,8 @@ const KeyPair = ({ isDuplicate, toggleSidebar, sidebarSecretId, - isSnapshot + isSnapshot, + isCapitalized }: KeyPairProps) => (
; } diff --git a/frontend/src/pages/api/workspace/toggleAutoCapitalization.ts b/frontend/src/pages/api/workspace/toggleAutoCapitalization.ts new file mode 100644 index 000000000..fa459d67a --- /dev/null +++ b/frontend/src/pages/api/workspace/toggleAutoCapitalization.ts @@ -0,0 +1,26 @@ +import SecurityClient from '@app/components/utilities/SecurityClient'; + +/** + * This route lets us change the auto capitalization settings in a project. + * @param {*} req + * @param {*} res + * @returns + */ +const toggleAutoCapitalization = (workspaceId: string, state: boolean) => + SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/settings`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + autoCapitalization: state + }) + }).then(async (res) => { + if (res && res.status === 200) { + return res; + } + console.log('Failed to toggle auto capitalization state'); + return undefined; + }); + +export default toggleAutoCapitalization; diff --git a/frontend/src/pages/dashboard/[id].tsx b/frontend/src/pages/dashboard/[id].tsx index c7f15ca70..25fd7fecd 100644 --- a/frontend/src/pages/dashboard/[id].tsx +++ b/frontend/src/pages/dashboard/[id].tsx @@ -123,6 +123,7 @@ export default function Dashboard() { const [snapshotData, setSnapshotData] = useState(); const [numSnapshots, setNumSnapshots] = useState(); const [saveLoading, setSaveLoading] = useState(false); + const [autoCapitalization, setAutoCapitalization] = useState(false); const { t } = useTranslation(); const { createNotification } = useNotificationContext(); @@ -213,6 +214,7 @@ export default function Dashboard() { if (!workspace) { router.push(`/dashboard/${userWorkspaces?.[0]?._id}`); } + setAutoCapitalization(workspace?.autoCapitalization ?? true); setWorkspaceEnvs(workspace?.environments || []); // set env @@ -758,6 +760,7 @@ export default function Dashboard() { .filter((row) => !sharedToHide.includes(row.id)) .map((keyPair) => ( ( ([]); const [environments, setEnvironments] = useState>([]); const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(''); @@ -63,6 +67,7 @@ export default function SettingsBasic() { userWorkspaces.forEach((userWorkspace) => { if (userWorkspace._id === workspaceId) { setWorkspaceName(userWorkspace.name); + setAutoCapitalization(userWorkspace.autoCapitalization ?? true); setEnvironments(userWorkspace.environments); } }); @@ -80,11 +85,22 @@ export default function SettingsBasic() { setWorkspaceName(newName); }; + const toggleAutoCapitalizationCheckBox = (state: boolean) => { + setAutoCapitalizationButtonReady(true); + setAutoCapitalization(state); + }; + const submitChanges = (newWorkspaceName: string) => { renameWorkspace(workspaceId, newWorkspaceName); setButtonReady(false); }; + const submitAutoCapitalizationChanges = () => { + toggleAutoCapitalization(workspaceId, autoCapitalization); + setAutoCapitalizationButtonReady(false); + }; + + const closeAddServiceTokenModal = () => { setIsAddServiceTokenDialogOpen(false); }; @@ -196,6 +212,28 @@ export default function SettingsBasic() {
+
+

{t('settings-project:auto-capitalization')}

+ toggleAutoCapitalizationCheckBox(state as boolean)} + > {t('settings-project:auto-capitalization-description')} +
+
+
+
+

{t('common:project-id')}

From 44b2bc1795fb48f7084ab6a30a0b9aee35c3637c Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 2 Feb 2023 12:57:54 -0800 Subject: [PATCH 04/52] modify method to check for cli updates --- cli/packages/util/check-for-update.go | 4 ++-- cli/packages/util/vault.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cli/packages/util/check-for-update.go b/cli/packages/util/check-for-update.go index 5fbf39a1e..a23b90ad1 100644 --- a/cli/packages/util/check-for-update.go +++ b/cli/packages/util/check-for-update.go @@ -2,10 +2,10 @@ package util import ( "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" - "errors" ) func CheckForUpdate() { @@ -42,5 +42,5 @@ func getLatestTag(repoOwner string, repoName string) (string, error) { json.Unmarshal(body, &tags) - return tags[0].Name, nil + return tags[0].Name[1:], nil } diff --git a/cli/packages/util/vault.go b/cli/packages/util/vault.go index 7e4ce1b31..0852e5f48 100644 --- a/cli/packages/util/vault.go +++ b/cli/packages/util/vault.go @@ -57,7 +57,7 @@ func fileKeyringPassphrasePrompt(prompt string) (string, error) { if password, ok := os.LookupEnv("INFISICAL_VAULT_FILE_PASSPHRASE"); ok { return password, nil } else { - fmt.Println("You may set the `INFISICAL_VAULT_FILE_PASSPHRASE` environment variable to avoid typing password") + fmt.Println("You may set the environment variable `INFISICAL_VAULT_FILE_PASSPHRASE` with your password to avoid typing it") } fmt.Fprintf(os.Stderr, "%s:", prompt) @@ -65,6 +65,7 @@ func fileKeyringPassphrasePrompt(prompt string) (string, error) { if err != nil { return "", err } + fmt.Println("") return string(b), nil } From a4926d8833edc2de40ac1329479233501c537bd5 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 3 Feb 2023 12:32:54 +0700 Subject: [PATCH 05/52] Add back requestIp middleware --- backend/src/app.ts | 2 +- backend/src/controllers/v1/membershipOrgController.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 066da16d4..79561d00c 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -73,7 +73,7 @@ app.use( }) ); -// app.use(requestIp.mw()) +app.use(requestIp.mw()) if (NODE_ENV === 'production') { // enable app-wide rate-limiting + helmet security diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index f3703b889..0040b583e 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -77,8 +77,6 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => { // change role for (target) organization membership with id // [membershipOrgId] - // TODO - let membershipToChangeRole; // try { // } catch (err) { From bd230a8b7d700dd4bca8510c083396692dce8f43 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 3 Feb 2023 12:40:35 +0700 Subject: [PATCH 06/52] Remove comment from loginSRPDetail --- backend/src/models/LoginSRPDetail.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/models/LoginSRPDetail.ts b/backend/src/models/LoginSRPDetail.ts index 6b615db97..6f3b579bd 100644 --- a/backend/src/models/LoginSRPDetail.ts +++ b/backend/src/models/LoginSRPDetail.ts @@ -26,6 +26,4 @@ const loginSRPDetailSchema = new Schema( const LoginSRPDetail = model('LoginSRPDetail', loginSRPDetailSchema); -// LoginSRPDetailSchema.index({ "expireAt": 1 }, { expireAfterSeconds: 0 }); - export default LoginSRPDetail; From ee555f3f1531dbbe876fb36337192a8e8c597915 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 3 Feb 2023 12:46:14 +0700 Subject: [PATCH 07/52] Rename loginSRPDetail file --- backend/src/models/{LoginSRPDetail.ts => loginSRPDetail.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/src/models/{LoginSRPDetail.ts => loginSRPDetail.ts} (100%) diff --git a/backend/src/models/LoginSRPDetail.ts b/backend/src/models/loginSRPDetail.ts similarity index 100% rename from backend/src/models/LoginSRPDetail.ts rename to backend/src/models/loginSRPDetail.ts From 76d01270293fad6e30fe9150600a15ae26e1fdeb Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 3 Feb 2023 15:50:26 +0700 Subject: [PATCH 08/52] Add docs for PM2 integration, update Docker/Docker-Compose integration docs --- docs/integrations/overview.mdx | 1 + .../integrations/platforms/docker-compose.mdx | 31 +++++------ docs/integrations/platforms/docker.mdx | 36 +++++++------ docs/integrations/platforms/pm2.mdx | 52 +++++++++++++++++++ docs/mint.json | 3 +- 5 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 docs/integrations/platforms/pm2.mdx diff --git a/docs/integrations/overview.mdx b/docs/integrations/overview.mdx index 4f1e99227..36423e787 100644 --- a/docs/integrations/overview.mdx +++ b/docs/integrations/overview.mdx @@ -12,6 +12,7 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi | [Docker](/integrations/platforms/docker) | Platform | Available | | [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available | | [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available | +| [PM2](/integrations/platforms/pm2) | Platform | Available | | [Heroku](/integrations/cloud/heroku) | Cloud | Available | | [Vercel](/integrations/cloud/vercel) | Cloud | Available | | [Netlify](/integrations/cloud/netlify) | Cloud | Available | diff --git a/docs/integrations/platforms/docker-compose.mdx b/docs/integrations/platforms/docker-compose.mdx index 698316582..e030869e3 100644 --- a/docs/integrations/platforms/docker-compose.mdx +++ b/docs/integrations/platforms/docker-compose.mdx @@ -1,45 +1,38 @@ --- title: "Docker Compose" -description: "How to use Infisical to inject environment variables into container defined in your Docker Compose file." +description: "How to use Infisical to inject environment variables into services defined in your Docker Compose file." --- -The Docker Compose integration enables you to inject environment variables from Infisical into the containers defined in your compose file. +Prerequisites: -## Add the CLI to your Dockerfile(s) start command +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) -Follow the [guide to configure Infisical CLI](./docker) in your your Dockerfile first. +## Configure the Infisical CLI for each service -## Generate Infisical Token +Follow this [guide](./docker) to configure the Infisical CLI for each service that you wish to inject environment variables into; you'll have to update the Dockerfile of each service. -In order for Infisical CLI to authenticate and retrieve your project's secrets without exposing your login credentials, you must generate a Infisical Token. -To learn how, visit [Infisical Token](../../getting-started/dashboard/token). Once you have generated the token, keep it handy. +## Generate Infisical Tokens - - If you have multiple services and they do not use the same secrets, you will - have to generate a Infisical Token for each service. - +Generate a unique [Infisical Token](https://infisical.com/docs/getting-started/dashboard/token) for each service. -## Tell Docker Compose your Infisical Token +## Add Infisical Tokens to your Docker Compose file -For each service you want to inject secrets into, set an environment variable called `INFISICAL_TOKEN` equal to a helpful identifier variable. -This will ensure that you can set Infisical Tokens for multiple services. +For each service you want to inject secrets into, set an environment variable called `INFISICAL_TOKEN` equal to a unique identifier variable. -For the example below, we have set `INFISICAL_TOKEN_FOR_WEB` and `INFISICAL_TOKEN_FOR_API` as the `INFISICAL_TOKEN` for the corresponding service. +In the example below, we set `INFISICAL_TOKEN_FOR_WEB` and `INFISICAL_TOKEN_FOR_API` as the `INFISICAL_TOKEN` for the services. ```yaml # Example Docker Compose file services: web: build: . - image: auledge-frontend - container_name: auledge-frontend + image: example-service-1 environment: - INFISICAL_TOKEN=${INFISICAL_TOKEN_FOR_WEB} api: build: . - image: auledge-backend - container_name: auledge-backend + image: example-service-2 environment: - INFISICAL_TOKEN=${INFISICAL_TOKEN_FOR_API} ``` diff --git a/docs/integrations/platforms/docker.mdx b/docs/integrations/platforms/docker.mdx index 8f53336ed..b4224d6d0 100644 --- a/docs/integrations/platforms/docker.mdx +++ b/docs/integrations/platforms/docker.mdx @@ -3,9 +3,11 @@ title: "Docker" description: "How to use Infisical to inject environment variables into a Docker container." --- -Infisical can be used in a Dockerfile to inject environment variables into a Docker container. +Prerequisites: -## Add the CLI to your Dockerfile +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) + +## Add the Infisical CLI to your Dockerfile @@ -32,32 +34,32 @@ Infisical can be used in a Dockerfile to inject environment variables into a Doc - ## Modify your Dockerfile start command +## Modify the start command in your Dockerfile - To make your Docker container consume Infisical secrets, you can start your application with Infisical. - This will automatically pull the necessary secrets and make them available to your application as if they were natively exposed within the container. +Starting your service with the Infisical CLI pulls your secrets from Infisical and injects them into your service. - ```dockerfile - CMD ["infisical", "run", "---", "[your application start command]"] +```dockerfile +CMD ["infisical", "run", "---", "[your service start command]"] - # example with single single command - CMD ["infisical", "run", "---", "npm run start"] +# example with single single command +CMD ["infisical", "run", "---", "npm run start"] - # example with multiple commands - CMD ["infisical", "run", "--command", "npm run start && ..."] - ``` +# example with multiple commands +CMD ["infisical", "run", "--command", "npm run start && ..."] +``` - View more options for the `run` command [here](../../cli/commands/run) +## Generate an Infisical Token + +Head to your project settings in Infisical Cloud to generate an [Infisical Token](https://infisical.com/docs/getting-started/dashboard/token). ## Feed Docker your Infisical Token -The CLI looks out for an environment variable called `INFISICAL_TOKEN`. If the token is detected, the CLI will authenticate, retrieve, and inject the environment variables which the token is authorized for. - ```bash docker run --env INFISICAL_TOKEN=[token]... ``` -## Generate an Infisical Token + -[Generate an Infisical Token](../../getting-started/dashboard/token) and keep it handy. +The Infisical CLI uses the detected `INFISICAL_TOKEN` environment variable to authenticate, retrieve, and inject the environment variables which the token is authorized for. + diff --git a/docs/integrations/platforms/pm2.mdx b/docs/integrations/platforms/pm2.mdx new file mode 100644 index 000000000..e9b04bbb2 --- /dev/null +++ b/docs/integrations/platforms/pm2.mdx @@ -0,0 +1,52 @@ +--- +title: "PM2" +description: "How to use Infisical to inject environment variables and secrets with PM2 into a Node.js app" +--- + +Prerequisites: + +- Set up and add envars to [Infisical Cloud](https://app.infisical.com) +- [Install the CLI](/cli/overview) + +## Initialize Infisical for your Node.js app + +```bash +# navigate to the root of your of your project +cd /path/to/project + +# then initialize infisical +infisical init +``` + +## Create a bash or js script + + + +```bash infisical-run.sh +infisical run -- npm start +``` + +```js infisical-run.js +const spawn = require("child_process").spawn; + +const infisical = spawn("infisical", ["run", "--", "npm", "start"]); + +infisical.stdout.on("data", (data) => console.log(`${data}`)); +infisical.stderr.on("data", (data) => console.error(`${data}`)); +``` + + + +## Start your application as usual but with the script + + + +```bash infisical-run.sh +pm2 start infisical-run.sh +``` + +```bash infisical-run.js +pm2 start infisical-run.js +``` + + diff --git a/docs/mint.json b/docs/mint.json index 4ce1d6c03..441769152 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -209,7 +209,8 @@ "pages": [ "integrations/platforms/docker", "integrations/platforms/docker-compose", - "integrations/platforms/kubernetes" + "integrations/platforms/kubernetes", + "integrations/platforms/pm2" ] }, { From 3859a7e09baccf54ea06f55994b022c09e593c63 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 3 Feb 2023 22:16:31 +0530 Subject: [PATCH 09/52] feat(ui): added new react-query hooks for settings page --- frontend/package-lock.json | 273 ++++++++++++------ frontend/package.json | 5 +- frontend/src/hooks/api/index.tsx | 6 +- frontend/src/hooks/api/keys/index.tsx | 1 + frontend/src/hooks/api/keys/queries.tsx | 24 ++ frontend/src/hooks/api/keys/types.ts | 22 ++ frontend/src/hooks/api/serviceTokens/index.ts | 1 + .../src/hooks/api/serviceTokens/queries.tsx | 60 ++++ frontend/src/hooks/api/serviceTokens/types.ts | 28 ++ .../src/hooks/api/subscriptions/index.tsx | 1 + .../src/hooks/api/subscriptions/queries.tsx | 30 ++ frontend/src/hooks/api/subscriptions/types.ts | 25 ++ frontend/src/hooks/api/types.ts | 13 + frontend/src/hooks/api/workspace/index.tsx | 8 + frontend/src/hooks/api/workspace/queries.tsx | 94 ++++++ frontend/src/hooks/api/workspace/types.ts | 29 ++ frontend/src/hooks/usePopUp.tsx | 6 +- 17 files changed, 529 insertions(+), 97 deletions(-) create mode 100644 frontend/src/hooks/api/keys/index.tsx create mode 100644 frontend/src/hooks/api/keys/queries.tsx create mode 100644 frontend/src/hooks/api/keys/types.ts create mode 100644 frontend/src/hooks/api/serviceTokens/index.ts create mode 100644 frontend/src/hooks/api/serviceTokens/queries.tsx create mode 100644 frontend/src/hooks/api/serviceTokens/types.ts create mode 100644 frontend/src/hooks/api/subscriptions/index.tsx create mode 100644 frontend/src/hooks/api/subscriptions/queries.tsx create mode 100644 frontend/src/hooks/api/subscriptions/types.ts create mode 100644 frontend/src/hooks/api/types.ts create mode 100644 frontend/src/hooks/api/workspace/index.tsx create mode 100644 frontend/src/hooks/api/workspace/queries.tsx create mode 100644 frontend/src/hooks/api/workspace/types.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f5bb934fe..283c69b53 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "@fortawesome/free-solid-svg-icons": "^6.1.2", "@fortawesome/react-fontawesome": "^0.1.19", "@headlessui/react": "^1.6.6", + "@hookform/resolvers": "^2.9.10", "@radix-ui/react-accordion": "^1.1.0", "@radix-ui/react-alert-dialog": "^1.0.2", "@radix-ui/react-checkbox": "^1.0.1", @@ -51,6 +52,7 @@ "react-code-input": "^3.10.1", "react-dom": "^17.0.2", "react-grid-layout": "^1.3.4", + "react-hook-form": "^7.43.0", "react-i18next": "^12.1.1", "react-mailchimp-subscribe": "^2.1.3", "react-markdown": "^8.0.3", @@ -64,7 +66,8 @@ "tweetnacl-util": "^0.15.1", "uuid": "^8.3.2", "uuidv4": "^6.2.13", - "yaml": "^2.2.0" + "yaml": "^2.2.0", + "yup": "^0.32.11" }, "devDependencies": { "@storybook/addon-essentials": "^7.0.0-beta.30", @@ -108,7 +111,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -121,7 +123,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -157,7 +158,6 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -166,7 +166,6 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -196,7 +195,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -212,14 +210,12 @@ "node_modules/@babel/core/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -265,7 +261,6 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.20.5", "@babel/helper-validator-option": "^7.18.6", @@ -284,7 +279,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "dependencies": { "yallist": "^3.0.2" } @@ -293,7 +287,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -301,8 +294,7 @@ "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.20.12", @@ -461,7 +453,6 @@ "version": "7.20.11", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-module-imports": "^7.18.6", @@ -535,7 +526,6 @@ "version": "7.20.2", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, "dependencies": { "@babel/types": "^7.20.2" }, @@ -586,7 +576,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -610,7 +599,6 @@ "version": "7.20.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", - "dev": true, "dependencies": { "@babel/template": "^7.18.10", "@babel/traverse": "^7.20.5", @@ -2897,6 +2885,14 @@ "react-dom": "^16 || ^17 || ^18" } }, + "node_modules/@hookform/resolvers": { + "version": "2.9.10", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-2.9.10.tgz", + "integrity": "sha512-JIL1DgJIlH9yuxcNGtyhsWX/PgNltz+5Gr6+8SX9fhXc/hPbEIk6wPI82nhgvp3uUb6ZfAM5mqg/x7KR7NAb+A==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -7005,8 +7001,7 @@ "node_modules/@types/lodash": { "version": "4.14.191", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", - "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", - "dev": true + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" }, "node_modules/@types/mdast": { "version": "3.0.10", @@ -8997,7 +8992,6 @@ "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -10680,8 +10674,7 @@ "node_modules/electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", - "dev": true + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -10977,7 +10970,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -12818,7 +12810,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -14991,7 +14982,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -15210,6 +15200,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -16183,6 +16178,11 @@ "object-assign": "^4.1.0" } }, + "node_modules/nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "node_modules/nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", @@ -16464,8 +16464,7 @@ "node_modules/node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "node_modules/normalize-package-data": { "version": "2.5.0", @@ -17625,6 +17624,11 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "node_modules/property-information": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", @@ -18142,6 +18146,21 @@ "react-dom": ">= 16.3.0" } }, + "node_modules/react-hook-form": { + "version": "7.43.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.0.tgz", + "integrity": "sha512-/rVEz7T0gLdSFwPqutJ1kn2e0sQNyb9ci/hmwEYr2YG0KF/LSuRLvNrf9QWJM+gj88CjDpDW5Bh/1AD7B2+z9Q==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-i18next": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.1.tgz", @@ -21168,6 +21187,11 @@ "node": ">=0.6" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -21404,7 +21428,7 @@ "version": "4.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21730,7 +21754,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -22480,6 +22503,23 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yup": { + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + }, + "engines": { + "node": ">=10" + } } }, "dependencies": { @@ -22487,7 +22527,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -22497,7 +22536,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, "requires": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -22525,14 +22563,12 @@ "@babel/compat-data": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", - "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==", - "dev": true + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==" }, "@babel/core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", - "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -22555,7 +22591,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -22563,14 +22598,12 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -22606,7 +22639,6 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, "requires": { "@babel/compat-data": "^7.20.5", "@babel/helper-validator-option": "^7.18.6", @@ -22619,7 +22651,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "requires": { "yallist": "^3.0.2" } @@ -22627,14 +22658,12 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -22753,7 +22782,6 @@ "version": "7.20.11", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-module-imports": "^7.18.6", @@ -22809,7 +22837,6 @@ "version": "7.20.2", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, "requires": { "@babel/types": "^7.20.2" } @@ -22844,8 +22871,7 @@ "@babel/helper-validator-option": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" }, "@babel/helper-wrap-function": { "version": "7.20.5", @@ -22863,7 +22889,6 @@ "version": "7.20.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", - "dev": true, "requires": { "@babel/template": "^7.18.10", "@babel/traverse": "^7.20.5", @@ -24061,7 +24086,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", - "dev": true + "dev": true, + "requires": {} }, "@emotion/utils": { "version": "1.2.0", @@ -24366,7 +24392,14 @@ "@headlessui/react": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.6.tgz", - "integrity": "sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q==" + "integrity": "sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q==", + "requires": {} + }, + "@hookform/resolvers": { + "version": "2.9.10", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-2.9.10.tgz", + "integrity": "sha512-JIL1DgJIlH9yuxcNGtyhsWX/PgNltz+5Gr6+8SX9fhXc/hPbEIk6wPI82nhgvp3uUb6ZfAM5mqg/x7KR7NAb+A==", + "requires": {} }, "@humanwhocodes/config-array": { "version": "0.11.8", @@ -27340,8 +27373,7 @@ "@types/lodash": { "version": "4.14.191", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", - "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", - "dev": true + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" }, "@types/mdast": { "version": "3.0.10", @@ -28016,7 +28048,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-node": { "version": "1.8.2", @@ -28150,7 +28183,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-align": { "version": "3.0.1", @@ -28444,7 +28478,8 @@ "axios-auth-refresh": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.3.tgz", - "integrity": "sha512-2IbDhJ/h6ddNBBnnzn1VFK/qx17pE9aVqiafB8rx5LVHsJ1HtFpUGkbXY7PzTG+8P9HJWcyA3fNZl9BikSuilg==" + "integrity": "sha512-2IbDhJ/h6ddNBBnnzn1VFK/qx17pE9aVqiafB8rx5LVHsJ1HtFpUGkbXY7PzTG+8P9HJWcyA3fNZl9BikSuilg==", + "requires": {} }, "axobject-query": { "version": "3.1.1", @@ -28459,7 +28494,8 @@ "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true + "dev": true, + "requires": {} }, "babel-loader": { "version": "9.1.2", @@ -28838,7 +28874,6 @@ "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, "requires": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -29668,7 +29703,8 @@ "cva": { "version": "npm:class-variance-authority@0.4.0", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.4.0.tgz", - "integrity": "sha512-74enNN8O9ZNieycac/y8FxqgyzZhZbxmCitAtAeUrLPlxjSd5zA7LfpprmxEcOmQBnaGs5hYhiSGnJ0mqrtBLQ==" + "integrity": "sha512-74enNN8O9ZNieycac/y8FxqgyzZhZbxmCitAtAeUrLPlxjSd5zA7LfpprmxEcOmQBnaGs5hYhiSGnJ0mqrtBLQ==", + "requires": {} }, "damerau-levenshtein": { "version": "1.0.8", @@ -30121,8 +30157,7 @@ "electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", - "dev": true + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" }, "emoji-regex": { "version": "9.2.2", @@ -30368,8 +30403,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-html": { "version": "1.0.3", @@ -30632,7 +30666,8 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", - "dev": true + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.7", @@ -30890,13 +30925,15 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-simple-import-sort": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-8.0.0.tgz", "integrity": "sha512-bXgJQ+lqhtQBCuWY/FUWdB27j4+lqcvXv5rUARkzbeWLwea+S5eBZEQrhnO+WgX3ZoJHVj0cn943iyXwByHHQw==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-storybook": { "version": "0.6.10", @@ -31760,8 +31797,7 @@ "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "get-caller-file": { "version": "2.0.5", @@ -32325,7 +32361,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -33339,8 +33376,7 @@ "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsonfile": { "version": "6.1.0", @@ -33508,6 +33544,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -33638,7 +33679,8 @@ "version": "7.1.8", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.8.tgz", "integrity": "sha512-rRSa1aFmFnpDRFAhv5vIkWM4nPaoB9vnzIjuIKa1wGupfn2hdCNeaQHKpu4/muoc8n8J7yowjTP2oncA4/Rbgg==", - "dev": true + "dev": true, + "requires": {} }, "md5.js": { "version": "1.3.5", @@ -34155,6 +34197,11 @@ "object-assign": "^4.1.0" } }, + "nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", @@ -34344,8 +34391,7 @@ "node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "normalize-package-data": { "version": "2.5.0", @@ -34935,7 +34981,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -35044,7 +35091,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.2.tgz", "integrity": "sha512-5RjUbWRe305pUpc48MosoIp6uxZvZxrM6GyOgsbGLTce+ehePKNm7ziW2dLG2air9aXbGuXlHVSQQw4Lbosq3w==", - "dev": true + "dev": true, + "requires": {} }, "pretty-error": { "version": "4.0.0", @@ -35132,6 +35180,11 @@ "react-is": "^16.13.1" } }, + "property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "property-information": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", @@ -35437,7 +35490,8 @@ "version": "5.6.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", - "dev": true + "dev": true, + "requires": {} }, "react-docgen": { "version": "5.4.3", @@ -35469,7 +35523,8 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", - "dev": true + "dev": true, + "requires": {} }, "react-dom": { "version": "17.0.2", @@ -35521,6 +35576,12 @@ "react-resizable": "^3.0.4" } }, + "react-hook-form": { + "version": "7.43.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.0.tgz", + "integrity": "sha512-/rVEz7T0gLdSFwPqutJ1kn2e0sQNyb9ci/hmwEYr2YG0KF/LSuRLvNrf9QWJM+gj88CjDpDW5Bh/1AD7B2+z9Q==", + "requires": {} + }, "react-i18next": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.1.tgz", @@ -35534,7 +35595,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.1.tgz", "integrity": "sha512-cxKSeFTf7jpSSVddm66sKdolG90qURAX3g1roTeaN6x0YEbtWc8JpmFN9+yIqLNH2uEkYerWLtJZIXRIFuBKrg==", - "dev": true + "dev": true, + "requires": {} }, "react-is": { "version": "16.13.1", @@ -35649,7 +35711,8 @@ "react-table": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", - "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==" + "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", + "requires": {} }, "read-cache": { "version": "1.0.0", @@ -35805,7 +35868,8 @@ "redux-thunk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==" + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "requires": {} }, "regenerate": { "version": "1.4.2", @@ -37258,7 +37322,8 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", - "dev": true + "dev": true, + "requires": {} }, "style-to-object": { "version": "0.3.0", @@ -37288,7 +37353,8 @@ "styled-jsx": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz", - "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==" + "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", + "requires": {} }, "stylis": { "version": "4.0.13", @@ -37750,6 +37816,11 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -37928,7 +37999,7 @@ "version": "4.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", - "dev": true + "devOptional": true }, "uc.micro": { "version": "1.0.6", @@ -38164,7 +38235,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -38202,12 +38272,14 @@ "use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "requires": {} }, "use-memo-one": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", - "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==" + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "requires": {} }, "use-sidecar": { "version": "1.1.2", @@ -38221,7 +38293,8 @@ "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} }, "util": { "version": "0.12.5", @@ -38412,7 +38485,8 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -38638,7 +38712,8 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "dev": true + "dev": true, + "requires": {} }, "xtend": { "version": "4.0.2", @@ -38698,6 +38773,20 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "yup": { + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", + "requires": { + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + } } } } diff --git a/frontend/package.json b/frontend/package.json index f97edd68f..322e60f31 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "@fortawesome/free-solid-svg-icons": "^6.1.2", "@fortawesome/react-fontawesome": "^0.1.19", "@headlessui/react": "^1.6.6", + "@hookform/resolvers": "^2.9.10", "@radix-ui/react-accordion": "^1.1.0", "@radix-ui/react-alert-dialog": "^1.0.2", "@radix-ui/react-checkbox": "^1.0.1", @@ -58,6 +59,7 @@ "react-code-input": "^3.10.1", "react-dom": "^17.0.2", "react-grid-layout": "^1.3.4", + "react-hook-form": "^7.43.0", "react-i18next": "^12.1.1", "react-mailchimp-subscribe": "^2.1.3", "react-markdown": "^8.0.3", @@ -71,7 +73,8 @@ "tweetnacl-util": "^0.15.1", "uuid": "^8.3.2", "uuidv4": "^6.2.13", - "yaml": "^2.2.0" + "yaml": "^2.2.0", + "yup": "^0.32.11" }, "devDependencies": { "@storybook/addon-essentials": "^7.0.0-beta.30", diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index 9df70dcf0..424fb514a 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -1 +1,5 @@ -export { useGetAuthToken } from './auth'; +export * from './auth'; +export * from './keys'; +export * from './serviceTokens'; +export * from './subscriptions'; +export * from './workspace'; diff --git a/frontend/src/hooks/api/keys/index.tsx b/frontend/src/hooks/api/keys/index.tsx new file mode 100644 index 000000000..23ffdf8d1 --- /dev/null +++ b/frontend/src/hooks/api/keys/index.tsx @@ -0,0 +1 @@ +export { useGetUserWsKey } from './queries'; diff --git a/frontend/src/hooks/api/keys/queries.tsx b/frontend/src/hooks/api/keys/queries.tsx new file mode 100644 index 000000000..d37c73f13 --- /dev/null +++ b/frontend/src/hooks/api/keys/queries.tsx @@ -0,0 +1,24 @@ +import { useQuery } from '@tanstack/react-query'; + +import { apiRequest } from '@app/config/request'; + +import { UserWsKeyPair } from './types'; + +const encKeyKeys = { + getUserWorkspaceKey: (workspaceID: string) => ['worksapce-key-pair', { workspaceID }] as const +}; + +const fetchUserWsKey = async (workspaceID: string) => { + const { data } = await apiRequest.get<{ latestKey: UserWsKeyPair }>( + `/api/v1/key/${workspaceID}/latest` + ); + + return data.latestKey; +}; + +export const useGetUserWsKey = (workspaceID: string) => + useQuery({ + queryKey: encKeyKeys.getUserWorkspaceKey(workspaceID), + queryFn: () => fetchUserWsKey(workspaceID), + enabled: Boolean(workspaceID) + }); diff --git a/frontend/src/hooks/api/keys/types.ts b/frontend/src/hooks/api/keys/types.ts new file mode 100644 index 000000000..bad61b5dd --- /dev/null +++ b/frontend/src/hooks/api/keys/types.ts @@ -0,0 +1,22 @@ +export type UserWsKeyPair = { + _id: string; + encryptedKey: string; + nonce: string; + sender: Sender; + receiver: string; + workspace: string; + createdAt: string; + updatedAt: string; + __v: number; +}; + +export type Sender = { + _id: string; + email: string; + createdAt: string; + updatedAt: string; + __v: number; + firstName: string; + lastName: string; + publicKey: string; +}; diff --git a/frontend/src/hooks/api/serviceTokens/index.ts b/frontend/src/hooks/api/serviceTokens/index.ts new file mode 100644 index 000000000..548ad313a --- /dev/null +++ b/frontend/src/hooks/api/serviceTokens/index.ts @@ -0,0 +1 @@ +export { useCreateServiceToken, useDeleteServiceToken, useGetUserWsServiceTokens } from './queries'; diff --git a/frontend/src/hooks/api/serviceTokens/queries.tsx b/frontend/src/hooks/api/serviceTokens/queries.tsx new file mode 100644 index 000000000..70e0736d3 --- /dev/null +++ b/frontend/src/hooks/api/serviceTokens/queries.tsx @@ -0,0 +1,60 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +import { apiRequest } from '@app/config/request'; + +import { + CreateServiceTokenDTO, + CreateServiceTokenRes, + DeleteServiceTokenRes, + ServiceToken +} from './types'; + +const serviceTokenKeys = { + getAllWorkspaceServiceToken: (workspaceID: string) => [{ workspaceID }, 'service-tokens'] as const +}; + +const fetchWorkspaceServiceTokens = async (workspaceID: string) => { + const { data } = await apiRequest.get<{ serviceTokenData: ServiceToken[] }>( + `/api/v2/workspace/${workspaceID}/service-token-data` + ); + + return data.serviceTokenData; +}; + +type UseGetWorkspaceServiceTokensProps = { workspaceID: string }; + +export const useGetUserWsServiceTokens = ({ workspaceID }: UseGetWorkspaceServiceTokensProps) => + useQuery({ + queryKey: serviceTokenKeys.getAllWorkspaceServiceToken(workspaceID), + queryFn: () => fetchWorkspaceServiceTokens(workspaceID), + enabled: Boolean(workspaceID) + }); + +// mutation +export const useCreateServiceToken = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (body) => { + const { data } = await apiRequest.post('/api/v2/service-token/', body); + return data; + }, + onSuccess: ({ serviceTokenData: { workspace } }) => { + queryClient.invalidateQueries(serviceTokenKeys.getAllWorkspaceServiceToken(workspace)); + } + }); +}; + +export const useDeleteServiceToken = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (serviceTokenId) => { + const { data } = await apiRequest.delete(`/api/v2/service-token/${serviceTokenId}`); + return data; + }, + onSuccess: ({ serviceTokenData: { workspace } }) => { + queryClient.invalidateQueries(serviceTokenKeys.getAllWorkspaceServiceToken(workspace)); + } + }); +}; diff --git a/frontend/src/hooks/api/serviceTokens/types.ts b/frontend/src/hooks/api/serviceTokens/types.ts new file mode 100644 index 000000000..a1eb73743 --- /dev/null +++ b/frontend/src/hooks/api/serviceTokens/types.ts @@ -0,0 +1,28 @@ +export type ServiceToken = { + _id: string; + name: string; + workspace: string; + environment: string; + user: string; + expiresAt: string; + createdAt: string; + updatedAt: string; + __v: number; +}; + +export type CreateServiceTokenDTO = { + name: string; + workspaceId: string; + environment: string; + expiresIn: number; + encryptedKey: string; + iv: string; + tag: string; +}; + +export type CreateServiceTokenRes = { + serviceToken: string; + serviceTokenData: ServiceToken; +}; + +export type DeleteServiceTokenRes = { serviceTokenData: ServiceToken }; diff --git a/frontend/src/hooks/api/subscriptions/index.tsx b/frontend/src/hooks/api/subscriptions/index.tsx new file mode 100644 index 000000000..4bb98e85a --- /dev/null +++ b/frontend/src/hooks/api/subscriptions/index.tsx @@ -0,0 +1 @@ +export { useGetOrgSubscription } from './queries'; diff --git a/frontend/src/hooks/api/subscriptions/queries.tsx b/frontend/src/hooks/api/subscriptions/queries.tsx new file mode 100644 index 000000000..4a65912ef --- /dev/null +++ b/frontend/src/hooks/api/subscriptions/queries.tsx @@ -0,0 +1,30 @@ +import { useQuery } from '@tanstack/react-query'; + +import { apiRequest } from '@app/config/request'; + +import { GetSubscriptionPlan } from './types'; + +// import { Workspace } from './types'; + +const subscriptionKeys = { + getOrgSubsription: (orgID: string) => ['subscription', { orgID }] as const +}; + +const fetchOrgSubscription = async (orgID: string) => { + const { data } = await apiRequest.get<{ subscriptions: GetSubscriptionPlan }>( + `/api/v1/organization/${orgID}/subscriptions` + ); + + return data.subscriptions; +}; + +type UseGetOrgSubscriptionProps = { + orgID: string; +}; + +export const useGetOrgSubscription = ({ orgID }: UseGetOrgSubscriptionProps) => + useQuery({ + queryKey: subscriptionKeys.getOrgSubsription(orgID), + queryFn: () => fetchOrgSubscription(orgID), + enabled: Boolean(orgID) + }); diff --git a/frontend/src/hooks/api/subscriptions/types.ts b/frontend/src/hooks/api/subscriptions/types.ts new file mode 100644 index 000000000..f9dfcbf0b --- /dev/null +++ b/frontend/src/hooks/api/subscriptions/types.ts @@ -0,0 +1,25 @@ +export type GetSubscriptionPlan = { + data: { plan: SubscriptionPlan }[]; +}; + +export type SubscriptionPlan = { + id: string; + object: string; + active: boolean; + aggregate_usage: unknown; + amount: 1400; + amount_decimal: 1400; + billing_scheme: string; + created: 1674833546; + currency: string; + interval: string; + interval_count: 1; + livemode: false; + metadata: {}; + nickname: null; + product: string; + tiers_mode: unknown; + transform_usage: unknown; + trial_period_days: unknown; + usage_type: string; +}; diff --git a/frontend/src/hooks/api/types.ts b/frontend/src/hooks/api/types.ts new file mode 100644 index 000000000..cc7498b72 --- /dev/null +++ b/frontend/src/hooks/api/types.ts @@ -0,0 +1,13 @@ +export type { GetAuthTokenAPI } from './auth/types'; +export type { UserWsKeyPair } from './keys/types'; +export type { CreateServiceTokenDTO, ServiceToken } from './serviceTokens/types'; +export type { GetSubscriptionPlan, SubscriptionPlan } from './subscriptions/types'; +export type { + CreateEnvironmentDTO, + DeleteEnvironmentDTO, + DeleteWorkspaceDTO, + RenameWorkspaceDTO, + UpdateEnvironmentDTO, + Workspace, + WorkspaceEnv +} from './workspace/types'; diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/workspace/index.tsx new file mode 100644 index 000000000..5de95ab05 --- /dev/null +++ b/frontend/src/hooks/api/workspace/index.tsx @@ -0,0 +1,8 @@ +export { + useCreateWsEnvironment, + useDeleteWorkspace, + useDeleteWsEnvironment, + useGetUserWorkspaces, + useRenameWorkspace, + useUpdateWsEnvironment +} from './queries'; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx new file mode 100644 index 000000000..b0566a43d --- /dev/null +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -0,0 +1,94 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +import { apiRequest } from '@app/config/request'; + +import { + CreateEnvironmentDTO, + DeleteEnvironmentDTO, + DeleteWorkspaceDTO, + RenameWorkspaceDTO, + UpdateEnvironmentDTO, + Workspace +} from './types'; + +const workspaceKeys = { + getAllUserWorkspace: ['workspaces'] as const +}; + +const fetchUserWorkspaces = async () => { + const { data } = await apiRequest.get<{ workspaces: Workspace[] }>('/api/v1/workspace'); + + return data.workspaces; +}; + +export const useGetUserWorkspaces = () => + useQuery(workspaceKeys.getAllUserWorkspace, fetchUserWorkspaces); + +// mutation +export const useRenameWorkspace = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, RenameWorkspaceDTO>({ + mutationFn: ({ workspaceID, newWorkspaceName }) => + apiRequest.post(`/api/v1/workspace/${workspaceID}/name`, { name: newWorkspaceName }), + onSuccess: () => { + queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); + } + }); +}; + +export const useDeleteWorkspace = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, DeleteWorkspaceDTO>({ + mutationFn: ({ workspaceID }) => apiRequest.delete(`/api/v1/workspace/${workspaceID}`), + onSuccess: () => { + queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); + } + }); +}; + +export const useCreateWsEnvironment = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, CreateEnvironmentDTO>({ + mutationFn: ({ workspaceID, environmentName, environmentSlug }) => + apiRequest.post(`/api/v2/workspace/${workspaceID}/environments`, { + environmentName, + environmentSlug + }), + onSuccess: () => { + queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); + } + }); +}; + +export const useUpdateWsEnvironment = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, UpdateEnvironmentDTO>({ + mutationFn: ({ workspaceID, environmentName, environmentSlug, oldEnvironmentSlug }) => + apiRequest.put(`/api/v2/workspace/${workspaceID}/environments`, { + environmentName, + environmentSlug, + oldEnvironmentSlug + }), + onSuccess: () => { + queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); + } + }); +}; + +export const useDeleteWsEnvironment = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, DeleteEnvironmentDTO>({ + mutationFn: ({ workspaceID, environmentSlug }) => + apiRequest.delete(`/api/v2/workspace/${workspaceID}/environments`, { + data: { environmentSlug } + }), + onSuccess: () => { + queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); + } + }); +}; diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts new file mode 100644 index 000000000..cc4709477 --- /dev/null +++ b/frontend/src/hooks/api/workspace/types.ts @@ -0,0 +1,29 @@ +export type Workspace = { + __v: number; + _id: string; + name: string; + organization: string; + environments: WorkspaceEnv[]; +}; + +export type WorkspaceEnv = { name: string; slug: string }; + +// mutation dto +export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string }; + +export type DeleteWorkspaceDTO = { workspaceID: string }; + +export type CreateEnvironmentDTO = { + workspaceID: string; + environmentSlug: string; + environmentName: string; +}; + +export type UpdateEnvironmentDTO = { + workspaceID: string; + oldEnvironmentSlug: string; + environmentSlug: string; + environmentName: string; +}; + +export type DeleteEnvironmentDTO = { workspaceID: string; environmentSlug: string }; diff --git a/frontend/src/hooks/usePopUp.tsx b/frontend/src/hooks/usePopUp.tsx index b0c4163be..52839142d 100644 --- a/frontend/src/hooks/usePopUp.tsx +++ b/frontend/src/hooks/usePopUp.tsx @@ -21,7 +21,7 @@ interface UsePopUpReturn | UsePopUpProps[]> { popUp: UsePopUpState; handlePopUpOpen: (popUpName: keyof UsePopUpState, data?: unknown) => void; handlePopUpClose: (popUpName: keyof UsePopUpState) => void; - handlePopUpToggle: (popUpName: keyof UsePopUpState) => void; + handlePopUpToggle: (popUpName: keyof UsePopUpState, state?: boolean) => void; } /** @@ -50,10 +50,10 @@ export const usePopUp = | UsePopUpProps[]>( setPopUp((oldState) => ({ ...oldState, [popUpName]: { isOpen: false } })); }, []); - const handlePopUpToggle = useCallback((popUpName: keyof UsePopUpState) => { + const handlePopUpToggle = useCallback((popUpName: keyof UsePopUpState, state?: boolean) => { setPopUp((oldState) => ({ ...oldState, - [popUpName]: { isOpen: !oldState[popUpName].isOpen } + [popUpName]: { isOpen: typeof state === 'undefined' ? !oldState[popUpName].isOpen : state } })); }, []); From 48cd84ce77cde98165a856a19cb0c3cd22ae2951 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 3 Feb 2023 22:17:54 +0530 Subject: [PATCH 10/52] feat(ui): fine tuning components library with exiting app design --- frontend/src/components/v2/Button/Button.tsx | 19 ++-- frontend/src/components/v2/Card/Card.tsx | 4 +- .../DeleteActionModal/DeleteActionModal.tsx | 89 +++++++++++++++++++ .../components/v2/DeleteActionModal/index.tsx | 1 + .../components/v2/FormControl/FormControl.tsx | 10 ++- .../components/v2/IconButton/IconButton.tsx | 2 +- frontend/src/components/v2/Input/Input.tsx | 5 +- frontend/src/components/v2/Modal/Modal.tsx | 16 ++-- frontend/src/components/v2/Modal/index.tsx | 4 +- frontend/src/components/v2/Select/Select.tsx | 22 ++--- frontend/src/components/v2/Table/Table.tsx | 28 +++--- .../v2/UpgradePlanModal/UpgradePlanModal.tsx | 36 ++++++++ .../components/v2/UpgradePlanModal/index.tsx | 1 + frontend/src/components/v2/index.tsx | 2 + 14 files changed, 192 insertions(+), 47 deletions(-) create mode 100644 frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx create mode 100644 frontend/src/components/v2/DeleteActionModal/index.tsx create mode 100644 frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx create mode 100644 frontend/src/components/v2/UpgradePlanModal/index.tsx diff --git a/frontend/src/components/v2/Button/Button.tsx b/frontend/src/components/v2/Button/Button.tsx index 21548d0e3..27f34838d 100644 --- a/frontend/src/components/v2/Button/Button.tsx +++ b/frontend/src/components/v2/Button/Button.tsx @@ -18,7 +18,8 @@ const buttonVariants = cva( 'font-inter font-medium', 'cursor-pointer', 'inline-flex items-center justify-center', - 'relative' + 'relative', + 'whitespace-nowrap' ], { variants: { @@ -33,7 +34,7 @@ const buttonVariants = cva( plain: '' }, isDisabled: { - true: 'bg-opacity-70 cursor-not-allowed', + true: 'bg-opacity-40 cursor-not-allowed', false: '' }, isFullWidth: { @@ -45,9 +46,9 @@ const buttonVariants = cva( false: '' }, size: { - xs: ['text-xs', 'py-1', 'px-2'], - sm: ['text-sm', 'py-2', 'px-4'], - md: ['text-md', 'py-2', 'px-6'], + xs: ['text-xs', 'py-1', 'px-1'], + sm: ['text-sm', 'py-2', 'px-2'], + md: ['text-md', 'py-2', 'px-4'], lg: ['text-lg', 'py-2', 'px-8'] } }, @@ -75,7 +76,7 @@ const buttonVariants = cva( { colorSchema: 'secondary', variant: 'plain', - className: 'text-mineshaft' + className: 'text-mineshaft-300' }, { colorSchema: 'danger', @@ -101,7 +102,7 @@ export const Button = forwardRef( children, isDisabled = false, className = '', - size = 'md', + size = 'sm', variant = 'solid', isFullWidth, isRounded = true, @@ -144,7 +145,7 @@ export const Button = forwardRef( )} ( {children} ( -

{children}
+
{children}
); export type CardProps = { @@ -47,7 +47,7 @@ export const Card = forwardRef(
void; + onChange?: (isOpen: boolean) => void; + deleteKey: string; + title: string; + onDeleteApproved: () => Promise; +}; + +export const DeleteActionModal = ({ + isOpen, + onClose, + onChange, + deleteKey, + onDeleteApproved, + title +}: Props): JSX.Element => { + const [inputData, setInputData] = useState(''); + const [isLoading, setIsLoading] = useToggle(); + + useEffect(() => { + setInputData(''); + }, [isOpen]); + + const onDelete = async () => { + setIsLoading.on(); + try { + await onDeleteApproved(); + } catch { + setIsLoading.off(); + } finally { + setIsLoading.off(); + } + }; + + return ( + { + setInputData(''); + if (onChange) onChange(isOpenState); + }} + > + + + +
+ } + onClose={onClose} + > +
+ + Type {deleteKey} to delete the resource +
+ } + className="mb-4" + > + setInputData(e.target.value)} /> + + + + + ); +}; diff --git a/frontend/src/components/v2/DeleteActionModal/index.tsx b/frontend/src/components/v2/DeleteActionModal/index.tsx new file mode 100644 index 000000000..b69763984 --- /dev/null +++ b/frontend/src/components/v2/DeleteActionModal/index.tsx @@ -0,0 +1 @@ +export { DeleteActionModal } from './DeleteActionModal'; diff --git a/frontend/src/components/v2/FormControl/FormControl.tsx b/frontend/src/components/v2/FormControl/FormControl.tsx index 14e58d5be..5a545acec 100644 --- a/frontend/src/components/v2/FormControl/FormControl.tsx +++ b/frontend/src/components/v2/FormControl/FormControl.tsx @@ -11,7 +11,7 @@ export type FormLabelProps = { }; export const FormLabel = ({ id, label, isRequired }: FormLabelProps) => ( - + {label} {isRequired && *} @@ -25,7 +25,7 @@ export type FormHelperTextProps = { export const FormHelperText = ({ isError, text }: FormHelperTextProps) => (
@@ -46,6 +46,7 @@ export type FormControlProps = { helperText?: ReactNode; errorText?: ReactNode; children: JSX.Element; + className?: string; }; export const FormControl = ({ @@ -55,10 +56,11 @@ export const FormControl = ({ helperText, errorText, id, - isError + isError, + className }: FormControlProps): JSX.Element => { return ( -
+
{typeof label === 'string' ? ( ) : ( diff --git a/frontend/src/components/v2/IconButton/IconButton.tsx b/frontend/src/components/v2/IconButton/IconButton.tsx index b1a1f4813..eb4919617 100644 --- a/frontend/src/components/v2/IconButton/IconButton.tsx +++ b/frontend/src/components/v2/IconButton/IconButton.tsx @@ -14,7 +14,7 @@ const iconButtonVariants = cva( [ 'button', 'transition-all', - 'font-inter font-medium', + 'font-inter font-medium user-select-none', 'cursor-pointer', 'inline-flex items-center justify-center', 'relative' diff --git a/frontend/src/components/v2/Input/Input.tsx b/frontend/src/components/v2/Input/Input.tsx index 4f502e31d..8c3685dac 100644 --- a/frontend/src/components/v2/Input/Input.tsx +++ b/frontend/src/components/v2/Input/Input.tsx @@ -8,6 +8,7 @@ type Props = { isRequired?: boolean; leftIcon?: ReactNode; rightIcon?: ReactNode; + isDisabled?: boolean; }; const inputVariants = cva( @@ -46,7 +47,7 @@ const inputParentContainerVariants = cva('inline-flex font-inter items-center bo }, isError: { true: 'border-red', - false: 'border-mineshaft-400' + false: 'border-mineshaft-500' }, isFullWidth: { true: 'w-full', @@ -70,6 +71,7 @@ export const Input = forwardRef( className, isRounded = true, isFullWidth = true, + isDisabled, isError = false, isRequired, leftIcon, @@ -87,6 +89,7 @@ export const Input = forwardRef( {...props} required={isRequired} ref={ref} + disabled={isDisabled} className={twMerge( leftIcon ? 'pl-9' : 'pl-4', rightIcon ? 'pr-9' : 'pr-4', diff --git a/frontend/src/components/v2/Modal/Modal.tsx b/frontend/src/components/v2/Modal/Modal.tsx index c7d42541d..eea12fbc5 100644 --- a/frontend/src/components/v2/Modal/Modal.tsx +++ b/frontend/src/components/v2/Modal/Modal.tsx @@ -11,31 +11,32 @@ export type ModalContentProps = DialogPrimitive.DialogContentProps & { title?: ReactNode; subTitle?: string; footerContent?: ReactNode; + onClose?: () => void; }; export const ModalContent = forwardRef( - ({ children, title, subTitle, className, footerContent, ...props }, forwardedRef) => ( + ({ children, title, subTitle, className, footerContent, onClose, ...props }, forwardedRef) => ( {title && {title}} {children} {footerContent && {footerContent}} - + @@ -55,3 +56,6 @@ export const Modal = ({ isOpen, ...props }: ModalProps) => ( export const ModalTrigger = DialogPrimitive.Trigger; export type ModalTriggerProps = DialogPrimitive.DialogTriggerProps; + +export const ModalClose = DialogPrimitive.Close; +export type ModalCloseProps = DialogPrimitive.DialogCloseProps; diff --git a/frontend/src/components/v2/Modal/index.tsx b/frontend/src/components/v2/Modal/index.tsx index c97a564fb..7517b3ec1 100644 --- a/frontend/src/components/v2/Modal/index.tsx +++ b/frontend/src/components/v2/Modal/index.tsx @@ -1,2 +1,2 @@ -export type { ModalContentProps, ModalProps, ModalTriggerProps } from './Modal'; -export { Modal, ModalContent, ModalTrigger } from './Modal'; +export type { ModalCloseProps, ModalContentProps, ModalProps, ModalTriggerProps } from './Modal'; +export { Modal, ModalClose, ModalContent, ModalTrigger } from './Modal'; diff --git a/frontend/src/components/v2/Select/Select.tsx b/frontend/src/components/v2/Select/Select.tsx index c093b8b70..e76e90260 100644 --- a/frontend/src/components/v2/Select/Select.tsx +++ b/frontend/src/components/v2/Select/Select.tsx @@ -22,21 +22,23 @@ export const Select = forwardRef( - {!props.disabled && - - } + {!props.disabled && ( + + + + )} @@ -75,11 +77,11 @@ export const SelectItem = forwardRef( (
( @@ -47,7 +47,7 @@ export type THeadProps = { }; export const THead = ({ children, className }: THeadProps): JSX.Element => ( - + {children} ); @@ -56,15 +56,17 @@ export const THead = ({ children, className }: THeadProps): JSX.Element => ( export type TrProps = { children: ReactNode; className?: string; -}; +} & HTMLAttributes; -export const Tr = ({ children, className }: TrProps): JSX.Element => ( - {children} +export const Tr = ({ children, className, ...props }: TrProps): JSX.Element => ( + + {children} + ); // table head columns export type ThProps = { - children: ReactNode; + children?: ReactNode; className?: string; }; @@ -84,10 +86,12 @@ export const TBody = ({ children, className }: TBodyProps): JSX.Element => ( // table body columns export type TdProps = { - children: ReactNode; + children?: ReactNode; className?: string; -}; +} & TdHTMLAttributes; -export const Td = ({ children, className }: TdProps): JSX.Element => ( - +export const Td = ({ children, className, ...props }: TdProps): JSX.Element => ( + ); diff --git a/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx b/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx new file mode 100644 index 000000000..e991ca3a8 --- /dev/null +++ b/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx @@ -0,0 +1,36 @@ +import Link from 'next/link'; + +import { Button } from '../Button'; +import { Modal, ModalClose, ModalContent } from '../Modal'; + +type Props = { + isOpen?: boolean; + onOpenChange?: (isOpen: boolean) => void; + text: string; +}; + +export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Element => ( + + + + , + + + + ]} + > +

{text}

+

+ Upgrade and get access to this, as well as to other powerful enhancements. +

+
+
+); diff --git a/frontend/src/components/v2/UpgradePlanModal/index.tsx b/frontend/src/components/v2/UpgradePlanModal/index.tsx new file mode 100644 index 000000000..e4649c2df --- /dev/null +++ b/frontend/src/components/v2/UpgradePlanModal/index.tsx @@ -0,0 +1 @@ +export { UpgradePlanModal } from './UpgradePlanModal'; diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index 437114047..0f89a3a2b 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -1,6 +1,7 @@ export * from './Button'; export * from './Card'; export * from './Checkbox'; +export * from './DeleteActionModal'; export * from './Dropdown'; export * from './FormControl'; export * from './IconButton'; @@ -12,3 +13,4 @@ export * from './Spinner'; export * from './Switch'; export * from './Table'; export * from './TextArea'; +export * from './UpgradePlanModal'; From a4f78437278a593a92513283742b7ac461f3b9b2 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 3 Feb 2023 22:18:30 +0530 Subject: [PATCH 11/52] feat(ui): global workspace and subscription context --- .../SubscriptionContext.tsx | 47 +++++++++++++++++ .../src/context/SubscriptionContext/index.tsx | 1 + .../WorkspaceContext/WorkspaceContext.tsx | 52 +++++++++++++++++++ .../src/context/WorkspaceContext/index.tsx | 1 + frontend/src/context/index.tsx | 2 + 5 files changed, 103 insertions(+) create mode 100644 frontend/src/context/SubscriptionContext/SubscriptionContext.tsx create mode 100644 frontend/src/context/SubscriptionContext/index.tsx create mode 100644 frontend/src/context/WorkspaceContext/WorkspaceContext.tsx create mode 100644 frontend/src/context/WorkspaceContext/index.tsx create mode 100644 frontend/src/context/index.tsx diff --git a/frontend/src/context/SubscriptionContext/SubscriptionContext.tsx b/frontend/src/context/SubscriptionContext/SubscriptionContext.tsx new file mode 100644 index 000000000..ae9910224 --- /dev/null +++ b/frontend/src/context/SubscriptionContext/SubscriptionContext.tsx @@ -0,0 +1,47 @@ +import { createContext, ReactNode, useContext, useMemo } from 'react'; + +import { useGetOrgSubscription } from '@app/hooks/api'; +import { GetSubscriptionPlan } from '@app/hooks/api/types'; + +import { useWorkspace } from '../WorkspaceContext'; +// import { Subscription } from '@app/hooks/api/workspace/types'; + +type TSubscriptionContext = { + subscription?: GetSubscriptionPlan; + subscriptionPlan: string; + isLoading: boolean; +}; + +const SubscriptionContext = createContext(null); + +type Props = { + children: ReactNode; +}; + +export const SubscriptionProvider = ({ children }: Props): JSX.Element => { + const { currentWorkspace } = useWorkspace(); + const { data, isLoading } = useGetOrgSubscription({ + orgID: currentWorkspace?.organization || '' + }); + + // memorize the workspace details for the context + const value = useMemo( + () => ({ + subscription: data, + subscriptionPlan: data?.data?.[0]?.plan?.product || '', + isLoading + }), + [data, isLoading] + ); + + return {children}; +}; + +export const useSubscription = () => { + const ctx = useContext(SubscriptionContext); + if (!ctx) { + throw new Error('useSubscription has to be used within '); + } + + return ctx; +}; diff --git a/frontend/src/context/SubscriptionContext/index.tsx b/frontend/src/context/SubscriptionContext/index.tsx new file mode 100644 index 000000000..9a2b07b15 --- /dev/null +++ b/frontend/src/context/SubscriptionContext/index.tsx @@ -0,0 +1 @@ +export { SubscriptionProvider, useSubscription } from './SubscriptionContext'; diff --git a/frontend/src/context/WorkspaceContext/WorkspaceContext.tsx b/frontend/src/context/WorkspaceContext/WorkspaceContext.tsx new file mode 100644 index 000000000..ea82fbbe7 --- /dev/null +++ b/frontend/src/context/WorkspaceContext/WorkspaceContext.tsx @@ -0,0 +1,52 @@ +import { createContext, ReactNode, useContext, useEffect, useMemo } from 'react'; +import { useRouter } from 'next/router'; + +import { useGetUserWorkspaces } from '@app/hooks/api'; +import { Workspace } from '@app/hooks/api/workspace/types'; + +type TWorkspaceContext = { + workspaces: Workspace[]; + currentWorkspace?: Workspace; + isLoading: boolean; +}; + +const WorkspaceContext = createContext(null); + +type Props = { + children: ReactNode; +}; + +export const WorkspaceProvider = ({ children }: Props): JSX.Element => { + const { data: ws, isLoading } = useGetUserWorkspaces(); + const router = useRouter(); + const workspaceId = router.query.id; + + // memorize the workspace details for the context + const value = useMemo(() => { + return { + workspaces: ws || [], + currentWorkspace: (ws || []).find(({ _id: id }) => id === workspaceId), + isLoading + }; + }, [ws, workspaceId, isLoading]); + + useEffect(() => { + // not loading and current workspace is empty + // ws empty means user has no access to the ws + // push to the first workspace + if (!isLoading && !value?.currentWorkspace?._id) { + router.push(`/dashboard/${value.workspaces?.[0]?._id}`); + } + }, [value?.currentWorkspace?._id, isLoading, value.workspaces?.[0]?._id, router.pathname]); + + return {children}; +}; + +export const useWorkspace = () => { + const ctx = useContext(WorkspaceContext); + if (!ctx) { + throw new Error('useWorkspace has to be used within '); + } + + return ctx; +}; diff --git a/frontend/src/context/WorkspaceContext/index.tsx b/frontend/src/context/WorkspaceContext/index.tsx new file mode 100644 index 000000000..3496038c3 --- /dev/null +++ b/frontend/src/context/WorkspaceContext/index.tsx @@ -0,0 +1 @@ +export { useWorkspace, WorkspaceProvider } from './WorkspaceContext'; diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx new file mode 100644 index 000000000..1bf59f9ac --- /dev/null +++ b/frontend/src/context/index.tsx @@ -0,0 +1,2 @@ +export { SubscriptionProvider, useSubscription } from './SubscriptionContext'; +export { useWorkspace, WorkspaceProvider } from './WorkspaceContext'; From ed148a542d6671dc2f172e90d265777328e765cb Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 3 Feb 2023 22:19:03 +0530 Subject: [PATCH 12/52] feat(ui): implemented the new project settings page --- frontend/public/locales/en/section-token.json | 2 +- frontend/src/components/basic/Layout.tsx | 57 ++- .../basic/table/EnvironmentsTable.tsx | 57 ++- frontend/src/components/dashboard/SideBar.tsx | 4 +- .../components/navigation/NavBarDashboard.tsx | 2 +- .../src/components/navigation/NavHeader.tsx | 23 +- frontend/src/const.ts | 28 +- frontend/src/pages/_app.tsx | 16 +- frontend/src/pages/activity/[id].tsx | 2 +- frontend/src/pages/dashboard/[id].tsx | 4 +- frontend/src/pages/settings/project/[id].tsx | 328 +----------------- .../ProjectSettingsPage.tsx | 287 +++++++++++++++ .../CopyProjectIDSection.tsx | 66 ++++ .../components/CopyProjectIDSection/index.tsx | 1 + .../EnvironmentSection/EnvironmentSection.tsx | 239 +++++++++++++ .../components/EnvironmentSection/index.tsx | 1 + .../ProjectNameChangeSection.tsx | 71 ++++ .../ProjectNameChangeSection/index.tsx | 1 + .../ServiceTokenSection.tsx | 307 ++++++++++++++++ .../components/ServiceTokenSection/index.tsx | 1 + .../ProjectSettingsPage/components/index.tsx | 6 + .../Settings/ProjectSettingsPage/index.tsx | 1 + 22 files changed, 1099 insertions(+), 405 deletions(-) create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/CopyProjectIDSection/CopyProjectIDSection.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/CopyProjectIDSection/index.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentSection.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/index.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/ProjectNameChangeSection/ProjectNameChangeSection.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/ProjectNameChangeSection/index.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenSection.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/index.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/index.tsx diff --git a/frontend/public/locales/en/section-token.json b/frontend/public/locales/en/section-token.json index d12bd202c..ce468b1af 100644 --- a/frontend/public/locales/en/section-token.json +++ b/frontend/public/locales/en/section-token.json @@ -4,7 +4,7 @@ "add-new": "Add New Token", "add-dialog": { "title": "Add a service token for {{target}}", - "description": "Specify the name, environment, and expiry period. When a token is generated, you will only be able to see it once before it disappears. Make sure to save it somewhere.", + "description": "When a token is generated, you will only be able to see it once before it disappears. Make sure to save it somewhere.", "name": "Service Token Name", "add": "Add Service Token", "copy-service-token": "Copy your service token", diff --git a/frontend/src/components/basic/Layout.tsx b/frontend/src/components/basic/Layout.tsx index a3d6092a1..ba4cf73b2 100644 --- a/frontend/src/components/basic/Layout.tsx +++ b/frontend/src/components/basic/Layout.tsx @@ -187,10 +187,10 @@ const Layout = ({ children }: LayoutProps) => { }); const userWorkspaces = orgUserProjects; if ( - userWorkspaces.length === 0 && - router.asPath !== '/noprojects' && - !router.asPath.includes('home') && - !router.asPath.includes('settings') || + (userWorkspaces.length === 0 && + router.asPath !== '/noprojects' && + !router.asPath.includes('home') && + !router.asPath.includes('settings')) || router.asPath === '/dashboard/undefined' ) { router.push('/noprojects'); @@ -246,16 +246,15 @@ const Layout = ({ children }: LayoutProps) => { return ( <> -
-
{children} + {children} +