aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJohannes Zellner <johannes@cloudron.io>2017-02-07 16:26:17 +0100
committerJohannes Zellner <johannes@cloudron.io>2017-02-07 16:26:19 +0100
commite628921a338684a4bc3f196c5c39beba8b8f9b68 (patch)
treea4112c316e69b4d0a302af23f5bb8f4396c15c30
parent4b6cf0add4f4f89671f4553a9672a01fbb485df1 (diff)
downloadSurfer-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.js2
-rw-r--r--frontend/index.html35
-rw-r--r--frontend/js/app.js40
-rwxr-xr-xserver.js3
-rw-r--r--src/files.js33
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">&times;</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">&times;</span></button> 82 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</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
199function 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
207function 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
201function createDirectoryAsk() { 226function 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 }
diff --git a/server.js b/server.js
index edd8a75..67cbd91 100755
--- a/server.js
+++ b/server.js
@@ -24,7 +24,8 @@ var router = new express.Router();
24var multipart = multipart({ maxFieldsSize: 2 * 1024, limit: '512mb', timeout: 3 * 60 * 1000 }); 24var multipart = multipart({ maxFieldsSize: 2 * 1024, limit: '512mb', timeout: 3 * 60 * 1000 });
25 25
26router.get ('/api/files/*', auth.verify, files.get); 26router.get ('/api/files/*', auth.verify, files.get);
27router.put ('/api/files/*', auth.verify, multipart, files.put); 27router.post ('/api/files/*', auth.verify, multipart, files.post);
28router.put ('/api/files/*', auth.verify, files.put);
28router.delete('/api/files/*', auth.verify, files.del); 29router.delete('/api/files/*', auth.verify, files.del);
29router.get ('/api/healthcheck', function (req, res) { res.status(200).send(); }); 30router.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
114function put(req, res, next) { 115function 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
148function 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
147function del(req, res, next) { 172function 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;