-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a584917
commit d3bafff
Showing
11 changed files
with
799 additions
and
332 deletions.
There are no files selected for viewing
146 changes: 146 additions & 0 deletions
146
...ages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/AddTFVarForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
"use client"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { Card } from "@/components/ui/card"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; | ||
import { T } from "@/components/ui/Typography"; | ||
import { addTFVar } from "@/data/admin/env-vars"; | ||
import { useSAToastMutation } from "@/hooks/useSAToastMutation"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { Plus } from "lucide-react"; | ||
import { useState } from "react"; | ||
import { Controller, useForm } from "react-hook-form"; | ||
import { z } from "zod"; | ||
|
||
|
||
|
||
|
||
export default function AddTFVarForm({ | ||
projectId, | ||
organizationId, | ||
isAllowedSecrets, | ||
envVarNames, | ||
}: { | ||
projectId: string; | ||
organizationId: string; | ||
isAllowedSecrets: boolean; | ||
envVarNames: string[]; | ||
}) { | ||
const [showForm, setShowForm] = useState(false); | ||
|
||
const addTFVarSchema = z.object({ | ||
name: z.string() | ||
.min(1, "Name is required") | ||
.refine((value) => !envVarNames.includes(value), { | ||
message: "Environment variable name already exists", | ||
}), | ||
value: z.string().min(1, "Value is required"), | ||
is_secret: z.boolean(), | ||
}); | ||
|
||
const { mutate: addEnvVar, isLoading: isAddingEnvVar } = useSAToastMutation( | ||
async (data: z.infer<typeof addTFVarSchema>) => | ||
addTFVar(data.name, data.value, data.is_secret, projectId, organizationId), | ||
{ | ||
loadingMessage: "Adding environment variable...", | ||
successMessage: "Environment variable added!", | ||
errorMessage: "Failed to add environment variable", | ||
onSuccess: () => { | ||
reset(); | ||
setShowForm(false); | ||
}, | ||
} | ||
) | ||
|
||
const { handleSubmit, register, reset, control, watch, formState: { errors } } = useForm<z.infer<typeof addTFVarSchema>>({ | ||
resolver: zodResolver(addTFVarSchema), | ||
defaultValues: { | ||
name: '', | ||
value: '', | ||
is_secret: false, | ||
}, | ||
}); | ||
|
||
const onSubmit = (data: z.infer<typeof addTFVarSchema>) => { | ||
addEnvVar(data); | ||
} | ||
|
||
return ( | ||
<> | ||
{showForm ? ( | ||
<Card className="p-5 mt-4 bg-muted/50 rounded-lg"> | ||
<T.H4 className=" mt-1">Add a new environment variable</T.H4> | ||
<T.P className="mt-0 pt-0 mb-4">Enter the environment variable details. These variables will be assigned to your project</T.P> | ||
<form onSubmit={handleSubmit(onSubmit)}> | ||
<div className="grid gap-4 grid-cols-1 md:grid-cols-3"> | ||
<div className="relative"> | ||
<Label htmlFor="varName">Variable Name</Label> | ||
<Input | ||
id="varName" | ||
placeholder="e.g., API_KEY" | ||
{...register('name')} | ||
onChange={(e) => e.target.value = e.target.value.toUpperCase()} | ||
className={errors.name ? "border-destructive" : ""} | ||
/> | ||
{errors.name && <p className="text-destructive text-sm mt-1 absolute -bottom-6">{errors.name.message}</p>} | ||
</div> | ||
<div className="relative"> | ||
<Label htmlFor="varValue">Variable Value</Label> | ||
<Controller | ||
name="value" | ||
control={control} | ||
render={({ field }) => ( | ||
<Input | ||
id="varValue" | ||
type={watch('is_secret') ? "password" : "text"} | ||
placeholder="Enter value" | ||
{...field} | ||
className={errors.value ? "border-destructive" : ""} | ||
/> | ||
)} | ||
/> | ||
{errors.value && <p className="text-destructive text-sm mt-1 absolute -bottom-6">{errors.value.message}</p>} | ||
</div> | ||
<div> | ||
<Label htmlFor="varType">Variable Type</Label> | ||
<Controller | ||
name="is_secret" | ||
control={control} | ||
render={({ field }) => ( | ||
<Select | ||
value={field.value ? "secret" : "plain_text"} | ||
onValueChange={(value) => field.onChange(value === "secret")} | ||
> | ||
<SelectTrigger> | ||
<SelectValue placeholder="Select type" /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
<SelectItem value="plain_text">Plain Text</SelectItem> | ||
<SelectItem value="secret">Secret</SelectItem> | ||
</SelectContent> | ||
</Select> | ||
)} | ||
/> | ||
</div> | ||
</div> | ||
<div className="mt-4 flex justify-end w-full"> | ||
<Button type="submit" variant='outline' disabled={isAddingEnvVar}> | ||
<Plus className="h-4 w-4 mr-2" /> | ||
{isAddingEnvVar ? 'Adding...' : 'Add Variable'} | ||
</Button> | ||
</div> | ||
</form> | ||
</Card> | ||
) : ( | ||
<div className="mt-4 flex justify-end w-full" > | ||
<Button onClick={() => setShowForm(true)} variant='outline' disabled={!isAllowedSecrets} > | ||
<Plus className="h-4 w-4 mr-2" /> | ||
Add a new environment variable | ||
</Button> | ||
</div> | ||
)} | ||
</> | ||
); | ||
} |
48 changes: 48 additions & 0 deletions
48
...es)/(application-pages)/project/[projectSlug]/(specific-project-pages)/BulkEditTFVars.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
'use client' | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { Textarea } from "@/components/ui/textarea"; | ||
import { bulkUpdateTFVars } from "@/data/admin/env-vars"; | ||
import { useSAToastMutation } from "@/hooks/useSAToastMutation"; | ||
import { useState } from "react"; | ||
|
||
export function BulkEditTFVars({ projectId, organizationId, initialBulkEditValue, setIsBulkEditing }: { | ||
projectId: string; | ||
organizationId: string; | ||
initialBulkEditValue: string; | ||
setIsBulkEditing: (value: boolean) => void; | ||
}) { | ||
const [bulkEditValue, setBulkEditValue] = useState(initialBulkEditValue); | ||
|
||
|
||
const { mutate: bulkEditTFVars, isLoading: isBulkEditingTFVars } = useSAToastMutation( | ||
async (data: string) => bulkUpdateTFVars(data, projectId, organizationId), { | ||
loadingMessage: "Applying bulk edit...", | ||
successMessage: "Bulk edit applied!", | ||
errorMessage: "Failed to apply bulk edit", | ||
onSuccess: (response) => { | ||
setBulkEditValue(JSON.stringify(response, null, 2)); | ||
setIsBulkEditing(false); | ||
}, | ||
} | ||
) | ||
|
||
return ( | ||
<div className="space-y-4" > | ||
<Textarea | ||
value={bulkEditValue} | ||
onChange={(e) => setBulkEditValue(e.target.value)} | ||
rows={24} | ||
className="font-mono" | ||
/> | ||
<div className="flex gap-2 w-full justify-between"> | ||
<Button variant="outline" onClick={() => setIsBulkEditing(false)}>Cancel</Button> | ||
<Button onClick={() => bulkEditTFVars(bulkEditValue)} disabled={isBulkEditingTFVars}> | ||
{isBulkEditingTFVars ? 'Applying...' : 'Apply Bulk Edit'} | ||
</Button> | ||
</div> | ||
</div > | ||
) | ||
} | ||
|
||
|
136 changes: 136 additions & 0 deletions
136
...ication-pages)/project/[projectSlug]/(specific-project-pages)/EncryptionTestComponent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
'use client'; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import { Textarea } from "@/components/ui/textarea"; | ||
import { decryptWithPrivateKey, encryptWithPublicKey, formatKey } from "@/utils/crypto"; | ||
import { motion } from 'framer-motion'; | ||
import { useState } from 'react'; | ||
|
||
export default function EncryptionTestComponent({ | ||
organizationId, | ||
publicKey: publicKeyFromProps, | ||
}: { | ||
organizationId: string; | ||
publicKey: string | null; | ||
}) { | ||
|
||
|
||
const [envVar, setEnvVar] = useState(''); | ||
const [encryptedVar, setEncryptedVar] = useState(''); | ||
const [decryptedVar, setDecryptedVar] = useState(''); | ||
const [isMatch, setIsMatch] = useState<boolean | null>(null); | ||
|
||
|
||
const [publicKey, setPublicKey] = useState<string | null>(publicKeyFromProps || null); | ||
const [privateKey, setPrivateKey] = useState(''); | ||
|
||
const formattedPublicKey = publicKey ? formatKey(publicKey, 'public') : ''; | ||
const formattedPrivateKey = privateKey ? formatKey(privateKey, 'private') : ''; | ||
|
||
|
||
const handleEncrypt = async () => { | ||
try { | ||
if (!formattedPublicKey) { | ||
throw new Error('Public key is not available'); | ||
} | ||
console.log('---------------------------------'); | ||
console.log('formattedPublicKey', formattedPublicKey); | ||
|
||
const encrypted = encryptWithPublicKey(envVar, formattedPublicKey); | ||
console.log('encrypted', encrypted); | ||
setEncryptedVar(encrypted); | ||
} catch (error) { | ||
console.error('Encryption error:', error); | ||
setEncryptedVar('Encryption failed: ' + (error instanceof Error ? error.message : String(error))); | ||
} | ||
}; | ||
|
||
const handleDecrypt = async () => { | ||
try { | ||
console.log('---------------------------------'); | ||
console.log('formattedPrivateKey', formattedPrivateKey); | ||
const decrypted = decryptWithPrivateKey(encryptedVar, formattedPrivateKey); | ||
console.log('decrypted', decrypted); | ||
setDecryptedVar(decrypted); | ||
setIsMatch(decrypted === envVar); | ||
} catch (error) { | ||
console.error('Decryption error:', error); | ||
setDecryptedVar(`Decryption failed: ${error instanceof Error ? error.message : String(error)}`); | ||
setIsMatch(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<motion.div | ||
initial={{ opacity: 0, y: 20 }} | ||
animate={{ opacity: 1, y: 0 }} | ||
transition={{ duration: 0.5 }} | ||
> | ||
<pre>{JSON.stringify(publicKey, null, 2)}</pre> | ||
<pre>{JSON.stringify(privateKey, null, 2)}</pre> | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>Encryption/Decryption Test</CardTitle> | ||
</CardHeader> | ||
<CardContent className="space-y-4"> | ||
<div> | ||
<Label htmlFor="envVar">Environment Variable</Label> | ||
<Input | ||
id="envVar" | ||
value={envVar} | ||
onChange={(e) => setEnvVar(e.target.value)} | ||
placeholder="Enter environment variable" | ||
/> | ||
<Button onClick={handleEncrypt} className="mt-2">Encrypt</Button> | ||
</div> | ||
|
||
<div> | ||
<Label htmlFor="encryptedVar">Encrypted Variable</Label> | ||
<Input | ||
id="encryptedVar" | ||
value={encryptedVar} | ||
readOnly | ||
placeholder="Encrypted value will appear here" | ||
/> | ||
</div> | ||
|
||
<div> | ||
<Label htmlFor="privateKey">Private Key</Label> | ||
<Textarea | ||
id="privateKey" | ||
value={privateKey} | ||
onChange={(e) => setPrivateKey(e.target.value)} | ||
placeholder="Enter private key" | ||
className="w-full h-32 p-2 border rounded" | ||
/> | ||
<Button onClick={handleDecrypt} className="mt-2">Decrypt</Button> | ||
</div> | ||
|
||
<div> | ||
<Label htmlFor="decryptedVar">Decrypted Variable</Label> | ||
<Input | ||
id="decryptedVar" | ||
value={decryptedVar} | ||
readOnly | ||
placeholder="Decrypted value will appear here" | ||
/> | ||
{decryptedVar && <p>Decrypted: {decryptedVar}</p>} | ||
{encryptedVar && <p>Encrypted: {encryptedVar}</p>} | ||
</div> | ||
|
||
{isMatch !== null && ( | ||
<div> | ||
{isMatch | ||
? <p className="text-green-500">The decrypted value matches the original value!</p> | ||
: <p className="text-red-500">The decrypted value does not match the original value.</p> | ||
} | ||
</div> | ||
)} | ||
</CardContent> | ||
</Card> | ||
</motion.div> | ||
); | ||
} |
Oops, something went wrong.