├── .gitignore ├── Documentation.docx ├── LICENSE ├── README.md ├── app.js ├── package.json ├── public ├── css │ └── style.css ├── img │ └── 200.gif └── js │ ├── getfilelist.js │ └── uploader.js ├── setup project (Linux).sh ├── setup project (windows).bat └── view └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # node 2 | node_modules 3 | package-lock.json 4 | 5 | # user files 6 | userfiles 7 | 8 | # documentation files 9 | ~$cumentation.docx 10 | 11 | # temps 12 | *.tmp -------------------------------------------------------------------------------- /Documentation.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GamEditor/Node.js-FileManager/3d01c74969a46c407163902dabe175cb56c15b1d/Documentation.docx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 GamEditors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js-FileManager 2 | This is a simple file manager for download and upload files on the server by categories. the file manager is made up with Node.js and Express. you can change it, improve it and publish it for yourself. 3 | 4 | also you can use this simple file server for sharing files on a local network with every devices (such as desktop and mobiles) too! 5 | 6 | Please follow me on instagram 7 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const fs = require('fs'); 4 | const multer = require('multer'); 5 | 6 | const port = 8000; 7 | const responsedelay = 50; // miliseconds 8 | 9 | // static folders 10 | app.use(express.static('public')); 11 | app.use(express.static('userfiles')); 12 | app.use(express.static('view')); 13 | 14 | // home page 15 | app.get('/', function(req, res) 16 | { 17 | res.sendFile('index.html'); 18 | }); 19 | 20 | // upload handler 21 | var uploadStorage = multer.diskStorage( 22 | { 23 | destination: function (req, file, cb) 24 | { 25 | cb(null, `./userfiles/${req.query.type}`); ///${req.folder}`); 26 | }, 27 | filename: function (req, file, cb) 28 | { 29 | //let fileName = checkFileExistense(req.query.folder ,file.originalname); 30 | cb(null, file.originalname); 31 | } 32 | }); 33 | 34 | var upload = multer({ storage: uploadStorage }); 35 | 36 | app.post('/', upload.single('file'), function(req, res) 37 | { 38 | console.log(req.file); 39 | console.log('file upload...'); 40 | }); 41 | 42 | // all type of files except images will explored here 43 | app.post('/files-list', function(req, res) 44 | { 45 | let folder = req.query.folder; 46 | let contents = ''; 47 | 48 | let readingdirectory = `./userfiles/${folder}`; 49 | 50 | fs.readdir(readingdirectory, function(err, files) 51 | { 52 | if(err) { console.log(err); } 53 | else if(files.length > 0) 54 | { 55 | files.forEach(function(value, index, array) 56 | { 57 | fs.stat(`${readingdirectory}/${value}`, function(err, stats) 58 | { 59 | let filesize = ConvertSize(stats.size); 60 | contents += '' + value + '' + filesize + '/' + folder + '/' + value + '' + '\n'; 61 | 62 | if(index == (array.length - 1)) { setTimeout(function() {res.send(contents);}, responsedelay); } 63 | }); 64 | }); 65 | } 66 | else 67 | { 68 | setTimeout(function() {res.send(contents);}, responsedelay); 69 | } 70 | }); 71 | }); 72 | 73 | /* 74 | this part is written because i wanted to show you how you can explore in differnt directories 75 | for search a specific type of files (in this case images) 76 | */ 77 | app.post('/image-list', function(req, res) 78 | { 79 | // this part is a little different because i wanted to find all images without looking at file extentions 80 | // and instead i will looking for image files due to the binary information of files and check several first bytes 81 | // those bytes are unique for each file type (or mime type) 82 | 83 | var contents = ''; 84 | 85 | var directories = ['/font', '/html', '/image', '/pdf', '/video']; 86 | var dirindex = 0; 87 | 88 | /** 89 | * this is an inline function for iterating the directories array instead of loops. it cause avoiding conflitcness of loops and async jobs. 90 | * @param dindex index of directories 91 | */ 92 | var readNextDirectory = function(dindex) 93 | { 94 | let directory = directories[dindex]; 95 | 96 | fs.readdir(`./userfiles${directory}`, function(err, files) 97 | { 98 | if(err) 99 | { 100 | console.log(`Error: ${err}`); 101 | } 102 | else if(files.length > 0) 103 | { 104 | files.forEach(function(value, index, array) 105 | { 106 | fs.readFile(`./userfiles${directory}/${value}`, function(err, data) 107 | { 108 | if(err) 109 | { 110 | console.log(err); 111 | } 112 | // ffd8 and 89504e47 are magic number of image files (jpeg and png) 113 | // this condition searches for "jpeg" and "png" images (you must add other magic numbers yourself) 114 | else if(data.toString('hex').search('ffd8') == 0 || data.toString('hex').search('89504e47') == 0) // findig jpeg and png images by watching files as binary data and checking the 2 or 4 magic bytes/numbers. 115 | { 116 | fs.stat(`./userfiles${directory}/${value}`, function(err, stats) 117 | { 118 | let filesize = ConvertSize(stats.size); 119 | contents += '
' + value + '

' + value + '

' + filesize + '

' + directory + '/' + value + '

' + '\n'; 120 | }); 121 | } 122 | 123 | if(index == array.length - 1 && dirindex == directories.length - 1) 124 | { 125 | setTimeout(function() { res.send(contents); }, responsedelay); 126 | return; 127 | } 128 | else if(index == array.length - 1 && dirindex < directories.length) 129 | { 130 | readNextDirectory(++dirindex); 131 | } 132 | }); 133 | }); 134 | } 135 | else if(dirindex == directories.length - 1) 136 | { 137 | setTimeout(function() { res.send(contents); }, responsedelay); 138 | } 139 | else if(files.length == 0) 140 | { 141 | readNextDirectory(++dirindex); 142 | } 143 | }); 144 | }; 145 | 146 | readNextDirectory(dirindex); 147 | }); 148 | 149 | /** 150 | * it gives a number as byte and convert it to KB, MB and GB (depends on file size) and return the result as string. 151 | * @param number file size in Byte 152 | */ 153 | function ConvertSize(number) 154 | { 155 | if(number <= 1024) { return (`${number} Byte`); } 156 | else if(number > 1024 && number <= 1048576) { return ((number / 1024).toPrecision(3) + ' KB'); } 157 | else if(number > 1048576 && number <= 1073741824) { return ((number / 1048576).toPrecision(3) + ' MB'); } 158 | else if(number > 1073741824 && number <= 1099511627776) { return ((number / 1073741824).toPrecision(3) + ' GB'); } 159 | } 160 | 161 | // start server 162 | app.listen(port, function() 163 | { 164 | console.log(`Server is started on port: ${port}`); 165 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-manager", 3 | "version": "1.0.0", 4 | "description": "a simple file manager with upload ability", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "file-manager" 11 | ], 12 | "author": "mohammad fathi", 13 | "license": "ISC", 14 | "dependencies": { 15 | "express": "^4.16.3", 16 | "multer": "^1.4.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: Arial; 4 | } 5 | 6 | /* Style the tab */ 7 | .tab { 8 | overflow: hidden; 9 | border: 1px solid #ccc; 10 | background-color: #f1f1f1; 11 | } 12 | 13 | /* Style the buttons inside the tab */ 14 | .tab button { 15 | background-color: inherit; 16 | float: left; 17 | border: none; 18 | outline: none; 19 | cursor: pointer; 20 | padding: 14px 16px; 21 | transition: 0.3s; 22 | font-size: 17px; 23 | } 24 | 25 | /* Change background color of buttons on hover */ 26 | .tab button:hover { 27 | background-color: #ddd; 28 | } 29 | 30 | /* Create an active/current tablink class */ 31 | .tab button.active { 32 | background-color: #ccc; 33 | } 34 | 35 | /* Style the tab content */ 36 | .tabcontent { 37 | display: none; 38 | padding: 6px 12px; 39 | border: 1px solid #ccc; 40 | border-top: none; 41 | } 42 | 43 | div#viewoptions { 44 | display: none; 45 | float: right; 46 | padding: 14px 16px; 47 | } 48 | 49 | div#progressBar { 50 | background-color: gray; 51 | height: 10px; 52 | width: 0%; /* for start */ 53 | } 54 | 55 | table.fileproperties { 56 | width: 100%; 57 | background-color: #e4e4e4; 58 | border-collapse: collapse; 59 | } 60 | 61 | table.fileproperties th, table.fileproperties td { 62 | padding: 8px; 63 | width: 33.33%; 64 | } 65 | 66 | table.fileproperties tr:nth-child(even) { 67 | background-color: #f2f2f2; 68 | } 69 | table.fileproperties tr:hover { 70 | background-color: rgba(170, 239, 248, 0.5); 71 | } 72 | 73 | table.fileproperties th { 74 | padding-top: 8px; 75 | padding-bottom: 8px; 76 | text-align: left; 77 | border-left: 1px solid #ddd; 78 | background-color: #ccc; 79 | color: black; 80 | } 81 | 82 | table.fileproperties td a { 83 | text-decoration: none; 84 | } 85 | 86 | table.fileproperties td a:hover, table.fileproperties td a:link, table.fileproperties td a:visited, table.fileproperties td a:active { 87 | text-decoration: none; 88 | color: blue; 89 | } 90 | 91 | #loadinggif { 92 | position: fixed; 93 | display: none; 94 | top: 50%; 95 | left: 50%; 96 | width: 5vw; 97 | height: 5vw; 98 | transform: translate(-50%, -50%); 99 | } 100 | 101 | /* tile */ 102 | div.tile { 103 | width: 350px; 104 | height: 100px; 105 | margin: 2px; 106 | float: left; 107 | background-color: rgb(231, 231, 231); 108 | } 109 | 110 | div.tile:hover { 111 | background-color: rgba(170, 239, 248, 0.5); 112 | } 113 | 114 | div.tile div.image { 115 | display: flex; 116 | width: 30%; 117 | height: 100%; 118 | float: left; 119 | background-color: rgb(211, 211, 211); 120 | } 121 | 122 | div.tile div.image img { 123 | display: flex; 124 | margin: auto; 125 | width: 100%; 126 | } 127 | 128 | div.tile p { 129 | line-height: 1; 130 | } 131 | 132 | div.tile p a { 133 | text-decoration: none; 134 | } 135 | 136 | /* details */ 137 | div.details { 138 | position: relative; 139 | margin-bottom: 5px; 140 | width: 100%; 141 | height: 100px; 142 | float: left; 143 | 144 | background-color: rgb(231, 231, 231); 145 | } 146 | 147 | div.details:hover { 148 | background-color: rgba(170, 239, 248, 0.5); 149 | } 150 | 151 | div.details div.image { 152 | display: flex; 153 | width: 25%; 154 | height: 100%; 155 | float: left; 156 | margin: auto; 157 | 158 | background-color: rgb(211, 211, 211); 159 | } 160 | 161 | div.details div.image img { 162 | height: 100%; 163 | margin: auto; 164 | } 165 | 166 | div.details p { 167 | position: absolute; 168 | float: left; 169 | width: 25%; 170 | text-align: center; 171 | line-height: 1.5; 172 | vertical-align: middle; 173 | top: 50%; 174 | margin: 0; 175 | transform: translateY(-50%); 176 | } 177 | 178 | div.details p a { 179 | text-decoration: none; 180 | } 181 | 182 | 183 | div.details p:nth-of-type(1) { 184 | left: 24%; 185 | } 186 | 187 | div.details p:nth-of-type(2) { 188 | left: 45%; 189 | } 190 | 191 | div.details p:nth-of-type(3) { 192 | left: 70%; 193 | } 194 | 195 | @media screen and (max-width: 850px){ 196 | div.details p:nth-of-type(1) { 197 | left: 35%; 198 | } 199 | 200 | div.details p:nth-of-type(2) { 201 | left: 45%; 202 | } 203 | 204 | div.details p:nth-of-type(3) { 205 | left: 70%; 206 | } 207 | } -------------------------------------------------------------------------------- /public/img/200.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GamEditor/Node.js-FileManager/3d01c74969a46c407163902dabe175cb56c15b1d/public/img/200.gif -------------------------------------------------------------------------------- /public/js/getfilelist.js: -------------------------------------------------------------------------------- 1 | const table = 2 | '\ 3 | \ 4 | \ 5 | \ 6 | \ 7 | '; 8 | 9 | /** 10 | * request files due to the type 11 | * @param filestype type of the files. 12 | * @param fileholderid the place of showing files at innerHTML. 13 | * @param loadinggifid until waiting for response from server, this (gif) image will be displayed. 14 | */ 15 | function getListOfFiles(filestype, fileholderid, loadinggifid) 16 | { 17 | const fileholder = document.getElementById(fileholderid); 18 | const loadinggif = document.getElementById(loadinggifid); 19 | let requestedfileslist = ""; 20 | 21 | var xhr = new XMLHttpRequest(); 22 | xhr.onreadystatechange = function() 23 | { 24 | if(this.readyState < 4) 25 | { 26 | fileholder.innerHTML = ""; 27 | loadinggif.style.display = "block"; 28 | } 29 | 30 | if(this.readyState == 4 && this.status == 200) 31 | { 32 | loadinggif.style.display = "none"; 33 | 34 | if(filestype == "image") { fileholder.innerHTML = this.responseText; } 35 | else { fileholder.innerHTML = table + this.responseText + "
NameSizeAddress
"; } 36 | } 37 | }; 38 | 39 | switch(filestype) 40 | { 41 | case "image": 42 | requestedfileslist = "image-list"; 43 | break; 44 | default: 45 | requestedfileslist = "files-list"; 46 | break; 47 | } 48 | 49 | if(requestedfileslist != "") 50 | { 51 | xhr.open("POST", "/" + requestedfileslist + "?folder=" + filestype, true); 52 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 53 | xhr.send(); 54 | } 55 | } 56 | 57 | /** 58 | * opening the clicked tab and highlight it and it will open the upload panel of each file type 59 | */ 60 | function openTab(evt, tabid) 61 | { 62 | var i, tabcontent, tablinks; 63 | tabcontent = document.getElementsByClassName("tabcontent"); 64 | 65 | for (i = 0; i < tabcontent.length; i++) 66 | { 67 | tabcontent[i].style.display = "none"; 68 | } 69 | 70 | tablinks = document.getElementsByClassName("tablinks"); 71 | 72 | for (i = 0; i < tablinks.length; i++) 73 | { 74 | tablinks[i].className = tablinks[i].className.replace(" active", ""); 75 | } 76 | 77 | document.getElementById(tabid).style.display = "block"; 78 | evt.currentTarget.className += " active"; 79 | } 80 | 81 | /** 82 | * replace a className by another className for changing the style of desired elements 83 | */ 84 | function replaceClassName(originalClassName, desiredClassName) 85 | { 86 | let elements = document.getElementsByClassName(originalClassName); 87 | 88 | while(elements.length > 0) 89 | { 90 | elements[0].className = desiredClassName; 91 | } 92 | } -------------------------------------------------------------------------------- /public/js/uploader.js: -------------------------------------------------------------------------------- 1 | function uploadToServer(form, uploadProgressDisplayerId) 2 | { 3 | let formData = new FormData(form); 4 | 5 | let xhr = new XMLHttpRequest(); 6 | 7 | xhr.open(form.method, form.action, true); 8 | 9 | xhr.upload.addEventListener('progress', function(ev) 10 | { 11 | let progress = ev.loaded / ev.total * 100; 12 | 13 | if (ev.lengthComputable) 14 | { 15 | uploadProgressDisplayerId.style.width = `${progress}%`; 16 | 17 | if(progress == 100) 18 | { 19 | alert('Your file is uploaded successfuly'); 20 | } 21 | } 22 | else 23 | { 24 | uploadProgressDisplayerId.style.width = 0; 25 | console.log('the length is not calcutable!'); 26 | } 27 | }); 28 | 29 | xhr.send(formData); 30 | } -------------------------------------------------------------------------------- /setup project (Linux).sh: -------------------------------------------------------------------------------- 1 | if [[ ! -d "userfiles" ]] 2 | then 3 | if [ -L "userfiles" ] 4 | then 5 | echo "./userfiles exists" 6 | else 7 | mkdir "./userfiles" 8 | echo "./userfiles created" 9 | fi 10 | fi 11 | 12 | if [[ ! -d "userfiles/file" ]] 13 | then 14 | if [ -L "userfiles/file" ] 15 | then 16 | echo "./userfiles/file exists" 17 | else 18 | mkdir "./userfiles/file" 19 | echo "./userfiles/file created" 20 | fi 21 | fi 22 | 23 | if [[ ! -d "userfiles/font" ]] 24 | then 25 | if [ -L "userfiles/font" ] 26 | then 27 | echo "./userfiles/font exists" 28 | else 29 | mkdir "./userfiles/font" 30 | echo "./userfiles/font created" 31 | fi 32 | fi 33 | 34 | if [[ ! -d "userfiles/html" ]] 35 | then 36 | if [ -L "userfiles/html" ] 37 | then 38 | echo "./userfiles/html exists" 39 | else 40 | mkdir "./userfiles/html" 41 | echo "./userfiles/html created" 42 | fi 43 | fi 44 | 45 | if [[ ! -d "userfiles/image" ]] 46 | then 47 | if [ -L "userfiles/image" ] 48 | then 49 | echo "./userfiles/image exists" 50 | else 51 | mkdir "./userfiles/image" 52 | echo "./userfiles/image created" 53 | fi 54 | fi 55 | 56 | if [[ ! -d "userfiles/pdf" ]] 57 | then 58 | if [ -L "userfiles/pdf" ] 59 | then 60 | echo "./userfiles/pdf exists" 61 | else 62 | mkdir "./userfiles/pdf" 63 | echo "./userfiles/pdf created" 64 | fi 65 | fi 66 | 67 | if [[ ! -d "userfiles/video" ]] 68 | then 69 | if [ -L "userfiles/video" ] 70 | then 71 | echo "./userfiles/video exists" 72 | else 73 | mkdir "./userfiles/video" 74 | echo "./userfiles/video created" 75 | fi 76 | fi 77 | -------------------------------------------------------------------------------- /setup project (windows).bat: -------------------------------------------------------------------------------- 1 | if not exist "userfiles" mkdir userfiles 2 | if not exist "userfiles\file" mkdir userfiles\file 3 | if not exist "userfiles\font" mkdir userfiles\font 4 | if not exist "userfiles\html" mkdir userfiles\html 5 | if not exist "userfiles\image" mkdir userfiles\image 6 | if not exist "userfiles\pdf" mkdir userfiles\pdf 7 | if not exist "userfiles\video" mkdir userfiles\video -------------------------------------------------------------------------------- /view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | File Manager 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | View options: 26 | Details 27 | Tile 28 |
29 |
30 |
31 | 32 |
33 | 34 |
35 |

file

36 |
37 | 38 | 39 |
40 |
41 | 42 |
43 |

font

44 |
45 | 46 | 47 |
48 |
49 | 50 |
51 |

html

52 |
53 | 54 | 55 |
56 |
57 | 58 |
59 |

image

60 |
61 | 62 | 63 |
64 |
65 | 66 |
67 |

pdf

68 |
69 | 70 | 71 |
72 |
73 | 74 |
75 |

video

76 |
77 | 78 | 79 |
80 |
81 | 82 |
Loading Files...
83 | 84 |
85 | 86 | --------------------------------------------------------------------------------