├── .gitignore
├── .nojekyll
├── .travis.yml
├── API.md
├── LICENSE
├── README.md
├── bridges
├── java
│ ├── resources
│ │ ├── log4j2.xml
│ │ └── spring-security.xml
│ ├── src
│ │ └── main
│ │ │ └── com
│ │ │ └── monosand
│ │ │ └── drinkout
│ │ │ └── web
│ │ │ └── servlet
│ │ │ └── AngularFileManagerServlet.java
│ └── webapp
│ │ ├── META-INF
│ │ └── context.xml
│ │ └── WEB-INF
│ │ └── web.xml
├── openwrt-lua
│ ├── etc
│ │ └── config
│ │ │ └── filemanager
│ ├── usr
│ │ └── lib
│ │ │ └── lua
│ │ │ ├── cgi.lua
│ │ │ └── luafm.lua
│ └── www
│ │ └── cgi-bin
│ │ ├── fs
│ │ ├── fs-download
│ │ └── fs-upload
├── php-local
│ ├── LocalBridge
│ │ ├── FileManagerApi.php
│ │ ├── Response.php
│ │ ├── Rest.php
│ │ └── Translate.php
│ ├── index.php
│ └── lang
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── fr.json
│ │ ├── it.json
│ │ ├── ko.json
│ │ └── sk.json
└── php
│ ├── handler.php
│ └── includes
│ ├── ExceptionCatcher.php
│ └── Ftp.php
├── docs
└── index.html
├── gulpfile.js
├── header.txt
├── index.html
├── package-lock.json
├── package.json
└── src
├── css
├── animations.scss
├── dialogs.scss
├── main.scss
├── sprite.scss
├── style.scss
└── variables.scss
├── js
├── app.js
├── controllers
│ ├── main.js
│ └── selector-controller.js
├── directives
│ └── directives.js
├── entities
│ ├── chmod.js
│ └── item.js
├── filters
│ └── filters.js
├── providers
│ ├── config.js
│ └── translations.js
└── services
│ ├── apihandler.js
│ ├── apimiddleware.js
│ └── filenavigator.js
├── svg
├── chevron-down.svg
├── download.svg
├── ext
│ ├── css.svg
│ ├── doc.svg
│ ├── html.svg
│ ├── jpg.svg
│ ├── js.svg
│ ├── pdf.svg
│ ├── png.svg
│ ├── txt.svg
│ ├── unknown.svg
│ └── zip.svg
├── folder.svg
├── folder_closed.svg
├── folder_open.svg
├── grid.svg
├── language.svg
├── list.svg
├── more.svg
├── positive.svg
├── search.svg
└── upload.svg
└── templates
├── current-folder-breadcrumb.html
├── item-context-menu.html
├── main-icons.html
├── main-table-modal.html
├── main-table.html
├── main.html
├── modals.html
├── navbar.html
├── sidebar.html
└── spinner.html
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | /nbproject/private/
3 | nbproject/
4 | node_modules/
5 | dist/
6 |
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 |
5 | before_script:
6 | - npm install
7 |
8 | script:
9 | - gulp lint
10 |
--------------------------------------------------------------------------------
/API.md:
--------------------------------------------------------------------------------
1 | ## angular-filemanager API docs [multiple file support]
2 |
3 | #### Listing (URL: fileManagerConfig.listUrl, Method: POST)
4 |
5 | **JSON Request content**
6 | ```json
7 | {
8 | "action": "list",
9 | "path": "/public_html"
10 | }
11 | ```
12 | **JSON Response**
13 | ```json
14 | { "result": [
15 | {
16 | "name": "magento",
17 | "rights": "drwxr-xr-x",
18 | "size": "4096",
19 | "date": "2016-03-03 15:31:40",
20 | "type": "dir"
21 | }, {
22 | "name": "index.php",
23 | "rights": "-rw-r--r--",
24 | "size": "549923",
25 | "date": "2016-03-03 15:31:40",
26 | "type": "file"
27 | }
28 | ]}
29 | ```
30 | --------------------
31 | #### Rename (URL: fileManagerConfig.renameUrl, Method: POST)
32 | **JSON Request content**
33 | ```json
34 | {
35 | "action": "rename",
36 | "item": "/public_html/index.php",
37 | "newItemPath": "/public_html/index2.php"
38 | }
39 | ```
40 | **JSON Response**
41 | ```json
42 | { "result": { "success": true, "error": null } }
43 | ```
44 | --------------------
45 | #### Move (URL: fileManagerConfig.moveUrl, Method: POST)
46 | **JSON Request content**
47 | ```json
48 | {
49 | "action": "move",
50 | "items": ["/public_html/libs", "/public_html/config.php"],
51 | "newPath": "/public_html/includes"
52 | }
53 | ```
54 | **JSON Response**
55 | ```json
56 | { "result": { "success": true, "error": null } }
57 | ```
58 | --------------------
59 | #### Copy (URL: fileManagerConfig.copyUrl, Method: POST)
60 | **JSON Request content**
61 | ```json
62 | {
63 | "action": "copy",
64 | "items": ["/public_html/index.php", "/public_html/config.php"],
65 | "newPath": "/includes",
66 | "singleFilename": "renamed.php" <-- (only present in single selection copy)
67 | }
68 | ```
69 | **JSON Response**
70 | ```json
71 | { "result": { "success": true, "error": null } }
72 | ```
73 | --------------------
74 | #### Remove (URL: fileManagerConfig.removeUrl, Method: POST)
75 | **JSON Request content**
76 | ```json
77 | {
78 | "action": "remove",
79 | "items": ["/public_html/index.php"],
80 | }
81 | ```
82 | **JSON Response**
83 | ```json
84 | { "result": { "success": true, "error": null } }
85 | ```
86 | --------------------
87 | #### Edit file (URL: fileManagerConfig.editUrl, Method: POST)
88 | **JSON Request content**
89 | ```json
90 | {
91 | "action": "edit",
92 | "item": "/public_html/index.php",
93 | "content": ""
94 | }
95 | ```
96 | **JSON Response**
97 | ```json
98 | { "result": { "success": true, "error": null } }
99 | ```
100 | --------------------
101 | #### Get content of a file (URL: fileManagerConfig.getContentUrl, Method: POST)
102 | **JSON Request content**
103 | ```json
104 | {
105 | "action": "getContent",
106 | "item": "/public_html/index.php"
107 | }
108 | ```
109 | **JSON Response**
110 | ```json
111 | { "result": "" }
112 | ```
113 | --------------------
114 | #### Create folder (URL: fileManagerConfig.createFolderUrl, Method: POST)
115 | **JSON Request content**
116 | ```json
117 | {
118 | "action": "createFolder",
119 | "newPath": "/public_html/new-folder"
120 | }
121 | ```
122 | **JSON Response**
123 | ```json
124 | { "result": { "success": true, "error": null } }
125 | ```
126 | --------------------
127 | #### Set permissions (URL: fileManagerConfig.permissionsUrl, Method: POST)
128 | **JSON Request content**
129 | ```json
130 | {
131 | "action": "changePermissions",
132 | "items": ["/public_html/root", "/public_html/index.php"],
133 | "perms": "rw-r-x-wx",
134 | "permsCode": "653",
135 | "recursive": true
136 | }
137 | ```
138 | **JSON Response**
139 | ```json
140 | { "result": { "success": true, "error": null } }
141 | ```
142 | --------------------
143 | #### Compress file (URL: fileManagerConfig.compressUrl, Method: POST)
144 | **JSON Request content**
145 | ```json
146 | {
147 | "action": "compress",
148 | "items": ["/public_html/photos", "/public_html/docs"],
149 | "destination": "/public_html/backups",
150 | "compressedFilename": "random-files.zip"
151 | }}
152 | ```
153 | **JSON Response**
154 | ```json
155 | { "result": { "success": true, "error": null } }
156 | ```
157 | --------------------
158 | #### Extract file (URL: fileManagerConfig.extractUrl, Method: POST)
159 | **JSON Request content**
160 | ```json
161 | {
162 | "action": "extract",
163 | "destination": "/public_html/extracted-files",
164 | "item": "/public_html/compressed.zip",
165 | "folderName": "extract_dir"
166 | }
167 | ```
168 | **JSON Response**
169 | ```json
170 | { "result": { "success": true, "error": null } }
171 | ```
172 | --------------------
173 | #### Upload file (URL: fileManagerConfig.uploadUrl, Method: POST, Content-Type: multipart/form-data)
174 |
175 | **Http post request payload**
176 | ```
177 | ------WebKitFormBoundaryqBnbHc6RKfXVAf9j
178 | Content-Disposition: form-data; name="destination"
179 | /
180 |
181 | ------WebKitFormBoundaryqBnbHc6RKfXVAf9j
182 | Content-Disposition: form-data; name="file-0"; filename="github.txt"
183 | Content-Type: text/plain
184 | ```
185 | **JSON Response**
186 | ```json
187 | { "result": { "success": true, "error": null } }
188 | ```
189 |
190 | Unlimited file items to upload, each item will be enumerated as file-0, file-1, etc.
191 |
192 | For example, you may retrieve the file in PHP using:
193 | ```php
194 | $destination = $_POST['destination'];
195 | $_FILES['file-0'] or foreach($_FILES)
196 | ```
197 | --------------------
198 | #### Download / Preview file (URL: fileManagerConfig.downloadMultipleUrl, Method: GET)
199 | **Http query params**
200 | ```
201 | [fileManagerConfig.downloadFileUrl]?action=download&path=/public_html/image.jpg
202 | ```
203 | **Response**
204 | ```
205 | -File content
206 | ```
207 | --------------------
208 | #### Download multiples files in ZIP/TAR (URL: fileManagerConfig.downloadFileUrl, Method: GET)
209 | **JSON Request content**
210 | ```json
211 | {
212 | "action": "downloadMultiple",
213 | "items": ["/public_html/image1.jpg", "/public_html/image2.jpg"],
214 | "toFilename": "multiple-items.zip"
215 | }}
216 | ```
217 | **Response**
218 | ```
219 | -File content
220 | ```
221 | --------------------
222 | ##### Errors / Exceptions
223 | Any backend error should be with an error 500 HTTP code.
224 |
225 | Btw, you can also report errors with a 200 response both using this json structure
226 | ```json
227 | { "result": {
228 | "success": false,
229 | "error": "Access denied to remove file"
230 | }}
231 | ```
232 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 joni2back
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AngularJS File Manager
2 |
3 |
4 | [](https://travis-ci.com/azerafati/angularjs-filemanager)
5 | [](https://www.codacy.com/manual/azerafati/angularjs-filemanager?utm_source=github.com&utm_medium=referral&utm_content=azerafati/angularjs-filemanager&utm_campaign=Badge_Grade)
6 | [](https://www.npmjs.com/package/angularjs-filemanager)
7 | [](https://github.com/azerafati/angularjs-filemanager/issues)
8 | 
9 | [](https://coveralls.io/github/azerafati/angularjs-filemanager?branch=master)
10 | 
11 | [](https://github.com/azerafati/angularjs-filemanager/blob/master/LICENSE)
12 |
13 |
14 | This is a minimal and light-weight file manager to explore your files on the server developed in AngularJS using Bootstrap 4
15 |
16 | This project is based on excellent work of [Jonas Sciangula Street](https://github.com/joni2back), but since he's not planning to maintain it I'm moving forward with my own fork.
17 |
18 | *There are example backend connectors in (e.g. PHP, PHP-FTP, Python, Java) or you can create your own backend connector following the [connector API](API.md).*
19 |
20 |
21 |
22 |
23 | ### [Try the DEMO](https://angularjs-filemanager.azerafati.com/)
24 | 
25 | ---------
26 |
27 | ### Install
28 |
29 | **1) Run `npm i @azerafati/angularjs-filemanager --save`**
30 |
31 | **2) Install dependencies**
32 |
33 | **3) Include the dependencies in your project**
34 | ```html
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ```
46 | **4) Use the angular directive in your HTML**
47 | ```html
48 |
49 | ```
50 | ```javascript
51 | angular.module('AngularJS-FileManager').config(['fileManagerConfigProvider', function (config) {
52 | var defaults = config.$get();
53 | config.setBridge('/bridges/php-local/index.php');
54 | config.set({
55 | appName: 'AngularJS File Manager',
56 | pickCallback: function(item) {
57 | var msg = 'Picked %s "%s" for external use'
58 | .replace('%s', item.type)
59 | .replace('%s', item.fullPath());
60 | window.alert(msg);
61 | },
62 |
63 | allowedActions: angular.extend(defaults.allowedActions, {
64 | pickFiles: true,
65 | pickFolders: false,
66 | }),
67 | });
68 | }]);
69 | ```
70 |
71 | ---------
72 |
73 | ### Features
74 | - Multiple file support
75 | - Multilanguage
76 | - List and Icon view
77 | - Multiple file upload
78 | - Pick files callback for third parties apps
79 | - Search files
80 | - Directory tree navigation
81 | - Copy, Move, Rename (Interactive UX)
82 | - Delete, Edit, Preview, Download
83 | - File permissions (Unix chmod style)
84 | - Mobile support
85 |
86 | ### TODO
87 | - Drag and drop
88 | - Dropbox and Google Drive connectors
89 | - Remove usage of jQuery
90 |
91 | ### Backend API
92 | [Read the docs](API.md)
93 |
94 | ---------
95 |
96 | ### Create a new build with your changes
97 | ```sh
98 | gulp build || node node_modules/gulp/bin/gulp.js build
99 | ```
100 |
101 | You can do many things by extending the configuration. Like hide the sidebar or the search button. See [the list of default configurations](/src/js/providers/config.js).
102 |
103 | ---------
104 |
105 | ### Contribute
106 | To contribute to the project you can simply fork this repo. To build a minified version, you can simply run the Gulp
107 | task `gulp build`. The minified/uglified files are created in the `dist` folder.
108 |
109 | ### Versioning
110 | For transparency into our release cycle and in striving to maintain backward compatibility, angular-filemanager is maintained under [the Semantic Versioning guidelines](http://semver.org/).
111 |
112 | ### Copyright and license
113 | Code and documentation released under [the MIT license](https://github.com/joni2back/angular-filemanager/blob/master/LICENSE).
114 |
--------------------------------------------------------------------------------
/bridges/java/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 | %d %p [%t] %C{1.}.%M(%L) %m%n%ex{full}%n
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/bridges/java/resources/spring-security.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/bridges/java/webapp/META-INF/context.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bridges/java/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | angular-filemanager
5 |
6 | contextConfigLocation
7 | classpath:spring-security.xml
8 |
9 |
10 | Spring Security filter
11 | springSecurityFilterChain
12 | org.springframework.web.filter.DelegatingFilterProxy
13 |
14 |
15 | springSecurityFilterChain
16 | /*
17 |
18 |
19 | org.springframework.web.context.ContextLoaderListener
20 |
21 |
22 |
23 | AngularFileManagerServlet
24 | com.monosand.drinkout.web.servlet.AngularFileManagerServlet
25 |
26 | repository.base.path
27 | /tmp
28 |
29 |
30 | date.format
31 | yyyy-MM-dd hh:mm:ss
32 |
33 |
34 | enabled.action
35 | rename,move,remove,edit,createFolder,changePermissions,compress,extract,copy,upload
36 |
37 | 1
38 |
39 |
40 | AngularFileManagerServlet
41 | /bridges/php/handler.php
42 |
43 |
44 | index.html
45 |
46 |
47 |
--------------------------------------------------------------------------------
/bridges/openwrt-lua/etc/config/filemanager:
--------------------------------------------------------------------------------
1 |
2 | config filemanager 'config'
3 | option basedir '/mnt'
4 | option tmpdir '/tmp'
5 |
--------------------------------------------------------------------------------
/bridges/openwrt-lua/usr/lib/lua/cgi.lua:
--------------------------------------------------------------------------------
1 | -- cgi util module
2 |
3 | local uci = require("uci")
4 | local u_c = uci.cursor()
5 | local tmppath = u_c.get("filemanager","config","tmpdir")
6 |
7 | local prevbuf = ""
8 | local blocksize = 4096
9 | local _M = {}
10 |
11 | _M.statusmsg = {
12 | [200] = "OK",
13 | [206] = "Partial Content",
14 | [301] = "Moved Permanently",
15 | [302] = "Found",
16 | [304] = "Not Modified",
17 | [400] = "Bad Request",
18 | [403] = "Forbidden",
19 | [404] = "Not Found",
20 | [405] = "Method Not Allowed",
21 | [408] = "Request Time-out",
22 | [411] = "Length Required",
23 | [412] = "Precondition Failed",
24 | [416] = "Requested range not satisfiable",
25 | [500] = "Internal Server Error",
26 | [503] = "Server Unavailable",
27 | }
28 |
29 | -- call this function passing an empy table. use that table for successive calls.
30 | function _M.new(req)
31 | req.content_length = os.getenv("CONTENT_LENGTH")
32 | req.request_method = os.getenv("REQUEST_METHOD")
33 | if req.request_method == "POST" then
34 | req.content_type, req.boundary = string.match(os.getenv("CONTENT_TYPE"),"^(multipart/form%-data); boundary=\"?(.+)\"?$")
35 | req.boundary = "--" .. req.boundary
36 | end
37 | -- this is useful only if you have /tmp on tmpfs like in openwrt. otherwise can be set to ""
38 | req.tempdir = tmppath
39 | req.post = {}
40 | end
41 |
42 | -- this function is needed to clean temp file since and hide implementation details
43 | function _M.cleanup(req)
44 | for k, v in pairs(req.post) do
45 | for j, v in pairs(req.post[k]) do
46 | if req.post[k][j].tempname then
47 | os.remove(req.post[k][j].tempname) -- if file unused
48 | os.remove("/tmp/" .. string.match(req.post[k][j].tempname,"^" .. req.tempdir .. "(.+)"))
49 | end
50 | end
51 | end
52 | end
53 |
54 | -- del: delimiter
55 | -- return chunk (string), found (boolean)
56 | local function chunkread(del)
57 | local buf, found = 0
58 | local del = del or "\r\n"
59 |
60 | buf = io.read(math.max(0,blocksize + #del - #prevbuf))
61 | if prevbuf ~= "" then buf = prevbuf .. ( buf or "" ); prevbuf = "" end
62 | if not buf then return end
63 |
64 | s, e = string.find(buf,del,1,true)
65 | if s and e then
66 | found = 1
67 | prevbuf = string.sub(buf,e+1)
68 | buf = string.sub(buf,1,s-1)
69 | else
70 | prevbuf = string.sub(buf,math.min(blocksize,#buf)+1)
71 | buf = string.sub(buf,1,math.min(blocksize,#buf))
72 | end
73 |
74 | return buf, found
75 | end
76 |
77 |
78 | function _M.parse_request_body (req)
79 | local chunk, found, type, tempname, tempfile
80 | local param = {}
81 |
82 | -- read first boundary line
83 | chunk, found = chunkread(req.boundary)
84 | chunk, found = chunkread("\r\n")
85 | while chunk == "" do
86 | -- read part headers and get parameters value
87 | repeat
88 | chunk, found = chunkread("\r\n")
89 | if not found then return 400, "Malformed POST. Missing Part Header or Part Header too long." end
90 | string.gsub(chunk, ';%s*([^%s=]+)="(.-[^\\])"', function(k, v) param[k] = v end)
91 | param.type = param.type or string.match(chunk, "^Content%-Type: (.+)$")
92 | until chunk == ""
93 |
94 | -- prepare file data read
95 | if not param.name then return 400, "Malformed POST. Check Header parameters." end
96 | param.size=0
97 | param.tempname = req.tempdir .. string.match(os.tmpname(), "^/tmp(.+)")
98 | tempfile = io.open(param.tempname, "w")
99 |
100 | -- read part body content until boundary
101 | repeat
102 | chunk, found = chunkread("\r\n" .. req.boundary)
103 | if not chunk then return 400, "Malformed POST. Incomplete Part received." end
104 | tempfile:write(chunk)
105 | param.size = param.size + #chunk
106 | until found
107 | tempfile:close()
108 | req.post[param.name] = req.post[param.name] or {}
109 | table.insert(req.post[param.name], 1, param)
110 | param = {}
111 |
112 | -- read after boundary. if CRLF ("") repeat. if "--" end POST processing
113 | chunk, found = chunkread("\r\n")
114 | end
115 |
116 | if found and chunk == "--" then return 0, "OK" end
117 | return 400, "Malformed POST. Boundary not properly ended with CRLF or --."
118 | end
119 |
120 | return _M
121 |
--------------------------------------------------------------------------------
/bridges/openwrt-lua/usr/lib/lua/luafm.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local uci = require "uci"
4 | local fs = require "nixio.fs"
5 | local u_c = uci.cursor()
6 | local basepath = u_c.get("filemanager","config","basedir")
7 |
8 | local _M = {}
9 |
10 | local bp = basepath:match("(.*)/")
11 | if bp and bp ~= "" then
12 | basepath = bp
13 | end
14 | _M.basepath = basepath;
15 |
16 | function _M.is_in_dir(file, dir)
17 | if file == dir then
18 | return true
19 | else
20 | return file:sub(1, #dir) == dir
21 | end
22 | end
23 |
24 | function _M.path_valid(path)
25 | return _M.is_in_dir(path,_M.basepath) and fs.access(path,"r")
26 | end
27 |
28 | function _M.dir_path_valid(path)
29 | return _M.is_in_dir(path,_M.basepath) and fs.stat(path,"type")=="dir" and fs.access(path,"w")
30 | end
31 |
32 | function _M.new_path_valid(path)
33 | local dirpath = fs.dirname(path)
34 | return _M.is_in_dir(dirpath,_M.basepath) and fs.access(dirpath,"w")
35 | end
36 |
37 | function _M.make_path(path)
38 | local realpath = fs.realpath(_M.basepath..'/'..path)
39 | if _M.path_valid(realpath) then
40 | return realpath
41 | else
42 | return nil
43 | end
44 | end
45 |
46 | function _M.make_new_path(path)
47 | local realpath = fs.realpath(fs.dirname(_M.basepath..'/'..path))..'/'..fs.basename(path)
48 | if _M.new_path_valid(realpath) then
49 | return realpath
50 | else
51 | return nil
52 | end
53 | end
54 |
55 | function _M.make_dir_path(path)
56 | local realpath = fs.realpath(_M.basepath..'/'..path)
57 | if _M.dir_path_valid(realpath) then
58 | return realpath
59 | else
60 | return nil
61 | end
62 | end
63 |
64 | function _M.rm(item)
65 | local ftype = fs.stat(item,"type")
66 | if ftype == "reg" then
67 | return fs.remove(item)
68 | elseif ftype == "dir" then
69 | local dir = fs.dir(item)
70 | for file in dir do
71 | if not _M.rm(item..'/'..file) then
72 | return false
73 | end
74 | end
75 | return fs.rmdir(item)
76 | else
77 | return false
78 | end
79 | end
80 |
81 | function _M.chmod(item,mode,recursive)
82 | local result = fs.chmod(item,mode)
83 | if result and recursive then
84 | local dir = fs.dir(item)
85 | if dir then
86 | for file in dir do
87 | local ftype = fs.stat(item..'/'..file,"type")
88 | if ftype == "dir" then
89 | result = _M.chmod(item..'/'..file,mode,recursive)
90 | elseif ftype == "reg" then
91 | result = _M.chmod(item..'/'..file,string.gsub(mode,"x","-"),false)
92 | end
93 | if not result then
94 | break
95 | end
96 | end
97 | end
98 | end
99 | return result
100 | end
101 |
102 | return _M
103 |
--------------------------------------------------------------------------------
/bridges/openwrt-lua/www/cgi-bin/fs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local os = require "os"
4 | local fs = require "nixio.fs"
5 | local json = require "json"
6 | local luafm = require("luafm")
7 |
8 | print("Content-type: text/html; charset=utf-8")
9 | print("Cache-control: no-cache")
10 | print("Pragma: no-cache")
11 |
12 | -- prepare the browser for content:
13 | print("\r")
14 |
15 | local result
16 |
17 | local http_headers = nixio.getenv()
18 | local request=io.read("*all")
19 |
20 | local params=json.decode(request)
21 | local action=params["action"]
22 |
23 | local path
24 | local items
25 | local item
26 | local newItemPath
27 | local newPath
28 | local singleFilename
29 | local content
30 | local mode
31 | local destination
32 | local compressedFilename
33 | local recursive
34 | local folderName
35 |
36 | local files
37 | local fstat
38 | local ftype
39 | local basename
40 | local realitem
41 | local command
42 |
43 | if action == "list" then
44 |
45 | path = luafm.make_path(params["path"])
46 |
47 | if path then
48 | files = {}
49 | local rec
50 | for name in fs.dir(path) do
51 | basename=fs.realpath(path..'/'..name)
52 | if basename then
53 | fstat=fs.stat(basename)
54 | if fstat["type"]=="reg" then
55 | ftype="file"
56 | elseif fstat["type"]=="dir" then
57 | ftype="dir"
58 | else
59 | ftype=""
60 | end
61 | if ftype then
62 | rec={name=name,rights=fstat["modestr"],size=fstat["size"],type=ftype,date=os.date('%Y-%m-%d %H:%M:%S',fstat["mtime"])}
63 | table.insert(files,rec)
64 | end
65 | end
66 | end
67 | result = { result = files }
68 | else
69 | result = { result = {} }
70 | end
71 |
72 | elseif action == "rename" then
73 |
74 | item = luafm.make_path(params["item"])
75 | newItemPath = luafm.make_new_path(params["newItemPath"])
76 |
77 | if item and newItemPath and item ~= luafm.basepath and newItemPath ~= luafm.basepath then
78 | result = fs.rename(item,newItemPath)
79 | if result then
80 | result = { result = { success=true, error="" } }
81 | else
82 | result = { result = { success=false, error="Cannot rename requested file/directory" } }
83 | end
84 | else
85 | result = { result = { success=false, error="Invalid path request" } }
86 | end
87 |
88 | elseif action == "move" then
89 |
90 | items = params["items"]
91 | newPath = luafm.make_dir_path(params["newPath"])
92 |
93 | if newPath then
94 |
95 | result = true
96 |
97 | for key,item in pairs(items) do
98 |
99 | item = luafm.make_path(item)
100 |
101 | if item and item ~= luafm.basepath then
102 | basename = fs.basename(item)
103 | result = fs.move(item,newPath.."/"..basename)
104 | if not result then
105 | break
106 | end
107 | else
108 | result = false
109 | break
110 | end
111 |
112 | end
113 |
114 | if result then
115 | result = { result = { success=true, error="" } }
116 | else
117 | result = { result = { success=false, error="Cannot move requested file/directory" } }
118 | end
119 |
120 | else
121 |
122 | result = { result = { success=false, error="Invalid destination request" } }
123 |
124 | end
125 |
126 | elseif action == "copy" then
127 |
128 | items = params["items"]
129 | newPath = luafm.make_dir_path(params["newPath"])
130 |
131 | if newPath then
132 |
133 | singleFilename = params["singleFilename"]
134 | if singleFilename then
135 | singleFilename = fs.basename(singleFilename)
136 | end
137 |
138 | result = true
139 |
140 | for key,item in pairs(items) do
141 |
142 | item = luafm.make_path(item)
143 |
144 | if item and item ~= luafm.basepath then
145 | if singleFilename then
146 | basename = singleFilename
147 | else
148 | basename = fs.basename(item)
149 | end
150 | result = fs.copy(item,newPath.."/"..basename)
151 | if not result then
152 | break
153 | end
154 | else
155 | result = false
156 | break
157 | end
158 |
159 | end
160 |
161 | if result then
162 | result = { result = { success=true, error="" } }
163 | else
164 | result = { result = { success=false, error="Cannot copy requested file/directory" } }
165 | end
166 |
167 | else
168 |
169 | result = { result = { success=false, error="Invalid destination request" } }
170 |
171 | end
172 |
173 | elseif action == "remove" then
174 |
175 | items = params["items"]
176 |
177 | result = true
178 |
179 | for key,item in pairs(items) do
180 |
181 | item = luafm.make_path(item)
182 |
183 | if item and item ~= luafm.basepath then
184 | result = luafm.rm(item)
185 | if not result then
186 | break
187 | end
188 | else
189 | result = false
190 | break
191 | end
192 |
193 | end
194 |
195 | if result then
196 | result = { result = { success=true, error="" } }
197 | else
198 | result = { result = { success=false, error="Cannot remove requested file/directory" } }
199 | end
200 |
201 | elseif action == "getContent" then
202 |
203 | item = luafm.make_path(params["item"])
204 |
205 | if item and item ~= luafm.basepath then
206 | content = fs.readfile(item)
207 | result = { result = content }
208 | else
209 | result = { result = { success=false, error="Invalid path request" } }
210 | end
211 |
212 | elseif action == "edit" then
213 |
214 | item = luafm.make_path(params["item"])
215 | content = params["content"]
216 |
217 | if item and item ~= luafm.basepath then
218 | result = fs.writefile(item,content)
219 | if result then
220 | result = { result = { success=true, error="" } }
221 | else
222 | result = { result = { success=false, error="Cannot write requested file content" } }
223 | end
224 | else
225 | result = { result = { success=false, error="Invalid path request" } }
226 | end
227 |
228 | elseif action == "createFolder" then
229 |
230 | newPath = luafm.make_new_path(params["newPath"])
231 |
232 | if newPath and newPath ~= luafm.basepath then
233 | result = fs.mkdir(newPath)
234 | if result then
235 | result = { result = { success=true, error="" } }
236 | else
237 | result = { result = { success=false, error="Cannot create folder" } }
238 | end
239 | else
240 | result = { result = { success=false, error="Invalid path request" } }
241 | end
242 |
243 | elseif action == "changePermissions" then
244 |
245 | items = params["items"]
246 |
247 | if params["perms"] then
248 | mode = params["perms"]
249 | else
250 | mode = params["permsCode"]
251 | end
252 |
253 | if mode then
254 |
255 | recursive = params["recursive"]
256 |
257 | result = true
258 |
259 | for key,item in pairs(items) do
260 |
261 | item = luafm.make_path(item)
262 |
263 | if item and item ~= luafm.basepath then
264 | result = luafm.chmod(item,mode,recursive)
265 | if not result then
266 | break
267 | end
268 | else
269 | result = false
270 | break
271 | end
272 |
273 | end
274 |
275 | if result then
276 | result = { result = { success=true, error="" } }
277 | else
278 | result = { result = { success=false, error="Cannot change permissions" } }
279 | end
280 |
281 | else
282 |
283 | result = { result = { success=false, error="No permission requested" } }
284 |
285 | end
286 |
287 | elseif action == "compress" then
288 |
289 | items = params["items"]
290 | destination = params["destination"]
291 | compressedFilename = params["compressedFilename"]
292 |
293 | newPath = luafm.make_new_path(destination..'/'..fs.basename(params["compressedFilename"]))
294 |
295 | result = true
296 | files = ""
297 |
298 | for key,item in pairs(items) do
299 |
300 | realitem = luafm.make_path(item)
301 |
302 | if realitem and realitem ~= luafm.basepath then
303 | item = item:match("/(.*)")
304 | files = files.." "..item
305 | else
306 | result = false
307 | break
308 | end
309 |
310 | end
311 |
312 | if files then
313 |
314 | command = "cd "..luafm.basepath.."; zip -r "..newPath..files
315 | result = os.execute(command)
316 |
317 | if result then
318 | result = { result = { success=true, error="" } }
319 | else
320 | result = { result = { success=false, error="Archiver returned error" } }
321 | end
322 |
323 | else
324 |
325 | result = { result = { success=false, error="No files selected" } }
326 |
327 | end
328 |
329 | elseif action == "extract" then
330 |
331 | item = luafm.make_path(params["item"])
332 | destination = params["destination"]
333 | folderName = params["folderName"]
334 |
335 | newPath = luafm.make_new_path(destination..'/'..fs.basename(params["folderName"]))
336 |
337 | result = true
338 | files = ""
339 |
340 | if item and newPath and item ~= luafm.basepath then
341 | command = "unzip "..item.." -d "..newPath
342 | result = os.execute(command)
343 | if result then
344 | result = { result = { success=true, error="" } }
345 | else
346 | result = { result = { success=false, error="Archiver returned error" } }
347 | end
348 | else
349 | result = { result = { success=false, error="Invalid path request" } }
350 | end
351 |
352 |
353 | else
354 |
355 | result = { result = { success=false, error="Operation not impolemented yet" } }
356 |
357 | end
358 |
359 | print(json.encode(result))
360 |
--------------------------------------------------------------------------------
/bridges/openwrt-lua/www/cgi-bin/fs-download:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | nixio = require "nixio"
4 | fs = require "nixio.fs"
5 | util = require "luci.util"
6 | ltn12 = require "ltn12"
7 | luafm = require "luafm"
8 |
9 | args = {}
10 |
11 | for dummy,param in pairs(util.split(nixio.getenv('QUERY_STRING'),'&')) do
12 | val = util.split(param,'=')
13 | args[val[1]] = util.urldecode(val[2],true)
14 | end
15 |
16 | path = luafm.make_path(args["path"])
17 | if path then
18 | file = io.open(path,"rb")
19 | else
20 | file = nil
21 | end
22 |
23 | if file then
24 | print("Content-Type: application/octet-stream")
25 | print('Content-Disposition: attachment; filename="'..fs.basename(args["path"])..'"')
26 | print("\r")
27 | ltn12.pump.all(
28 | ltn12.source.file(file),
29 | ltn12.sink.file(io.stdout)
30 | )
31 | else
32 | print("Status: 404 Not Found")
33 | print("Content-Type: text/plain")
34 | print("\r")
35 | print("File not found")
36 | end
37 |
--------------------------------------------------------------------------------
/bridges/openwrt-lua/www/cgi-bin/fs-upload:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local cgi = require "cgi"
4 | local fs = require "nixio.fs"
5 | local luafm = require "luafm"
6 |
7 | local req = {}
8 |
9 | print("\r")
10 |
11 | cgi.new(req)
12 |
13 | if req.boundary then
14 | ret, msg = cgi.parse_request_body(req)
15 | end
16 |
17 | destfile=io.open(req.post["destination"][1]["tempname"],"r")
18 | destination=destfile:read("*all")
19 |
20 | for k, v in pairs(req.post) do
21 | for j, v in pairs(req.post[k]) do
22 | if v["filename"] then
23 | path = luafm.make_new_path(destination..'/'..v.filename)
24 | if path then
25 | fs.copy(req.post[k][j].tempname, path)
26 | end
27 | end
28 | end
29 | end
30 |
31 | cgi.cleanup(req)
32 |
33 | print('{ "result": { "success": true, "error": null } }')
34 |
--------------------------------------------------------------------------------
/bridges/php-local/LocalBridge/Response.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class Response
12 | {
13 | /**
14 | * @var integer
15 | */
16 | private $statusCode = 200;
17 |
18 | /**
19 | * @var string
20 | */
21 | private $status = 'OK';
22 |
23 | /**
24 | * @var array
25 | */
26 | private $additionalHeaders = [];
27 |
28 | /**
29 | * @var mixed
30 | */
31 | private $data = null;
32 |
33 | /**
34 | * Body which will be used instead of data if set
35 | * @var string
36 | */
37 | private $body = null;
38 |
39 | /**
40 | * HTTP Status according to http://tools.ietf.org/html/rfc7231
41 | * @param integer $statusCode
42 | * @param string $status without status code
43 | * @return object this
44 | */
45 | public function setStatus($statusCode, $status)
46 | {
47 | $this->statusCode = $statusCode;
48 | $this->status = $status;
49 |
50 | return $this;
51 | }
52 |
53 | /**
54 | * Add additional HTTP header
55 | * @param string $header
56 | * @return object this
57 | */
58 | public function addHeaders($header)
59 | {
60 | $this->additionalHeaders[] = $header;
61 |
62 | return $this;
63 | }
64 |
65 | /**
66 | * @param mixed $body used instead of data if set
67 | */
68 | public function setBody($body)
69 | {
70 | $this->body = $body;
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * @param mixed $data any data which should be later encoded to body
77 | */
78 | public function setData($data)
79 | {
80 | $this->data = $data;
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * @return mixed data which should be later encoded to body
87 | */
88 | public function getData()
89 | {
90 | return $this->data;
91 | }
92 |
93 | /**
94 | * @return mixed used instead of data if set
95 | */
96 | public function getBody()
97 | {
98 | if ($this->body !== null) {
99 | return $this->body;
100 | }
101 |
102 | if ($this->data === null) {
103 | return null;
104 | }
105 |
106 | return json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
107 | }
108 |
109 | /**
110 | * HTTP Status according to http://tools.ietf.org/html/rfc7231 in form of array
111 | * @return array associative, e.g. ['code' => 200, 'status' => 'OK']
112 | */
113 | public function getStatus()
114 | {
115 | return [
116 | 'code' => $this->statusCode,
117 | 'status' => $this->status
118 | ];
119 | }
120 |
121 | /**
122 | * @return array list of all headers including first status line
123 | */
124 | public function getHeaders()
125 | {
126 | return array_merge(
127 | [$_SERVER['SERVER_PROTOCOL'].' '.$this->statusCode.' '.$this->status],
128 | $this->additionalHeaders
129 | );
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/bridges/php-local/LocalBridge/Rest.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class Rest
12 | {
13 | /**
14 | * List of callbacks which are assigned and later called in handle method: post, get, put, delete, before and after
15 | * @var array
16 | */
17 | private $callbacks = [];
18 |
19 | /**
20 | * @var boolean
21 | */
22 | private $requireAuthentication = false;
23 |
24 | /**
25 | * Add callback for specific HTTP method (post, get, put, delete)
26 | * @param string $method name of the HTTP method
27 | * @param array $arguments expects only one argument, callback, with number of arguments based on request method ($queries, $body['data'], $body['files'])
28 | * @return object this
29 | */
30 | public function __call($method, $arguments)
31 | {
32 | if (!in_array($method, ['post', 'get', 'put', 'delete'])) {
33 | throw new Exception('REST method "'.$method.'" not supported.');
34 | }
35 |
36 | $this->callbacks[$method] = $arguments[0];
37 |
38 | return $this;
39 | }
40 |
41 | /**
42 | * Should authentication be required
43 | * @param boolean $option defaults to true
44 | */
45 | public function setRequireAuthentication($option = true)
46 | {
47 | $this->requireAuthentication = $option;
48 |
49 | return $this;
50 | }
51 |
52 | /**
53 | * Add callback called before every request
54 | * @param callable $callback arguments: $queries, $body['data'], $body['files']
55 | * @return object this
56 | */
57 | public function before($callback)
58 | {
59 | $this->callbacks['before'] = $callback;
60 |
61 | return $this;
62 | }
63 |
64 | /**
65 | * Add callback called after every request
66 | * @param callable $callback arguments: $queries, $body['data'], $body['files']
67 | * @return object this
68 | */
69 | public function after($callback)
70 | {
71 | $this->callbacks['after'] = $callback;
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * Should be called manually as last method
78 | */
79 | public function handle()
80 | {
81 | if ($this->requireAuthentication) {
82 | $authenticateResponse = $this->verifyAuthentication();
83 | if ($authenticateResponse instanceof Response) {
84 | $this->respond($response);
85 | return;
86 | }
87 | }
88 |
89 | $request_method = $_SERVER['REQUEST_METHOD'];
90 |
91 | $body = [
92 | 'data' => null,
93 | 'files' => []
94 | ];
95 | $queries = [];
96 | $parameters = [];
97 |
98 | //Get body data and files only from requests with body
99 | if ($request_method == 'POST' || $request_method == 'PUT') {
100 | $body = $this->parseBody();
101 | }
102 |
103 | if (isset($_GET)) {
104 | $queries = $_GET;
105 | }
106 |
107 | if (isset($this->callbacks['before'])) {
108 | $this->callbacks['before']($queries, $body['data'], $body['files']);
109 | }
110 |
111 | switch ($request_method) {
112 | case 'POST':
113 | $response = $this->callbacks['post']($queries, $body['data'], $body['files']);
114 | break;
115 |
116 | case 'GET':
117 | $response = $this->callbacks['get']($queries);
118 | break;
119 |
120 | case 'PUT':
121 | $response = $this->callbacks['put']($queries, $body['data'], $body['files']);
122 | break;
123 |
124 | case 'DELETE':
125 | $response = $this->callbacks['delete']($queries);
126 | break;
127 |
128 | default:
129 | //Not supported
130 | $response = new Response();
131 | $response->setStatus(501, 'Not Implemented');
132 | break;
133 | }
134 |
135 | if (isset($this->callbacks['after'])) {
136 | $this->callbacks['after']($queries, $body['data'], $body['files']);
137 | }
138 |
139 | $this->respond($response);
140 | }
141 |
142 | /**
143 | * Uses _POST and _FILES superglobals if available,
144 | * otherwise tries to parse JSON body if Content Type header is set to application/json,
145 | * otherwise manually parses body as form data
146 | * @return array associative array with data and files ['data' => ?, 'files' => ?]
147 | */
148 | private function parseBody()
149 | {
150 | $data = null;
151 | $files = null;
152 |
153 | if (isset($_POST) && !empty($_POST)) {
154 | $data = $_POST;
155 | }
156 |
157 | if (isset($_FILES) && !empty($_FILES)) {
158 | $files = $_FILES;
159 | }
160 |
161 | //In case of request with json body
162 | if ($data === null) {
163 | if (isset($_SERVER["CONTENT_TYPE"]) && strpos($_SERVER["CONTENT_TYPE"], 'application/json') !== false) {
164 | $input = file_get_contents('php://input');
165 |
166 | $data = json_decode($input, true);
167 | }
168 | }
169 |
170 | return [
171 | 'data' => $data,
172 | 'files' => $files
173 | ];
174 | }
175 |
176 | /**
177 | * Check wheter client is authorized and returns Response object with autorization request if not
178 | * @return mixed Response object if client is not authorized, otherwise nothing
179 | */
180 | private function verifyAuthentication()
181 | {
182 | $authenticated = false;
183 | $headers = getallheaders();
184 |
185 | if (isset($headers['Authorization'])) {
186 | $token = str_replace('Token ', '', $headers['Authorization']);
187 |
188 | $authenticated = token::verify($token);
189 | }
190 |
191 | if ($authenticated === false) {
192 | $response = new Response();
193 | $response->setStatus(401, 'Unauthorized')
194 | ->addHeaders('WWW-Authenticate: Token');
195 |
196 | return $response;
197 | }
198 | }
199 |
200 | /**
201 | * Use Response object to modify headers and output body
202 | * @param Response $response
203 | */
204 | private function respond(Response $response)
205 | {
206 | foreach ($response->getHeaders() as $header) {
207 | header($header);
208 | }
209 |
210 | echo $response->getBody();
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/bridges/php-local/LocalBridge/Translate.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class Translate
11 | {
12 | private $strings = [];
13 |
14 | public function __construct($lang)
15 | {
16 | if (!file_exists('lang/'.$lang.'.json')) {
17 | throw new \Exception('No language file for chosen language');
18 | return;
19 | }
20 |
21 | $json = file_get_contents('lang/'.$lang.'.json');
22 |
23 | $this->strings = json_decode($json, true);
24 | }
25 |
26 | public function __get($name)
27 | {
28 | if (isset($this->strings[$name])) {
29 | return $this->strings[$name];
30 | } else {
31 | return $name;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/bridges/php-local/index.php:
--------------------------------------------------------------------------------
1 |
8 | * @version 0.2.0
9 | */
10 | include 'LocalBridge/Response.php';
11 | include 'LocalBridge/Rest.php';
12 | include 'LocalBridge/Translate.php';
13 | include 'LocalBridge/FileManagerApi.php';
14 |
15 | /**
16 | * Takes two arguments
17 | * - base path without last slash (default: '$currentDirectory/../files')
18 | * - language (default: 'en'); mute_errors (default: true, will call ini_set('display_errors', 0))
19 | */
20 | $fileManagerApi = new FileManagerApi("/");
21 |
22 | $rest = new Rest();
23 | $rest->post([$fileManagerApi, 'postHandler'])
24 | ->get([$fileManagerApi, 'getHandler'])
25 | ->handle();
26 |
--------------------------------------------------------------------------------
/bridges/php-local/lang/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "function_not_implemented": "Funktion nicht implementiert",
3 | "file_not_found": "Datei nicht gefunden",
4 | "listing_failed": "Auflistung fehlgeschlagen",
5 | "moving_failed": "Verschiebung fehlgeschlagen",
6 | "renaming_failed": "Umbenennung fehlgeschlagen",
7 | "copying_failed": "Kopieren fehlgeschlagen",
8 | "removing_failed": "Löschen fehlgeschlagen",
9 | "removing_failed_directory_not_empty": "Entfernen fehlgeschlagen, der Ordner, welchen Sie versuchen zu löschen, ist nicht leer.",
10 | "saving_failed": "Speichern fehlgeschlagen",
11 | "folder_already_exists": "Ordner existiert bereits",
12 | "folder_creation_failed": "Ordner Anlage fehlgeschlagen",
13 | "permissions_change_failed": "Berechtigungsänderung fehlgeschlagen",
14 | "compression_failed": "Kompression fehlgeschlagen",
15 | "archive_opening_failed": "Öffnen des Archivs fehlgeschlagen, es ist entweder korrupt oder nicht unterstützt.",
16 | "extraction_failed": "Auspacken fehlgeschlagen",
17 | "upload_failed": "Hochladen fehlgeschlagen"
18 | }
19 |
--------------------------------------------------------------------------------
/bridges/php-local/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "function_not_implemented": "Function not implemented",
3 | "file_not_found": "File not found",
4 | "listing_failed": "Listing failed",
5 | "moving_failed": "Moving failed",
6 | "renaming_failed": "Renaming failed",
7 | "copying_failed": "Copying failed",
8 | "removing_failed": "Removing failed",
9 | "removing_failed_directory_not_empty": "Removing failed, the directory you are trying to remove is not empty",
10 | "saving_failed": "Saving failed",
11 | "folder_already_exists": "Folder already exists",
12 | "folder_creation_failed": "Folder creation failed",
13 | "permissions_change_failed": "Permissions change failed",
14 | "compression_failed": "Compression failed",
15 | "archive_opening_failed": "Could not open the archive, it is either corrupted or unsupported",
16 | "extraction_failed": "Extraction failed",
17 | "upload_failed": "Upload failed"
18 | }
19 |
--------------------------------------------------------------------------------
/bridges/php-local/lang/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "function_not_implemented": "Fonction non implémentée",
3 | "file_not_found": "Fichier non trouvé",
4 | "listing_failed": "Le listing a échoué",
5 | "moving_failed": "Le déplacement a échoué",
6 | "renaming_failed": "Le renommage a échoué",
7 | "copying_failed": "La copie a échouée",
8 | "removing_failed": "La suppression a échouée",
9 | "removing_failed_directory_not_empty": "La suppression a échouée, le répertoire que vous voulez supprimer n'est pas vide",
10 | "saving_failed": "L'enregistrement a échoué",
11 | "folder_already_exists": "Le répertoire existe déjà",
12 | "folder_creation_failed": "La création du répertoire a échoué",
13 | "permissions_change_failed": "Le changement de permission a échoué",
14 | "compression_failed": "La compression a échouée",
15 | "archive_opening_failed": "L'ouverture de l'archive est impossible, le fichier est corrompu ou non supporté",
16 | "extraction_failed": "L'extraction a échouée",
17 | "upload_failed": "L'upload a échoué"
18 | }
19 |
--------------------------------------------------------------------------------
/bridges/php-local/lang/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "function_not_implemented": "Funzione non implementata",
3 | "file_not_found": "File non trovato",
4 | "listing_failed": "Elenco fallito",
5 | "moving_failed": "Spostamento fallito",
6 | "renaming_failed": "Rinomina fallita",
7 | "copying_failed": "Copia fallita",
8 | "removing_failed": "Rimozione fallita",
9 | "removing_failed_directory_not_empty": "Rimozione fallita, la cartella che stai tentando di rimuovere non è vuota",
10 | "saving_failed": "Salvataggio fallito",
11 | "folder_already_exists": "La cartella esiste già",
12 | "folder_creation_failed": "Creazione della cartella fallita",
13 | "permissions_change_failed": "Modifica dei permessi non riuscita",
14 | "compression_failed": "Compressione fallita",
15 | "archive_opening_failed": "Impossibile aprire l'archivio, è danneggiato o non supportato",
16 | "extraction_failed": "Estrazione fallita",
17 | "upload_failed": "Caricamento fallito"
18 | }
19 |
--------------------------------------------------------------------------------
/bridges/php-local/lang/ko.json:
--------------------------------------------------------------------------------
1 | {
2 | "function_not_implemented": "구현되지 않은 기능입니다",
3 | "file_not_found": "파일을 찾을 수 없습니다",
4 | "listing_failed": "목록 작성 실패",
5 | "moving_failed": "이동 실패",
6 | "renaming_failed": "이름 변경 실패",
7 | "copying_failed": "복사 실패",
8 | "removing_failed": "삭제 실패",
9 | "removing_failed_directory_not_empty": "폴더가 비어 있지 않아, 삭제할 수 없습니다",
10 | "saving_failed": "저장 실패",
11 | "folder_already_exists": "폴더가 이미 존재합니다",
12 | "folder_creation_failed": "폴더 생성 실패",
13 | "permissions_change_failed": "권한 변경 실패",
14 | "compression_failed": "압축 실패",
15 | "archive_opening_failed": "파일이 손상되었거나 지원되지 않아, 압축 파일을 열 수 없습니다",
16 | "extraction_failed": "추출 실패",
17 | "upload_failed": "업로드 실패"
18 | }
19 |
--------------------------------------------------------------------------------
/bridges/php-local/lang/sk.json:
--------------------------------------------------------------------------------
1 | {
2 | "function_not_implemented": "Funkcia nie je implementovaná",
3 | "file_not_found": "Súbor nenájdený",
4 | "listing_failed": "Výpis súborov zlyhal",
5 | "moving_failed": "Presunutie zlyhalo",
6 | "renaming_failed": "Premenovanie zlyhalo",
7 | "copying_failed": "Kopírovanie zlyhalo",
8 | "removing_failed": "Odstraňovanie zlyhalo",
9 | "removing_failed_directory_not_empty": "Odstraňovanie zlyhalo, priečinok, ktorý sa snažíte odstrániť nie je prázdny",
10 | "saving_failed": "Ukladanie zlyhalo",
11 | "folder_already_exists": "Priečinok s daným názvom už existuje",
12 | "folder_creation_failed": "Priečinok sa nepodarilo vytvoriť",
13 | "permissions_change_failed": "Nepodarilo sa zmeniť oprávnenia",
14 | "compression_failed": "Komprimovanie zlyhalo",
15 | "archive_opening_failed": "Nepodarilo sa otvoriť archív je buď poškodený alebo nepodporovaného formátu",
16 | "extraction_failed": "Extrahovanie zlyhalo",
17 | "upload_failed": "Nahrávanie zlyhalo"
18 | }
19 |
--------------------------------------------------------------------------------
/bridges/php/handler.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 |
7 | require_once __DIR__ . '/includes/Ftp.php';
8 | require_once __DIR__ . '/includes/ExceptionCatcher.php';
9 |
10 | use PHPClassic\ExceptionCatcher;
11 | use PHPClassic\Ftp;
12 |
13 | class ExceptionCatcherJSON extends ExceptionCatcher
14 | {
15 | public static function draw(\Exception $oExp)
16 | {
17 | @header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
18 | $oResponse = new Response;
19 | $oResponse->setData(array(
20 | 'success' => false,
21 | 'error' => $oExp->getMessage(),
22 | 'errorDetail' => array(
23 | 'type' => get_class($oExp),
24 | 'code' => $oExp->getCode()
25 | )
26 | ));
27 | return $oResponse->flushJson();
28 | }
29 |
30 | public static function register()
31 | {
32 | set_exception_handler(array(__CLASS__, 'handle'));
33 | }
34 | }
35 |
36 | abstract class Request
37 | {
38 | public static function getQuery($param = null, $default = null)
39 | {
40 | if ($param) {
41 | return isset($_GET[$param]) ?
42 | $_GET[$param] : $default;
43 | }
44 | return $_GET;
45 | }
46 |
47 | public static function getPost($param = null, $default = null)
48 | {
49 | if ($param) {
50 | return isset($_POST[$param]) ?
51 | $_POST[$param] : $default;
52 | }
53 | return $_POST;
54 | }
55 |
56 | public static function getFile($param = null, $default = null)
57 | {
58 | if ($param) {
59 | return isset($_FILES[$param]) ?
60 | $_FILES[$param] : $default;
61 | }
62 | return $_FILES;
63 | }
64 |
65 | public static function getPostContent()
66 | {
67 | $rawData = file_get_contents('php://input');
68 | return json_decode($rawData);
69 | }
70 |
71 | public static function getApiParam($param)
72 | {
73 | $oData = static::getPostContent();
74 | return isset($oData->$param) ? $oData->$param : null;
75 | }
76 |
77 | public static function getApiOrQueryParam($param)
78 | {
79 | return Request::getApiParam($param) ?
80 | Request::getApiParam($param) : Request::getQuery($param);
81 | }
82 | }
83 |
84 | class Response
85 | {
86 | protected $data;
87 |
88 | public function __construct($data = null)
89 | {
90 | $this->setData($data);
91 | }
92 |
93 | public function flushJson()
94 | {
95 | $this->data = json_encode(array('result' => $this->data));
96 | return $this->flush();
97 | }
98 |
99 | public function flush()
100 | {
101 | echo $this->data;
102 | exit;
103 | }
104 |
105 | public function setData($data)
106 | {
107 | $this->data = $data;
108 | return $this;
109 | }
110 |
111 | public function setHeaders($params)
112 | {
113 | if (! headers_sent()) {
114 | if (is_scalar($params)) {
115 | header($params);
116 | } else {
117 | foreach($params as $key => $value) {
118 | header(sprintf('%s: %s', $key, $value));
119 | }
120 | }
121 | }
122 | return $this;
123 | }
124 | }
125 |
126 | class FileManager extends Ftp
127 | {
128 | public function downloadTemp($path)
129 | {
130 | $localPath = tempnam(sys_get_temp_dir(), 'fmanager_');
131 | if ($this->download($path, $localPath)) {
132 | return $localPath;
133 | }
134 | }
135 |
136 | public function getContent($path)
137 | {
138 | $localPath = $this->downloadTemp($path);
139 | if ($localPath) {
140 | return @file_get_contents($localPath);
141 | }
142 | }
143 |
144 | }
145 |
146 | ExceptionCatcherJSON::register();
147 | $oResponse = new Response();
148 | $oFtp = new FileManager(array(
149 | 'hostname' => '',
150 | 'username' => '',
151 | 'password' => ''
152 | ));
153 |
154 | if (! $oFtp->connect()) {
155 | throw new Exception("Cannot connect to the FTP server");
156 | }
157 |
158 | if (Request::getFile() && $dest = Request::getPost('destination')) {
159 | $errors = array();
160 | foreach (Request::getFile() as $file) {
161 | $filePath = $file['tmp_name'];
162 | $destPath = $dest .'/'. $file['name'];
163 | $result = $oFtp->upload($filePath, $destPath);
164 | if (! $result) {
165 | $errors[] = $file['name'];
166 | }
167 | }
168 |
169 | if ($errors) {
170 | throw new Exception("Unknown error uploading: \n\n" . implode(", \n", $errors));
171 | }
172 |
173 | $oResponse->setData($result);
174 | $oResponse->flushJson();
175 | }
176 |
177 | if (Request::getApiParam('action') === 'list') {
178 | $list = $oFtp->listFilesRaw(Request::getApiParam('path'));
179 | $list = is_array($list) ? $list : array();
180 | $list = array_map(function($item) {
181 | $date = new \DateTime('now');
182 | $item['date'] = $date->format('Y-m-d H:i:s');
183 | return $item;
184 | }, $list);
185 | $oResponse->setData($list);
186 | $oResponse->flushJson();
187 | }
188 |
189 | if (Request::getApiParam('action') === 'getContent') {
190 | $oResponse->setData($oFtp->getContent(Request::getApiParam('item')));
191 | $oResponse->flushJson();
192 | }
193 |
194 | if (Request::getApiParam('action') === 'rename') {
195 | $item = Request::getApiParam('item');
196 | $newItemPath = Request::getApiParam('newItemPath');
197 | $result = $oFtp->move($item, $newItemPath);
198 | if (! $result) {
199 | throw new Exception("Unknown error renaming this item");
200 | }
201 | $oResponse->setData($result);
202 | $oResponse->flushJson();
203 | }
204 |
205 | if (Request::getApiParam('action') === 'move') {
206 | $items = Request::getApiParam('items');
207 | $newPath = Request::getApiParam('newPath');
208 | $errors = array();
209 |
210 | foreach($items as $item) {
211 | $fileName = explode('/', $item);
212 | $fileName = end($fileName);
213 | $finalPath = $newPath . '/' . $fileName;
214 | $result = $item ? $oFtp->move($item, $finalPath) : false;
215 | if (! $result) {
216 | $errors[] = $item . ' to ' . $finalPath;
217 | }
218 | }
219 | if ($errors) {
220 | throw new Exception("Unknown error moving: \n\n" . implode(", \n", $errors));
221 | }
222 | $oResponse->setData($result);
223 | $oResponse->flushJson();
224 | }
225 |
226 | if (Request::getApiParam('action') === 'remove') {
227 | $items = Request::getApiParam('items');
228 | $errors = array();
229 | foreach($items as $item) {
230 | $result = $item ? $oFtp->delete($item) : false;
231 | if (! $result) {
232 | $errors[] = $item;
233 | }
234 | }
235 | if ($errors) {
236 | throw new Exception("Unknown error deleting: \n\n" . implode(", \n", $errors));
237 | }
238 | $oResponse->setData($result);
239 | $oResponse->flushJson();
240 | }
241 |
242 | if (Request::getApiParam('action') === 'createFolder') {
243 | $newPath = Request::getApiParam('newPath');
244 | $result = $oFtp->mkdir($newPath);
245 | if (! $result) {
246 | throw new Exception("Unknown error creating this folder");
247 | }
248 | $oResponse->setData($result);
249 | $oResponse->flushJson();
250 | }
251 |
252 | if (Request::getApiParam('action') === 'compress') {
253 | $items = Request::getApiParam('items');
254 | $destination = Request::getApiParam('destination');
255 | $compressedFilename = Request::getApiParam('compressedFilename');
256 |
257 | //example
258 | $temp = tempnam(sys_get_temp_dir(), microtime());
259 | $finalDest = $destination . '/' . $compressedFilename;
260 |
261 | $finalDest = preg_match('/\.(tar|zip)$/', $finalDest) ? $finalDest : $finalDest . '.zip';
262 | $result = $oFtp->upload($temp, $finalDest);
263 | if (! $result) {
264 | throw new Exception("Unknown error compressing these file(s)");
265 | }
266 |
267 | $oResponse->setData(true);
268 | $oResponse->flushJson();
269 | }
270 |
271 | if (Request::getQuery('action') === 'download') {
272 | $download = Request::getQuery('preview') === 'true' ? '' : 'attachment;';
273 | $filePath = Request::getQuery('path');
274 | $fileName = explode('/', $filePath);
275 | $fileName = end($fileName);
276 | $tmpFilePath = $oFtp->downloadTemp($filePath);
277 |
278 | @register_shutdown_function(function() use ($tmpFilePath) {
279 | if (file_exists($tmpFilePath)) {
280 | @unlink($tmpFilePath);
281 | }
282 | });
283 |
284 | if ($fileContent = @file_get_contents($tmpFilePath)) {
285 | $oResponse->setData($fileContent);
286 | $oResponse->setHeaders(array(
287 | 'Content-Type' => @mime_content_type($tmpFilePath),
288 | 'Content-disposition' => sprintf('%s filename="%s"', $download, $fileName)
289 | ));
290 | }
291 | $oResponse->flush();
292 | }
293 |
294 | if (Request::getQuery('action') === 'downloadMultiple') {
295 | $items = Request::getApiOrQueryParam('items');
296 | $toFilename = Request::getApiOrQueryParam('toFilename');
297 | $errors = array();
298 |
299 | $fileContent = is_array($items) ? implode($items, ", \n") : '';
300 | if ($errors) {
301 | throw new Exception("Unknown compressing to download: \n\n" . implode(", \n", $errors));
302 | }
303 |
304 | if ($fileContent) {
305 | $oResponse->setData($fileContent);
306 | $oResponse->setHeaders(array(
307 | 'Content-Type' => @mime_content_type($fileContent),
308 | 'Content-disposition' => sprintf('attachment; filename="%s"', $toFilename)
309 | ));
310 | }
311 | $oResponse->flush();
312 | }
313 |
314 | throw new \Exception('This action is not available in the demo');
315 |
--------------------------------------------------------------------------------
/bridges/php/includes/ExceptionCatcher.php:
--------------------------------------------------------------------------------
1 |
11 | * @todo Method to set view template and remove html code from draw() method
12 | * @todo Avoid the use of highlight_string() to make php syntax highlight
13 | */
14 |
15 | abstract class ExceptionCatcher
16 | {
17 |
18 | /**
19 | * @param Exception $oExp
20 | */
21 | public static function handle(Exception $oExp)
22 | {
23 | echo static::draw($oExp);
24 | }
25 |
26 | /**
27 | * @param Exception $oExp
28 | * @return string
29 | */
30 | public static function draw(Exception $oExp)
31 | {
32 | $details = static::buildDetails($oExp);
33 | $content = sprintf('
Exception: %s
', $oExp->getMessage());
34 | $content .= sprintf('At: %s:%s
', $oExp->getFile(), $oExp->getLine());
35 | $content .= sprintf('Trace
');
36 | foreach ($details as $items) {
37 | if (isset($items['trace'])) {
38 | $content .= sprintf("
%s:%d
\n", $items['file'], $items['line']);
39 | $content .= static::getHighlightedCode($items['trace'], true);
40 | }
41 | }
42 | return $content;
43 | }
44 |
45 | /**
46 | * @param array $trace
47 | * @return string
48 | */
49 | protected function getHighlightedCode(array $trace, $showLineNumber = false)
50 | {
51 | $content = '';
52 | foreach ($trace as $line) {
53 | if (! isset($line['lineContent'])) {
54 | return;
55 | }
56 | $content .= $showLineNumber ?
57 | sprintf("%d %s\n", $line['lineNumber'], $line['lineContent']) :
58 | sprintf("%s\n", $line['lineContent']);
59 | }
60 | $str = highlight_string('getFile(), $oExp->getLine());
72 | foreach ($oExp->getTrace() as $files) {
73 | $arr[] = static::getDetails($files['file'], $files['line']);
74 | }
75 | return $arr;
76 | }
77 |
78 | /**
79 | * @param string $file
80 | * @param int $line
81 | * @return array
82 | */
83 | protected function getDetails($file, $line)
84 | {
85 | $lines = file_get_contents($file); //validate
86 | $lines = explode("\n", $lines);
87 | $trace = array();
88 | foreach (range($line - 4, $line + 2) as $fetchLine) {
89 | if (isset($lines[$fetchLine])) {
90 | $n = strlen($fetchLine + 10);
91 | $lineKey = sprintf('%0'.$n.'d', $fetchLine + 1);
92 | $trace[$lineKey] = array(
93 | 'lineContent' => $lines[$fetchLine],
94 | 'lineNumber' => $lineKey
95 | );
96 | }
97 | }
98 | return array(
99 | 'file' => $file,
100 | 'line' => $line,
101 | 'trace' => $trace,
102 | );
103 | }
104 |
105 | /**
106 | * @return void
107 | */
108 | public static function register()
109 | {
110 | set_exception_handler(array(__CLASS__, 'handle'));
111 | }
112 |
113 | /**
114 | * @return void
115 | */
116 | public static function unregister()
117 | {
118 | restore_exception_handler();
119 | }
120 | }
121 |
122 |
123 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 | angular-filemanager
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const browserSync = require('browser-sync').create();
3 | const sass = require('gulp-sass');
4 | const concat = require('gulp-concat');
5 | const sourcemaps = require('gulp-sourcemaps');
6 | const path = require("path");
7 | const del = require('del');
8 | const header = require('gulp-header');
9 | const cleanCSS = require('gulp-clean-css');
10 | const htmlmin = require('gulp-htmlmin');
11 | const templateCache = require('gulp-angular-templatecache');
12 | const package = require('./package.json')
13 | const iife = require("gulp-iife");
14 | const uglifyes = require('uglify-es');
15 | const composer = require('gulp-uglify/composer');
16 | const minifyes = composer(uglifyes, console);
17 | const fs = require('fs');
18 | const replace = require('gulp-replace');
19 | const bulkSass = require('gulp-sass-bulk-import');
20 | const babel = require('gulp-babel');
21 | const eslint = require('gulp-eslint');
22 |
23 | const distributionFolder = 'dist/';
24 |
25 | gulp.task('clean', function () {
26 | return del('./' + distributionFolder + '**');
27 | });
28 |
29 | /*
30 | gulp.task('copy-files', function () {
31 | return gulp.src([
32 | 'index.html',
33 |
34 | ], {base: ".", nodir: true})
35 | .pipe(gulp.dest(distributionFolder));
36 | });
37 | */
38 |
39 |
40 | // Static server
41 | gulp.task('browser-sync', function (done) {
42 |
43 | historyApiFallback = require('connect-history-api-fallback');
44 | browserSync.init({
45 | ui: false,
46 | middleware: [historyApiFallback()],
47 | //browser: "google chrome"
48 | });
49 |
50 | done();
51 | });
52 |
53 | gulp.task('javascript', function (done) {
54 | var sources = ['src/js/app.js', 'src/js/**/*.js'];
55 |
56 | return gulp.src(sources, {base: '.'})
57 | .pipe(sourcemaps.init())
58 | .pipe(concat('angularjs-filemanager.js'))
59 | .on('error', logError)
60 | .pipe(babel({presets: ['@babel/env']}))
61 | .pipe(sourcemaps.write({includeContent: false, sourceRoot: '/'}))
62 | .pipe(gulp.dest(distributionFolder));
63 | });
64 |
65 | gulp.task('scss', function () {
66 | return gulp.src('src/css/style.scss')
67 | .pipe(bulkSass())
68 | .pipe(sourcemaps.init())
69 | .pipe(sass({importer: importer}))
70 | .on('error', logError)
71 | // since gulp-sass doesn't let you rename the file
72 | .pipe(sourcemaps.write({includeContent: false, sourceRoot: '/src/css'}))
73 | .pipe(concat('angularjs-filemanager.css'))
74 | .pipe(gulp.dest(distributionFolder))
75 | .pipe(browserSync.stream());
76 | });
77 |
78 |
79 | let svgSprite = require("gulp-svg-sprite");
80 | let svgIconConfig = {
81 | shape: {
82 | id: { // SVG shape ID related options
83 | generator: "svg-", // SVG shape ID generator callback
84 | },
85 | dimension: { // Set maximum dimensions
86 | maxWidth: 24,
87 | maxHeight: 24
88 | },
89 | spacing: { // Add padding
90 | padding: 0,
91 | box: 'icon'
92 | },
93 | transform: ['svgo'],
94 | meta: null,
95 |
96 | },
97 | mode: {
98 | symbol: { // Activate the «view» mode
99 | common: 'svg',
100 | bust: false,
101 | dimensions: false,
102 | sprite: 'angularjs-filemanager.svg',
103 | //example: true,
104 | render: {
105 | css: false
106 | },
107 | inline: true,
108 | dest: ""
109 | }
110 | },
111 | svg: { // General options for created SVG files
112 | xmlDeclaration: false, // Add XML declaration to SVG sprite
113 | doctypeDeclaration: true, // Add DOCTYPE declaration to SVG sprite
114 | namespaceIDs: true, // Add namespace token to all IDs in SVG shapes
115 | namespaceClassnames: true, // Add namespace token to all CSS class names in SVG shapes
116 | dimensionAttributes: false // Width and height attributes on the sprite
117 | }
118 |
119 | };
120 |
121 | gulp.task('sprites_icon', function () {
122 | return gulp.src("src/svg/**/*.svg").pipe(svgSprite(svgIconConfig)).pipe(gulp.dest(distributionFolder));
123 | });
124 |
125 |
126 | function browserSyncReload(done) {
127 | browserSync.reload();
128 | done();
129 | }
130 |
131 |
132 | gulp.task('watch', gulp.series(gulp.parallel('scss', 'javascript', 'sprites_icon'), function watchTask(done) {
133 | gulp.watch(['src/**/*css'], gulp.parallel('scss'));
134 | gulp.watch(['src/svg/*.svg'], gulp.series('sprites_icon', browserSyncReload));
135 | gulp.watch(['src/js/**/*.js'], gulp.series('javascript', browserSyncReload));
136 | gulp.watch(["src/**/*.html", "index.html"], gulp.parallel(browserSyncReload));
137 |
138 | done();
139 | }));
140 |
141 |
142 | gulp.task('default', gulp.series(gulp.parallel('clean'), 'watch', 'browser-sync'));
143 |
144 | function importer(url, prev, done) {
145 | if (url[0] === '~') {
146 | url = path.resolve('./node_modules/' + url.substr(1));
147 | } else if (url[0] === '/') {
148 | //url = path.resolve( url.substr(1));
149 | }
150 | return {file: url};
151 | }
152 |
153 | function logError(error) {
154 |
155 | // If you want details of the error in the console
156 | console.log(error.toString());
157 | this.emit('end')
158 | }
159 |
160 | gulp.task('ng-templates', function () {
161 | return gulp.src(['src/**/*.html'])
162 | .pipe(htmlmin({
163 | collapseWhitespace: true,
164 | removeComments: true,
165 | sortAttributes: true,
166 | sortClassName: true,
167 | ignoreCustomFragments: [/<%[\s\S]*?%>/, /<\?[\s\S]*?(\?>|$)/],
168 | trimCustomFragments: true
169 | }))
170 | .pipe(templateCache({root: 'src/'}))
171 | .pipe(replace("angular.module('templates')", 'app'))
172 | .pipe(gulp.dest(distributionFolder));
173 | });
174 |
175 |
176 | gulp.task('concat-ng-templates', function () {
177 | return gulp.src(['angularjs-filemanager.js', 'templates.js'], {base: distributionFolder, cwd: distributionFolder})
178 | .pipe(concat('angularjs-filemanager.js'))
179 | .pipe(gulp.dest(distributionFolder));
180 | });
181 |
182 |
183 | gulp.task('scripts-minify', gulp.series('javascript', 'sprites_icon', 'ng-templates', 'concat-ng-templates', function minify(done) {
184 | // Minify and copy all JavaScript (except vendor scripts)
185 | // with sourcemaps all the way down
186 | del('./' + distributionFolder + 'templates.js');
187 | return gulp.src(['angularjs-filemanager.js'], {base: distributionFolder, cwd: distributionFolder})
188 | .pipe(iife({
189 | useStrict: false
190 | }))
191 | .pipe(minifyes().on('error', function (e) {
192 | console.log(e);
193 | //callback(e);
194 | }))
195 | .pipe(header(fs.readFileSync('header.txt', 'utf8'), {pkg: package}))
196 | .pipe(gulp.dest(distributionFolder));
197 | }));
198 |
199 |
200 | gulp.task('styles-minify', gulp.series(gulp.parallel('scss'), function () {
201 | return gulp.src(['angularjs-filemanager.css'], {base: distributionFolder, cwd: distributionFolder})
202 | //.pipe(sourcemaps.init())
203 | .pipe(cleanCSS({
204 | level: {
205 | 1: {
206 | specialComments: 'none'
207 | },
208 | 2: {
209 | //normalizeUrls: false
210 | }
211 | },
212 | //inline: ['local'],
213 | rebase: false
214 | }))
215 | //.pipe(sourcemaps.write())
216 | .pipe(header(fs.readFileSync('header.txt', 'utf8'), {pkg: package}))
217 | .pipe(gulp.dest(distributionFolder));
218 | }));
219 |
220 |
221 | gulp.task('production-replace', function (done) {
222 |
223 | return gulp.src(['angularjs-filemanager.css', 'angularjs-filemanager.js'], {
224 | base: distributionFolder,
225 | cwd: distributionFolder
226 | })
227 | //adding version to stop caching
228 | .pipe(replace('debugInfoEnabled(!0)', 'debugInfoEnabled(false)'))
229 | .pipe(replace('[[version]]', package.version))
230 | .pipe(replace(/<\/ng-include>/g, function (match, p1) {
231 | const svg = fs.readFileSync(path.resolve('.' + p1));
232 | del('.' + p1);
233 | return svg;
234 | }))
235 | .pipe(replace('/dist/', '/'))
236 | .pipe(replace('/assets/app/', '/'))
237 |
238 | .pipe(gulp.dest(distributionFolder));
239 |
240 | });
241 |
242 | gulp.task('distribute', gulp.series('clean',
243 | gulp.parallel('styles-minify', 'scripts-minify'),
244 | 'production-replace'
245 | ));
246 |
247 |
248 | gulp.task('lint', function () {
249 | let src = 'src/';
250 | return gulp.src([src + 'js/app.js', src + 'js/*/*.js'])
251 | .pipe(eslint({
252 | 'rules': {
253 | 'quotes': [2, 'single'],
254 | //'linebreak-style': [2, 'unix'],
255 | 'semi': [2, 'always']
256 | },
257 | 'env': {
258 | 'browser': true
259 | },
260 | 'globals': [
261 | 'angular',
262 | 'jQuery'
263 | ],
264 | "parser": "babel-eslint",
265 | "parserOptions": {
266 | "sourceType": "module",
267 | "allowImportExportEverywhere": false,
268 | "codeFrame": true
269 | }
270 | }))
271 | .pipe(eslint.format())
272 | .pipe(eslint.failOnError());
273 | });
274 |
--------------------------------------------------------------------------------
/header.txt:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | _____ __________ _____ __ __
4 | / _ \ \____ /________________ _/ ____\____ _/ |_|__| ____ ____ _____
5 | / /_\ \ / // __ \_ __ \__ \\ __\\__ \\ __\ | _/ ___\/ _ \ / \
6 | / | \/ _/\ ___/| | \// __ \| | / __ \| | | | \ \__( <_> ) Y Y \
7 | \____|__ /_______ \___ >__| (____ /__| (____ /__| |__| /\ \___ >____/|__|_| /
8 | \/ \/ \/ \/ \/ \/ \/ \/
9 |
10 | * ${pkg.name}
11 | * @version v${pkg.version}
12 | * @website ${pkg.homepage}
13 | */
14 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 | angular-filemanager
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
59 |
60 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@azerafati/angularjs-filemanager",
3 | "version": "2.1.3",
4 | "description": "A very smart filemanager to manage your files in the browser.",
5 | "main": "src/js/app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "develop": "/usr/bin/php -S localhost:8080 & gulp",
9 | "distribute": "gulp distribute"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/azerafati/angularjs-filemanager.git"
14 | },
15 | "keywords": [
16 | "filemanager"
17 | ],
18 | "author": "Alireza Zerafati based on excellent work from Jonas Sciangula Street",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/azerafati/angular-filemanager/issues"
22 | },
23 | "files": [
24 | "dist/*"
25 | ],
26 | "homepage": "https://angularjs-filemanager.azerafati.com/",
27 | "devDependencies": {
28 | "@babel/core": "^7.8.4",
29 | "@babel/preset-env": "^7.8.4",
30 | "babel-eslint": "^10.0.3",
31 | "browser-sync": "^2.26.3",
32 | "connect-history-api-fallback": "^1.6.0",
33 | "del": "^5.1.0",
34 | "gulp": "^4.0.0",
35 | "gulp-angular-templatecache": "^3.0.0",
36 | "gulp-babel": "^8.0.0",
37 | "gulp-clean-css": "^4.0.0",
38 | "gulp-concat": "^2.6.1",
39 | "gulp-eslint": "^6.0.0",
40 | "gulp-header": "^2.0.9",
41 | "gulp-htmlmin": "^5.0.1",
42 | "gulp-iife": "^0.4.0",
43 | "gulp-replace": "^1.0.0",
44 | "gulp-sass": "^4.0.2",
45 | "gulp-sass-bulk-import": "^1.0.1",
46 | "gulp-sourcemaps": "^2.6.5",
47 | "gulp-svg-sprite": "^1.5.0",
48 | "gulp-uglify": "^3.0.2",
49 | "gulp-useref": "^4.0.1",
50 | "uglify-es": "^3.3.9"
51 | },
52 | "dependencies": {
53 | "angular": "^1.7.9",
54 | "angular-translate": "^2.18.2",
55 | "bootstrap": "^4.4.1",
56 | "jquery": "^3.4.0",
57 | "ng-file-upload": "^12.2.13"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/css/animations.scss:
--------------------------------------------------------------------------------
1 | @-webkit-keyframes fadeIn {
2 | 0% {
3 | opacity: 0;
4 | }
5 |
6 | 100% {
7 | opacity: 1;
8 | };
9 | }
10 |
11 | @keyframes fadeIn {
12 | 0% {
13 | opacity: 0;
14 | }
15 |
16 | 100% {
17 | opacity: 1;
18 | };
19 | }
20 |
21 | @-webkit-keyframes fadeInDown {
22 | 0% {
23 | opacity: 0;
24 | -webkit-transform: translate3d(0, -100%, 0);
25 | transform: translate3d(0, -100%, 0);
26 | }
27 |
28 | 100% {
29 | opacity: 1;
30 | -webkit-transform: none;
31 | transform: none;
32 | };
33 | }
34 |
35 | @keyframes fadeInDown {
36 | 0% {
37 | opacity: 0;
38 | -webkit-transform: translate3d(0, -100%, 0);
39 | transform: translate3d(0, -100%, 0);
40 | }
41 |
42 | 100% {
43 | opacity: 1;
44 | -webkit-transform: none;
45 | transform: none;
46 | };
47 | }
48 |
49 | @keyframes rotate {
50 | 100% {
51 | transform: rotate(360deg);
52 | };
53 | }
54 |
55 | @-webkit-keyframes rotate {
56 | 100% {
57 | -webkit-transform: rotate(360deg);
58 | };
59 | }
60 |
61 | @keyframes colors {
62 | 0% {
63 | stroke: #4285F4;
64 | }
65 |
66 | 25% {
67 | stroke: #DE3E35;
68 | }
69 |
70 | 50% {
71 | stroke: #F7C223;
72 | }
73 |
74 | 75% {
75 | stroke: #1B9A59;
76 | }
77 |
78 | 100% {
79 | stroke: #4285F4;
80 | };
81 | }
82 |
83 | @keyframes dash {
84 | 0% {
85 | stroke-dasharray: 1,150;
86 | stroke-dashoffset: 0;
87 | stroke: red;
88 | }
89 |
90 | 50% {
91 | stroke-dasharray: 90,150;
92 | stroke-dashoffset: -35;
93 | stroke: yellow;
94 | }
95 |
96 | 100% {
97 | stroke-dasharray: 90,150;
98 | stroke-dashoffset: -124;
99 | stroke: green;
100 | };
101 | }
102 |
103 | @-webkit-keyframes dash {
104 | 0% {
105 | stroke-dasharray: 1,150;
106 | stroke-dashoffset: 0;
107 | }
108 |
109 | 50% {
110 | stroke-dasharray: 90,150;
111 | stroke-dashoffset: -35;
112 | }
113 |
114 | 100% {
115 | stroke-dasharray: 90,150;
116 | stroke-dashoffset: -124;
117 | };
118 | }
119 |
120 | .animated {
121 | -webkit-animation-duration: .7s;
122 | animation-duration: .7s;
123 | -webkit-animation-fill-mode: both;
124 | animation-fill-mode: both;
125 | }
126 |
127 | .modal.animated,
128 | .animated.fast {
129 | -webkit-animation-duration: .2s;
130 | animation-duration: .2s;
131 | }
132 |
133 | .animated.slow {
134 | -webkit-animation-duration: 1.1s;
135 | animation-duration: 1.1s;
136 | }
137 |
138 | .animated.fadeInDown {
139 | -webkit-animation-name: fadeInDown;
140 | animation-name: fadeInDown;
141 | }
142 |
143 | .animated.fadeIn {
144 | -webkit-animation-name: fadeIn;
145 | animation-name: fadeIn;
146 | }
147 |
148 | .spinner-container {
149 | -webkit-animation: rotate 2s linear infinite;
150 | animation: rotate 2s linear infinite;
151 | z-index: 2;
152 | width: 65px;
153 | height: 65px;
154 | }
155 |
156 | .spinner-container .path {
157 | stroke-dasharray: 1,150;
158 | stroke-dashoffset: 0;
159 | stroke: #2196F3;
160 | stroke-linecap: round;
161 | -webkit-animation: dash 1.5s ease-in-out infinite, colors 5.6s ease-in-out infinite;
162 | animation: dash 1.5s ease-in-out infinite, colors 5.6s ease-in-out infinite;
163 | }
--------------------------------------------------------------------------------
/src/css/dialogs.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 | word-wrap: break-word;
3 | }
4 |
5 | .modal .label.error-msg {
6 | display: block;
7 | font-size: 12px;
8 | margin-top: 5px;
9 | padding: 0;
10 | padding: 5px;
11 | margin-top: 10px;
12 | text-align: left;
13 | }
14 |
15 | .modal .label.error-msg > span {
16 | white-space: pre-wrap;
17 | }
18 |
19 | .modal .breadcrumb {
20 | margin: 0;
21 | background: #00bcd4;
22 | font-size: 16px;
23 | max-height: inherit;
24 | padding: 0 10px;
25 | margin-bottom: 5px;
26 | }
27 |
28 | .modal-fullscreen .modal-dialog,
29 | .modal-fullscreen .modal-content {
30 | bottom: 0;
31 | left: 0;
32 | position: absolute;
33 | right: 0;
34 | top: 0;
35 | }
36 |
37 | .modal-fullscreen .modal-dialog {
38 | margin: 0;
39 | width: 100%;
40 | }
41 |
42 | .modal-fullscreen .modal-content {
43 | border: none;
44 | -moz-border-radius: 0;
45 | border-radius: 0;
46 | -webkit-box-shadow: inherit;
47 | -moz-box-shadow: inherit;
48 | -o-box-shadow: inherit;
49 | box-shadow: inherit;
50 | }
51 |
52 | .modal-fullscreen textarea.code {
53 | min-height: 450px;
54 | }
55 |
56 | .modal img.preview {
57 | max-width: 100%;
58 | max-height: 640px;
59 | border-radius: 3px;
60 | }
61 |
62 | .modal img.preview.loading {
63 | width: 100%;
64 | height: 1px;
65 | opacity: 0;
66 | }
67 |
68 |
69 | .modal .modal-content {
70 | border-radius: 10px 10px 4px 4px;
71 | }
72 |
73 | .modal .modal-header {
74 | border-radius: 4px 4px 0 0;
75 | background: #2196F3;
76 | padding: 1.3em;
77 | }
78 |
79 | .modal .modal-header .modal-title {
80 | font-size: 20px;
81 | line-height: 100%;
82 | color: #D4E5F5;
83 | margin: 0;
84 | }
85 |
86 | .modal .modal-header .close {
87 | opacity: 1;
88 | color: #D4E5F5;
89 | }
90 |
91 | .modal .modal-header .close.fullscreen {
92 | font-size: 14px;
93 | position: relative;
94 | top: 4px;
95 | margin-right: .8em;
96 | }
--------------------------------------------------------------------------------
/src/css/main.scss:
--------------------------------------------------------------------------------
1 |
2 | *,
3 | *:focus {
4 | outline: 0 !important;
5 | }
6 |
7 | .navbar {
8 | min-height: 32px;
9 | margin-bottom: 0;
10 | border: 0;
11 | border-radius: 0;
12 | color: #fff;
13 | }
14 |
15 | .navbar .navbar-collapse {
16 | overflow: visible;
17 | padding: 0;
18 | }
19 |
20 | .navbar .navbar-toggle {
21 | padding: 5px 10px;
22 | }
23 |
24 | .navbar .navbar-brand {
25 | font-size: inherit;
26 | height: 55px;
27 | line-height: 100%;
28 | }
29 |
30 | .btn.btn-default {
31 | color: #444;
32 | background-color: #FAFAFA;
33 | }
34 |
35 | .btn {
36 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .26);
37 | font-weight: 500;
38 | letter-spacing: .01em;
39 | border: none;
40 | }
41 |
42 | textarea.code {
43 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
44 | font-size: 13px;
45 | min-height: 250px;
46 | resize: vertical;
47 | color: #000;
48 | }
49 |
50 | .sub-header {
51 | padding-bottom: 10px;
52 | border-bottom: 1px solid #eee;
53 | }
54 |
55 | .sidebar {
56 | display: none;
57 | background: #fafafa;
58 | margin-top: 2px;
59 | padding: 0;
60 | overflow-x: hidden;
61 | overflow-y: auto;
62 | border-right: 1px solid #eee;
63 | }
64 |
65 | .btn-go-back {
66 | margin-top: -5px;
67 | }
68 |
69 | .main .page-header {
70 | margin-top: 0;
71 | }
72 |
73 | .file-tree-root {
74 | margin-left: 0;
75 | button.btn {
76 | border: none;
77 | box-shadow: none;
78 | svg.icon {
79 | width: 1.5rem;
80 | height: 1.5rem;
81 | }
82 | }
83 | .nav-sidebar {
84 | margin-right: -21px;
85 | margin-left: 20px;
86 |
87 | svg.icon.chevron {
88 | color: $text-color;
89 | width: 1rem;
90 | height: 1rem;
91 | }
92 | }
93 |
94 | }
95 |
96 | .table td {
97 | vertical-align: middle;
98 | }
99 |
100 | .iconset {
101 | padding: 10px;
102 | }
103 |
104 | .col-120 {
105 | width: 100px;
106 | max-height: 100px;
107 | float: left;
108 | margin-bottom: 9px;
109 | margin-right: 9px;
110 | }
111 |
112 | .col-120:last-child {
113 | margin-right: 0;
114 | }
115 |
116 | .noselect {
117 | -webkit-touch-callout: none; /* iOS Safari */
118 | -webkit-user-select: none; /* Chrome/Safari/Opera */
119 | -khtml-user-select: none; /* Konqueror */
120 | -moz-user-select: none; /* Firefox */
121 | -ms-user-select: none; /* IE/Edge */
122 | user-select: none; /* non-prefixed version, currently */
123 | }
124 |
125 | .iconset .thumbnail {
126 | border-radius: 0;
127 | overflow: hidden;
128 | margin: 0;
129 | padding: 10px 0;
130 | border: none;
131 | background: none;
132 | display: block;
133 | svg.icon {
134 | width: 4rem;
135 | height: 4rem;
136 | margin-bottom: 1px;
137 | }
138 | }
139 |
140 | .table-files .selected,
141 | .iconset .thumbnail.selected {
142 | background: #2196F3;
143 | }
144 |
145 | .iconset .thumbnail.selected,
146 | .table-files .selected td,
147 | .table-files .selected td a {
148 | color: #fff;
149 | }
150 |
151 | .detail-sources {
152 | text-overflow: ellipsis;
153 | overflow: hidden;
154 | word-wrap: break-word;
155 | }
156 |
157 | .upload-dragover .main {
158 | opacity: .4;
159 | }
160 |
161 | .upload-dragover:before {
162 | content: "\e198";
163 | position: absolute;
164 | left: 50%;
165 | top: 50%;
166 | transform: translate(-50%, -50%);
167 | z-index: 100;
168 | color: #2196F3;
169 | font-size: 8em;
170 | font-family: 'Glyphicons Halflings';
171 | }
172 |
173 | .upload-list {
174 | margin-top: 20px;
175 | }
176 |
177 | .spinner-wrapper {
178 | margin: 0 auto;
179 | text-align: center;
180 | margin-top: 8%;
181 | }
182 |
183 | a:hover,
184 | a:active,
185 | a:focus,
186 | table th > a:hover,
187 | table th > a:active,
188 | table th > a:focus {
189 | text-decoration: none;
190 | }
191 |
192 | .sortorder:after {
193 | color: #2196f3;
194 | content: '\25bc';
195 | }
196 |
197 | .sortorder.reverse:after {
198 | color: #2196f3;
199 | content: '\25b2';
200 | }
201 |
202 | [ng\:cloak], [ng-cloak],
203 | [data-ng-cloak], [x-ng-cloak],
204 | .ng-cloak, .x-ng-cloak {
205 | display: none !important;
206 | }
207 |
208 | .mr2 {
209 | margin-right: 2px;
210 | }
211 |
212 | .mr5 {
213 | margin-right: 5px;
214 | }
215 |
216 | .mt10 {
217 | margin-top: 10px;
218 | }
219 |
220 | .mb0 {
221 | margin-bottom: 0;
222 | }
223 |
224 | .pointer {
225 | cursor: pointer;
226 | }
227 |
228 | .block {
229 | display: block;
230 | }
231 |
232 | .ellipsis {
233 | overflow: hidden;
234 | text-overflow: ellipsis;
235 | white-space: nowrap;
236 | }
237 |
238 | .bold {
239 | font-weight: bold;
240 | }
241 |
242 | .main {
243 | overflow-y: auto;
244 | }
245 |
246 | @media (min-width: 768px) {
247 | .main {
248 | padding-right: 0;
249 | padding-left: 0;
250 | }
251 |
252 | /* The view should fill all available vertical space */
253 | angular-filemanager > div, .row, .main, .sidebar {
254 | height: 100%;
255 | }
256 |
257 | .container-fluid {
258 | height: -webkit-calc(100% - 58px);
259 | height: -moz-calc(100% - 58px);
260 | height: calc(100% - 58px);
261 | }
262 |
263 | .sidebar {
264 | display: block;
265 | }
266 | }
267 |
268 | .selected-file-details {
269 | padding-left: 20px;
270 | }
271 |
272 | .item-extension::after {
273 | font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif;
274 | content: attr(data-ext);
275 | left: 4px;
276 | position: absolute;
277 | color: #fff;
278 | font-size: 9px;
279 | text-transform: uppercase;
280 | top: 21px;
281 | }
282 |
283 | .selected .item-extension::after {
284 | color: #2196F3;
285 | }
286 |
287 | .form-control.search-input {
288 | max-width: 20em;
289 | display: inline;
290 | }
291 |
292 | .like-code {
293 | display: inline;
294 | }
295 |
296 | .point {
297 | margin-right: 8px;
298 | font-size: 10px;
299 | }
300 |
301 | .navbar .btn.btn-flat {
302 | padding: 2px;
303 | width: 32px;
304 | height: 30px;
305 | margin-left: 5px;
306 | }
307 |
308 | .navbar-inverse .navbar-toggle .icon-bar {
309 | background: #fff;
310 | }
311 |
312 | .navbar-inverse .navbar-form input[type="text"] {
313 | color: #7a7a7a;
314 | box-shadow: none;
315 | margin: 0 10px;
316 | }
317 |
318 | .navbar .navbar-form {
319 | border-bottom: none;
320 | border-top: none;
321 | box-shadow: none;
322 | padding: 0;
323 | margin: 12px 0;
324 | }
325 |
326 | .breadcrumb {
327 | background: none;
328 | padding: 0;
329 | font-size: 17px;
330 | margin: 12px 0;
331 | overflow: hidden;
332 | max-height: 30px
333 | }
334 |
335 | .breadcrumb > .active,
336 | .breadcrumb a {
337 | color: #fff;
338 | }
339 |
340 | .breadcrumb-item + .breadcrumb-item::before {
341 | color: #ffffff;
342 | }
343 |
344 | .scrollable-menu {
345 | height: auto;
346 | max-height: 200px;
347 | overflow-x: hidden;
348 | }
349 |
350 | .btn.btn-flat {
351 | background: none;
352 | color: #fff;
353 | }
354 |
355 | .btn-group.open > .btn-flat,
356 | .btn.btn-flat,
357 | .btn.btn-flat:active {
358 | box-shadow: none;
359 | }
360 |
361 | .btn.btn-flat > i {
362 | font-size: 18px;
363 | width: 18px;
364 | height: 18px;
365 | line-height: 100%;
366 | }
367 |
--------------------------------------------------------------------------------
/src/css/sprite.scss:
--------------------------------------------------------------------------------
1 | svg.icon {
2 | width: 1rem;
3 | height: 1rem;
4 | display: inline-block;
5 | position: relative;
6 | vertical-align: middle;
7 | fill: currentColor;
8 | transition: fill 0.15s ease-in-out;
9 | &.r-90 {
10 | transform: rotate(90deg);
11 | }
12 | &.r-180 {
13 | transform: rotate(180deg);
14 | }
15 | &.r-270 {
16 | transform: rotate(270deg);
17 | }
18 | &.icon-lg {
19 | width: 2rem;
20 | height: 2rem;
21 | }
22 | &.disabled {
23 | filter: grayscale(100%);
24 | -webkit-filter: grayscale(100%);
25 | }
26 | }
27 |
28 | @-moz-document url-prefix() {
29 | svg.icon {
30 | top: 0;
31 | }
32 | }
33 |
34 | .btn-icon {
35 | border-radius: 500px;
36 | padding: 0;
37 | svg {
38 | margin: 0;
39 | top: 0;
40 | width: 35px;
41 | height: 35px;
42 | }
43 | &.btn-sm {
44 | svg {
45 | width: 25px;
46 | height: 25px;
47 | }
48 | }
49 | }
50 |
51 | h2 {
52 | svg.icon {
53 | width: 30px;
54 | height: 30px;
55 | top: 8px;
56 | }
57 | }
58 |
59 | h3, .h3 {
60 | svg.icon {
61 | width: 1.75rem;
62 | height: 1.75rem;
63 | line-height: 1.2;
64 | top: 0.5rem;
65 | }
66 | }
--------------------------------------------------------------------------------
/src/css/style.scss:
--------------------------------------------------------------------------------
1 | @import "animations";
2 |
3 | angularjs-filemanager { // name-spacing all the styles prevents conflicts when imported in other projects
4 |
5 | @import "variables";
6 | @import "dialogs";
7 | @import "main";
8 | @import "sprite";
9 |
10 | position: relative;
11 | display: block;
12 | height: 100%;
13 | overflow: hidden;
14 | color: $text-color;
15 | a {
16 | color: $text-color;
17 | }
18 |
19 | }
20 |
21 | #angularjs-filemanager-context-menu {
22 | position: absolute;
23 | display: none;
24 | z-index: 9999;
25 |
26 | .dropdown-menu > li > a {
27 | padding: 6px 20px;
28 | }
29 |
30 | .dropdown-menu > li > a > i {
31 | margin-right: 4px;
32 | }
33 |
34 | .dropdown-menu.dropdown-right-click {
35 | display: block;
36 | position: static;
37 | margin-bottom: 5px;
38 | }
39 |
40 | .dropdown-menu.dropdown-right-click .divider {
41 | margin: 3px 0;
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/src/css/variables.scss:
--------------------------------------------------------------------------------
1 | $text-color: #474747;
2 | $angularjs-primary-color: #3067cd
--------------------------------------------------------------------------------
/src/js/app.js:
--------------------------------------------------------------------------------
1 | const app = angular.module('AngularJS-FileManager', ['pascalprecht.translate', 'ngFileUpload']);
2 |
3 | /**
4 | * jQuery inits
5 | */
6 | angular.element(window.document).on('shown.bs.modal', '.modal', function () {
7 | window.setTimeout(function () {
8 | angular.element('[autofocus]', this).focus();
9 | }.bind(this), 100);
10 | });
11 |
12 | angular.element(window.document).on('click', function () {
13 | angular.element('#angularjs-filemanager-context-menu').hide();
14 | });
15 |
16 | angular.element(window.document).on('contextmenu', '.main-navigation .table-files tr.item-list:has("td"), .item-list', function (e) {
17 | var menu = angular.element('#angularjs-filemanager-context-menu');
18 |
19 | if (e.pageX >= window.innerWidth - menu.width()) {
20 | e.pageX -= menu.width();
21 | }
22 | if (e.pageY >= window.innerHeight - menu.height()) {
23 | e.pageY -= menu.height();
24 | }
25 |
26 | menu.hide().css({
27 | left: e.pageX,
28 | top: e.pageY
29 | }).appendTo('body').show();
30 | e.preventDefault();
31 | });
32 |
--------------------------------------------------------------------------------
/src/js/controllers/selector-controller.js:
--------------------------------------------------------------------------------
1 | app.controller('ModalFileManagerCtrl',
2 | ['$scope', '$rootScope', 'fileNavigator', function ($scope, $rootScope, FileNavigator) {
3 |
4 | $scope.reverse = false;
5 | $scope.predicate = ['model.type', 'model.name'];
6 | $scope.fileNavigator = new FileNavigator();
7 | $rootScope.selectedModalPath = [];
8 |
9 | $scope.order = function (predicate) {
10 | $scope.reverse = ($scope.predicate[1] === predicate) ? !$scope.reverse : false;
11 | $scope.predicate[1] = predicate;
12 | };
13 |
14 | $scope.select = function (item) {
15 | $rootScope.selectedModalPath = item.model.relativePath().split('/').filter(Boolean);
16 | $scope.modal('selector', true);
17 | };
18 |
19 | $scope.selectCurrent = function () {
20 | $rootScope.selectedModalPath = $scope.fileNavigator.currentPath;
21 | $scope.modal('selector', true);
22 | };
23 |
24 | $scope.selectedFilesAreChildOfPath = function (item) {
25 | var path = item.model.relativePath();
26 | return $scope.temps.find(function (item) {
27 | var itemPath = item.model.relativePath();
28 | if (path === itemPath) {
29 | return true;
30 | }
31 | /*
32 | if (path.startsWith(itemPath)) {
33 | fixme names in same folder like folder-one and folder-one-two
34 | at the moment fixed hidding affected folders
35 | }
36 | */
37 | });
38 | };
39 |
40 | $rootScope.openNavigator = function (path) {
41 | $scope.fileNavigator.currentPath = path;
42 | $scope.fileNavigator.refresh();
43 | $scope.modal('selector');
44 | };
45 |
46 | $rootScope.getSelectedPath = function () {
47 | var path = $rootScope.selectedModalPath.filter(Boolean);
48 | var result = '/' + path.join('/');
49 | if ($scope.singleSelection() && !$scope.singleSelection().isFolder()) {
50 | result += '/' + $scope.singleSelection().tempModel.name;
51 | }
52 | return result.replace(/\/\//, '/');
53 | };
54 |
55 | }]);
56 |
--------------------------------------------------------------------------------
/src/js/directives/directives.js:
--------------------------------------------------------------------------------
1 | app.directive('angularjsFilemanager', ['$parse', 'fileManagerConfig', function ($parse, fileManagerConfig) {
2 | return {
3 | restrict: 'E',
4 | templateUrl: fileManagerConfig.tplPath + '/main.html',
5 | scope: {
6 | onSelect: '&'
7 | }
8 | };
9 | }]);
10 |
11 | app.directive('ngFile', ['$parse', function ($parse) {
12 | return {
13 | restrict: 'A',
14 | link: function (scope, element, attrs) {
15 | var model = $parse(attrs.ngFile);
16 | var modelSetter = model.assign;
17 |
18 | element.bind('change', function () {
19 | scope.$apply(function () {
20 | modelSetter(scope, element[0].files);
21 | });
22 | });
23 | }
24 | };
25 | }]);
26 |
27 | app.directive('ngRightClick', ['$parse', function ($parse) {
28 | return function (scope, element, attrs) {
29 | var fn = $parse(attrs.ngRightClick);
30 | element.bind('contextmenu', function (event) {
31 | scope.$apply(function () {
32 | event.preventDefault();
33 | fn(scope, {$event: event});
34 | });
35 | });
36 | };
37 | }]);
38 |
39 |
--------------------------------------------------------------------------------
/src/js/entities/chmod.js:
--------------------------------------------------------------------------------
1 | app.service('chmod', function () {
2 |
3 | var Chmod = function (initValue) {
4 | this.owner = this.getRwxObj();
5 | this.group = this.getRwxObj();
6 | this.others = this.getRwxObj();
7 |
8 | if (initValue) {
9 | var codes = isNaN(initValue) ?
10 | this.convertfromCode(initValue) :
11 | this.convertfromOctal(initValue);
12 |
13 | if (!codes) {
14 | throw new Error('Invalid chmod input data (%s)'.replace('%s', initValue));
15 | }
16 |
17 | this.owner = codes.owner;
18 | this.group = codes.group;
19 | this.others = codes.others;
20 | }
21 | };
22 |
23 | Chmod.prototype.toOctal = function (prepend, append) {
24 | var result = [];
25 | ['owner', 'group', 'others'].forEach(function (key, i) {
26 | result[i] = this[key].read && this.octalValues.read || 0;
27 | result[i] += this[key].write && this.octalValues.write || 0;
28 | result[i] += this[key].exec && this.octalValues.exec || 0;
29 | }.bind(this));
30 | return (prepend || '') + result.join('') + (append || '');
31 | };
32 |
33 | Chmod.prototype.toCode = function (prepend, append) {
34 | var result = [];
35 | ['owner', 'group', 'others'].forEach(function (key, i) {
36 | result[i] = this[key].read && this.codeValues.read || '-';
37 | result[i] += this[key].write && this.codeValues.write || '-';
38 | result[i] += this[key].exec && this.codeValues.exec || '-';
39 | }.bind(this));
40 | return (prepend || '') + result.join('') + (append || '');
41 | };
42 |
43 | Chmod.prototype.getRwxObj = function () {
44 | return {
45 | read: false,
46 | write: false,
47 | exec: false
48 | };
49 | };
50 |
51 | Chmod.prototype.octalValues = {
52 | read: 4, write: 2, exec: 1
53 | };
54 |
55 | Chmod.prototype.codeValues = {
56 | read: 'r', write: 'w', exec: 'x'
57 | };
58 |
59 | Chmod.prototype.convertfromCode = function (str) {
60 | str = ('' + str).replace(/\s/g, '');
61 | str = str.length === 10 ? str.substr(1) : str;
62 | if (!/^[-rwxts]{9}$/.test(str)) {
63 | return;
64 | }
65 |
66 | var result = [], vals = str.match(/.{1,3}/g);
67 | for (var i in vals) {
68 | var rwxObj = this.getRwxObj();
69 | rwxObj.read = /r/.test(vals[i]);
70 | rwxObj.write = /w/.test(vals[i]);
71 | rwxObj.exec = /x|t/.test(vals[i]);
72 | result.push(rwxObj);
73 | }
74 |
75 | return {
76 | owner: result[0],
77 | group: result[1],
78 | others: result[2]
79 | };
80 | };
81 |
82 | Chmod.prototype.convertfromOctal = function (str) {
83 | str = ('' + str).replace(/\s/g, '');
84 | str = str.length === 4 ? str.substr(1) : str;
85 | if (!/^[0-7]{3}$/.test(str)) {
86 | return;
87 | }
88 |
89 | var result = [], vals = str.match(/.{1}/g);
90 | for (var i in vals) {
91 | var rwxObj = this.getRwxObj();
92 | rwxObj.read = /[4567]/.test(vals[i]);
93 | rwxObj.write = /[2367]/.test(vals[i]);
94 | rwxObj.exec = /[1357]/.test(vals[i]);
95 | result.push(rwxObj);
96 | }
97 |
98 | return {
99 | owner: result[0],
100 | group: result[1],
101 | others: result[2]
102 | };
103 | };
104 |
105 | return Chmod;
106 | });
--------------------------------------------------------------------------------
/src/js/entities/item.js:
--------------------------------------------------------------------------------
1 | app.factory('item', ['fileManagerConfig', 'chmod', function (fileManagerConfig, Chmod) {
2 |
3 | var Item = function (model, path) {
4 | var rawModel = {
5 | name: model && model.name || '',
6 | path: path || [],
7 | type: model && model.type || 'file',
8 | size: model && parseInt(model.size || 0),
9 | date: parseMySQLDate(model && model.date),
10 | perms: new Chmod(model && model.rights),
11 | content: model && model.content || '',
12 | recursive: false,
13 | relativePath: function () {
14 | var path = this.path.filter(Boolean);
15 | return ('/' + path.join('/') + '/' + this.name).replace(/\/\//, '/');
16 | },
17 | fullPath: function () {
18 | return (fileManagerConfig.serverUrl + this.relativePath());
19 | }
20 | };
21 |
22 | this.error = '';
23 | this.processing = false;
24 |
25 | this.model = angular.copy(rawModel);
26 | this.tempModel = angular.copy(rawModel);
27 |
28 | function parseMySQLDate(mysqlDate) {
29 | var d = (mysqlDate || '').toString().split(/[- :]/);
30 | return new Date(d[0], d[1] - 1, d[2], d[3], d[4], d[5]);
31 | }
32 | };
33 |
34 | Item.prototype.update = function () {
35 | angular.extend(this.model, angular.copy(this.tempModel));
36 | };
37 |
38 | Item.prototype.revert = function () {
39 | angular.extend(this.tempModel, angular.copy(this.model));
40 | this.error = '';
41 | };
42 |
43 | Item.prototype.isFolder = function () {
44 | return this.model.type === 'dir';
45 | };
46 |
47 | Item.prototype.isEditable = function () {
48 | return !this.isFolder() && fileManagerConfig.isEditableFilePattern.test(this.model.name);
49 | };
50 |
51 | Item.prototype.isImage = function () {
52 | return fileManagerConfig.isImageFilePattern.test(this.model.name);
53 | };
54 |
55 | Item.prototype.isCompressible = function () {
56 | return this.isFolder();
57 | };
58 |
59 | Item.prototype.isExtractable = function () {
60 | return !this.isFolder() && fileManagerConfig.isExtractableFilePattern.test(this.model.name);
61 | };
62 |
63 | Item.prototype.isSelectable = function () {
64 | return (this.isFolder() && fileManagerConfig.allowedActions.pickFolders) || (!this.isFolder() && fileManagerConfig.allowedActions.pickFiles);
65 | };
66 |
67 | return Item;
68 | }]);
--------------------------------------------------------------------------------
/src/js/filters/filters.js:
--------------------------------------------------------------------------------
1 | app.filter('strLimit', ['$filter', function ($filter) {
2 | return function (input, limit, more) {
3 | if (input.length <= limit) {
4 | return input;
5 | }
6 | return $filter('limitTo')(input, limit) + (more || '...');
7 | };
8 | }]);
9 |
10 | app.filter('fileExtension', ['$filter', function ($filter) {
11 | return function (input) {
12 | return /\./.test(input) && $filter('strLimit')(input.split('.').pop(), 3, '..') || '';
13 | };
14 | }]);
15 |
16 | app.filter('formatDate', ['$filter', function () {
17 | return function (input) {
18 | return input instanceof Date ?
19 | input.toISOString().substring(0, 19).replace('T', ' ') :
20 | (input.toLocaleString || input.toString).apply(input);
21 | };
22 | }]);
23 |
24 | app.filter('humanReadableFileSize', ['$filter', 'fileManagerConfig', function ($filter, fileManagerConfig) {
25 | // See https://en.wikipedia.org/wiki/Binary_prefix
26 | var decimalByteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
27 | var binaryByteUnits = ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
28 |
29 | return function (input) {
30 | var i = -1;
31 | var fileSizeInBytes = input;
32 |
33 | do {
34 | fileSizeInBytes = fileSizeInBytes / 1024;
35 | i++;
36 | } while (fileSizeInBytes > 1024);
37 |
38 | var result = fileManagerConfig.useBinarySizePrefixes ? binaryByteUnits[i] : decimalByteUnits[i];
39 | return Math.max(fileSizeInBytes, 0.1).toFixed(1) + ' ' + result;
40 | };
41 | }]);
42 |
--------------------------------------------------------------------------------
/src/js/providers/config.js:
--------------------------------------------------------------------------------
1 | app.provider('fileManagerConfig', function () {
2 | var bridgePath = 'bridges/php/handler.php';
3 | var values = {
4 | appName: 'AngularJS File Manager',
5 | defaultLang: 'en',
6 | multiLang: true,
7 | bridgePath: bridgePath,
8 | listUrl: bridgePath,
9 | uploadUrl: bridgePath,
10 | renameUrl: bridgePath,
11 | copyUrl: bridgePath,
12 | moveUrl: bridgePath,
13 | removeUrl: bridgePath,
14 | editUrl: bridgePath,
15 | getContentUrl: bridgePath,
16 | createFolderUrl: bridgePath,
17 | downloadFileUrl: bridgePath,
18 | downloadMultipleUrl: bridgePath,
19 | compressUrl: bridgePath,
20 | extractUrl: bridgePath,
21 | permissionsUrl: bridgePath,
22 | basePath: '/',
23 | serverUrl: '', // leading string in building public path for files (e.g. https://example.com/uploads)
24 |
25 | searchForm: true,
26 | sidebar: true,
27 | breadcrumb: true,
28 | allowedActions: {
29 | upload: true,
30 | rename: true,
31 | move: true,
32 | copy: true,
33 | edit: true,
34 | changePermissions: true,
35 | compress: true,
36 | compressChooseName: true,
37 | extract: true,
38 | download: true,
39 | downloadMultiple: true,
40 | preview: true,
41 | remove: true,
42 | createFolder: true,
43 | pickFiles: false,
44 | pickFolders: false
45 | },
46 |
47 | multipleDownloadFileName: 'angularjs-filemanager.zip',
48 | filterFileExtensions: [],
49 | showExtensionIcons: true,
50 | showSizeForDirectories: false,
51 | useBinarySizePrefixes: false,
52 | downloadFilesByAjax: true,
53 | previewImagesInModal: true,
54 | enablePermissionsRecursive: true,
55 | compressAsync: false,
56 | extractAsync: false,
57 | pickCallback: null,
58 |
59 | isEditableFilePattern: /\.(txt|diff?|patch|svg|asc|cnf|cfg|conf|html?|.html|cfm|cgi|aspx?|ini|pl|py|md|css|cs|js|jsp|log|htaccess|htpasswd|gitignore|gitattributes|env|json|atom|eml|rss|markdown|sql|xml|xslt?|sh|rb|as|bat|cmd|cob|for|ftn|frm|frx|inc|lisp|scm|coffee|php[3-6]?|java|c|cbl|go|h|scala|vb|tmpl|lock|go|yml|yaml|tsv|lst)$/i,
60 | isImageFilePattern: /\.(jpe?g|gif|bmp|png|svg|tiff?)$/i,
61 | isExtractableFilePattern: /\.(gz|tar|rar|g?zip)$/i,
62 | tplPath: 'src/templates'
63 | };
64 |
65 | return {
66 | $get: function () {
67 | return values;
68 | },
69 | set: function (constants) {
70 | angular.extend(values, constants);
71 | },
72 | setBridge: function (path) {
73 |
74 | bridgePath = path;
75 | values.bridgePath = path;
76 | values.listUrl = path;
77 | values.uploadUrl = bridgePath;
78 | values.renameUrl = bridgePath;
79 | values.copyUrl = bridgePath;
80 | values.moveUrl = bridgePath;
81 | values.removeUrl = bridgePath;
82 | values.editUrl = bridgePath;
83 | values.getContentUrl = bridgePath;
84 | values.createFolderUrl = bridgePath;
85 | values.downloadFileUrl = bridgePath;
86 | values.downloadMultipleUrl = bridgePath;
87 | values.compressUrl = bridgePath;
88 | values.extractUrl = bridgePath;
89 | values.permissionsUrl = bridgePath;
90 |
91 | }
92 | };
93 |
94 | });
95 |
--------------------------------------------------------------------------------
/src/js/services/apihandler.js:
--------------------------------------------------------------------------------
1 | app.service('apiHandler', ['$http', '$q', '$window', '$translate', '$httpParamSerializer', 'Upload',
2 | function ($http, $q, $window, $translate, $httpParamSerializer, Upload) {
3 |
4 | $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
5 |
6 | var ApiHandler = function () {
7 | this.inprocess = false;
8 | this.asyncSuccess = false;
9 | this.error = '';
10 | };
11 |
12 | ApiHandler.prototype.deferredHandler = function (data, deferred, code, defaultMsg) {
13 | if (!data || typeof data !== 'object') {
14 | this.error = 'Error %s - Bridge response error, please check the API docs or this ajax response.'.replace('%s', code);
15 | }
16 | if (code == 404) {
17 | this.error = 'Error 404 - Backend bridge is not working, please check the ajax response.';
18 | }
19 | if (data.result && data.result.error) {
20 | this.error = data.result.error;
21 | }
22 | if (!this.error && data.error) {
23 | this.error = data.error.message;
24 | }
25 | if (!this.error && defaultMsg) {
26 | this.error = defaultMsg;
27 | }
28 | if (this.error) {
29 | return deferred.reject(data);
30 | }
31 | return deferred.resolve(data);
32 | };
33 |
34 | ApiHandler.prototype.list = function (apiUrl, path, customDeferredHandler, exts) {
35 | var self = this;
36 | var dfHandler = customDeferredHandler || self.deferredHandler;
37 | var deferred = $q.defer();
38 | var data = {
39 | action: 'list',
40 | path: path,
41 | fileExtensions: exts && exts.length ? exts : undefined
42 | };
43 |
44 | self.inprocess = true;
45 | self.error = '';
46 |
47 | $http.post(apiUrl, data).then(function (response) {
48 | dfHandler(response.data, deferred, response.status);
49 | }, function (response) {
50 | dfHandler(response.data, deferred, response.status, 'Unknown error listing, check the response');
51 | })['finally'](function () {
52 | self.inprocess = false;
53 | });
54 | return deferred.promise;
55 | };
56 |
57 | ApiHandler.prototype.copy = function (apiUrl, items, path, singleFilename) {
58 | var self = this;
59 | var deferred = $q.defer();
60 | var data = {
61 | action: 'copy',
62 | items: items,
63 | newPath: path
64 | };
65 |
66 | if (singleFilename && items.length === 1) {
67 | data.singleFilename = singleFilename;
68 | }
69 |
70 | self.inprocess = true;
71 | self.error = '';
72 | $http.post(apiUrl, data).then(function (response) {
73 | self.deferredHandler(response.data, deferred, response.status);
74 | }, function (response) {
75 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_copying'));
76 | })['finally'](function () {
77 | self.inprocess = false;
78 | });
79 | return deferred.promise;
80 | };
81 |
82 | ApiHandler.prototype.move = function (apiUrl, items, path) {
83 | var self = this;
84 | var deferred = $q.defer();
85 | var data = {
86 | action: 'move',
87 | items: items,
88 | newPath: path
89 | };
90 | self.inprocess = true;
91 | self.error = '';
92 | $http.post(apiUrl, data).then(function (response) {
93 | self.deferredHandler(response.data, deferred, response.status);
94 | }, function (response) {
95 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_moving'));
96 | })['finally'](function () {
97 | self.inprocess = false;
98 | });
99 | return deferred.promise;
100 | };
101 |
102 | ApiHandler.prototype.remove = function (apiUrl, items) {
103 | var self = this;
104 | var deferred = $q.defer();
105 | var data = {
106 | action: 'remove',
107 | items: items
108 | };
109 |
110 | self.inprocess = true;
111 | self.error = '';
112 | $http.post(apiUrl, data).then(function (response) {
113 | self.deferredHandler(response.data, deferred, response.status);
114 | }, function (response) {
115 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_deleting'));
116 | })['finally'](function () {
117 | self.inprocess = false;
118 | });
119 | return deferred.promise;
120 | };
121 |
122 | ApiHandler.prototype.upload = function (apiUrl, destination, files) {
123 | var self = this;
124 | var deferred = $q.defer();
125 | self.inprocess = true;
126 | self.progress = 0;
127 | self.error = '';
128 |
129 | var data = {
130 | destination: destination
131 | };
132 |
133 | for (var i = 0; i < files.length; i++) {
134 | data['file-' + i] = files[i];
135 | }
136 |
137 | if (files && files.length) {
138 | Upload.upload({
139 | url: apiUrl,
140 | data: data
141 | }).then(function (data) {
142 | self.deferredHandler(data.data, deferred, data.status);
143 | }, function (data) {
144 | self.deferredHandler(data.data, deferred, data.status, 'Unknown error uploading files');
145 | }, function (evt) {
146 | self.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total)) - 1;
147 | })['finally'](function () {
148 | self.inprocess = false;
149 | self.progress = 0;
150 | });
151 | }
152 |
153 | return deferred.promise;
154 | };
155 |
156 | ApiHandler.prototype.getContent = function (apiUrl, itemPath) {
157 | var self = this;
158 | var deferred = $q.defer();
159 | var data = {
160 | action: 'getContent',
161 | item: itemPath
162 | };
163 |
164 | self.inprocess = true;
165 | self.error = '';
166 | $http.post(apiUrl, data).then(function (response) {
167 | self.deferredHandler(response.data, deferred, response.status);
168 | }, function (response) {
169 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_getting_content'));
170 | })['finally'](function () {
171 | self.inprocess = false;
172 | });
173 | return deferred.promise;
174 | };
175 |
176 | ApiHandler.prototype.edit = function (apiUrl, itemPath, content) {
177 | var self = this;
178 | var deferred = $q.defer();
179 | var data = {
180 | action: 'edit',
181 | item: itemPath,
182 | content: content
183 | };
184 |
185 | self.inprocess = true;
186 | self.error = '';
187 |
188 | $http.post(apiUrl, data).then(function (response) {
189 | self.deferredHandler(response.data, deferred, response.status);
190 | }, function (response) {
191 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_modifying'));
192 | })['finally'](function () {
193 | self.inprocess = false;
194 | });
195 | return deferred.promise;
196 | };
197 |
198 | ApiHandler.prototype.rename = function (apiUrl, itemPath, newPath) {
199 | var self = this;
200 | var deferred = $q.defer();
201 | var data = {
202 | action: 'rename',
203 | item: itemPath,
204 | newItemPath: newPath
205 | };
206 | self.inprocess = true;
207 | self.error = '';
208 | $http.post(apiUrl, data).then(function (response) {
209 | self.deferredHandler(response.data, deferred, response.status);
210 | }, function (response) {
211 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_renaming'));
212 | })['finally'](function () {
213 | self.inprocess = false;
214 | });
215 | return deferred.promise;
216 | };
217 |
218 | ApiHandler.prototype.getUrl = function (apiUrl, path) {
219 | var data = {
220 | action: 'download',
221 | path: path
222 | };
223 | return path && [apiUrl, $httpParamSerializer(data)].join('?');
224 | };
225 |
226 | ApiHandler.prototype.download = function (apiUrl, itemPath, toFilename, downloadByAjax, forceNewWindow) {
227 | var self = this;
228 | var url = this.getUrl(apiUrl, itemPath);
229 |
230 | if (!downloadByAjax || forceNewWindow || !$window.saveAs) {
231 | !$window.saveAs && $window.console.log('Your browser dont support ajax download, downloading by default');
232 | return !!$window.open(url, '_blank', '');
233 | }
234 |
235 | var deferred = $q.defer();
236 | self.inprocess = true;
237 | $http.get(url).then(function (response) {
238 | var bin = new $window.Blob([response.data]);
239 | deferred.resolve(response.data);
240 | $window.saveAs(bin, toFilename);
241 | }, function (response) {
242 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_downloading'));
243 | })['finally'](function () {
244 | self.inprocess = false;
245 | });
246 | return deferred.promise;
247 | };
248 |
249 | ApiHandler.prototype.downloadMultiple = function (apiUrl, items, toFilename, downloadByAjax, forceNewWindow) {
250 | var self = this;
251 | var deferred = $q.defer();
252 | var data = {
253 | action: 'downloadMultiple',
254 | items: items,
255 | toFilename: toFilename
256 | };
257 | var url = [apiUrl, $httpParamSerializer(data)].join('?');
258 |
259 | if (!downloadByAjax || forceNewWindow || !$window.saveAs) {
260 | !$window.saveAs && $window.console.log('Your browser dont support ajax download, downloading by default');
261 | return !!$window.open(url, '_blank', '');
262 | }
263 |
264 | self.inprocess = true;
265 | $http.get(apiUrl).then(function (response) {
266 | var bin = new $window.Blob([response.data]);
267 | deferred.resolve(response.data);
268 | $window.saveAs(bin, toFilename);
269 | }, function (response) {
270 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_downloading'));
271 | })['finally'](function () {
272 | self.inprocess = false;
273 | });
274 | return deferred.promise;
275 | };
276 |
277 | ApiHandler.prototype.compress = function (apiUrl, items, compressedFilename, path) {
278 | var self = this;
279 | var deferred = $q.defer();
280 | var data = {
281 | action: 'compress',
282 | items: items,
283 | destination: path,
284 | compressedFilename: compressedFilename
285 | };
286 |
287 | self.inprocess = true;
288 | self.error = '';
289 | $http.post(apiUrl, data).then(function (response) {
290 | self.deferredHandler(response.data, deferred, response.status);
291 | }, function (response) {
292 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_compressing'));
293 | })['finally'](function () {
294 | self.inprocess = false;
295 | });
296 | return deferred.promise;
297 | };
298 |
299 | ApiHandler.prototype.extract = function (apiUrl, item, folderName, path) {
300 | var self = this;
301 | var deferred = $q.defer();
302 | var data = {
303 | action: 'extract',
304 | item: item,
305 | destination: path,
306 | folderName: folderName
307 | };
308 |
309 | self.inprocess = true;
310 | self.error = '';
311 | $http.post(apiUrl, data).then(function (response) {
312 | self.deferredHandler(response.data, deferred, response.status);
313 | }, function (response) {
314 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_extracting'));
315 | })['finally'](function () {
316 | self.inprocess = false;
317 | });
318 | return deferred.promise;
319 | };
320 |
321 | ApiHandler.prototype.changePermissions = function (apiUrl, items, permsOctal, permsCode, recursive) {
322 | var self = this;
323 | var deferred = $q.defer();
324 | var data = {
325 | action: 'changePermissions',
326 | items: items,
327 | perms: permsOctal,
328 | permsCode: permsCode,
329 | recursive: !!recursive
330 | };
331 |
332 | self.inprocess = true;
333 | self.error = '';
334 | $http.post(apiUrl, data).then(function (response) {
335 | self.deferredHandler(response.data, deferred, response.status);
336 | }, function (response) {
337 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_changing_perms'));
338 | })['finally'](function () {
339 | self.inprocess = false;
340 | });
341 | return deferred.promise;
342 | };
343 |
344 | ApiHandler.prototype.createFolder = function (apiUrl, path) {
345 | var self = this;
346 | var deferred = $q.defer();
347 | var data = {
348 | action: 'createFolder',
349 | newPath: path
350 | };
351 |
352 | self.inprocess = true;
353 | self.error = '';
354 | $http.post(apiUrl, data).then(function (response) {
355 | self.deferredHandler(response.data, deferred, response.status);
356 | }, function (response) {
357 | self.deferredHandler(response.data, deferred, response.status, $translate.instant('error_creating_folder'));
358 | })['finally'](function () {
359 | self.inprocess = false;
360 | });
361 |
362 | return deferred.promise;
363 | };
364 |
365 | return ApiHandler;
366 |
367 | }]);
368 |
--------------------------------------------------------------------------------
/src/js/services/apimiddleware.js:
--------------------------------------------------------------------------------
1 | app.service('apiMiddleware', ['$window', 'fileManagerConfig', 'apiHandler',
2 | function ($window, fileManagerConfig, ApiHandler) {
3 |
4 | var ApiMiddleware = function () {
5 | this.apiHandler = new ApiHandler();
6 | };
7 |
8 | ApiMiddleware.prototype.getPath = function (arrayPath) {
9 | return '/' + arrayPath.join('/');
10 | };
11 |
12 | ApiMiddleware.prototype.getFileList = function (files) {
13 | return (files || []).map(function (file) {
14 | return file && file.model.relativePath();
15 | });
16 | };
17 |
18 | ApiMiddleware.prototype.getFilePath = function (item) {
19 | return item && item.model.relativePath();
20 | };
21 |
22 | ApiMiddleware.prototype.list = function (path, customDeferredHandler) {
23 | return this.apiHandler.list(fileManagerConfig.listUrl, this.getPath(path), customDeferredHandler);
24 | };
25 |
26 | ApiMiddleware.prototype.copy = function (files, path) {
27 | var items = this.getFileList(files);
28 | var singleFilename = items.length === 1 ? files[0].tempModel.name : undefined;
29 | return this.apiHandler.copy(fileManagerConfig.copyUrl, items, this.getPath(path), singleFilename);
30 | };
31 |
32 | ApiMiddleware.prototype.move = function (files, path) {
33 | var items = this.getFileList(files);
34 | return this.apiHandler.move(fileManagerConfig.moveUrl, items, this.getPath(path));
35 | };
36 |
37 | ApiMiddleware.prototype.remove = function (files) {
38 | var items = this.getFileList(files);
39 | return this.apiHandler.remove(fileManagerConfig.removeUrl, items);
40 | };
41 |
42 | ApiMiddleware.prototype.upload = function (files, path) {
43 | if (!$window.FormData) {
44 | throw new Error('Unsupported browser version');
45 | }
46 |
47 | var destination = this.getPath(path);
48 |
49 | return this.apiHandler.upload(fileManagerConfig.uploadUrl, destination, files);
50 | };
51 |
52 | ApiMiddleware.prototype.getContent = function (item) {
53 | var itemPath = this.getFilePath(item);
54 | return this.apiHandler.getContent(fileManagerConfig.getContentUrl, itemPath);
55 | };
56 |
57 | ApiMiddleware.prototype.edit = function (item) {
58 | var itemPath = this.getFilePath(item);
59 | return this.apiHandler.edit(fileManagerConfig.editUrl, itemPath, item.tempModel.content);
60 | };
61 |
62 | ApiMiddleware.prototype.rename = function (item) {
63 | var itemPath = this.getFilePath(item);
64 | var newPath = item.tempModel.relativePath();
65 |
66 | return this.apiHandler.rename(fileManagerConfig.renameUrl, itemPath, newPath);
67 | };
68 |
69 | ApiMiddleware.prototype.getUrl = function (item) {
70 | var itemPath = this.getFilePath(item);
71 | return this.apiHandler.getUrl(fileManagerConfig.downloadFileUrl, itemPath);
72 | };
73 |
74 | ApiMiddleware.prototype.download = function (item, forceNewWindow) {
75 | //TODO: add spinner to indicate file is downloading
76 | var itemPath = this.getFilePath(item);
77 | var toFilename = item.model.name;
78 |
79 | if (item.isFolder()) {
80 | return;
81 | }
82 |
83 | return this.apiHandler.download(
84 | fileManagerConfig.downloadFileUrl,
85 | itemPath,
86 | toFilename,
87 | fileManagerConfig.downloadFilesByAjax,
88 | forceNewWindow
89 | );
90 | };
91 |
92 | ApiMiddleware.prototype.downloadMultiple = function (files, forceNewWindow) {
93 | var items = this.getFileList(files);
94 | var timestamp = new Date().getTime().toString().substr(8, 13);
95 | var toFilename = timestamp + '-' + fileManagerConfig.multipleDownloadFileName;
96 |
97 | return this.apiHandler.downloadMultiple(
98 | fileManagerConfig.downloadMultipleUrl,
99 | items,
100 | toFilename,
101 | fileManagerConfig.downloadFilesByAjax,
102 | forceNewWindow
103 | );
104 | };
105 |
106 | ApiMiddleware.prototype.compress = function (files, compressedFilename, path) {
107 | var items = this.getFileList(files);
108 | return this.apiHandler.compress(fileManagerConfig.compressUrl, items, compressedFilename, this.getPath(path));
109 | };
110 |
111 | ApiMiddleware.prototype.extract = function (item, folderName, path) {
112 | var itemPath = this.getFilePath(item);
113 | return this.apiHandler.extract(fileManagerConfig.extractUrl, itemPath, folderName, this.getPath(path));
114 | };
115 |
116 | ApiMiddleware.prototype.changePermissions = function (files, dataItem) {
117 | var items = this.getFileList(files);
118 | var code = dataItem.tempModel.perms.toCode();
119 | var octal = dataItem.tempModel.perms.toOctal();
120 | var recursive = !!dataItem.tempModel.recursive;
121 |
122 | return this.apiHandler.changePermissions(fileManagerConfig.permissionsUrl, items, code, octal, recursive);
123 | };
124 |
125 | ApiMiddleware.prototype.createFolder = function (item) {
126 | var path = item.tempModel.relativePath();
127 | return this.apiHandler.createFolder(fileManagerConfig.createFolderUrl, path);
128 | };
129 |
130 | return ApiMiddleware;
131 |
132 | }]);
--------------------------------------------------------------------------------
/src/js/services/filenavigator.js:
--------------------------------------------------------------------------------
1 | app.service('fileNavigator', [
2 | 'apiMiddleware', 'fileManagerConfig', 'item', function (ApiMiddleware, fileManagerConfig, Item) {
3 |
4 | var FileNavigator = function () {
5 | this.apiMiddleware = new ApiMiddleware();
6 | this.requesting = false;
7 | this.fileList = [];
8 | this.currentPath = this.getBasePath();
9 | this.history = [];
10 | this.error = '';
11 |
12 | this.onRefresh = function () {
13 | };
14 | };
15 |
16 | FileNavigator.prototype.getBasePath = function () {
17 | var path = (fileManagerConfig.basePath || '').replace(/^\//, '');
18 | return path.trim() ? path.split('/') : [];
19 | };
20 |
21 | FileNavigator.prototype.deferredHandler = function (data, deferred, code, defaultMsg) {
22 | if (!data || typeof data !== 'object') {
23 | this.error = 'Error %s - Bridge response error, please check the API docs or this ajax response.'.replace('%s', code);
24 | }
25 | if (code == 404) {
26 | this.error = 'Error 404 - Backend bridge is not working, please check the ajax response.';
27 | }
28 | if (code == 200) {
29 | this.error = null;
30 | }
31 | if (!this.error && data.result && data.result.error) {
32 | this.error = data.result.error;
33 | }
34 | if (!this.error && data.error) {
35 | this.error = data.error.message;
36 | }
37 | if (!this.error && defaultMsg) {
38 | this.error = defaultMsg;
39 | }
40 | if (this.error) {
41 | return deferred.reject(data);
42 | }
43 | return deferred.resolve(data);
44 | };
45 |
46 | FileNavigator.prototype.list = function () {
47 | return this.apiMiddleware.list(this.currentPath, this.deferredHandler.bind(this));
48 | };
49 |
50 | FileNavigator.prototype.refresh = function () {
51 | var self = this;
52 | if (!self.currentPath.length) {
53 | self.currentPath = this.getBasePath();
54 | }
55 | var path = self.currentPath.join('/');
56 | self.requesting = true;
57 | self.fileList = [];
58 | return self.list().then(function (data) {
59 | self.fileList = (data.result || []).map(function (file) {
60 | return new Item(file, self.currentPath);
61 | });
62 | self.buildTree(path);
63 | self.onRefresh();
64 | }).finally(function () {
65 | self.requesting = false;
66 | });
67 | };
68 |
69 | FileNavigator.prototype.buildTree = function (path) {
70 | var flatNodes = [], selectedNode = {};
71 |
72 | function recursive(parent, item, path) {
73 | var absName = path ? (path + '/' + item.model.name) : item.model.name;
74 | if (parent.name && parent.name.trim() && path.trim().indexOf(parent.name) !== 0) {
75 | parent.nodes = [];
76 | }
77 | if (parent.name !== path) {
78 | parent.nodes.forEach(function (nd) {
79 | recursive(nd, item, path);
80 | });
81 | } else {
82 | for (var e in parent.nodes) {
83 | if (parent.nodes[e].name === absName) {
84 | return;
85 | }
86 | }
87 | parent.nodes.push({item: item, name: absName, nodes: []});
88 | }
89 |
90 | parent.nodes = parent.nodes.sort(function (a, b) {
91 | return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() === b.name.toLowerCase() ? 0 : 1;
92 | });
93 | }
94 |
95 | function flatten(node, array) {
96 | array.push(node);
97 | for (var n in node.nodes) {
98 | flatten(node.nodes[n], array);
99 | }
100 | }
101 |
102 | function findNode(data, path) {
103 | return data.filter(function (n) {
104 | return n.name === path;
105 | })[0];
106 | }
107 |
108 | //!this.history.length && this.history.push({name: '', nodes: []});
109 | !this.history.length && this.history.push({name: this.getBasePath()[0] || '', nodes: []});
110 | flatten(this.history[0], flatNodes);
111 | selectedNode = findNode(flatNodes, path);
112 | selectedNode && (selectedNode.nodes = []);
113 |
114 | for (var o in this.fileList) {
115 | var item = this.fileList[o];
116 | item instanceof Item && item.isFolder() && recursive(this.history[0], item, path);
117 | }
118 | };
119 |
120 | FileNavigator.prototype.folderClick = function (item) {
121 | this.currentPath = [];
122 | if (item && item.isFolder()) {
123 | this.currentPath = item.model.relativePath().split('/').splice(1);
124 | }
125 | this.refresh();
126 | };
127 |
128 | FileNavigator.prototype.upDir = function () {
129 | if (this.currentPath[0]) {
130 | this.currentPath = this.currentPath.slice(0, -1);
131 | this.refresh();
132 | }
133 | };
134 |
135 | FileNavigator.prototype.goTo = function (index) {
136 | this.currentPath = this.currentPath.slice(0, index + 1);
137 | this.refresh();
138 | };
139 |
140 | FileNavigator.prototype.fileNameExists = function (fileName) {
141 | return this.fileList.find(function (item) {
142 | return fileName && item.model.name.trim() === fileName.trim();
143 | });
144 | };
145 |
146 | FileNavigator.prototype.listHasFolders = function () {
147 | return this.fileList.find(function (item) {
148 | return item.model.type === 'dir';
149 | });
150 | };
151 |
152 | FileNavigator.prototype.getCurrentFolderName = function () {
153 | return this.currentPath.slice(-1)[0] || '/';
154 | };
155 |
156 | return FileNavigator;
157 | }]);
158 |
--------------------------------------------------------------------------------
/src/svg/chevron-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
56 |
--------------------------------------------------------------------------------
/src/svg/ext/css.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/doc.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/html.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/jpg.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/js.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/pdf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/png.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/txt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/unknown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/ext/zip.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/folder.svg:
--------------------------------------------------------------------------------
1 |
2 |
68 |
--------------------------------------------------------------------------------
/src/svg/folder_closed.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/svg/folder_open.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/svg/grid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/language.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/src/svg/list.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/src/svg/more.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
44 |
--------------------------------------------------------------------------------
/src/svg/positive.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/svg/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/src/svg/upload.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
44 |
--------------------------------------------------------------------------------
/src/templates/current-folder-breadcrumb.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/item-context-menu.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/main-icons.html:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
22 |
23 |
24 | {{"no_files_in_folder" | translate}}...
25 |
26 |
27 |
28 | {{ fileNavigator.error }}
29 |
30 |
--------------------------------------------------------------------------------
/src/templates/main-table-modal.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/main-table.html:
--------------------------------------------------------------------------------
1 |
68 |
--------------------------------------------------------------------------------
/src/templates/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/templates/navbar.html:
--------------------------------------------------------------------------------
1 |
179 |
--------------------------------------------------------------------------------
/src/templates/sidebar.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/src/templates/spinner.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------