mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 01:52:22 +00:00
chore(frontend): fixed all eslint errors
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
|
||||
interface workspaceProps {
|
||||
actionId: string;
|
||||
interface WorkspaceProps {
|
||||
actionId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11,21 +10,18 @@ interface workspaceProps {
|
||||
* @param {string} obj.actionId - id of an action for which we are trying to get data
|
||||
* @returns
|
||||
*/
|
||||
const getActionData = async ({ actionId }: workspaceProps) => {
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/action/' + actionId, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
const getActionData = async ({ actionId }: WorkspaceProps) =>
|
||||
SecurityClient.fetchCall(`/api/v1/action/${actionId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).action;
|
||||
} else {
|
||||
console.log('Failed to get the info about an action');
|
||||
}
|
||||
console.log('Failed to get the info about an action');
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export default getActionData;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
|
||||
interface workspaceProps {
|
||||
workspaceId: string;
|
||||
interface WorkspaceProps {
|
||||
workspaceId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
userId: string;
|
||||
@@ -12,48 +11,53 @@ interface workspaceProps {
|
||||
/**
|
||||
* This function fetches the activity logs for a certain project
|
||||
* @param {object} obj
|
||||
* @param {string} obj.workspaceId - workspace id for which we are trying to get project log
|
||||
* @param {string} obj.workspaceId - workspace id for which we are trying to get project log
|
||||
* @param {object} obj.offset - teh starting point of logs that we want to pull
|
||||
* @param {object} obj.limit - how many logs will we output
|
||||
* @param {object} obj.userId - optional userId filter - will only query logs for that user
|
||||
* @param {string} obj.actionNames - optional actionNames filter - will only query logs for those actions
|
||||
* @returns
|
||||
*/
|
||||
const getProjectLogs = async ({ workspaceId, offset, limit, userId, actionNames }: workspaceProps) => {
|
||||
const getProjectLogs = async ({
|
||||
workspaceId,
|
||||
offset,
|
||||
limit,
|
||||
userId,
|
||||
actionNames
|
||||
}: WorkspaceProps) => {
|
||||
let payload;
|
||||
if (userId != "" && actionNames != '') {
|
||||
if (userId !== '' && actionNames !== '') {
|
||||
payload = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
sortBy: 'recent',
|
||||
userId: JSON.stringify(userId),
|
||||
actionNames: actionNames
|
||||
}
|
||||
} else if (userId != "") {
|
||||
actionNames
|
||||
};
|
||||
} else if (userId !== '') {
|
||||
payload = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
sortBy: 'recent',
|
||||
userId: JSON.stringify(userId)
|
||||
}
|
||||
} else if (actionNames != "") {
|
||||
};
|
||||
} else if (actionNames !== '') {
|
||||
payload = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
sortBy: 'recent',
|
||||
actionNames: actionNames
|
||||
}
|
||||
actionNames
|
||||
};
|
||||
} else {
|
||||
payload = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
sortBy: 'recent'
|
||||
}
|
||||
sortBy: 'recent'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/workspace/' + workspaceId + '/logs?' +
|
||||
new URLSearchParams(payload),
|
||||
`/api/v1/workspace/${workspaceId}/logs?${new URLSearchParams(payload)}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -61,11 +65,11 @@ const getProjectLogs = async ({ workspaceId, offset, limit, userId, actionNames
|
||||
}
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).logs;
|
||||
} else {
|
||||
console.log('Failed to get project logs');
|
||||
}
|
||||
console.log('Failed to get project logs');
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
|
||||
interface workspaceProps {
|
||||
workspaceId: string;
|
||||
interface WorkspaceProps {
|
||||
workspaceId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
@@ -10,30 +9,29 @@ interface workspaceProps {
|
||||
/**
|
||||
* This function fetches the secret snapshots for a certain project
|
||||
* @param {object} obj
|
||||
* @param {string} obj.workspaceId - project id for which we are trying to get project secret snapshots
|
||||
* @param {string} obj.workspaceId - project id for which we are trying to get project secret snapshots
|
||||
* @param {object} obj.offset - teh starting point of snapshots that we want to pull
|
||||
* @param {object} obj.limit - how many snapshots will we output
|
||||
* @returns
|
||||
*/
|
||||
const getProjectSecretShanpshots = async ({ workspaceId, offset, limit }: workspaceProps) => {
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/workspace/' + workspaceId + '/secret-snapshots?' +
|
||||
new URLSearchParams({
|
||||
const getProjectSecretShanpshots = async ({ workspaceId, offset, limit }: WorkspaceProps) =>
|
||||
SecurityClient.fetchCall(
|
||||
`/api/v1/workspace/${workspaceId}/secret-snapshots?${new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit)
|
||||
}), {
|
||||
})}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).secretSnapshots;
|
||||
} else {
|
||||
console.log('Failed to get project secret snapshots');
|
||||
}
|
||||
console.log('Failed to get project secret snapshots');
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export default getProjectSecretShanpshots;
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
|
||||
interface workspaceProps {
|
||||
workspaceId: string;
|
||||
interface WorkspaceProps {
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function fetches the count of secret snapshots for a certain project
|
||||
* @param {object} obj
|
||||
* @param {string} obj.workspaceId - project id for which we are trying to get project secret snapshots
|
||||
* @param {string} obj.workspaceId - project id for which we are trying to get project secret snapshots
|
||||
* @returns
|
||||
*/
|
||||
const getProjectSercetSnapshotsCount = async ({ workspaceId }: workspaceProps) => {
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/workspace/' + workspaceId + '/secret-snapshots/count', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
const getProjectSercetSnapshotsCount = async ({ workspaceId }: WorkspaceProps) =>
|
||||
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}/secret-snapshots/count`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).count;
|
||||
} else {
|
||||
console.log('Failed to get the count of project secret snapshots');
|
||||
}
|
||||
console.log('Failed to get the count of project secret snapshots');
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export default getProjectSercetSnapshotsCount;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
interface SnapshotProps {
|
||||
secretSnapshotId: string;
|
||||
secretSnapshotId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11,21 +10,18 @@ interface SnapshotProps {
|
||||
* @param {string} obj.secretSnapshotId - snapshot id for which we are trying to get secrets
|
||||
* @returns
|
||||
*/
|
||||
const getSecretSnapshotData = async ({ secretSnapshotId }: SnapshotProps) => {
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/secret-snapshot/' + secretSnapshotId, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
const getSecretSnapshotData = async ({ secretSnapshotId }: SnapshotProps) =>
|
||||
SecurityClient.fetchCall(`/api/v1/secret-snapshot/${secretSnapshotId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return (await res.json()).secretSnapshot;
|
||||
} else {
|
||||
console.log('Failed to get the secrets of a certain snapshot');
|
||||
}
|
||||
console.log('Failed to get the secrets of a certain snapshot');
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export default getSecretSnapshotData;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
|
||||
interface secretVersionProps {
|
||||
secretId: string;
|
||||
offset: number;
|
||||
interface SecretVersionProps {
|
||||
secretId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
@@ -15,13 +14,12 @@ interface secretVersionProps {
|
||||
* @param {number} obj.limit - how far our query goes
|
||||
* @returns
|
||||
*/
|
||||
const getSecretVersions = async ({ secretId, offset, limit }: secretVersionProps) => {
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/secret/' + secretId + '/secret-versions?' +
|
||||
new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit)
|
||||
}),
|
||||
const getSecretVersions = async ({ secretId, offset, limit }: SecretVersionProps) =>
|
||||
SecurityClient.fetchCall(
|
||||
`/api/v1/secret/${secretId}/secret-versions?${new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit)
|
||||
})}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -29,12 +27,11 @@ const getSecretVersions = async ({ secretId, offset, limit }: secretVersionProps
|
||||
}
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
return await res.json();
|
||||
} else {
|
||||
console.log('Failed to get secret version history');
|
||||
if (res && res.status === 200) {
|
||||
return res.json();
|
||||
}
|
||||
console.log('Failed to get secret version history');
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export default getSecretVersions;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
|
||||
/**
|
||||
* This function performs a rollback of secrets in a certain project
|
||||
@@ -7,24 +7,27 @@ import SecurityClient from '~/utilities/SecurityClient';
|
||||
* @param {number} obj.version - version to which we are rolling back
|
||||
* @returns
|
||||
*/
|
||||
const performSecretRollback = async ({ workspaceId, version }: { workspaceId: string; version: number; }) => {
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/workspace/' + workspaceId + "/secret-snapshots/rollback", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
version
|
||||
})
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
return (await res.json());
|
||||
} else {
|
||||
console.log('Failed to perform the secret rollback');
|
||||
const performSecretRollback = async ({
|
||||
workspaceId,
|
||||
version
|
||||
}: {
|
||||
workspaceId: string;
|
||||
version: number;
|
||||
}) =>
|
||||
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}/secret-snapshots/rollback`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
version
|
||||
})
|
||||
}).then(async (res) => {
|
||||
if (res && res.status === 200) {
|
||||
return res.json();
|
||||
}
|
||||
console.log('Failed to perform the secret rollback');
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export default performSecretRollback;
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import getActionData from "@app/ee/api/secrets/GetActionData";
|
||||
import { useEffect, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import getActionData from '@app/ee/api/secrets/GetActionData';
|
||||
import patienceDiff from '@app/ee/utilities/findTextDifferences';
|
||||
import getLatestFileKey from '@app/pages/api/workspace/getLatestFileKey';
|
||||
import { faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||
|
||||
import DashboardInputField from '../../components/dashboard/DashboardInputField';
|
||||
|
||||
|
||||
const {
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric
|
||||
} = require('../../components/utilities/cryptography/crypto');
|
||||
const nacl = require('tweetnacl');
|
||||
nacl.util = require('tweetnacl-util');
|
||||
|
||||
} from '../../components/utilities/cryptography/crypto';
|
||||
|
||||
interface SideBarProps {
|
||||
toggleSidebar: (value: string) => void;
|
||||
toggleSidebar: (value: string) => void;
|
||||
currentAction: string;
|
||||
}
|
||||
|
||||
@@ -41,11 +35,11 @@ interface DecryptedSecretProps {
|
||||
newSecretVersion: {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
};
|
||||
oldSecretVersion: {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface ActionProps {
|
||||
@@ -58,10 +52,7 @@ interface ActionProps {
|
||||
* @param {string} obj.currentAction - the action id for which a sidebar is being displayed
|
||||
* @returns the sidebar with the payload of user activity logs
|
||||
*/
|
||||
const ActivitySideBar = ({
|
||||
toggleSidebar,
|
||||
currentAction
|
||||
}: SideBarProps) => {
|
||||
const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [actionData, setActionData] = useState<DecryptedSecretProps[]>();
|
||||
@@ -72,7 +63,7 @@ const ActivitySideBar = ({
|
||||
const getLogData = async () => {
|
||||
setIsLoading(true);
|
||||
const tempActionData = await getActionData({ actionId: currentAction });
|
||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) })
|
||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||
|
||||
// #TODO: make this a separate function and reuse across the app
|
||||
@@ -86,12 +77,12 @@ const ActivitySideBar = ({
|
||||
privateKey: String(PRIVATE_KEY)
|
||||
});
|
||||
}
|
||||
|
||||
const decryptedSecretVersions = tempActionData.payload.secretVersions.map((encryptedSecretVersion: {
|
||||
newSecretVersion?: SecretProps;
|
||||
oldSecretVersion?: SecretProps;
|
||||
}) => {
|
||||
return {
|
||||
|
||||
const decryptedSecretVersions = tempActionData.payload.secretVersions.map(
|
||||
(encryptedSecretVersion: {
|
||||
newSecretVersion?: SecretProps;
|
||||
oldSecretVersion?: SecretProps;
|
||||
}) => ({
|
||||
newSecretVersion: {
|
||||
key: decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.newSecretVersion!.secretKeyCiphertext,
|
||||
@@ -107,79 +98,134 @@ const ActivitySideBar = ({
|
||||
})
|
||||
},
|
||||
oldSecretVersion: {
|
||||
key: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext
|
||||
? decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext,
|
||||
iv: encryptedSecretVersion.oldSecretVersion?.secretKeyIV,
|
||||
tag: encryptedSecretVersion.oldSecretVersion?.secretKeyTag,
|
||||
key: decryptedLatestKey
|
||||
}): undefined,
|
||||
key: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext
|
||||
? decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext,
|
||||
iv: encryptedSecretVersion.oldSecretVersion?.secretKeyIV,
|
||||
tag: encryptedSecretVersion.oldSecretVersion?.secretKeyTag,
|
||||
key: decryptedLatestKey
|
||||
})
|
||||
: undefined,
|
||||
value: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext
|
||||
? decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext,
|
||||
iv: encryptedSecretVersion.oldSecretVersion?.secretValueIV,
|
||||
tag: encryptedSecretVersion.oldSecretVersion?.secretValueTag,
|
||||
key: decryptedLatestKey
|
||||
}): undefined
|
||||
? decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext,
|
||||
iv: encryptedSecretVersion.oldSecretVersion?.secretValueIV,
|
||||
tag: encryptedSecretVersion.oldSecretVersion?.secretValueTag,
|
||||
key: decryptedLatestKey
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
setActionData(decryptedSecretVersions);
|
||||
setActionMetaData({name: tempActionData.name});
|
||||
setActionMetaData({ name: tempActionData.name });
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
getLogData();
|
||||
}, [currentAction]);
|
||||
|
||||
return <div className={`absolute border-l border-mineshaft-500 ${isLoading ? "bg-bunker-800" : "bg-bunker"} fixed h-full w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between`}>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full mb-8">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="infisical loading indicator"
|
||||
></Image>
|
||||
</div>
|
||||
) : (
|
||||
<div className='h-min overflow-y-auto'>
|
||||
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
|
||||
<p className="font-semibold text-lg text-bunker-200">{t("activity:event." + actionMetaData?.name)}</p>
|
||||
<div className='p-1' onClick={() => toggleSidebar("")}>
|
||||
<FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/>
|
||||
return (
|
||||
<div
|
||||
className={`absolute border-l border-mineshaft-500 ${
|
||||
isLoading ? 'bg-bunker-800' : 'bg-bunker'
|
||||
} fixed h-full w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full mb-8">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-min overflow-y-auto">
|
||||
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
|
||||
<p className="font-semibold text-lg text-bunker-200">
|
||||
{t(`activity:event.${actionMetaData?.name}`)}
|
||||
</p>
|
||||
<div
|
||||
className="p-1"
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => toggleSidebar('')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faX} className="w-4 h-4 text-bunker-300 cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col px-4">
|
||||
{(actionMetaData?.name === 'readSecrets' ||
|
||||
actionMetaData?.name === 'addSecrets' ||
|
||||
actionMetaData?.name === 'deleteSecrets') &&
|
||||
actionData?.map((item, id) => (
|
||||
<div key={`secret.${id + 1}`}>
|
||||
<div className="text-xs text-bunker-200 mt-4 pl-1">
|
||||
{item.newSecretVersion.key}
|
||||
</div>
|
||||
<DashboardInputField
|
||||
onChangeHandler={() => {}}
|
||||
type="value"
|
||||
position={1}
|
||||
value={item.newSecretVersion.value}
|
||||
isDuplicate={false}
|
||||
blurred={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{actionMetaData?.name === 'updateSecrets' &&
|
||||
actionData?.map((item, id) => (
|
||||
<>
|
||||
<div className="text-xs text-bunker-200 mt-4 pl-1">
|
||||
{item.newSecretVersion.key}
|
||||
</div>
|
||||
<div className="text-bunker-100 font-mono rounded-md overflow-hidden">
|
||||
<div className="bg-red/30 px-2">
|
||||
-{' '}
|
||||
{patienceDiff(
|
||||
item.oldSecretVersion.value.split(''),
|
||||
item.newSecretVersion.value.split(''),
|
||||
false
|
||||
).lines.map(
|
||||
(character, lineId) =>
|
||||
character.bIndex !== -1 && (
|
||||
<span
|
||||
key={`actionData.${id + 1}.line.${lineId + 1}`}
|
||||
className={`${character.aIndex === -1 && 'bg-red-700/80'}`}
|
||||
>
|
||||
{character.line}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-green-500/30 px-2">
|
||||
+{' '}
|
||||
{patienceDiff(
|
||||
item.oldSecretVersion.value.split(''),
|
||||
item.newSecretVersion.value.split(''),
|
||||
false
|
||||
).lines.map(
|
||||
(character, lineId) =>
|
||||
character.aIndex !== -1 && (
|
||||
<span
|
||||
key={`actionData.${id + 1}.linev2.${lineId + 1}`}
|
||||
className={`${character.bIndex === -1 && 'bg-green-700/80'}`}
|
||||
>
|
||||
{character.line}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col px-4'>
|
||||
{(actionMetaData?.name == 'readSecrets'
|
||||
|| actionMetaData?.name == 'addSecrets'
|
||||
|| actionMetaData?.name == 'deleteSecrets') && actionData?.map((item, id) =>
|
||||
<div key={id}>
|
||||
<div className='text-xs text-bunker-200 mt-4 pl-1'>{item.newSecretVersion.key}</div>
|
||||
<DashboardInputField
|
||||
key={id}
|
||||
onChangeHandler={() => {}}
|
||||
type="value"
|
||||
position={1}
|
||||
value={item.newSecretVersion.value}
|
||||
isDuplicate={false}
|
||||
blurred={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{actionMetaData?.name == 'updateSecrets' && actionData?.map((item, id) =>
|
||||
<>
|
||||
<div className='text-xs text-bunker-200 mt-4 pl-1'>{item.newSecretVersion.key}</div>
|
||||
<div className='text-bunker-100 font-mono rounded-md overflow-hidden'>
|
||||
<div className='bg-red/30 px-2'>- {patienceDiff(item.oldSecretVersion.value.split(''), item.newSecretVersion.value.split(''), false).lines.map((character, id) => character.bIndex != -1 && <span key={id} className={`${character.aIndex == -1 && "bg-red-700/80"}`}>{character.line}</span>)}</div>
|
||||
<div className='bg-green-500/30 px-2'>+ {patienceDiff(item.oldSecretVersion.value.split(''), item.newSecretVersion.value.split(''), false).lines.map((character, id) => character.aIndex != -1 && <span key={id} className={`${character.bIndex == -1 && "bg-green-700/80"}`}>{character.line}</span>)}</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActivitySideBar;
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
||||
import React, { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import timeSince from '@app/ee/utilities/timeSince';
|
||||
import {
|
||||
faAngleDown,
|
||||
faAngleRight,
|
||||
faUpRightFromSquare
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { faAngleDown, faAngleRight, faUpRightFromSquare } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import guidGenerator from '../../components/utilities/randomId';
|
||||
|
||||
|
||||
interface PayloadProps {
|
||||
_id: string;
|
||||
name: string;
|
||||
name: string;
|
||||
secretVersions: string[];
|
||||
}
|
||||
|
||||
interface logData {
|
||||
interface LogData {
|
||||
_id: string;
|
||||
channel: string;
|
||||
createdAt: string;
|
||||
@@ -27,15 +23,20 @@ interface logData {
|
||||
payload: PayloadProps[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a single row of the activity table
|
||||
* @param obj
|
||||
* @param {logData} obj.row - data for a certain event
|
||||
* @param obj
|
||||
* @param {LogData} obj.row - data for a certain event
|
||||
* @param {function} obj.toggleSidebar - open and close sidebar that displays data for a specific event
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar: (value: string) => void; }) => {
|
||||
const ActivityLogsRow = ({
|
||||
row,
|
||||
toggleSidebar
|
||||
}: {
|
||||
row: LogData;
|
||||
toggleSidebar: (value: string) => void;
|
||||
}) => {
|
||||
const [payloadOpened, setPayloadOpened] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -43,6 +44,7 @@ const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar:
|
||||
<>
|
||||
<tr key={guidGenerator()} className="bg-bunker-800 duration-100 w-full text-sm">
|
||||
<td
|
||||
onKeyDown={() => null}
|
||||
onClick={() => setPayloadOpened(!payloadOpened)}
|
||||
className="border-mineshaft-700 border-t text-gray-300 flex items-center cursor-pointer"
|
||||
>
|
||||
@@ -54,40 +56,58 @@ const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar:
|
||||
/>
|
||||
</td>
|
||||
<td className="py-3 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.payload?.map(action => String(action.secretVersions.length) + " " + t("activity:event." + action.name)).join(" and ")}
|
||||
</td>
|
||||
<td className="pl-6 py-3 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.user}
|
||||
</td>
|
||||
<td className="pl-6 py-3 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.channel}
|
||||
{row.payload
|
||||
?.map(
|
||||
(action) =>
|
||||
`${String(action.secretVersions.length)} ${t(`activity:event.${action.name}`)}`
|
||||
)
|
||||
.join(' and ')}
|
||||
</td>
|
||||
<td className="pl-6 py-3 border-mineshaft-700 border-t text-gray-300">{row.user}</td>
|
||||
<td className="pl-6 py-3 border-mineshaft-700 border-t text-gray-300">{row.channel}</td>
|
||||
<td className="pl-6 py-3 border-mineshaft-700 border-t text-gray-300">
|
||||
{timeSince(new Date(row.createdAt))}
|
||||
</td>
|
||||
</tr>
|
||||
{payloadOpened &&
|
||||
<tr className='h-9 text-bunker-200 border-mineshaft-700 border-t text-sm'>
|
||||
<td></td>
|
||||
<td>{String(t("common:timestamp"))}</td>
|
||||
<td>{row.createdAt}</td>
|
||||
</tr>}
|
||||
{payloadOpened && row.payload?.map((action, index) => {
|
||||
return action.secretVersions.length > 0 && <tr key={index} className="h-9 text-bunker-200 border-mineshaft-700 border-t text-sm">
|
||||
<td></td>
|
||||
<td className="">{t("activity:event." + action.name)}</td>
|
||||
<td className="text-primary-300 cursor-pointer hover:text-primary duration-200" onClick={() => toggleSidebar(action._id)}>
|
||||
{action.secretVersions.length + (action.secretVersions.length != 1 ? " secrets" : " secret")}
|
||||
<FontAwesomeIcon icon={faUpRightFromSquare} className="ml-2 mb-0.5 font-light w-3 h-3"/>
|
||||
</td>
|
||||
{payloadOpened && (
|
||||
<tr className="h-9 text-bunker-200 border-mineshaft-700 border-t text-sm">
|
||||
<td />
|
||||
<td>{String(t('common:timestamp'))}</td>
|
||||
<td>{row.createdAt}</td>
|
||||
</tr>
|
||||
})}
|
||||
)}
|
||||
{payloadOpened &&
|
||||
<tr className='h-9 text-bunker-200 border-mineshaft-700 border-t text-sm'>
|
||||
<td></td>
|
||||
<td>{String(t("activity:ip-address"))}</td>
|
||||
<td>{row.ipAddress}</td>
|
||||
</tr>}
|
||||
row.payload?.map(
|
||||
(action) =>
|
||||
action.secretVersions.length > 0 && (
|
||||
<tr
|
||||
key={action._id}
|
||||
className="h-9 text-bunker-200 border-mineshaft-700 border-t text-sm"
|
||||
>
|
||||
<td />
|
||||
<td className="">{t(`activity:event.${action.name}`)}</td>
|
||||
<td
|
||||
onKeyDown={() => null}
|
||||
className="text-primary-300 cursor-pointer hover:text-primary duration-200"
|
||||
onClick={() => toggleSidebar(action._id)}
|
||||
>
|
||||
{action.secretVersions.length +
|
||||
(action.secretVersions.length !== 1 ? ' secrets' : ' secret')}
|
||||
<FontAwesomeIcon
|
||||
icon={faUpRightFromSquare}
|
||||
className="ml-2 mb-0.5 font-light w-3 h-3"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
{payloadOpened && (
|
||||
<tr className="h-9 text-bunker-200 border-mineshaft-700 border-t text-sm">
|
||||
<td />
|
||||
<td>{String(t('activity:ip-address'))}</td>
|
||||
<td>{row.ipAddress}</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -100,37 +120,61 @@ const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar:
|
||||
* @param {boolean} obj.isLoading - whether the log data has been loaded yet or not
|
||||
* @returns
|
||||
*/
|
||||
const ActivityTable = ({ data, toggleSidebar, isLoading }: { data: logData[], toggleSidebar: (value: string) => void; isLoading: boolean; }) => {
|
||||
const ActivityTable = ({
|
||||
data,
|
||||
toggleSidebar,
|
||||
isLoading
|
||||
}: {
|
||||
data: LogData[];
|
||||
toggleSidebar: (value: string) => void;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="w-full px-6 mt-8">
|
||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative">
|
||||
<div className="absolute rounded-t-md w-full h-[3rem] bg-white/5"></div>
|
||||
<div className="absolute rounded-t-md w-full h-[3rem] bg-white/5" />
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-bunker-300">
|
||||
<tr className='text-sm'>
|
||||
<th className="text-left pl-6 pt-2.5 pb-3"></th>
|
||||
<th className="text-left font-semibold pt-2.5 pb-3">{String(t("common:event")).toUpperCase()}</th>
|
||||
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">{String(t("common:user")).toUpperCase()}</th>
|
||||
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">{String(t("common:source")).toUpperCase()}</th>
|
||||
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">{String(t("common:time")).toUpperCase()}</th>
|
||||
<th></th>
|
||||
<tr className="text-sm">
|
||||
<th aria-label="actions" className="text-left pl-6 pt-2.5 pb-3" />
|
||||
<th className="text-left font-semibold pt-2.5 pb-3">
|
||||
{String(t('common:event')).toUpperCase()}
|
||||
</th>
|
||||
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">
|
||||
{String(t('common:user')).toUpperCase()}
|
||||
</th>
|
||||
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">
|
||||
{String(t('common:source')).toUpperCase()}
|
||||
</th>
|
||||
<th className="text-left font-semibold pl-6 pt-2.5 pb-3">
|
||||
{String(t('common:time')).toUpperCase()}
|
||||
</th>
|
||||
<th aria-label="action" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.map((row, index) => {
|
||||
return <ActivityLogsRow key={index} row={row} toggleSidebar={toggleSidebar} />;
|
||||
})}
|
||||
{data?.map((row, index) => (
|
||||
<ActivityLogsRow
|
||||
key={`activity.${index + 1}.${row._id}`}
|
||||
row={row}
|
||||
toggleSidebar={toggleSidebar}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{isLoading && <div className='w-full flex justify-center mb-8 mt-4'><Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="loading animation"
|
||||
></Image></div>}
|
||||
{isLoading && (
|
||||
<div className="w-full flex justify-center mb-8 mt-4">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="loading animation"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import getProjectSecretShanpshots from "@app/ee/api/secrets/GetProjectSercetShanpshots";
|
||||
import getSecretSnapshotData from "@app/ee/api/secrets/GetSecretSnapshotData";
|
||||
import timeSince from "@app/ee/utilities/timeSince";
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { useEffect, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Button from '@app/components/basic/buttons/Button';
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric
|
||||
} from '@app/components/utilities/cryptography/crypto';
|
||||
import getProjectSecretShanpshots from '@app/ee/api/secrets/GetProjectSercetShanpshots';
|
||||
import getSecretSnapshotData from '@app/ee/api/secrets/GetSecretSnapshotData';
|
||||
import timeSince from '@app/ee/utilities/timeSince';
|
||||
import getLatestFileKey from '@app/pages/api/workspace/getLatestFileKey';
|
||||
import { faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import { decryptAssymmetric, decryptSymmetric } from "~/components/utilities/cryptography/crypto";
|
||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||
|
||||
|
||||
export interface SecretDataProps {
|
||||
pos: number;
|
||||
key: string;
|
||||
@@ -23,7 +25,7 @@ export interface SecretDataProps {
|
||||
}
|
||||
|
||||
interface SideBarProps {
|
||||
toggleSidebar: (value: boolean) => void;
|
||||
toggleSidebar: (value: boolean) => void;
|
||||
setSnapshotData: (value: any) => void;
|
||||
chosenSnapshot: string;
|
||||
}
|
||||
@@ -44,7 +46,7 @@ interface EncrypetedSecretVersionListProps {
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
environment: string;
|
||||
type: "personal" | "shared";
|
||||
type: 'personal' | 'shared';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,11 +56,7 @@ interface EncrypetedSecretVersionListProps {
|
||||
* @param {string} obj.chosenSnaphshot - the snapshot id which is currently selected
|
||||
* @returns the sidebar with the options for point-in-time recovery (commits)
|
||||
*/
|
||||
const PITRecoverySidebar = ({
|
||||
toggleSidebar,
|
||||
setSnapshotData,
|
||||
chosenSnapshot
|
||||
}: SideBarProps) => {
|
||||
const PITRecoverySidebar = ({ toggleSidebar, setSnapshotData, chosenSnapshot }: SideBarProps) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -68,22 +66,26 @@ const PITRecoverySidebar = ({
|
||||
|
||||
const loadMoreSnapshots = () => {
|
||||
setCurrentOffset(currentOffset + currentLimit);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const getLogData = async () => {
|
||||
setIsLoading(true);
|
||||
const results = await getProjectSecretShanpshots({ workspaceId: String(router.query.id), limit: currentLimit, offset: currentOffset })
|
||||
const results = await getProjectSecretShanpshots({
|
||||
workspaceId: String(router.query.id),
|
||||
limit: currentLimit,
|
||||
offset: currentOffset
|
||||
});
|
||||
setSecretSnapshotsMetadata(secretSnapshotsMetadata.concat(results));
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
getLogData();
|
||||
}, [currentOffset]);
|
||||
|
||||
const exploreSnapshot = async ({ snapshotId }: { snapshotId: string; }) => {
|
||||
const exploreSnapshot = async ({ snapshotId }: { snapshotId: string }) => {
|
||||
const secretSnapshotData = await getSecretSnapshotData({ secretSnapshotId: snapshotId });
|
||||
|
||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) })
|
||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||
|
||||
let decryptedLatestKey: string;
|
||||
@@ -97,10 +99,10 @@ const PITRecoverySidebar = ({
|
||||
});
|
||||
}
|
||||
|
||||
const decryptedSecretVersions = secretSnapshotData.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps, pos: number) => {
|
||||
return {
|
||||
const decryptedSecretVersions = secretSnapshotData.secretVersions.map(
|
||||
(encryptedSecretVersion: EncrypetedSecretVersionListProps, pos: number) => ({
|
||||
id: encryptedSecretVersion._id,
|
||||
pos: pos,
|
||||
pos,
|
||||
type: encryptedSecretVersion.type,
|
||||
environment: encryptedSecretVersion.environment,
|
||||
key: decryptSymmetric({
|
||||
@@ -115,69 +117,132 @@ const PITRecoverySidebar = ({
|
||||
tag: encryptedSecretVersion.secretValueTag,
|
||||
key: decryptedLatestKey
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
const secretKeys = [
|
||||
...new Set(decryptedSecretVersions.map((secret: SecretDataProps) => secret.key))
|
||||
];
|
||||
|
||||
const secretKeys = [...new Set(decryptedSecretVersions.map((secret: SecretDataProps) => secret.key))];
|
||||
|
||||
const result = secretKeys.map((key, index) => {
|
||||
return {
|
||||
id: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0].id,
|
||||
pos: index,
|
||||
key: key,
|
||||
environment: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0].environment,
|
||||
value: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0]?.value,
|
||||
valueOverride: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'personal')[0]?.value,
|
||||
}
|
||||
const result = secretKeys.map((key, index) => ({
|
||||
id: decryptedSecretVersions.filter(
|
||||
(secret: SecretDataProps) => secret.key === key && secret.type === 'shared'
|
||||
)[0].id,
|
||||
pos: index,
|
||||
key,
|
||||
environment: decryptedSecretVersions.filter(
|
||||
(secret: SecretDataProps) => secret.key === key && secret.type === 'shared'
|
||||
)[0].environment,
|
||||
value: decryptedSecretVersions.filter(
|
||||
(secret: SecretDataProps) => secret.key === key && secret.type === 'shared'
|
||||
)[0]?.value,
|
||||
valueOverride: decryptedSecretVersions.filter(
|
||||
(secret: SecretDataProps) => secret.key === key && secret.type === 'personal'
|
||||
)[0]?.value
|
||||
}));
|
||||
|
||||
setSnapshotData({
|
||||
id: secretSnapshotData._id,
|
||||
version: secretSnapshotData.version,
|
||||
createdAt: secretSnapshotData.createdAt,
|
||||
secretVersions: result,
|
||||
comment: ''
|
||||
});
|
||||
};
|
||||
|
||||
setSnapshotData({ id: secretSnapshotData._id, version: secretSnapshotData.version, createdAt: secretSnapshotData.createdAt, secretVersions: result, comment: '' })
|
||||
}
|
||||
|
||||
return <div className={`absolute border-l border-mineshaft-500 ${isLoading ? "bg-bunker-800" : "bg-bunker"} fixed h-full w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between`}>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full mb-8">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="infisical loading indicator"
|
||||
></Image>
|
||||
</div>
|
||||
) : (
|
||||
<div className='h-min'>
|
||||
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
|
||||
<p className="font-semibold text-lg text-bunker-200">{t("Point-in-time Recovery")}</p>
|
||||
<div className='p-1' onClick={() => toggleSidebar(false)}>
|
||||
<FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/>
|
||||
</div>
|
||||
return (
|
||||
<div
|
||||
className={`absolute border-l border-mineshaft-500 ${
|
||||
isLoading ? 'bg-bunker-800' : 'bg-bunker'
|
||||
} fixed h-full w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full mb-8">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col px-2 py-2 overflow-y-auto h-[92vh]'>
|
||||
{secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) =>
|
||||
<div
|
||||
key={snapshot._id}
|
||||
onClick={() => exploreSnapshot({ snapshotId: snapshot._id })}
|
||||
className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "bg-primary text-black pointer-events-none" : "bg-mineshaft-700 hover:bg-mineshaft-500 duration-200 cursor-pointer"} py-3 px-4 mb-2 rounded-md flex flex-row justify-between items-center`}
|
||||
) : (
|
||||
<div className="h-min">
|
||||
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
|
||||
<p className="font-semibold text-lg text-bunker-200">{t('Point-in-time Recovery')}</p>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="p-1"
|
||||
onClick={() => toggleSidebar(false)}
|
||||
>
|
||||
<div className="flex flex-row items-start">
|
||||
<div className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-800" : "text-bunker-200"} text-sm mr-1.5`}>{timeSince(new Date(snapshot.createdAt))}</div>
|
||||
<div className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-900" : "text-bunker-300"} text-sm `}>{" - " + snapshot.secretVersions.length + " Secrets"}</div>
|
||||
<FontAwesomeIcon icon={faX} className="w-4 h-4 text-bunker-300 cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col px-2 py-2 overflow-y-auto h-[92vh]">
|
||||
{secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) => (
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
key={snapshot._id}
|
||||
onClick={() => exploreSnapshot({ snapshotId: snapshot._id })}
|
||||
className={`${
|
||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === '')
|
||||
? 'bg-primary text-black pointer-events-none'
|
||||
: 'bg-mineshaft-700 hover:bg-mineshaft-500 duration-200 cursor-pointer'
|
||||
} py-3 px-4 mb-2 rounded-md flex flex-row justify-between items-center`}
|
||||
>
|
||||
<div className="flex flex-row items-start">
|
||||
<div
|
||||
className={`${
|
||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === '')
|
||||
? 'text-bunker-800'
|
||||
: 'text-bunker-200'
|
||||
} text-sm mr-1.5`}
|
||||
>
|
||||
{timeSince(new Date(snapshot.createdAt))}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === '')
|
||||
? 'text-bunker-900'
|
||||
: 'text-bunker-300'
|
||||
} text-sm `}
|
||||
>{` - ${snapshot.secretVersions.length} Secrets`}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === '')
|
||||
? 'text-bunker-800 pointer-events-none'
|
||||
: 'text-bunker-200 hover:text-primary duration-200 cursor-pointer'
|
||||
} text-sm`}
|
||||
>
|
||||
{id === 0
|
||||
? 'Current Version'
|
||||
: chosenSnapshot === snapshot._id
|
||||
? 'Currently Viewing'
|
||||
: 'Explore'}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-800 pointer-events-none" : "text-bunker-200 hover:text-primary duration-200 cursor-pointer"} text-sm`}>
|
||||
{id == 0 ? "Current Version" : chosenSnapshot == snapshot._id ? "Currently Viewing" : "Explore"}
|
||||
))}
|
||||
<div className="flex justify-center w-full mb-14">
|
||||
<div className="items-center w-40">
|
||||
<Button
|
||||
text="View More"
|
||||
textDisabled="End of History"
|
||||
active={secretSnapshotsMetadata.length % 15 === 0}
|
||||
onButtonPressed={loadMoreSnapshots}
|
||||
size="md"
|
||||
color="mineshaft"
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
<div className='flex justify-center w-full mb-14'>
|
||||
<div className='items-center w-40'>
|
||||
<Button text="View More" textDisabled="End of History" active={secretSnapshotsMetadata.length % 15 == 0 ? true : false} onButtonPressed={loadMoreSnapshots} size="md" color="mineshaft"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PITRecoverySidebar;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric
|
||||
} from '@app/components/utilities/cryptography/crypto';
|
||||
import getSecretVersions from '@app/ee/api/secrets/GetSecretVersions';
|
||||
import getLatestFileKey from '@app/pages/api/workspace/getLatestFileKey';
|
||||
import { faCircle, faDotCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import { decryptAssymmetric, decryptSymmetric } from '~/components/utilities/cryptography/crypto';
|
||||
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
|
||||
|
||||
interface DecryptedSecretVersionListProps {
|
||||
createdAt: string;
|
||||
value: string;
|
||||
@@ -21,24 +23,23 @@ interface EncrypetedSecretVersionListProps {
|
||||
secretValueTag: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} secretId - the id of a secret for which are querying version history
|
||||
* @returns a list of versions for a specific secret
|
||||
*/
|
||||
const SecretVersionList = ({ secretId }: { secretId: string; }) => {
|
||||
const SecretVersionList = ({ secretId }: { secretId: string }) => {
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [secretVersions, setSecretVersions] = useState<DecryptedSecretVersionListProps[]>([]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const getSecretVersionHistory = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const encryptedSecretVersions = await getSecretVersions({ secretId, offset: 0, limit: 10});
|
||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) })
|
||||
|
||||
const encryptedSecretVersions = await getSecretVersions({ secretId, offset: 0, limit: 10 });
|
||||
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
|
||||
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||
|
||||
let decryptedLatestKey: string;
|
||||
@@ -52,8 +53,8 @@ const SecretVersionList = ({ secretId }: { secretId: string; }) => {
|
||||
});
|
||||
}
|
||||
|
||||
const decryptedSecretVersions = encryptedSecretVersions?.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps) => {
|
||||
return {
|
||||
const decryptedSecretVersions = encryptedSecretVersions?.secretVersions.map(
|
||||
(encryptedSecretVersion: EncrypetedSecretVersionListProps) => ({
|
||||
createdAt: encryptedSecretVersion.createdAt,
|
||||
value: decryptSymmetric({
|
||||
ciphertext: encryptedSecretVersion.secretValueCiphertext,
|
||||
@@ -61,63 +62,76 @@ const SecretVersionList = ({ secretId }: { secretId: string; }) => {
|
||||
tag: encryptedSecretVersion.secretValueTag,
|
||||
key: decryptedLatestKey
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
setSecretVersions(decryptedSecretVersions);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
getSecretVersionHistory();
|
||||
}, [secretId]);
|
||||
|
||||
return <div className='w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none'>
|
||||
<p className=''>{t("dashboard:sidebar.version-history")}</p>
|
||||
<div className='p-1 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none h-full'>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="infisical loading indicator"
|
||||
></Image>
|
||||
</div>
|
||||
) : (
|
||||
<div className='h-48 overflow-y-auto overflow-x-none'>
|
||||
{secretVersions
|
||||
? secretVersions?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
||||
.map((version: DecryptedSecretVersionListProps, index: number) =>
|
||||
<div key={index} className='flex flex-row'>
|
||||
<div className='pr-1 flex flex-col items-center'>
|
||||
<div className='p-1'><FontAwesomeIcon icon={index == 0 ? faDotCircle : faCircle} /></div>
|
||||
<div className='w-0 h-full border-l mt-1'></div>
|
||||
</div>
|
||||
<div className='flex flex-col w-full max-w-[calc(100%-2.3rem)]'>
|
||||
<div className='pr-2 pt-1'>
|
||||
{(new Date(version.createdAt)).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}
|
||||
return (
|
||||
<div className="w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none">
|
||||
<p className="">{t('dashboard:sidebar.version-history')}</p>
|
||||
<div className="p-1 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none h-full">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={60}
|
||||
width={100}
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-48 overflow-y-auto overflow-x-none">
|
||||
{secretVersions ? (
|
||||
secretVersions
|
||||
?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
||||
.map((version: DecryptedSecretVersionListProps, index: number) => (
|
||||
<div key={`${version.createdAt}.${index + 1}`} className="flex flex-row">
|
||||
<div className="pr-1 flex flex-col items-center">
|
||||
<div className="p-1">
|
||||
<FontAwesomeIcon icon={index === 0 ? faDotCircle : faCircle} />
|
||||
</div>
|
||||
<div className="w-0 h-full border-l mt-1" />
|
||||
</div>
|
||||
<div className="flex flex-col w-full max-w-[calc(100%-2.3rem)]">
|
||||
<div className="pr-2 pt-1">
|
||||
{new Date(version.createdAt).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}
|
||||
</div>
|
||||
<div className="">
|
||||
<p className="break-words">
|
||||
<span className="py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5">
|
||||
Value:
|
||||
</span>
|
||||
{version.value}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5'>Value:</span>{version.value}</p></div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-bunker-400">
|
||||
No version history yet.
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='w-full h-full flex items-center justify-center text-bunker-400'>No version history yet.</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecretVersionList;
|
||||
|
||||
@@ -1,270 +1,284 @@
|
||||
/* eslint-disable */
|
||||
// TODO(akhilmhdh): Danger. This file functions needs to simplified.
|
||||
// Disabled eslinting as below algos uses unnary operator and need to be careful on handling it
|
||||
// Will revisit this and change to simplier non-recursive to dynamic programming with space allocation strategy
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param textOld - old secret
|
||||
* @param textNew - new (updated) secret
|
||||
* @param diffPlusFlag - a flag for whether we want to detect moving segments
|
||||
* @param diffPlusFlag - a flag for whether we want to detect moving segments
|
||||
* - doesn't work in some examples (e.g., when we have a full reverse ordering of the text)
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
function patienceDiff(textOld: string[], textNew: string[], diffPlusFlag?: boolean) {
|
||||
|
||||
/**
|
||||
* findUnique finds all unique values in arr[lo..hi], inclusive. This
|
||||
* function is used in preparation for determining the longest common
|
||||
* subsequence. Specifically, it first reduces the array range in question
|
||||
* to unique values.
|
||||
* @param chars - an array of characters
|
||||
* @param lo
|
||||
* @param hi
|
||||
* @param lo
|
||||
* @param hi
|
||||
* @returns - an ordered Map, with the arr[i] value as the Map key and the
|
||||
* array index i as the Map value.
|
||||
* array index i as the Map value.
|
||||
*/
|
||||
function findUnique(chars: string[], lo: number, hi: number) {
|
||||
const characterMap = new Map();
|
||||
function findUnique(chars: string[], lo: number, hi: number) {
|
||||
const characterMap = new Map();
|
||||
|
||||
for (let i=lo; i<=hi; i++) {
|
||||
const character = chars[i];
|
||||
|
||||
if (characterMap.has(character)) {
|
||||
characterMap.get(character).count++;
|
||||
characterMap.get(character).index = i;
|
||||
} else {
|
||||
characterMap.set(character, { count: 1, index: i });
|
||||
}
|
||||
}
|
||||
for (let i = lo; i <= hi; i += 1) {
|
||||
const character = chars[i];
|
||||
|
||||
characterMap.forEach((val, key, map) => {
|
||||
if (val.count !== 1) {
|
||||
map.delete(key);
|
||||
} else {
|
||||
map.set(key, val.index);
|
||||
}
|
||||
});
|
||||
if (characterMap.has(character)) {
|
||||
characterMap.get(character).count += 1;
|
||||
characterMap.get(character).index = i;
|
||||
} else {
|
||||
characterMap.set(character, { count: 1, index: i });
|
||||
}
|
||||
}
|
||||
|
||||
return characterMap;
|
||||
}
|
||||
characterMap.forEach((val, key, map) => {
|
||||
if (val.count !== 1) {
|
||||
map.delete(key);
|
||||
} else {
|
||||
map.set(key, val.index);
|
||||
}
|
||||
});
|
||||
|
||||
return characterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aArray
|
||||
* @param aLo
|
||||
* @param aHi
|
||||
* @param bArray
|
||||
* @param bLo
|
||||
* @param bHi
|
||||
* @param aArray
|
||||
* @param aLo
|
||||
* @param aHi
|
||||
* @param bArray
|
||||
* @param bLo
|
||||
* @param bHi
|
||||
* @returns an ordered Map, with the Map key as the common line between aArray
|
||||
* and bArray, with the Map value as an object containing the array indexes of
|
||||
* the matching unique lines.
|
||||
*
|
||||
*
|
||||
*/
|
||||
function uniqueCommon(aArray: string[], aLo: number, aHi: number, bArray: string[], bLo: number, bHi: number) {
|
||||
const ma = findUnique(aArray, aLo, aHi);
|
||||
const mb = findUnique(bArray, bLo, bHi);
|
||||
function uniqueCommon(
|
||||
aArray: string[],
|
||||
aLo: number,
|
||||
aHi: number,
|
||||
bArray: string[],
|
||||
bLo: number,
|
||||
bHi: number
|
||||
) {
|
||||
const ma = findUnique(aArray, aLo, aHi);
|
||||
const mb = findUnique(bArray, bLo, bHi);
|
||||
|
||||
ma.forEach((val, key, map) => {
|
||||
if (mb.has(key)) {
|
||||
map.set(key, {
|
||||
indexA: val,
|
||||
indexB: mb.get(key)
|
||||
});
|
||||
} else {
|
||||
map.delete(key);
|
||||
}
|
||||
});
|
||||
ma.forEach((val, key, map) => {
|
||||
if (mb.has(key)) {
|
||||
map.set(key, {
|
||||
indexA: val,
|
||||
indexB: mb.get(key)
|
||||
});
|
||||
} else {
|
||||
map.delete(key);
|
||||
}
|
||||
});
|
||||
|
||||
return ma;
|
||||
}
|
||||
return ma;
|
||||
}
|
||||
|
||||
/**
|
||||
* longestCommonSubsequence takes an ordered Map from the function uniqueCommon
|
||||
* and determines the Longest Common Subsequence (LCS).
|
||||
* @param abMap
|
||||
* @param abMap
|
||||
* @returns an ordered array of objects containing the array indexes of the
|
||||
* matching lines for a LCS.
|
||||
*/
|
||||
function longestCommonSubsequence(abMap: Map<number, { indexA: number, indexB: number, prev?: number }>) {
|
||||
const ja: any = [];
|
||||
function longestCommonSubsequence(
|
||||
abMap: Map<number, { indexA: number; indexB: number; prev?: number }>
|
||||
) {
|
||||
const ja: any = [];
|
||||
|
||||
// First, walk the list creating the jagged array.
|
||||
abMap.forEach((val, key, map) => {
|
||||
let i = 0;
|
||||
|
||||
while (ja[i] && ja[i][ja[i].length - 1].indexB < val.indexB) {
|
||||
i++;
|
||||
}
|
||||
// First, walk the list creating the jagged array.
|
||||
abMap.forEach((val, key, map) => {
|
||||
let i = 0;
|
||||
|
||||
if (!ja[i]) {
|
||||
ja[i] = [];
|
||||
}
|
||||
while (ja[i] && ja[i][ja[i].length - 1].indexB < val.indexB) {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (0 < i) {
|
||||
val.prev = ja[i-1][ja[i - 1].length - 1];
|
||||
}
|
||||
ja[i].push(val);
|
||||
});
|
||||
if (!ja[i]) {
|
||||
ja[i] = [];
|
||||
}
|
||||
|
||||
// Now, pull out the longest common subsequence.
|
||||
let lcs: any[] = [];
|
||||
|
||||
if (0 < ja.length) {
|
||||
const n = ja.length - 1;
|
||||
lcs = [ja[n][ja[n].length - 1]];
|
||||
|
||||
while (lcs[lcs.length - 1].prev) {
|
||||
lcs.push(lcs[lcs.length - 1].prev);
|
||||
}
|
||||
}
|
||||
if (i > 0) {
|
||||
val.prev = ja[i - 1][ja[i - 1].length - 1];
|
||||
}
|
||||
ja[i].push(val);
|
||||
});
|
||||
|
||||
return lcs.reverse();
|
||||
}
|
||||
// Now, pull out the longest common subsequence.
|
||||
let lcs: any[] = [];
|
||||
|
||||
// "result" is the array used to accumulate the textOld that are deleted, the
|
||||
// lines that are shared between textOld and textNew, and the textNew that were
|
||||
// inserted.
|
||||
|
||||
const result: any[] = [];
|
||||
let deleted = 0;
|
||||
let inserted = 0;
|
||||
if (ja.length > 0) {
|
||||
const n = ja.length - 1;
|
||||
lcs = [ja[n][ja[n].length - 1]];
|
||||
|
||||
// aMove and bMove will contain the lines that don't match, and will be returned
|
||||
// for possible searching of lines that moved.
|
||||
|
||||
const aMove: any[] = [];
|
||||
const aMoveIndex: any[] = [];
|
||||
const bMove: any[] = [];
|
||||
const bMoveIndex: any[] = [];
|
||||
while (lcs[lcs.length - 1].prev) {
|
||||
lcs.push(lcs[lcs.length - 1].prev);
|
||||
}
|
||||
}
|
||||
|
||||
return lcs.reverse();
|
||||
}
|
||||
|
||||
// "result" is the array used to accumulate the textOld that are deleted, the
|
||||
// lines that are shared between textOld and textNew, and the textNew that were
|
||||
// inserted.
|
||||
|
||||
const result: any[] = [];
|
||||
let deleted = 0;
|
||||
let inserted = 0;
|
||||
|
||||
// aMove and bMove will contain the lines that don't match, and will be returned
|
||||
// for possible searching of lines that moved.
|
||||
|
||||
const aMove: any[] = [];
|
||||
const aMoveIndex: any[] = [];
|
||||
const bMove: any[] = [];
|
||||
const bMoveIndex: any[] = [];
|
||||
|
||||
/**
|
||||
* addToResult simply pushes the latest value onto the "result" array. This
|
||||
* array captures the diff of the line, aIndex, and bIndex from the textOld
|
||||
* and textNew array.
|
||||
* @param aIndex
|
||||
* @param bIndex
|
||||
* @param aIndex
|
||||
* @param bIndex
|
||||
*/
|
||||
function addToResult(aIndex: number, bIndex: number) {
|
||||
if (bIndex < 0) {
|
||||
aMove.push(textOld[aIndex]);
|
||||
aMoveIndex.push(result.length);
|
||||
deleted++;
|
||||
} else if (aIndex < 0) {
|
||||
bMove.push(textNew[bIndex]);
|
||||
bMoveIndex.push(result.length);
|
||||
inserted++;
|
||||
}
|
||||
function addToResult(aIndex: number, bIndex: number) {
|
||||
if (bIndex < 0) {
|
||||
aMove.push(textOld[aIndex]);
|
||||
aMoveIndex.push(result.length);
|
||||
deleted += 1;
|
||||
} else if (aIndex < 0) {
|
||||
bMove.push(textNew[bIndex]);
|
||||
bMoveIndex.push(result.length);
|
||||
inserted += 1;
|
||||
}
|
||||
|
||||
result.push({
|
||||
line: aIndex >= 0 ? textOld[aIndex] : textNew[bIndex],
|
||||
aIndex,
|
||||
bIndex
|
||||
});
|
||||
}
|
||||
|
||||
result.push({
|
||||
line: 0 <= aIndex ? textOld[aIndex] : textNew[bIndex],
|
||||
aIndex: aIndex,
|
||||
bIndex: bIndex,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* addSubMatch handles the lines between a pair of entries in the LCS. Thus,
|
||||
* this function might recursively call recurseLCS to further match the lines
|
||||
* between textOld and textNew.
|
||||
* @param aLo
|
||||
* @param aHi
|
||||
* @param bLo
|
||||
* @param bHi
|
||||
* @param aLo
|
||||
* @param aHi
|
||||
* @param bLo
|
||||
* @param bHi
|
||||
*/
|
||||
function addSubMatch(aLo: number, aHi: number, bLo: number, bHi: number) {
|
||||
// Match any lines at the beginning of textOld and textNew.
|
||||
while (aLo <= aHi && bLo <= bHi && textOld[aLo] === textNew[bLo]) {
|
||||
addToResult(aLo++, bLo++);
|
||||
}
|
||||
function addSubMatch(aLo: number, aHi: number, bLo: number, bHi: number) {
|
||||
// Match any lines at the beginning of textOld and textNew.
|
||||
while (aLo <= aHi && bLo <= bHi && textOld[aLo] === textNew[bLo]) {
|
||||
addToResult(aLo++, bLo++);
|
||||
}
|
||||
|
||||
// Match any lines at the end of textOld and textNew, but don't place them
|
||||
// in the "result" array just yet, as the lines between these matches at
|
||||
// the beginning and the end need to be analyzed first.
|
||||
|
||||
const aHiTemp = aHi;
|
||||
while (aLo <= aHi && bLo <= bHi && textOld[aHi] === textNew[bHi]) {
|
||||
aHi--;
|
||||
bHi--;
|
||||
}
|
||||
// Match any lines at the end of textOld and textNew, but don't place them
|
||||
// in the "result" array just yet, as the lines between these matches at
|
||||
// the beginning and the end need to be analyzed first.
|
||||
|
||||
// Now, check to determine with the remaining lines in the subsequence
|
||||
// whether there are any unique common lines between textOld and textNew.
|
||||
//
|
||||
// If not, add the subsequence to the result (all textOld having been
|
||||
// deleted, and all textNew having been inserted).
|
||||
//
|
||||
// If there are unique common lines between textOld and textNew, then let's
|
||||
// recursively perform the patience diff on the subsequence.
|
||||
|
||||
const uniqueCommonMap = uniqueCommon(textOld, aLo, aHi, textNew, bLo, bHi);
|
||||
|
||||
if (uniqueCommonMap.size === 0) {
|
||||
while (aLo <= aHi) {
|
||||
addToResult(aLo++, -1);
|
||||
}
|
||||
const aHiTemp = aHi;
|
||||
while (aLo <= aHi && bLo <= bHi && textOld[aHi] === textNew[bHi]) {
|
||||
aHi -= 1;
|
||||
bHi -= 1;
|
||||
}
|
||||
|
||||
while (bLo <= bHi) {
|
||||
addToResult(-1, bLo++);
|
||||
}
|
||||
} else {
|
||||
recurseLCS(aLo, aHi, bLo, bHi, uniqueCommonMap);
|
||||
}
|
||||
// Now, check to determine with the remaining lines in the subsequence
|
||||
// whether there are any unique common lines between textOld and textNew.
|
||||
//
|
||||
// If not, add the subsequence to the result (all textOld having been
|
||||
// deleted, and all textNew having been inserted).
|
||||
//
|
||||
// If there are unique common lines between textOld and textNew, then let's
|
||||
// recursively perform the patience diff on the subsequence.
|
||||
|
||||
const uniqueCommonMap = uniqueCommon(textOld, aLo, aHi, textNew, bLo, bHi);
|
||||
|
||||
if (uniqueCommonMap.size === 0) {
|
||||
while (aLo <= aHi) {
|
||||
addToResult(aLo++, -1);
|
||||
}
|
||||
|
||||
while (bLo <= bHi) {
|
||||
addToResult(-1, bLo++);
|
||||
}
|
||||
} else {
|
||||
recurseLCS(aLo, aHi, bLo, bHi, uniqueCommonMap);
|
||||
}
|
||||
|
||||
// Finally, let's add the matches at the end to the result.
|
||||
while (aHi < aHiTemp) {
|
||||
addToResult(++aHi, ++bHi);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, let's add the matches at the end to the result.
|
||||
while (aHi < aHiTemp) {
|
||||
addToResult(++aHi, ++bHi);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* recurseLCS finds the longest common subsequence (LCS) between the arrays
|
||||
* textOld[aLo..aHi] and textNew[bLo..bHi] inclusive. Then for each subsequence
|
||||
* recursively performs another LCS search (via addSubMatch), until there are
|
||||
* none found, at which point the subsequence is dumped to the result.
|
||||
* @param aLo
|
||||
* @param aHi
|
||||
* @param bLo
|
||||
* @param bHi
|
||||
* @param uniqueCommonMap
|
||||
* @param aLo
|
||||
* @param aHi
|
||||
* @param bLo
|
||||
* @param bHi
|
||||
* @param uniqueCommonMap
|
||||
*/
|
||||
function recurseLCS(aLo: number, aHi: number, bLo: number, bHi: number, uniqueCommonMap?: any) {
|
||||
const x = longestCommonSubsequence(uniqueCommonMap || uniqueCommon(textOld, aLo, aHi, textNew, bLo, bHi));
|
||||
|
||||
if (x.length === 0) {
|
||||
addSubMatch(aLo, aHi, bLo, bHi);
|
||||
} else {
|
||||
if (aLo < x[0].indexA || bLo < x[0].indexB) {
|
||||
addSubMatch(aLo, x[0].indexA - 1, bLo, x[0].indexB - 1);
|
||||
}
|
||||
function recurseLCS(aLo: number, aHi: number, bLo: number, bHi: number, uniqueCommonMap?: any) {
|
||||
const x = longestCommonSubsequence(
|
||||
uniqueCommonMap || uniqueCommon(textOld, aLo, aHi, textNew, bLo, bHi)
|
||||
);
|
||||
|
||||
let i;
|
||||
for (i = 0; i < x.length - 1; i++) {
|
||||
addSubMatch(x[i].indexA, x[i+1].indexA - 1, x[i].indexB, x[i+1].indexB - 1);
|
||||
}
|
||||
if (x.length === 0) {
|
||||
addSubMatch(aLo, aHi, bLo, bHi);
|
||||
} else {
|
||||
if (aLo < x[0].indexA || bLo < x[0].indexB) {
|
||||
addSubMatch(aLo, x[0].indexA - 1, bLo, x[0].indexB - 1);
|
||||
}
|
||||
|
||||
if (x[i].indexA <= aHi || x[i].indexB <= bHi) {
|
||||
addSubMatch(x[i].indexA, aHi, x[i].indexB, bHi);
|
||||
}
|
||||
}
|
||||
}
|
||||
let i;
|
||||
for (i = 0; i < x.length - 1; i += 1) {
|
||||
addSubMatch(x[i].indexA, x[i + 1].indexA - 1, x[i].indexB, x[i + 1].indexB - 1);
|
||||
}
|
||||
|
||||
recurseLCS(0, textOld.length - 1, 0, textNew.length - 1);
|
||||
if (x[i].indexA <= aHi || x[i].indexB <= bHi) {
|
||||
addSubMatch(x[i].indexA, aHi, x[i].indexB, bHi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diffPlusFlag) {
|
||||
return {
|
||||
lines: result,
|
||||
lineCountDeleted: deleted,
|
||||
lineCountInserted: inserted,
|
||||
lineCountMoved: 0,
|
||||
aMove: aMove,
|
||||
aMoveIndex: aMoveIndex,
|
||||
bMove: bMove,
|
||||
bMoveIndex: bMoveIndex,
|
||||
};
|
||||
}
|
||||
recurseLCS(0, textOld.length - 1, 0, textNew.length - 1);
|
||||
|
||||
return {
|
||||
lines: result,
|
||||
lineCountDeleted: deleted,
|
||||
lineCountInserted: inserted,
|
||||
lineCountMoved: 0,
|
||||
};
|
||||
if (diffPlusFlag) {
|
||||
return {
|
||||
lines: result,
|
||||
lineCountDeleted: deleted,
|
||||
lineCountInserted: inserted,
|
||||
lineCountMoved: 0,
|
||||
aMove,
|
||||
aMoveIndex,
|
||||
bMove,
|
||||
bMoveIndex
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
lines: result,
|
||||
lineCountDeleted: deleted,
|
||||
lineCountInserted: inserted,
|
||||
lineCountMoved: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,58 +303,54 @@ function patienceDiff(textOld: string[], textNew: string[], diffPlusFlag?: boole
|
||||
*/
|
||||
|
||||
function patienceDiffPlus(textOld: string[], textNew: string[]) {
|
||||
const difference = patienceDiff(textOld, textNew, true);
|
||||
|
||||
const difference = patienceDiff(textOld, textNew, true);
|
||||
let aMoveNext = difference.aMove;
|
||||
let aMoveIndexNext = difference.aMoveIndex;
|
||||
let bMoveNext = difference.bMove;
|
||||
let bMoveIndexNext = difference.bMoveIndex;
|
||||
|
||||
let aMoveNext = difference.aMove;
|
||||
let aMoveIndexNext = difference.aMoveIndex;
|
||||
let bMoveNext = difference.bMove;
|
||||
let bMoveIndexNext = difference.bMoveIndex;
|
||||
delete difference.aMove;
|
||||
delete difference.aMoveIndex;
|
||||
delete difference.bMove;
|
||||
delete difference.bMoveIndex;
|
||||
|
||||
delete difference.aMove;
|
||||
delete difference.aMoveIndex;
|
||||
delete difference.bMove;
|
||||
delete difference.bMoveIndex;
|
||||
|
||||
let lastLineCountMoved;
|
||||
let lastLineCountMoved;
|
||||
|
||||
do {
|
||||
const aMove = aMoveNext;
|
||||
const aMoveIndex = aMoveIndexNext;
|
||||
const bMove = bMoveNext;
|
||||
const bMoveIndex = bMoveIndexNext;
|
||||
do {
|
||||
const aMove = aMoveNext;
|
||||
const aMoveIndex = aMoveIndexNext;
|
||||
const bMove = bMoveNext;
|
||||
const bMoveIndex = bMoveIndexNext;
|
||||
|
||||
aMoveNext = [];
|
||||
aMoveIndexNext = [];
|
||||
bMoveNext = [];
|
||||
bMoveIndexNext = [];
|
||||
aMoveNext = [];
|
||||
aMoveIndexNext = [];
|
||||
bMoveNext = [];
|
||||
bMoveIndexNext = [];
|
||||
|
||||
const subDiff = patienceDiff(aMove!, bMove!);
|
||||
|
||||
lastLineCountMoved = difference.lineCountMoved;
|
||||
const subDiff = patienceDiff(aMove!, bMove!);
|
||||
|
||||
subDiff.lines.forEach((v, i) => {
|
||||
lastLineCountMoved = difference.lineCountMoved;
|
||||
|
||||
if (0 <= v.aIndex && 0 <= v.bIndex) {
|
||||
|
||||
difference.lines[aMoveIndex![v.aIndex]].moved = true;
|
||||
difference.lines[bMoveIndex![v.bIndex]].aIndex = aMoveIndex![v.aIndex];
|
||||
difference.lines[bMoveIndex![v.bIndex]].moved = true;
|
||||
difference.lineCountInserted--;
|
||||
difference.lineCountDeleted--;
|
||||
difference.lineCountMoved++;
|
||||
} else if (v.bIndex < 0) {
|
||||
aMoveNext!.push(aMove![v.aIndex]);
|
||||
aMoveIndexNext!.push(aMoveIndex![v.aIndex]);
|
||||
} else {
|
||||
bMoveNext!.push(bMove![v.bIndex]);
|
||||
bMoveIndexNext!.push(bMoveIndex![v.bIndex]);
|
||||
}
|
||||
});
|
||||
} while (0 < difference.lineCountMoved - lastLineCountMoved);
|
||||
|
||||
return difference;
|
||||
subDiff.lines.forEach((v, i) => {
|
||||
if (v.aIndex >= 0 && v.bIndex >= 0) {
|
||||
difference.lines[aMoveIndex![v.aIndex]].moved = true;
|
||||
difference.lines[bMoveIndex![v.bIndex]].aIndex = aMoveIndex![v.aIndex];
|
||||
difference.lines[bMoveIndex![v.bIndex]].moved = true;
|
||||
difference.lineCountInserted -= 1;
|
||||
difference.lineCountDeleted -= 1;
|
||||
difference.lineCountMoved += 1;
|
||||
} else if (v.bIndex < 0) {
|
||||
aMoveNext!.push(aMove![v.aIndex]);
|
||||
aMoveIndexNext!.push(aMoveIndex![v.aIndex]);
|
||||
} else {
|
||||
bMoveNext!.push(bMove![v.bIndex]);
|
||||
bMoveIndexNext!.push(bMoveIndex![v.bIndex]);
|
||||
}
|
||||
});
|
||||
} while (difference.lineCountMoved - lastLineCountMoved > 0);
|
||||
|
||||
return difference;
|
||||
}
|
||||
|
||||
export default patienceDiff;
|
||||
|
||||
@@ -11,25 +11,25 @@ function timeSince(date: Date) {
|
||||
let interval = seconds / 31536000;
|
||||
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + ' years ago';
|
||||
return `${Math.floor(interval) } years ago`;
|
||||
}
|
||||
interval = seconds / 2592000;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + ' months ago';
|
||||
return `${Math.floor(interval) } months ago`;
|
||||
}
|
||||
interval = seconds / 86400;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + ' days ago';
|
||||
return `${Math.floor(interval) } days ago`;
|
||||
}
|
||||
interval = seconds / 3600;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + ' hours ago';
|
||||
return `${Math.floor(interval) } hours ago`;
|
||||
}
|
||||
interval = seconds / 60;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + ' minutes ago';
|
||||
return `${Math.floor(interval) } minutes ago`;
|
||||
}
|
||||
return Math.floor(seconds) + ' seconds ago';
|
||||
return `${Math.floor(seconds) } seconds ago`;
|
||||
}
|
||||
|
||||
export default timeSince;
|
||||
|
||||
Reference in New Issue
Block a user