add back drag & drop to upload

This commit is contained in:
proddy
2024-08-15 12:14:14 +02:00
parent 4e640a0abe
commit 817b2d1ad7
17 changed files with 3235 additions and 1873 deletions

View File

@@ -0,0 +1,104 @@
import { type ChangeEvent, useRef, useState } from 'react';
import { AiOutlineCloudUpload } from 'react-icons/ai';
import { MdClear } from 'react-icons/md';
import UploadIcon from '@mui/icons-material/Upload';
import { Button } from '@mui/material';
import { useI18nContext } from 'i18n/i18n-react';
import './drag-drop.css';
const DragNdrop = ({ onFileSelected, width, height }) => {
const [file, setFile] = useState<File>();
const inputRef = useRef<HTMLInputElement | null>(null);
const { LL } = useI18nContext();
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) {
return;
}
setFile(e.target.files[0]);
};
const handleDrop = (event) => {
event.preventDefault();
const droppedFiles = event.dataTransfer.files;
if (droppedFiles.length > 0) {
setFile(droppedFiles[0]);
}
};
const handleRemoveFile = (event) => {
event.stopPropagation();
setFile(undefined);
};
const handleUploadClick = (event) => {
event.stopPropagation();
onFileSelected(file);
};
const handleBrowseClick = () => {
inputRef.current?.click();
};
return (
<section className="drag-drop" style={{ width: width, height: height }}>
<div
className={`document-uploader ${file ? 'upload-box active' : 'upload-box'}`}
onDrop={handleDrop}
onDragOver={(event) => event.preventDefault()}
onClick={handleBrowseClick}
>
<>
<div className="upload-info">
<AiOutlineCloudUpload />
<div>
<p>Drag and drop a file here or click to select one</p>
</div>
</div>
<input
type="file"
hidden
id="browse"
onChange={handleFileChange}
ref={inputRef}
accept=".json,.txt,.csv,.bin"
multiple={false}
/>
</>
{file && (
<>
<div className="file-list">
<div className="file-item">
<div className="file-info">
<p>{file.name}</p>
</div>
<div className="file-actions">
<MdClear
style={{ width: 18, verticalAlign: 'middle' }}
onClick={(e) => handleRemoveFile(e)}
/>
</div>
</div>
</div>
<Button
startIcon={<UploadIcon />}
variant="outlined"
color="secondary"
onClick={handleUploadClick}
>
{LL.UPLOAD()}
</Button>
</>
)}
</div>
</section>
);
};
export default DragNdrop;

View File

@@ -1,19 +1,26 @@
import { ChangeEvent, useRef, useState } from 'react'; import { useEffect, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import UploadIcon from '@mui/icons-material/Upload'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
import { Box, Button, LinearProgress, Typography } from '@mui/material'; import { Box, Button, LinearProgress, Typography } from '@mui/material';
import * as SystemApi from 'api/system'; import * as SystemApi from 'api/system';
import { useRequest } from 'alova/client'; import { useRequest } from 'alova/client';
import RestartMonitor from 'app/status/RestartMonitor'; import RestartMonitor from 'app/status/RestartMonitor';
import MessageBox from 'components/MessageBox';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
import DragNdrop from './DragNdrop';
const SingleUpload = () => { const SingleUpload = () => {
const [md5, setMd5] = useState<string>(); const [md5, setMd5] = useState<string>();
const [restarting, setRestarting] = useState<boolean>(); const [restarting, setRestarting] = useState<boolean>(false);
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
const [file, setFile] = useState<File>();
const { LL } = useI18nContext();
const { const {
loading: isUploading, loading: isUploading,
@@ -28,55 +35,40 @@ const SingleUpload = () => {
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL()); toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
setFile(undefined); setFile(undefined);
} else { } else {
setRestarting(true); setRestartNeeded(true);
// setRestarting(true);
} }
}); });
const [file, setFile] = useState<File>(); const { send: restartCommand } = useRequest(SystemApi.restart(), {
const inputRef = useRef<HTMLInputElement | null>(null); immediate: false
});
const { LL } = useI18nContext(); const restart = async () => {
await restartCommand().catch((error: Error) => {
const handleUploadClick = () => { toast.error(error.message);
inputRef.current?.click();
};
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) {
return;
}
setFile(e.target.files[0]);
await sendUpload(e.target.files[0]).catch((error: Error) => {
if (error.message === 'The user aborted a request') {
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
} else if (error.message === 'Network Error') {
toast.warning('Invalid file extension or incompatible bin file');
} else {
toast.error(error.message);
}
}); });
setRestarting(true);
}; };
useEffect(async () => {
if (file) {
console.log('going to upload file ', file.name);
await sendUpload(file).catch((error: Error) => {
if (error.message === 'The user aborted a request') {
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
} else if (error.message === 'Network Error') {
toast.warning('Invalid file extension or incompatible bin file');
} else {
toast.error(error.message);
}
});
}
}, [file]);
return ( return (
<> <>
<input <DragNdrop onFileSelected={setFile} width="340px" height="140px" />
type="file"
ref={inputRef}
style={{ display: 'none' }}
onChange={handleFileChange}
/>
<Button
sx={{ ml: 2 }}
startIcon={<UploadIcon />}
variant="outlined"
color="secondary"
onClick={handleUploadClick}
>
{file ? LL.UPLOADING() + ` ${file.name}` : LL.UPLOAD()}
</Button>
{isUploading && ( {isUploading && (
<> <>
@@ -111,6 +103,19 @@ const SingleUpload = () => {
</Box> </Box>
)} )}
{restartNeeded && (
<MessageBox mt={4} level="warning" message={LL.RESTART_TEXT(0)}>
<Button
startIcon={<PowerSettingsNewIcon />}
variant="contained"
color="error"
onClick={restart}
>
{LL.RESTART()}
</Button>
</MessageBox>
)}
{restarting && <RestartMonitor />} {restarting && <RestartMonitor />}
</> </>
); );

View File

@@ -0,0 +1,83 @@
.drag-drop {
/* background: #fff; */
border: 1px solid var(--border-color);
border-radius: 8px;
}
.document-uploader {
border: 2px dashed #4282fe;
/* background-color: #f4fbff; */
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
border-radius: 8px;
cursor: pointer;
&.active {
border-color: #6dc24b;
}
.upload-info {
display: flex;
align-items: center;
margin-bottom: 1rem;
svg {
font-size: 36px;
margin-right: 1rem;
}
div {
p {
margin: 0;
font-size: 14px;
}
}
}
.file-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border: 1px solid var(--border-color);
border-radius: 8px;
.file-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
flex: 1;
p {
margin: 0;
font-size: 14px;
color: #6dc24b;
}
}
.file-actions {
cursor: pointer;
&:hover {
svg {
color: #d44;
}
}
}
}
input[type='file'] {
display: none;
}
}

View File

@@ -194,7 +194,6 @@ const de: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Herunterladen geplanter Befehle', DOWNLOAD_SCHEDULE_TEXT: 'Herunterladen geplanter Befehle',
DOWNLOAD_SETTINGS_TEXT: 'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten', DOWNLOAD_SETTINGS_TEXT: 'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten',
UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json), zur optionalen Validitätsprüfung zuerst die (.md5) Datei hochladen', UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json), zur optionalen Validitätsprüfung zuerst die (.md5) Datei hochladen',
UPLOADING: 'Hochladen',
UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher', UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen eine Datei hierher',
ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut', ERROR: 'Unerwarteter Fehler, bitter versuchen Sie es erneut',
TIME_SET: 'Zeit gesetzt', TIME_SET: 'Zeit gesetzt',

View File

@@ -194,7 +194,6 @@ const en: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events',
DOWNLOAD_SETTINGS_TEXT: 'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information', DOWNLOAD_SETTINGS_TEXT: 'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below, for optional validation upload (.md5) first', UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below, for optional validation upload (.md5) first',
UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Drop file or click here', UPLOAD_DROP_TEXT: 'Drop file or click here',
ERROR: 'Unexpected Error, please try again', ERROR: 'Unexpected Error, please try again',
TIME_SET: 'Time set', TIME_SET: 'Time set',

View File

@@ -194,7 +194,6 @@ const fr: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate
DOWNLOAD_SETTINGS_TEXT: "Téléchargez les paramètres de l'application. Soyez prudent lorsque vous partagez vos paramètres car ce fichier contient des mots de passe et d'autres informations système sensibles.", DOWNLOAD_SETTINGS_TEXT: "Téléchargez les paramètres de l'application. Soyez prudent lorsque vous partagez vos paramètres car ce fichier contient des mots de passe et d'autres informations système sensibles.",
UPLOAD_TEXT: "Téléchargez un nouveau fichier de firmware (.bin), un fichier de paramètres ou de personnalisations (.json) ci-dessous, pour une validation optionnelle téléchargez d'abord un fichier (.md5)", UPLOAD_TEXT: "Téléchargez un nouveau fichier de firmware (.bin), un fichier de paramètres ou de personnalisations (.json) ci-dessous, pour une validation optionnelle téléchargez d'abord un fichier (.md5)",
UPLOADING: 'Téléchargement',
UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici', UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici',
ERROR: 'Erreur inattendue, veuillez réessayer', ERROR: 'Erreur inattendue, veuillez réessayer',
TIME_SET: 'Time set', TIME_SET: 'Time set',

View File

@@ -194,7 +194,6 @@ const it: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events',
DOWNLOAD_SETTINGS_TEXT: 'Scarica le impostazioni dell applicazione. Fai attenzione quando condividi le tue impostazioni poiché questo file contiene password e altre informazioni di sistema riservate', DOWNLOAD_SETTINGS_TEXT: 'Scarica le impostazioni dell applicazione. Fai attenzione quando condividi le tue impostazioni poiché questo file contiene password e altre informazioni di sistema riservate',
UPLOAD_TEXT: 'Carica un nuovo file firmware (.bin) , file delle impostazioni o delle personalizzazioni (.json) di seguito, per un opzione di convalida scaricare dapprima un file "*.MD5" ', UPLOAD_TEXT: 'Carica un nuovo file firmware (.bin) , file delle impostazioni o delle personalizzazioni (.json) di seguito, per un opzione di convalida scaricare dapprima un file "*.MD5" ',
UPLOADING: 'Caricamento',
UPLOAD_DROP_TEXT: 'Trascina il file o clicca qui', UPLOAD_DROP_TEXT: 'Trascina il file o clicca qui',
ERROR: 'Errore Inaspettato, prego tenta ancora', ERROR: 'Errore Inaspettato, prego tenta ancora',
TIME_SET: 'Imposta Ora', TIME_SET: 'Imposta Ora',

View File

@@ -194,7 +194,6 @@ const nl: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events',
DOWNLOAD_SETTINGS_TEXT: 'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text', DOWNLOAD_SETTINGS_TEXT: 'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text',
UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder', UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder',
UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier', UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier',
ERROR: 'Onverwachte fout, probeer opnieuw', ERROR: 'Onverwachte fout, probeer opnieuw',
TIME_SET: 'Tijd ingesteld', TIME_SET: 'Tijd ingesteld',

View File

@@ -194,7 +194,6 @@ const no: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Last ned planlagte oppgaver', DOWNLOAD_SCHEDULE_TEXT: 'Last ned planlagte oppgaver',
DOWNLOAD_SETTINGS_TEXT: 'Last ned applikasjonskonfigurasjon. Vær varsom med å dele fila da den inneholder passord og annen sensitiv system informasjon', DOWNLOAD_SETTINGS_TEXT: 'Last ned applikasjonskonfigurasjon. Vær varsom med å dele fila da den inneholder passord og annen sensitiv system informasjon',
UPLOAD_TEXT: 'Last opp en ny firmware (.bin) fil, innstillinger eller tilpassninger (.json) fil nedenfor', UPLOAD_TEXT: 'Last opp en ny firmware (.bin) fil, innstillinger eller tilpassninger (.json) fil nedenfor',
UPLOADING: 'Opplasting',
UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her', UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her',
ERROR: 'Ukjent feil, prøv igjen', ERROR: 'Ukjent feil, prøv igjen',
TIME_SET: 'Still in tid', TIME_SET: 'Still in tid',

View File

@@ -194,7 +194,6 @@ const pl: BaseTranslation = {
DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.', DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.',
DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uwaga! Plik z ustawieniami zawiera hasła oraz inne wrażliwe informacje systemowe! Nie udostepniaj go pochopnie!', DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uwaga! Plik z ustawieniami zawiera hasła oraz inne wrażliwe informacje systemowe! Nie udostepniaj go pochopnie!',
UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji z sumą kontrolną (.md5).', UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji z sumą kontrolną (.md5).',
UPLOADING: 'Wysłano',
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij', UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!', ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
TIME_SET: 'Zegar został ustawiony.', TIME_SET: 'Zegar został ustawiony.',

View File

@@ -194,7 +194,6 @@ const sk: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Stiahnutie plánovača udalostí', DOWNLOAD_SCHEDULE_TEXT: 'Stiahnutie plánovača udalostí',
DOWNLOAD_SETTINGS_TEXT: 'Stiahnite si nastavenia aplikácie. Pri zdieľaní nastavení buďte opatrní, pretože tento súbor obsahuje heslá a iné citlivé systémové informácie.', DOWNLOAD_SETTINGS_TEXT: 'Stiahnite si nastavenia aplikácie. Pri zdieľaní nastavení buďte opatrní, pretože tento súbor obsahuje heslá a iné citlivé systémové informácie.',
UPLOAD_TEXT: 'Najskôr nahrajte nový súbor firmvéru (.bin), nastavenia alebo prispôsobenia (.json), pre voliteľné overenie nahrajte súbor (.md5)', UPLOAD_TEXT: 'Najskôr nahrajte nový súbor firmvéru (.bin), nastavenia alebo prispôsobenia (.json), pre voliteľné overenie nahrajte súbor (.md5)',
UPLOADING: 'Nahrávanie',
UPLOAD_DROP_TEXT: 'Potiahnúť a pripnúť súbor alebo kliknúť sem', UPLOAD_DROP_TEXT: 'Potiahnúť a pripnúť súbor alebo kliknúť sem',
ERROR: 'Neočakávaná chyba, prosím skúste to znova', ERROR: 'Neočakávaná chyba, prosím skúste to znova',
TIME_SET: 'Nastavený čas', TIME_SET: 'Nastavený čas',

View File

@@ -194,7 +194,6 @@ const sv: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate
DOWNLOAD_SETTINGS_TEXT: 'Ladda ner applikationsinställningar. Var försiktig om du delar dina iställlningar då de innehåller lösenord och annan känslig systeminformation', DOWNLOAD_SETTINGS_TEXT: 'Ladda ner applikationsinställningar. Var försiktig om du delar dina iställlningar då de innehåller lösenord och annan känslig systeminformation',
UPLOAD_TEXT: 'Ladda upp ett nytt firmware (.bin), inställningar eller anpassningar (.json) nedan', UPLOAD_TEXT: 'Ladda upp ett nytt firmware (.bin), inställningar eller anpassningar (.json) nedan',
UPLOADING: 'Laddar upp',
UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här', UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här',
ERROR: 'Okänt Fel, var god försök igen', ERROR: 'Okänt Fel, var god försök igen',
TIME_SET: 'Ställ in tid', TIME_SET: 'Ställ in tid',

View File

@@ -194,7 +194,6 @@ const tr: Translation = {
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate
DOWNLOAD_SETTINGS_TEXT: 'Uygulama ayarlarını indir. Bu dosya hassas sistem bilgileri ve şifrelerinizi içerdiğinden ayarlarınızı paylaşırken dikkatli olun', DOWNLOAD_SETTINGS_TEXT: 'Uygulama ayarlarını indir. Bu dosya hassas sistem bilgileri ve şifrelerinizi içerdiğinden ayarlarınızı paylaşırken dikkatli olun',
UPLOAD_TEXT: 'Yeni bir bellenim(.bin) dosyası yükleyin, ayarlar ve özelleştirmeler(.json) dosyası aşağıda, sçenekli denetim yüklemesi(.md5) için önce', UPLOAD_TEXT: 'Yeni bir bellenim(.bin) dosyası yükleyin, ayarlar ve özelleştirmeler(.json) dosyası aşağıda, sçenekli denetim yüklemesi(.md5) için önce',
UPLOADING: 'Yüklüyor',
UPLOAD_DROP_TEXT: 'Buraya tıklayın yada dosyayı sürükleyip bırakın', UPLOAD_DROP_TEXT: 'Buraya tıklayın yada dosyayı sürükleyip bırakın',
ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.', ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.',
TIME_SET: 'Zaman ayarı', TIME_SET: 'Zaman ayarı',

View File

@@ -1,127 +1,123 @@
import formidable from "formidable"; import formidable from 'formidable';
function pad(number) { function pad(number) {
var r = String(number); var r = String(number);
if (r.length === 1) { if (r.length === 1) {
r = '0' + r; r = '0' + r;
} }
return r; return r;
} }
// e.g. 2024-03-29 07:02:37.856 // e.g. 2024-03-29 07:02:37.856
Date.prototype.toISOString = function () { Date.prototype.toISOString = function () {
return ( return (
this.getUTCFullYear() + this.getUTCFullYear() +
'-' + '-' +
pad(this.getUTCMonth() + 1) + pad(this.getUTCMonth() + 1) +
'-' + '-' +
pad(this.getUTCDate()) + pad(this.getUTCDate()) +
' ' + ' ' +
pad(this.getUTCHours()) + pad(this.getUTCHours()) +
':' + ':' +
pad(this.getUTCMinutes()) + pad(this.getUTCMinutes()) +
':' + ':' +
pad(this.getUTCSeconds()) + pad(this.getUTCSeconds()) +
'.' + '.' +
String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5)
); );
}; };
export default () => { export default () => {
return { return {
name: "vite:mockserver", name: 'vite:mockserver',
configureServer: async (server) => { configureServer: async (server) => {
server.middlewares.use(async (req, res, next) => {
// catch any file uploads
if (req.url.startsWith('/rest/uploadFile')) {
let progress = 0;
const file_size = req.headers['content-length'];
server.middlewares.use(async (req, res, next) => { req.on('data', async (chunk) => {
progress += chunk.length;
const percentage = (progress / file_size) * 100;
console.log(`Progress: ${Math.round(percentage)}%`);
// await new Promise((resolve) => setTimeout(() => resolve(), 3000)); // slow it down
});
// catch any file uploads const form = formidable({});
if (req.url.startsWith("/rest/uploadFile")) { let fields;
let files;
let progress = 0; try {
const file_size = req.headers['content-length']; [fields, files] = await form.parse(req);
} catch (err) {
req.on('data', async (chunk) => { console.error(err);
progress += chunk.length; res.writeHead(err.httpCode || 400, {
const percentage = (progress / file_size) * 100; 'Content-Type': 'text/plain'
console.log(`Progress: ${Math.round(percentage)}%`);
// await new Promise((resolve) => setTimeout(() => resolve(), 3000)); // slow it down
});
const form = formidable({});
let fields;
let files;
try {
[fields, files] = await form.parse(req);
} catch (err) {
console.error(err);
res.writeHead(err.httpCode || 400, {
"Content-Type": "text/plain",
});
res.end(String(err));
return;
}
// only process when we have a file
if (Object.keys(files).length > 0) {
const uploaded_file = files.file[0];
const file_name = uploaded_file.originalFilename;
const file_extension = file_name.substring(file_name.lastIndexOf('.') + 1);
console.log("Filename: " + file_name);
console.log("Extension: " + file_extension);
console.log("File size: " + file_size);
if (file_extension === 'bin' || file_extension === 'json') {
console.log("File uploaded successfully!");
} else if (file_extension === 'md5') {
console.log("MD5 hash generated successfully!");
res.end(
JSON.stringify({
md5: 'ef4304fc4d9025a58dcf25d71c882d2c',
}),
);
} else {
res.statusCode = 400;
console.log("Invalid file extension!");
}
}
res.end();
}
// SSE Eventsource
else if (req.url.startsWith("/es/log")) {
res.writeHead(200, {
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
'Content-Type': 'text/event-stream'
});
let count = 0;
const interval = setInterval(() => {
const data = {
t: new Date().toISOString(),
l: (3 + (count % 6)),
i: count,
n: 'system',
m: 'message #' + count++
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
// if client closes connection
res.on('close', () => {
console.log('Closing ES connection');
clearInterval(interval);
res.end();
});
} else {
next(); // move on to the next middleware function in chain
}
}); });
}, res.end(String(err));
}; return;
}
// only process when we have a file
if (Object.keys(files).length > 0) {
const uploaded_file = files.file[0];
const file_name = uploaded_file.originalFilename;
const file_extension = file_name.substring(
file_name.lastIndexOf('.') + 1
);
console.log('Filename: ' + file_name);
console.log('Extension: ' + file_extension);
console.log('File size: ' + file_size);
if (file_extension === 'bin' || file_extension === 'json') {
console.log('File uploaded successfully!');
} else if (file_extension === 'md5') {
console.log('MD5 hash generated successfully!');
res.end(
JSON.stringify({
md5: 'ef4304fc4d9025a58dcf25d71c882d2c'
})
);
} else {
res.statusCode = 400;
console.log('Invalid file extension!');
}
}
res.end();
}
// SSE Eventsource
else if (req.url.startsWith('/es/log')) {
res.writeHead(200, {
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
'Content-Type': 'text/event-stream'
});
let count = 0;
const interval = setInterval(() => {
const data = {
t: new Date().toISOString(),
l: 3 + (count % 6),
i: count,
n: 'system',
m: 'message #' + count++
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
// if client closes connection
res.on('close', () => {
console.log('Closing ES connection');
clearInterval(interval);
res.end();
});
} else {
next(); // move on to the next middleware function in chain
}
});
}
};
}; };

View File

@@ -5,11 +5,17 @@
"author": "proddy", "author": "proddy",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"mock-rest": "bun --watch rest_server.ts" "mock-rest": "bun --watch rest_server.ts",
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'"
}, },
"dependencies": { "dependencies": {
"@msgpack/msgpack": "^2.8.0", "@msgpack/msgpack": "^2.8.0",
"itty-router": "^5.0.17" "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
"formidable": "^3.5.1",
"itty-router": "^5.0.17",
"prettier": "^3.3.3"
}, },
"packageManager": "yarn@4.4.0" "packageManager": "yarn@4.4.0"
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff