diff options
author | Johannes Zellner <johannes@cloudron.io> | 2017-02-07 16:26:17 +0100 |
---|---|---|
committer | Johannes Zellner <johannes@cloudron.io> | 2017-02-07 16:26:19 +0100 |
commit | e628921a338684a4bc3f196c5c39beba8b8f9b68 (patch) | |
tree | a4112c316e69b4d0a302af23f5bb8f4396c15c30 | |
parent | 4b6cf0add4f4f89671f4553a9672a01fbb485df1 (diff) | |
download | Surfer-e628921a338684a4bc3f196c5c39beba8b8f9b68.tar.gz Surfer-e628921a338684a4bc3f196c5c39beba8b8f9b68.tar.zst Surfer-e628921a338684a4bc3f196c5c39beba8b8f9b68.zip |
Add rename functionality
This also break backwardscompat since PUT is now POST
and PUT is used for renaming
-rw-r--r-- | cli/actions.js | 2 | ||||
-rw-r--r-- | frontend/index.html | 35 | ||||
-rw-r--r-- | frontend/js/app.js | 40 | ||||
-rwxr-xr-x | server.js | 3 | ||||
-rw-r--r-- | src/files.js | 33 |
5 files changed, 99 insertions, 14 deletions
diff --git a/cli/actions.js b/cli/actions.js index 69ffa10..ea2a3a1 100644 --- a/cli/actions.js +++ b/cli/actions.js | |||
@@ -118,7 +118,7 @@ function put(filePath, otherFilePaths, options) { | |||
118 | var destinationPath = (destination ? '/' + destination : '') + '/' + relativeFilePath; | 118 | var destinationPath = (destination ? '/' + destination : '') + '/' + relativeFilePath; |
119 | console.log('Uploading file %s -> %s', relativeFilePath.cyan, destinationPath.cyan); | 119 | console.log('Uploading file %s -> %s', relativeFilePath.cyan, destinationPath.cyan); |
120 | 120 | ||
121 | superagent.put(config.server() + API + destinationPath).query(gQuery).attach('file', file).end(function (error, result) { | 121 | superagent.post(config.server() + API + destinationPath).query(gQuery).attach('file', file).end(function (error, result) { |
122 | if (result && result.statusCode === 403) return callback(new Error('Upload destination ' + destinationPath + ' not allowed')); | 122 | if (result && result.statusCode === 403) return callback(new Error('Upload destination ' + destinationPath + ' not allowed')); |
123 | if (result && result.statusCode !== 201) return callback(new Error('Error uploading file: ' + result.statusCode)); | 123 | if (result && result.statusCode !== 201) return callback(new Error('Error uploading file: ' + result.statusCode)); |
124 | if (error) return callback(error); | 124 | if (error) return callback(error); |
diff --git a/frontend/index.html b/frontend/index.html index e9775e8..cbbd85e 100644 --- a/frontend/index.html +++ b/frontend/index.html | |||
@@ -41,7 +41,7 @@ | |||
41 | </div> | 41 | </div> |
42 | <div class="modal-body"> | 42 | <div class="modal-body"> |
43 | <h5 v-show="deleteData.isFile">Really delete <span style="font-weight: bold;">{{ deleteData.filePath }}</span>?</h5> | 43 | <h5 v-show="deleteData.isFile">Really delete <span style="font-weight: bold;">{{ deleteData.filePath }}</span>?</h5> |
44 | <h5 v-show="deleteData.isDirectory">Really delete directory <span style="font-weight: bold;">{{ deleteData.filePath }}</span> and all its content?</h5> | 44 | <h5 v-show="deleteData.isDirectory">Really delete folder <span style="font-weight: bold;">{{ deleteData.filePath }}</span> and all its content?</h5> |
45 | </div> | 45 | </div> |
46 | <div class="modal-footer"> | 46 | <div class="modal-footer"> |
47 | <button type="button" class="btn btn-default" data-dismiss="modal">No</button> | 47 | <button type="button" class="btn btn-default" data-dismiss="modal">No</button> |
@@ -51,12 +51,36 @@ | |||
51 | </div> | 51 | </div> |
52 | </div> | 52 | </div> |
53 | 53 | ||
54 | <div class="modal fade" tabindex="-1" role="dialog" id="modalRename" v-cloak> | ||
55 | <div class="modal-dialog"> | ||
56 | <div class="modal-content"> | ||
57 | <div class="modal-header"> | ||
58 | <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> | ||
59 | <h4 class="modal-title">Rename {{ renameData.entry.filePath }}</h4> | ||
60 | </div> | ||
61 | <div class="modal-body"> | ||
62 | <form v-on:submit.prevent="rename(renameData)"> | ||
63 | <div class="form-group" v-bind:class="{ 'has-error': renameData.error }"> | ||
64 | <input type="text" class="form-control" v-model="renameData.newFilePath" placeholder="Name" autofocus="true"> | ||
65 | <label class="control-label">{{ renameData.error }}</label> | ||
66 | </div> | ||
67 | <button type="submit" style="display: none;"></button> | ||
68 | </form> | ||
69 | </div> | ||
70 | <div class="modal-footer"> | ||
71 | <button type="button" class="btn btn-default" data-dismiss="modal">No</button> | ||
72 | <button type="button" class="btn btn-success" v-on:click="rename(renameData)">Yes</button> | ||
73 | </div> | ||
74 | </div> | ||
75 | </div> | ||
76 | </div> | ||
77 | |||
54 | <div class="modal fade" tabindex="-1" role="dialog" id="modalcreateDirectory" v-cloak> | 78 | <div class="modal fade" tabindex="-1" role="dialog" id="modalcreateDirectory" v-cloak> |
55 | <div class="modal-dialog"> | 79 | <div class="modal-dialog"> |
56 | <div class="modal-content"> | 80 | <div class="modal-content"> |
57 | <div class="modal-header"> | 81 | <div class="modal-header"> |
58 | <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> | 82 | <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> |
59 | <h4 class="modal-title">New Directory Name</h4> | 83 | <h4 class="modal-title">New directory name</h4> |
60 | </div> | 84 | </div> |
61 | <div class="modal-body"> | 85 | <div class="modal-body"> |
62 | <form v-on:submit.prevent="createDirectory(createDirectoryData)"> | 86 | <form v-on:submit.prevent="createDirectory(createDirectoryData)"> |
@@ -135,7 +159,7 @@ | |||
135 | <a href="{{ part.link }}">{{ part.name }}</a> | 159 | <a href="{{ part.link }}">{{ part.name }}</a> |
136 | </li> | 160 | </li> |
137 | <li>{{ pathParts.slice(-1)[0].name }}</li> | 161 | <li>{{ pathParts.slice(-1)[0].name }}</li> |
138 | <button class="btn btn-default btn-sm pull-right" v-on:click="createDirectoryAsk()">Create Directory</button> | 162 | <button class="btn btn-default btn-sm pull-right" v-on:click="createDirectoryAsk()">Create Folder</button> |
139 | </ol> | 163 | </ol> |
140 | </div> | 164 | </div> |
141 | <div class="col-lg-12"> | 165 | <div class="col-lg-12"> |
@@ -150,6 +174,9 @@ | |||
150 | </tr> | 174 | </tr> |
151 | </thead> | 175 | </thead> |
152 | <tbody> | 176 | <tbody> |
177 | <tr v-show="entries.length === 0"> | ||
178 | <th><i>Empty folder</i></th> | ||
179 | </tr> | ||
153 | <tr v-for="entry in entries" v-on:click="open(entry)" class="hand"> | 180 | <tr v-for="entry in entries" v-on:click="open(entry)" class="hand"> |
154 | <th> | 181 | <th> |
155 | <img v-bind:src="entry.previewUrl" height="48px" width="48px"/> | 182 | <img v-bind:src="entry.previewUrl" height="48px" width="48px"/> |
@@ -159,7 +186,7 @@ | |||
159 | <th><span v-my-tooltip="foobar" data-toggle="tooltip" title="{{ entry.mtime }}">{{ entry.mtime | prettyDate }}</span></th> | 186 | <th><span v-my-tooltip="foobar" data-toggle="tooltip" title="{{ entry.mtime }}">{{ entry.mtime | prettyDate }}</span></th> |
160 | <th style="text-align: right;"> | 187 | <th style="text-align: right;"> |
161 | <span class="entry-toolbar"> | 188 | <span class="entry-toolbar"> |
162 | <!-- <button class="btn btn-sm btn-default" v-on:click.stop="renameAsk(entry)" title="Rename"><i class="fa fa-pencil"></i></button> --> | 189 | <button class="btn btn-sm btn-default" v-on:click.stop="renameAsk(entry)" title="Rename"><i class="fa fa-pencil"></i></button> |
163 | <button class="btn btn-sm btn-danger" v-on:click.stop="delAsk(entry)" title="Delete"><i class="fa fa-trash"></i></button> | 190 | <button class="btn btn-sm btn-danger" v-on:click.stop="delAsk(entry)" title="Delete"><i class="fa fa-trash"></i></button> |
164 | </span> | 191 | </span> |
165 | </th> | 192 | </th> |
diff --git a/frontend/js/app.js b/frontend/js/app.js index e0464f8..078473a 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js | |||
@@ -141,7 +141,7 @@ function upload() { | |||
141 | var formData = new FormData(); | 141 | var formData = new FormData(); |
142 | formData.append('file', file); | 142 | formData.append('file', file); |
143 | 143 | ||
144 | superagent.put('/api/files' + path).query({ username: app.session.username, password: app.session.password }).send(formData).end(function (error, result) { | 144 | superagent.post('/api/files' + path).query({ username: app.session.username, password: app.session.password }).send(formData).end(function (error, result) { |
145 | if (result && result.statusCode === 401) return logout(); | 145 | if (result && result.statusCode === 401) return logout(); |
146 | if (result && result.statusCode !== 201) console.error('Error uploading file: ', result.statusCode); | 146 | if (result && result.statusCode !== 201) console.error('Error uploading file: ', result.statusCode); |
147 | if (error) console.error(error); | 147 | if (error) console.error(error); |
@@ -149,8 +149,6 @@ function upload() { | |||
149 | app.uploadStatus.done += 1; | 149 | app.uploadStatus.done += 1; |
150 | app.uploadStatus.percentDone = Math.round(app.uploadStatus.done / app.uploadStatus.count * 100); | 150 | app.uploadStatus.percentDone = Math.round(app.uploadStatus.done / app.uploadStatus.count * 100); |
151 | 151 | ||
152 | console.log(Math.round(app.uploadStatus.done / app.uploadStatus.count * 100)) | ||
153 | |||
154 | if (app.uploadStatus.done >= app.uploadStatus.count) { | 152 | if (app.uploadStatus.done >= app.uploadStatus.count) { |
155 | app.uploadStatus = { | 153 | app.uploadStatus = { |
156 | busy: false, | 154 | busy: false, |
@@ -198,6 +196,33 @@ function del(entry) { | |||
198 | }); | 196 | }); |
199 | } | 197 | } |
200 | 198 | ||
199 | function renameAsk(entry) { | ||
200 | app.renameData.entry = entry; | ||
201 | app.renameData.error = null; | ||
202 | app.renameData.newFilePath = entry.filePath; | ||
203 | |||
204 | $('#modalRename').modal('show'); | ||
205 | } | ||
206 | |||
207 | function rename(data) { | ||
208 | app.busy = true; | ||
209 | |||
210 | var path = encode(sanitize(app.path + '/' + data.entry.filePath)); | ||
211 | var newFilePath = sanitize(app.path + '/' + data.newFilePath); | ||
212 | |||
213 | superagent.put('/api/files' + path).query({ username: app.session.username, password: app.session.password }).send({ newFilePath: newFilePath }).end(function (error, result) { | ||
214 | app.busy = false; | ||
215 | |||
216 | if (result && result.statusCode === 401) return logout(); | ||
217 | if (result && result.statusCode !== 200) return console.error('Error renaming file: ', result.statusCode); | ||
218 | if (error) return console.error(error); | ||
219 | |||
220 | refresh(); | ||
221 | |||
222 | $('#modalRename').modal('hide'); | ||
223 | }); | ||
224 | } | ||
225 | |||
201 | function createDirectoryAsk() { | 226 | function createDirectoryAsk() { |
202 | $('#modalcreateDirectory').modal('show'); | 227 | $('#modalcreateDirectory').modal('show'); |
203 | app.createDirectoryData = ''; | 228 | app.createDirectoryData = ''; |
@@ -210,7 +235,7 @@ function createDirectory(name) { | |||
210 | 235 | ||
211 | var path = encode(sanitize(app.path + '/' + name)); | 236 | var path = encode(sanitize(app.path + '/' + name)); |
212 | 237 | ||
213 | superagent.put('/api/files' + path).query({ username: app.session.username, password: app.session.password, directory: true }).end(function (error, result) { | 238 | superagent.post('/api/files' + path).query({ username: app.session.username, password: app.session.password, directory: true }).end(function (error, result) { |
214 | app.busy = false; | 239 | app.busy = false; |
215 | 240 | ||
216 | if (result && result.statusCode === 401) return logout(); | 241 | if (result && result.statusCode === 401) return logout(); |
@@ -258,6 +283,11 @@ var app = new Vue({ | |||
258 | }, | 283 | }, |
259 | loginData: {}, | 284 | loginData: {}, |
260 | deleteData: {}, | 285 | deleteData: {}, |
286 | renameData: { | ||
287 | entry: {}, | ||
288 | error: null, | ||
289 | newFilePath: '' | ||
290 | }, | ||
261 | createDirectoryData: '', | 291 | createDirectoryData: '', |
262 | createDirectoryError: null, | 292 | createDirectoryError: null, |
263 | entries: [] | 293 | entries: [] |
@@ -271,6 +301,8 @@ var app = new Vue({ | |||
271 | upload: upload, | 301 | upload: upload, |
272 | delAsk: delAsk, | 302 | delAsk: delAsk, |
273 | del: del, | 303 | del: del, |
304 | renameAsk: renameAsk, | ||
305 | rename: rename, | ||
274 | createDirectoryAsk: createDirectoryAsk, | 306 | createDirectoryAsk: createDirectoryAsk, |
275 | createDirectory: createDirectory | 307 | createDirectory: createDirectory |
276 | } | 308 | } |
@@ -24,7 +24,8 @@ var router = new express.Router(); | |||
24 | var multipart = multipart({ maxFieldsSize: 2 * 1024, limit: '512mb', timeout: 3 * 60 * 1000 }); | 24 | var multipart = multipart({ maxFieldsSize: 2 * 1024, limit: '512mb', timeout: 3 * 60 * 1000 }); |
25 | 25 | ||
26 | router.get ('/api/files/*', auth.verify, files.get); | 26 | router.get ('/api/files/*', auth.verify, files.get); |
27 | router.put ('/api/files/*', auth.verify, multipart, files.put); | 27 | router.post ('/api/files/*', auth.verify, multipart, files.post); |
28 | router.put ('/api/files/*', auth.verify, files.put); | ||
28 | router.delete('/api/files/*', auth.verify, files.del); | 29 | router.delete('/api/files/*', auth.verify, files.del); |
29 | router.get ('/api/healthcheck', function (req, res) { res.status(200).send(); }); | 30 | router.get ('/api/healthcheck', function (req, res) { res.status(200).send(); }); |
30 | 31 | ||
diff --git a/src/files.js b/src/files.js index 747acf7..876ff4e 100644 --- a/src/files.js +++ b/src/files.js | |||
@@ -17,6 +17,7 @@ exports = module.exports = function (basePath) { | |||
17 | return { | 17 | return { |
18 | get: get, | 18 | get: get, |
19 | put: put, | 19 | put: put, |
20 | post: post, | ||
20 | del: del | 21 | del: del |
21 | }; | 22 | }; |
22 | }; | 23 | }; |
@@ -111,22 +112,22 @@ function get(req, res, next) { | |||
111 | }); | 112 | }); |
112 | } | 113 | } |
113 | 114 | ||
114 | function put(req, res, next) { | 115 | function post(req, res, next) { |
115 | var filePath = decodeURIComponent(req.params[0]); | 116 | var filePath = decodeURIComponent(req.params[0]); |
116 | 117 | ||
117 | if (!(req.files && req.files.file) && !req.query.directory) return next(new HttpError(400, 'missing file or directory')); | 118 | if (!(req.files && req.files.file) && !req.query.directory) return next(new HttpError(400, 'missing file or directory')); |
118 | if ((req.files && req.files.file) && req.query.directory) return next(new HttpError(400, 'either file or directory')); | 119 | if ((req.files && req.files.file) && req.query.directory) return next(new HttpError(400, 'either file or directory')); |
119 | 120 | ||
121 | debug('post:', filePath); | ||
122 | |||
120 | var absoluteFilePath = getAbsolutePath(filePath); | 123 | var absoluteFilePath = getAbsolutePath(filePath); |
121 | if (!absoluteFilePath || isProtected(absoluteFilePath)) return next(new HttpError(403, 'Path not allowed')); | 124 | if (!absoluteFilePath || isProtected(absoluteFilePath)) return next(new HttpError(403, 'Path not allowed')); |
122 | 125 | ||
123 | fs.stat(absoluteFilePath, function (error, result) { | 126 | fs.stat(absoluteFilePath, function (error, result) { |
124 | if (error && error.code !== 'ENOENT') return next(new HttpError(500, error)); | 127 | if (error && error.code !== 'ENOENT') return next(new HttpError(500, error)); |
125 | 128 | ||
126 | debug('put', absoluteFilePath); | ||
127 | |||
128 | if (result && req.query.directory) return next(new HttpError(409, 'name already exists')); | 129 | if (result && req.query.directory) return next(new HttpError(409, 'name already exists')); |
129 | if (result && result.isDirectory()) return next(new HttpError(409, 'cannot put on directories')); | 130 | if (result && result.isDirectory()) return next(new HttpError(409, 'cannot post on directories')); |
130 | 131 | ||
131 | if (req.query.directory) { | 132 | if (req.query.directory) { |
132 | return createDirectory(absoluteFilePath, function (error) { | 133 | return createDirectory(absoluteFilePath, function (error) { |
@@ -144,6 +145,30 @@ function put(req, res, next) { | |||
144 | }); | 145 | }); |
145 | } | 146 | } |
146 | 147 | ||
148 | function put(req, res, next) { | ||
149 | var oldFilePath = decodeURIComponent(req.params[0]); | ||
150 | |||
151 | if (!req.body || !req.body.newFilePath) return next(new HttpError(400, 'missing newFilePath')); | ||
152 | |||
153 | var newFilePath = decodeURIComponent(req.body.newFilePath); | ||
154 | |||
155 | debug('put: %s -> %s', oldFilePath, newFilePath); | ||
156 | |||
157 | var absoluteOldFilePath = getAbsolutePath(oldFilePath); | ||
158 | if (!absoluteOldFilePath || isProtected(absoluteOldFilePath)) return next(new HttpError(403, 'Path not allowed')); | ||
159 | |||
160 | var absoluteNewFilePath = getAbsolutePath(newFilePath); | ||
161 | if (!absoluteNewFilePath || isProtected(absoluteNewFilePath)) return next(new HttpError(403, 'Path not allowed')); | ||
162 | |||
163 | fs.rename(absoluteOldFilePath, absoluteNewFilePath, function (error) { | ||
164 | if (error) return next (new HttpError(500, error)); | ||
165 | |||
166 | debug('put: successful'); | ||
167 | |||
168 | return next(new HttpSuccess(200, {})); | ||
169 | }); | ||
170 | } | ||
171 | |||
147 | function del(req, res, next) { | 172 | function del(req, res, next) { |
148 | var filePath = decodeURIComponent(req.params[0]); | 173 | var filePath = decodeURIComponent(req.params[0]); |
149 | var recursive = !!req.query.recursive; | 174 | var recursive = !!req.query.recursive; |