More updates to file manager

Not doing individual commits for this, tons of changes for tons of
different aspects across multiple files.
This commit is contained in:
Dane Everitt 2016-10-01 23:09:55 -04:00
parent fe9c573533
commit fb4d122a2a
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
12 changed files with 308 additions and 164 deletions

View file

@ -8,6 +8,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
### Added
* Support for creating server without having to assign a node and allocation manually. Simply select the checkbox or pass `auto_deploy=true` to the API to auto-select a node and allocation given a location.
* Support for setting IP Aliases through the panel on the node overview page. Also cleaned up allocation removal.
* Support for renaming files through the panel's file mananger.
### Changed
* Prevent clicking server start button until server is completely off, not just stopping.
@ -15,6 +16,10 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
* Trying to add a new node if no location exists redirects user to location management page and alerts them to add a location first.
* `Server\AjaxController@postSetConnection` is now `Server\AjaxController@postSetPrimary` and accepts one post parameter of `allocation` rather than a combined `ip:port` value.
* Port allocations on server view are now cleaner and should make more sense.
* Improved File Manager
* Rewritten Javascript to load, rename, and handle other file actions.
* Uses Ace Editor for editing files rather than a non-formatted textarea
* File actions that were previously icons to the right are now contained in a menu that appears when right-clicking a file or folder.
### Fixed
* Team Fortress named 'Insurgency' in panel in database seeder. ([#96](https://github.com/Pterodactyl/Panel/issues/96), PR by [@MeltedLux](https://github.com/MeltedLux))

View file

@ -1,5 +1,5 @@
## Pterodactyl Panel
Pterodactyl is the free game server management panel designed by users, for users. Featuring support for Vanilla Minecraft, Spigot, Source Dedicated Servers, BungeeCord, and many more. Pterodactyl is built on the `Laravel PHP Framework (v5.2)`.
Pterodactyl is the free game server management panel designed by users, for users. Featuring support for Vanilla Minecraft, Spigot, Source Dedicated Servers, BungeeCord, and many more. Pterodactyl is built on the `Laravel PHP Framework (v5.3)`.
## Support & Documentation
Support for using Pterodactyl can be found on our [wiki](https://github.com/Pterodactyl/Panel/wiki) or on our [Discord chat](https://discord.gg/0gYt8oU8QOkDhKLS).
@ -28,6 +28,8 @@ SOFTWARE.
```
### Credits
Ace Editor - [license](https://github.com/ajaxorg/ace/blob/master/LICENSE) - [homepage](https://ace.c9.io)
Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICENSE) - [homepage](http://daneden.github.io/animate.css/)
Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/)
@ -48,6 +50,8 @@ jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [
jQuery Terminal - [license](https://github.com/jcubic/jquery.terminal/blob/master/LICENSE) - [homepage](http://terminal.jcubic.pl)
Lodash - [license](https://github.com/lodash/lodash/blob/master/LICENSE) - [homepage](https://lodash.com/)
MetricsGraphics.js - [license](https://github.com/mozilla/metrics-graphics/blob/master/LICENSE) - [homepage](http://metricsgraphicsjs.org/)
Socket.io - [license](https://github.com/socketio/socket.io/blob/master/LICENSE) - [homepage](http://socket.io)

View file

@ -137,7 +137,7 @@ class AjaxController extends Controller
'server' => $server,
'files' => $directoryContents->files,
'folders' => $directoryContents->folders,
'extensions' => Repositories\HelperRepository::editableFiles(),
'editableMime' => Repositories\HelperRepository::editableFiles(),
'directory' => $prevDir
]);

View file

@ -145,9 +145,9 @@ class ServerController extends Controller
'server' => $server,
'node' => Models\Node::find($server->node),
'file' => $file,
'contents' => $fileContent->content,
'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/',
'extension' => $fileInfo->extension
'stat' => $fileContent['stat'],
'contents' => $fileContent['file']->content,
'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/'
]);
}

View file

@ -85,7 +85,7 @@ class FileRepository
* Get the contents of a requested file for the server.
*
* @param string $file
* @return string
* @return array
*/
public function returnFileContents($file)
{
@ -95,22 +95,39 @@ class FileRepository
}
$file = (object) pathinfo($file);
if (!in_array($file->extension, HelperRepository::editableFiles())) {
throw new DisplayException('You do not have permission to edit this type of file.');
}
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
$res = $this->client->request('GET', '/server/file/' . rawurlencode($file->dirname.$file->basename), [
$res = $this->client->request('GET', '/server/files/stat/' . rawurlencode($file->dirname.$file->basename) , [
'headers' => $this->headers
]);
$stat = json_decode($res->getBody());
if($res->getStatusCode() !== 200 || !isset($stat->size)) {
throw new DisplayException('The daemon provided a non-200 error code on stat lookup: HTTP\\' . $res->getStatusCode());
}
if (!in_array($stat->mime, HelperRepository::editableFiles())) {
throw new DisplayException('You cannot edit that type of file (' . $stat->mime . ') through the panel.');
}
if ($stat->size > 5000000) {
throw new DisplayException('That file is too large to open in the browser, consider using a SFTP client.');
}
$res = $this->client->request('GET', '/server/file/' . rawurlencode($file->dirname.$file->basename) , [
'headers' => $this->headers
]);
$json = json_decode($res->getBody());
if($res->getStatusCode() !== 200 || !isset($json->content)) {
throw new DisplayException('Scales provided a non-200 error code: HTTP\\' . $res->getStatusCode());
throw new DisplayException('The daemon provided a non-200 error code: HTTP\\' . $res->getStatusCode());
}
return $json;
return [
'file' => $json,
'stat' => $stat
];
}
@ -130,11 +147,24 @@ class FileRepository
$file = (object) pathinfo($file);
if(!in_array($file->extension, HelperRepository::editableFiles())) {
throw new DisplayException('You do not have permission to edit this type of file.');
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
$res = $this->client->request('GET', '/server/files/stat/' . rawurlencode($file->dirname.$file->basename) , [
'headers' => $this->headers
]);
$stat = json_decode($res->getBody());
if($res->getStatusCode() !== 200 || !isset($stat->size)) {
throw new DisplayException('The daemon provided a non-200 error code on stat lookup: HTTP\\' . $res->getStatusCode());
}
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
if (!in_array($stat->mime, HelperRepository::editableFiles())) {
throw new DisplayException('You cannot edit that type of file (' . $stat->mime . ') through the panel.');
}
if ($stat->size > 5000000) {
throw new DisplayException('That file is too large to save in the browser, consider using a SFTP client.');
}
$res = $this->client->request('POST', '/server/file/' . rawurlencode($file->dirname.$file->basename), [
'headers' => $this->headers,

View file

@ -30,25 +30,20 @@ class HelperRepository {
* @var array
*/
protected static $editable = [
'txt',
'yml',
'yaml',
'log',
'conf',
'config',
'html',
'json',
'properties',
'props',
'cfg',
'lang',
'ini',
'cmd',
'sh',
'lua',
'0' // Supports BungeeCord Files
'application/json',
'application/javascript',
'application/xml',
'application/xhtml+xml',
'text/xml',
'text/css',
'text/html',
'text/plain',
'text/x-perl',
'text/x-shellscript',
'inode/x-empty'
];
public function __construct()
{
//

View file

@ -197,3 +197,41 @@ li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default.
.use-pointer {
cursor: pointer !important;
}
.dropdown-menu > li.bg-danger {
background-color:#fdf7f7;
color:#474a54;
border-left: 4px solid #d9534f !important;
}
.dropdown-menu > li.bg-info {
background-color:#fcf8f2;
color:#474a54;
border-left: 4px solid #f0ad4e !important;
}
.dropdown-menu > li.bg-success {
background-color:#f4f8fa;
color:#474a54;
border-left: 4px solid #5bc0de !important;
}
.dropdown-menu > li.bg-warning {
background-color:#fdf7f7;
color:#474a54;
border-left: 4px solid #d9534f !important;
}
.dropdown-menu > li.bg-default {
border-left: 4px solid #bbbbbb !important;
}
.dropdown-menu > li.bg-danger > a,
.dropdown-menu > li.bg-info > a,
.dropdown-menu > li.bg-success > a,
.dropdown-menu > li.bg-warning > a,
.dropdown-menu > li.bg-default > a {
padding-left: 11px !important;
}
/*.bg-danger:active,.bg-danger:focus,.bg-danger:hover{color:#fff;background-color:#d32a0e;border-color:#b1240c}
.bg-danger.disabled,.bg-danger.disabled:active,.bg-danger.disabled:focus,.bg-danger.disabled:hover,.bg-danger[disabled]{background-color:#f04124;border-color:#ea2f10}*/

View file

@ -26,65 +26,52 @@
@section('content')
<div class="col-md-12">
<h3 class="nopad"><small>Editing File: /home/container/{{ $file }}</small></h3>
<form method="post" id="editing_file">
<div class="form-group">
<div>
@if (in_array($extension, ['yaml', 'yml']))
<div class="alert alert-info">
{!! trans('server.files.yaml_notice', [
'dropdown' => '<select id="space_yaml">
<option value="2">2</option>
<option value="4" selected="selected">4</option>
<option value="8">8</option>
</select>'
]) !!}
</div>
@endif
<textarea name="file_contents" id="fileContent" style="border: 1px solid #dddddd;height:500px;" class="form-control console">{{ $contents }}</textarea>
<div class="row">
<div class="col-md-12">
<div id="editor" style="height:500px;">{{ $contents }}</div>
</div>
</div>
@can('save-files', $server)
<div class="row">
<div class="col-md-12">
<hr />
<input type="hidden" name="file" value="{{ $file }}" />
<button class="btn btn-primary btn-sm" id="save_file" type="submit">{{ trans('strings.save') }}</button>
<a href="/server/{{ $server->uuidShort }}/files?dir={{ rawurlencode($directory) }}" class="text-muted pull-right"><small>{{ trans('server.files.back') }}</small></a>
</div>
</div>
@can('save-files', $server)
<div class="form-group">
<div>
<input type="hidden" name="file" value="{{ $file }}" />
{!! csrf_field() !!}
<button class="btn btn-primary btn-sm" id="save_file" type="submit">{{ trans('strings.save') }}</button>
<a href="/server/{{ $server->uuidShort }}/files?dir={{ rawurlencode($directory) }}" class="text-muted pull-right"><small>{{ trans('server.files.back') }}</small></a>
</div>
</div>
@endcan
</form>
@endcan
</div>
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script>
$(document).ready(function () {
$('.server-files').addClass('active');
$('textarea').keydown(function (e) {
if (e.keyCode === 9) {
const Editor = ace.edit('editor');
const Modelist = ace.require('ace/ext/modelist')
var start = this.selectionStart;
var end = this.selectionEnd;
var value = $(this).val();
var joinYML = '\t';
var yamlSpaces = 1;
Editor.setTheme('ace/theme/github');
Editor.getSession().setMode(Modelist.getModeForPath('{{ $stat->name }}').mode);
Editor.getSession().setUseWrapMode(true);
Editor.setShowPrintMargin(false);
@if (in_array($extension, ['yaml', 'yml']))
yamlSpaces = parseInt($("#space_yaml").val());
joinYML = Array(yamlSpaces + 1).join(" ");
@endif
$(this).val(value.substring(0, start) + joinYML + value.substring(end));
this.selectionStart = this.selectionEnd = start + yamlSpaces;
e.preventDefault();
}
});
@can('save-files', $server)
$('#save_file').click(function (e) {
Editor.commands.addCommand({
name: 'save',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
save();
},
readOnly: false
});
$('#save_file').on('click', function (e) {
e.preventDefault();
save();
});
function save() {
var fileName = $('input[name="file"]').val();
var fileContents = $('#fileContent').val();
$('#save_file').append(' <i class="fa fa-spinner fa fa-spin"></i>').addClass('disabled');
$.ajax({
type: 'POST',
@ -92,17 +79,24 @@ $(document).ready(function () {
headers: { 'X-CSRF-Token': '{{ csrf_token() }}' },
data: {
file: fileName,
contents: fileContents
contents: Editor.getValue()
}
}).done(function (data) {
$('#tpl_messages').html('<div class="alert alert-success">{{ trans('server.files.saved') }}</div>').show().delay(3000).slideUp();
$.notify({
message: '{{ trans('server.files.saved') }}'
}, {
type: 'success'
});
}).fail(function (jqXHR) {
$('#tpl_messages').html('<div class="alert alert-danger">' + jqXHR.responseText + '</div>');
$.notify({
message: jqXHR.responseText
}, {
type: 'danger'
});
}).always(function () {
$('#save_file').html('{{ trans('strings.save') }}').removeClass('disabled');
});
});
}
@endcan
});
</script>

View file

@ -17,63 +17,109 @@
{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}}
{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}}
{{-- SOFTWARE. --}}
<h4 class="nopad">/home/container{{ $directory['header'] }} &nbsp;<small><a href="/server/{{ $server->uuidShort }}/files/add/@if($directory['header'] !== '')?dir={{ $directory['header'] }}@endif" class="text-muted"><i class="fa fa-plus" data-toggle="tooltip" data-placement="top" title="Add New File(s)"></i></a></small></h4>
<table class="table table-striped table-bordered table-hover" id="file_listing">
<table class="table table-hover" id="file_listing">
<thead>
<tr>
<th style="width:2%;text-align:center;"></th>
<th style="width:45%">File Name</th>
<th style="width:15%">Size</th>
<th style="width:20%">Last Modified</th>
{{-- <th style="width:20%;text-align:center;">Options</th> --}}
</tr>
<tr>
<th><i class="fa fa-folder-open"></i></th>
<th colspan="3">
<code>/home/container{{ $directory['header'] }}</code>
<small>
<a href="/server/{{ $server->uuidShort }}/files/add/@if($directory['header'] !== '')?dir={{ $directory['header'] }}@endif" class="text-muted">
<i class="fa fa-plus" data-toggle="tooltip" data-placement="top" title="Add New File(s)"></i>
</a>
</small>
</th>
</tr>
</thead>
<tbody>
@if (isset($directory['first']) && $directory['first'] === true)
<tr data-type="disabled">
<td><i class="fa fa-folder-open" style="margin-left: 0.859px;"></i></td>
<td><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td><a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">&larr;</a></a></td>
<td></td>
<td></td>
{{-- <td></td> --}}
</tr>
@endif
@if (isset($directory['show']) && $directory['show'] === true)
<tr data-type="disabled">
<td><i class="fa fa-folder-open" style="margin-left: 0.859px;"></i></td>
<td><a href="/server/{{ $server->uuidShort }}/files?dir={{ rawurlencode($directory['link']) }}" data-action="directory-view">&larr; {{ $directory['link_show'] }}</a></a></td>
<td><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td data-name="{{ rawurlencode($directory['link']) }}">
<a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">&larr; {{ $directory['link_show'] }}</a>
</td>
<td></td>
<td></td>
{{-- <td></td> --}}
</tr>
@endif
@foreach ($folders as $folder)
<tr class="align-middle" data-type="folder" data-path="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}/@endif{{ rawurlencode($folder['entry']) }}">
<td data-identifier="type"><i class="fa fa-folder-open" style="margin-left: 0.859px;"></i></td>
<td data-identifier="name" data-hash="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}/@endif{{ rawurlencode($folder['entry']) }}">
<tr class="align-middle" data-type="folder">
<td data-identifier="type"><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td data-identifier="name" data-name="{{ rawurlencode($folder['entry']) }}" data-path="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}@endif/">
<a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">{{ $folder['entry'] }}</a>
</td>
<td data-identifier="size">{{ $folder['size'] }}</td>
<td data-identifier="modified">{{ date('m/d/y H:i:s', $folder['date']) }}</td>
{{-- <td class="text-center">
<div class="row" style="text-align:center;">
<div class="col-md-3 hidden-xs hidden-sm"></div>
<div class="col-md-3 hidden-xs hidden-sm">
</div>
<div class="col-md-3">
@can('delete-files', $server)
<a href="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}/@endif{{ rawurlencode($folder['entry']) }}" data-action="delete_file" data-name="{{ $folder['entry'] }}"><button class="btn btn-danger btn-xxs"><i class="fa fa-trash-o"></i></button></a>
@endcan
</div>
</div>
</td> --}}
</tr>
@endforeach
@foreach ($files as $file)
<tr class="align-middle" data-type="file" data-mime="{{ $file['mime'] }}">
<td data-identifier="type"><i class="fa fa-file-text" style="margin-left: 2px;"></i></td>
<td data-identifier="name" data-hash="@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}">
@if(in_array($file['extension'], $extensions))
<td data-identifier="type">
{{-- oh boy --}}
@if(in_array($file['mime'], [
'application/x-7z-compressed',
'application/zip',
'application/x-compressed-zip',
'application/x-tar',
'application/x-gzip',
'application/x-bzip',
'application/x-bzip2',
'application/java-archive'
]))
<i class="fa fa-file-archive-o" style="margin-left: 2px;"></i>
@elseif(in_array($file['mime'], [
'application/json',
'application/javascript',
'application/xml',
'application/xhtml+xml',
'text/xml',
'text/css',
'text/html',
'text/x-perl',
'text/x-shellscript'
]))
<i class="fa fa-file-code-o" style="margin-left: 2px;"></i>
@elseif(starts_with($file['mime'], 'image'))
<i class="fa fa-file-image-o" style="margin-left: 2px;"></i>
@elseif(starts_with($file['mime'], 'video'))
<i class="fa fa-file-video-o" style="margin-left: 2px;"></i>
@elseif(starts_with($file['mime'], 'video'))
<i class="fa fa-file-audio-o" style="margin-left: 2px;"></i>
@elseif(starts_with($file['mime'], 'application/vnd.ms-powerpoint'))
<i class="fa fa-file-powerpoint-o" style="margin-left: 2px;"></i>
@elseif(in_array($file['mime'], [
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'application/msword'
]) || starts_with($file['mime'], 'application/vnd.ms-word'))
<i class="fa fa-file-word-o" style="margin-left: 2px;"></i>
@elseif(in_array($file['mime'], [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
]) || starts_with($file['mime'], 'application/vnd.ms-excel'))
<i class="fa fa-file-excel-o" style="margin-left: 2px;"></i>
@elseif($file['mime'] === 'application/pdf')
<i class="fa fa-file-pdf-o" style="margin-left: 2px;"></i>
@else
<i class="fa fa-file-text-o" style="margin-left: 2px;"></i>
@endif
</td>
<td data-identifier="name" data-name="{{ rawurlencode($file['entry']) }}" data-path="@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}@endif/">
@if(in_array($file['mime'], $editableMime))
@can('edit-files', $server)
<a href="/server/{{ $server->uuidShort }}/files/edit/@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}" class="edit_file">{{ $file['entry'] }}</a>
@else
@ -85,22 +131,6 @@
</td>
<td data-identifier="size">{{ $file['size'] }}</td>
<td data-identifier="modified">{{ date('m/d/y H:i:s', $file['date']) }}</td>
{{-- <td style="text-align:center;">
<div class="row" style="text-align:center;">
<div class="col-md-3 hidden-xs hidden-sm">
</div>
<div class="col-md-3 hidden-xs hidden-sm">
@can('download-files', $server)
<a href="/server/{{ $server->uuidShort }}/files/download/@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}"><span class="badge"><i class="fa fa-download"></i></span></a>
@endcan
</div>
<div class="col-md-3">
@can('delete-files', $server)
<a href="@if($file['directory'] !== ''){{ rawurlencode($file['directory']) }}/@endif{{ rawurlencode($file['entry']) }}" data-action="delete_file" data-name="{{ $file['entry'] }}"><span class="badge label-danger"><i class="fa fa-trash-o"></i></span>
@endcan
</div>
</div>
</td> --}}
</tr>
@endforeach
</tbody>

View file

@ -21,7 +21,7 @@
// SOFTWARE.
class FileActions {
constructor() {
//
this.activeLine = null;
}
run() {
@ -31,53 +31,67 @@ class FileActions {
makeMenu() {
$(document).find('#fileOptionMenu').remove();
return $('<ul id="fileOptionMenu" class="dropdown-menu" role="menu" style="display:none" > \
if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active');
return '<ul id="fileOptionMenu" class="dropdown-menu" role="menu" style="display:none" > \
<li data-action="move"><a tabindex="-1" href="#"><i class="fa fa-arrow-right"></i> Move</a></li> \
<li data-action="rename"><a tabindex="-1" href="#"><i class="fa fa-pencil-square-o"></i> Rename</a></li> \
<li data-action="compress" class="hidden"><a tabindex="-1" href="#"><i class="fa fa-file-archive-o"></i> Compress</a></li> \
<li data-action="decompress" class="hidden"><a tabindex="-1" href="#"><i class="fa fa-expand"></i> Decompress</a></li> \
<li class="divider"></li> \
<li data-action="download" class="hidden"><a tabindex="-1" href="#"><i class="fa fa-download"></i> Download</a></li> \
<li data-action="delete"><a tabindex="-1" href="#"><i class="fa fa-trash-o"></i> Delete</a></li> \
</ul>');
<li data-action="download" class="hidden"><a tabindex="-1" href="/server/{{ $server->uuidShort }}/files/download/"><i class="fa fa-download"></i> Download</a></li> \
<li data-action="delete" class="bg-danger"><a tabindex="-1" href="#"><i class="fa fa-trash-o"></i> Delete</a></li> \
</ul>';
}
rightClick() {
$('#file_listing > tbody').on('contextmenu', event => {
$('#file_listing > tbody td').on('contextmenu', event => {
const parent = $(event.target).parent();
const menu = this.makeMenu();
const menu = $(this.makeMenu());
if (parent.data('type') === 'disabled') return;
event.preventDefault();
menu.appendTo('body');
menu.data('invokedOn', $(event.target)).show().css({
$(menu).appendTo('body');
$(menu).data('invokedOn', $(event.target)).show().css({
position: 'absolute',
left: event.pageX,
top: event.pageY,
});
this.activeLine = parent;
this.activeLine.addClass('active');
if (parent.data('type') === 'file') {
menu.find('li[data-action="download"]').removeClass('hidden');
$(menu).find('li[data-action="download"]').removeClass('hidden');
}
if (parent.data('type') === 'folder') {
$(menu).find('li[data-action="compress"]').removeClass('hidden');
}
if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) {
menu.find('li[data-action="decompress"]').removeClass('hidden');
$(menu).find('li[data-action="decompress"]').removeClass('hidden');
}
// Handle Events
var Context = new ContextMenuActions(parent);
menu.find('li[data-action="move"]').unbind().on('click', e => {
var Context = new ContextMenuActions(parent, menu);
$(menu).find('li[data-action="move"]').unbind().on('click', e => {
Context.move();
});
menu.find('li[data-action="rename"]').unbind().on('click', e => {
$(menu).find('li[data-action="rename"]').unbind().on('click', e => {
Context.rename();
});
$(menu).find('li[data-action="download"]').unbind().on('click', e => {
e.preventDefault();
Context.download();
});
$(window).on('click', () => {
menu.remove();
$(menu).remove();
if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active');
});
});
}
@ -85,7 +99,12 @@ class FileActions {
directoryClick() {
$('a[data-action="directory-view"]').on('click', function (event) {
event.preventDefault();
window.location.hash = encodeURIComponent($(this).parent().data('hash') || '');
const path = $(this).parent().data('path') || '';
const name = $(this).parent().data('name') || '';
console.log('changing hash');
window.location.hash = encodeURIComponent(path + name);
Files.list();
});
}

View file

@ -20,8 +20,9 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
class ContextMenuActions {
constructor(element) {
constructor(element, menu) {
this.element = element;
this.menu = menu;
}
destroy() {
@ -32,28 +33,46 @@ class ContextMenuActions {
alert($(this.element).data('path'));
}
download() {
var baseURL = $(this.menu).find('li[data-action="download"] a').attr('href');
var toURL = baseURL + $(this.element).find('td[data-identifier="name"]').data('name');
window.location = toURL;
}
rename() {
var desiredElement = $(this.element).find('td[data-identifier="name"]');
var linkElement = desiredElement.find('a');
var currentName = linkElement.html();
var editField = `<input class="form-control input-sm" type="text" value="${currentName}" />`;
desiredElement.find('a').remove();
desiredElement.html(editField);
const nameBlock = $(this.element).find('td[data-identifier="name"]');
const currentLink = nameBlock.find('a');
const currentName = decodeURIComponent(nameBlock.attr('data-name'));
const attachEditor = `
<input class="form-control input-sm" type="text" value="${currentName}" />
<span class="input-loader"><i class="fa fa-refresh fa-spin fa-fw"></i></span>
`;
nameBlock.html(attachEditor);
const inputField = nameBlock.find('input');
const inputLoader = nameBlock.find('.input-loader');
const inputField = desiredElement.find('input');
inputField.focus();
inputField.on('blur keypress', e => {
// Save Field
if (e.type === 'blur' || (e.type === 'keypress' && e.which !== 13)) {
// Escape Key Pressed, don't save.
if (e.which === 27 || e.type === 'blur') {
desiredElement.html(linkElement);
if (!_.isEmpty(currentLink)) {
nameBlock.html(currentLink);
} else {
nameBlock.html(currentName);
}
inputField.remove();
Actions.run();
}
return;
}
inputLoader.show();
const currentPath = decodeURIComponent(nameBlock.data('path'));
$.ajax({
type: 'POST',
headers: {
@ -64,16 +83,24 @@ class ContextMenuActions {
url: '{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/server/files/rename',
timeout: 10000,
data: JSON.stringify({
from: currentName,
to: inputField.val(),
from: `${currentPath}${currentName}`,
to: `${currentPath}${inputField.val()}`,
}),
}).done(data => {
this.element.attr('data-path', inputField.val());
desiredElement.attr('data-hash', inputField.val());
desiredElement.html(linkElement.html(inputField.val()));
nameBlock.attr('data-name', inputField.val());
if (!_.isEmpty(currentLink)) {
const newLink = currentLink.attr('href').substr(0, currentLink.attr('href').lastIndexOf('/')) + '/' + inputField.val();
currentLink.attr('href', newLink);
nameBlock.html(
currentLink.html(inputField.val())
);
} else {
nameBlock.html(inputField.val());
}
inputField.remove();
Actions.run();
}).fail(jqXHR => {
nameBlock.addClass('has-error');
inputLoader.remove();
console.error(jqXHR);
var error = 'An error occured while trying to process this request.';
if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {
@ -84,6 +111,8 @@ class ContextMenuActions {
title: '',
text: error,
});
}).always(() => {
inputLoader.remove();
});
});
}

View file

@ -44,11 +44,13 @@ class FileManager {
directory: path,
},
}).done(data => {
$('#load_files').slideUp().html(data).slideDown(() => {
this.loader(false);
$('#load_files').slideUp().html(data).slideDown(100, () => {
Actions.run();
});
$('#internal_alert').slideUp();
}).fail(jqXHR => {
this.loader(false);
swal({
type: 'error',
title: 'File Error',
@ -56,9 +58,7 @@ class FileManager {
});
if (!isError) this.list('/', true);
console.log(jqXHR);
}).always(() => {
this.loader(false);
});
})
}
loader(show) {
@ -76,9 +76,9 @@ class FileManager {
'font-size': '60px'
});
$('.ajax_loading_box').css('height', (height + 5)).fadeIn();
$('.ajax_loading_box').css('height', (height + 5)).show();
} else {
$('.ajax_loading_box').fadeOut(100);
$('.ajax_loading_box').hide();
}
}