-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
File upload capabilities #3
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as React from "react" | ||
import {useEffect, useState} from "react" | ||
AnomalRoil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import {APIConfig, fetchAll} from "../api" | ||
import {Box, Typography} from "@mui/material" | ||
import {SidebarEntry, SidebarEntryPanel} from "./SidebarPanel" | ||
import {remapPlaintexts} from "./mappings" | ||
|
||
type AllListProps = { | ||
config: APIConfig | ||
} | ||
export const AllList = (props: AllListProps) => { | ||
const [entries, setEntries] = useState<Array<SidebarEntry>>([]) | ||
const [error, setError] = useState("") | ||
|
||
useEffect(() => { | ||
fetchAll(props.config) | ||
.then(ciphertexts => setEntries(remapPlaintexts(ciphertexts))) | ||
.catch(err => setError(`there was an error fetching ciphertexts ${err}`)) | ||
}, []); | ||
|
||
return ( | ||
<Box padding={2}> | ||
<Typography variant={"h5"}>All Ciphertexts</Typography> | ||
{entries.map(c => <SidebarEntryPanel entry={c}/>)} | ||
{entries.length === 0 && <Typography>There are no ciphertexts yet :(</Typography>} | ||
{!!error && <Typography color={"red"}>{error}</Typography>} | ||
</Box> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,102 @@ | ||
import * as React from "react" | ||
import dayjs from "dayjs" | ||
import {useCallback, useState} from "react" | ||
import {Box, Button, CircularProgress, Stack, TextField, Typography} from "@mui/material" | ||
import {useCallback, useEffect, useState} from "react" | ||
import {Buffer} from "tlock-js" | ||
import { | ||
Box, | ||
Button, | ||
CircularProgress, | ||
FormControl, | ||
FormControlLabel, FormLabel, Radio, | ||
RadioGroup, | ||
Stack, | ||
TextField, | ||
Typography | ||
} from "@mui/material" | ||
import {DateTimePicker} from "@mui/x-date-pickers/DateTimePicker" | ||
import {APIConfig, encryptAndUpload} from "../api" | ||
import {TagsInput} from "./TagsInput" | ||
import {FileInput} from "./FileInput" | ||
|
||
type EncryptFormProps = { | ||
config: APIConfig | ||
} | ||
|
||
export const EncryptForm = (props: EncryptFormProps) => { | ||
const [time, setTime] = useState(dayjs(formatDate(Date.now()))) | ||
const [plaintext, setPlaintext] = useState("") | ||
const [uploadType, setUploadType] = useState("text") | ||
const [file, setFile] = useState<File>() | ||
const [bytes, setBytes] = useState<Buffer>() | ||
const [ciphertext, setCiphertext] = useState("") | ||
const [tags, setTags] = useState<Array<string>>([]) | ||
const [isLoading, setIsLoading] = useState(false) | ||
const [error, setError] = useState("") | ||
|
||
// sets the `bytes` var and clears the `plaintext` var if a file is uploaded | ||
useEffect(() => { | ||
if (!file) { | ||
return | ||
} | ||
|
||
file.arrayBuffer() | ||
.then(buf => setBytes(Buffer.from(buf))) | ||
}, [file]) | ||
|
||
// sets the `bytes` var and clears the `file` var if text is input | ||
useEffect(() => { | ||
if (!plaintext) { | ||
return | ||
} | ||
|
||
setBytes(Buffer.from(plaintext)) | ||
}, [plaintext]); | ||
|
||
// changing the upload type resets everything | ||
useEffect(() => { | ||
setCiphertext("") | ||
setPlaintext("") | ||
setFile(undefined) | ||
setBytes(undefined) | ||
}, [uploadType]); | ||
|
||
// called when a file is uploaded | ||
const extractFile = (files: FileList) => { | ||
if (files.length !== 1) { | ||
setError("you must upload a single file") | ||
return | ||
} | ||
// let's automatically add a "file" tag as well | ||
setTags([...tags.filter(t => t === "file"), "file"]) | ||
setFile(files[0]) | ||
Comment on lines
+65
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about setting a file size limit too? Maybe a few MBs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is set on the server - 20mb There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see src/server/index.ts |
||
} | ||
|
||
const encryptAndStore = useCallback(() => { | ||
if (plaintext.trim() === "") { | ||
setError("You must enter a plaintext to encrypt") | ||
if (!bytes) { | ||
setError("you must upload text or a file") | ||
return | ||
} | ||
if (!time) { | ||
setError("You must enter a time") | ||
return | ||
} | ||
if (!uploadType) { | ||
setError("somehow you deselected upload type") | ||
return | ||
} | ||
|
||
setError("") | ||
setIsLoading(true) | ||
encryptAndUpload(props.config, time.toDate().getTime(), plaintext, tags) | ||
encryptAndUpload(props.config, time.toDate().getTime(), uploadType, bytes, tags) | ||
.then(c => setCiphertext(c)) | ||
.catch(err => setError(err.message)) | ||
.then(() => setIsLoading(false)) | ||
}, [plaintext, time]) | ||
}, [time, bytes]) | ||
|
||
const clear = useCallback(() => { | ||
setTime(dayjs(formatDate(Date.now()))) | ||
setPlaintext("") | ||
setBytes(undefined) | ||
setCiphertext("") | ||
setError("") | ||
setTags([]) | ||
|
@@ -87,30 +146,52 @@ export const EncryptForm = (props: EncryptFormProps) => { | |
tags={tags} | ||
onChange={t => setTags(t)} | ||
/> | ||
<Stack | ||
direction={"row"} | ||
spacing={2}> | ||
<FormControl fullWidth> | ||
<FormLabel>Type</FormLabel> | ||
<RadioGroup | ||
row | ||
defaultValue={"text"} | ||
value={uploadType} | ||
onChange={event => setUploadType(event.target.value)} | ||
> | ||
<FormControlLabel value="text" control={<Radio/>} label={"Text"}/> | ||
<FormControlLabel value="file" control={<Radio/>} label={"File"}/> | ||
</RadioGroup> | ||
</FormControl> | ||
</Stack> | ||
<Stack | ||
direction={"row"} | ||
width={"100%"} | ||
spacing={2} | ||
> | ||
<TextField | ||
label={"Plaintext"} | ||
value={plaintext} | ||
variant={"filled"} | ||
fullWidth | ||
onChange={event => setPlaintext(event.target.value)} | ||
multiline | ||
minRows={20} | ||
/> | ||
<TextField | ||
label={"Ciphertext"} | ||
value={ciphertext} | ||
helperText={ciphertextAdvisory} | ||
variant={"filled"} | ||
fullWidth | ||
multiline | ||
disabled | ||
minRows={20} | ||
/> | ||
<Box flex={1}> | ||
{uploadType === "text" && <TextField | ||
label={"Plaintext"} | ||
value={plaintext} | ||
variant={"filled"} | ||
fullWidth | ||
onChange={event => setPlaintext(event.target.value)} | ||
multiline | ||
minRows={20} | ||
/> | ||
} | ||
{uploadType === "file" && <FileInput onChange={extractFile}/>} | ||
</Box> | ||
<Box flex={1}> | ||
<TextField | ||
label={"Ciphertext"} | ||
value={ciphertext} | ||
helperText={ciphertextAdvisory} | ||
variant={"filled"} | ||
fullWidth | ||
multiline | ||
disabled | ||
rows={20} | ||
/> | ||
</Box> | ||
</Stack> | ||
|
||
<Stack | ||
|
@@ -119,21 +200,21 @@ export const EncryptForm = (props: EncryptFormProps) => { | |
> | ||
<Button | ||
onClick={() => encryptAndStore()} | ||
disabled={isLoading || plaintext === ""} | ||
disabled={isLoading || (plaintext === "" && !file)} | ||
variant={"outlined"} | ||
> | ||
Upload | ||
</Button> | ||
<Button | ||
onClick={() => clear()} | ||
disabled={plaintext === ""} | ||
disabled={plaintext === "" && !file} | ||
variant={"outlined"} | ||
> | ||
Clear | ||
</Button> | ||
{isLoading && <CircularProgress/>} | ||
</Stack> | ||
{!!error && <Typography>{error}</Typography>} | ||
{!!error && <Typography color={"red"}>{error}</Typography>} | ||
</Box> | ||
) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import * as React from "react" | ||
import {FormControlLabel, Input} from "@mui/material" | ||
|
||
type FileInputProps = { | ||
onChange: (files: FileList) => void | ||
} | ||
|
||
export const FileInput = (props: FileInputProps) => | ||
<FormControlLabel | ||
label={""} /* labels look a bit shit with the default file input element */ | ||
control={ | ||
<Input | ||
type="file" | ||
className={"form-control"} | ||
onChange={(event: any) => event.currentTarget.files && props.onChange(event.currentTarget.files)} | ||
/> | ||
} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are to use an insecure password, could you pretty please not expose the port to the outside world xD
I think you want to use the
expose
keyword instead of theports
one.ports
is binding your host machine's port to that container's port, whereasexpose
exposes a port of a container to other containers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this docker compose is only for local usage - there's a separate db in heroku configured using env vars