diff --git a/setup.py b/setup.py index beff0f06e..fdef2edf9 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,8 @@ 'ovpn-initpki = ovpn_util:initpki', 'ovpn-client-gen = ovpn_util:client_gen', 'ovpn-client-print = ovpn_util:client_retrieve', + 'qgroup-clean = qgroup_clean:main', + 'rockon-json = rockon_util:main', ], }, diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/add_shares_form.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/add_shares_form.jst index 043e1669f..e50bfc4e9 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/add_shares_form.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/add_shares_form.jst @@ -2,28 +2,34 @@
-
+
<% if (shares.length == 0) { %>

There are no Shares left to add. Create a new one and try again.

<% } else { %>
- -
- + <% _.each(shares, function(share) { %> <% }); %>
-
- -
- + +
+
+ +
+
+
- <% } %> -
+ <% } %> +
+ diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/custom_choice.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/custom_choice.jst index 89e4f731f..4f9948c20 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/custom_choice.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/custom_choice.jst @@ -4,7 +4,7 @@
-

Additional configuration is needed for this Rock-on. Make sure to read tooltips for specific information before making your selection.

+

Additional configuration is needed for this Rock-on. Read mouseover tooltips for specific information before making your selection.

diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/install_choice.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/install_choice.jst index d38127f84..d41f9f3a8 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/install_choice.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/install_choice.jst @@ -2,6 +2,6 @@
-

Shares provide storage to the Rock-on. Make sure to read tooltips for specific information before making a selection. We strongly recommend creating dedicated Share assignments. If a Share is assigned to more than one Rock-On, it could cause strange behavior.

+

Shares provide storage to the Rock-on. Read mousever tooltips for specific information before making a selection. We strongly recommend creating dedicated Share assignments. If a Config or Data Share is assigned to more than one Rock-on, it could cause strange behavior.

diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/install_complete.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/install_complete.jst index 691791064..f356eb50c 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/install_complete.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/install_complete.jst @@ -1,3 +1,3 @@ -
-

Installation is in progress.
It can take a while depending on the type of Rock-on, network speed and other factors.
You can monitor the Rock-ons page which refreshes periodically during the installation.

+
+ Installation is in progress. It can take a while depending on the type of Rock-on, network speed and other factors. You can monitor the Rock-ons page which refreshes periodically during the installation.
diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/port_choice.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/port_choice.jst index 32acedf76..4b5c7a5a4 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/port_choice.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/port_choice.jst @@ -3,6 +3,6 @@
-

Ports provide network access to the Rock-on. Preferred default values are provided for convenience. Read tooltips for more information.

+

Ports provide network access to the Rock-on. Preferred default values are provided for convenience. Read mouseover tooltips for more information.

diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/vol_table.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/vol_table.jst index 396ae49d2..223512869 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/vol_table.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/rockons/vol_table.jst @@ -8,7 +8,7 @@
- <% shares.each(function(share, index) { %> diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/rockons.js b/src/rockstor/storageadmin/static/storageadmin/js/views/rockons.js index da662eb42..06bd98cab 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/rockons.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/rockons.js @@ -136,7 +136,7 @@ RockonsView = RockstorLayoutView.extend({ if (buttonDisabled(button)) return false; var rockon_id = button.attr('data-name'); var rockon_o = _this.rockons.get(rockon_id); - if (confirm("Are you sure you want to uninstall this Rockon(" + rockon_o.get('name') + ")?")) { + if (confirm("Are you sure you want to uninstall this Rock-on (" + rockon_o.get('name') + ")?")) { disableButton(button); $.ajax({ url: '/api/rockons/' + rockon_id + '/uninstall', @@ -693,12 +693,12 @@ RockonSettingsWizardView = WizardView.extend({ modifyButtonText: function() { if (this.currentPageNum == 0) { this.$('#prev-page').hide(); - this.$('#next-page').html('Add a Share'); + this.$('#next-page').html('Add Storage'); if (!this.rockon.get('volume_add_support')) { this.$('#next-page').hide(); } } else if (this.currentPageNum == (this.pages.length - 2)) { - this.$('#prev-page').html('Add a Share'); + this.$('#prev-page').html('Add Storage'); this.$('#next-page').html('Next'); } else if (this.currentPageNum == (this.pages.length - 1)) { this.$('#prev-page').hide(); @@ -759,8 +759,10 @@ RockonAddShare = RockstorWizardPage.extend({ })); this.share_form = this.$('#vol-select-form'); this.validator = this.share_form.validate({ - rules: { "volume": "required" }, - messages: { "volume": "Must be a valid unix path. Eg: /data/media" } + rules: { "volume": "required", + "share": "required" }, + messages: { "volume": "Must be a valid unix path. Eg: /data/media", + "share": "Select an appropriate Share to map"} }); return this; }, diff --git a/src/rockstor/storageadmin/templates/storageadmin/base.html b/src/rockstor/storageadmin/templates/storageadmin/base.html index 9754f86e8..9dbf0d9c7 100644 --- a/src/rockstor/storageadmin/templates/storageadmin/base.html +++ b/src/rockstor/storageadmin/templates/storageadmin/base.html @@ -25,6 +25,7 @@ + RockStor | {% block title %}{% endblock %} diff --git a/src/rockstor/storageadmin/views/rockon.py b/src/rockstor/storageadmin/views/rockon.py index 029aba164..7264659a8 100644 --- a/src/rockstor/storageadmin/views/rockon.py +++ b/src/rockstor/storageadmin/views/rockon.py @@ -181,6 +181,9 @@ def post(self, request, command=None): options = containers[c]['opts'] id_l = [] for o in options: + #there are no unique constraints on this model, so we need this bandaid. + if (ContainerOption.objects.filter(container=co, name=o[0], val=o[1]).count() > 1): + ContainerOption.objects.filter(container=co, name=o[0], val=o[1]).delete() oo, created = ContainerOption.objects.get_or_create(container=co, name=o[0], val=o[1]) diff --git a/src/rockstor/storageadmin/views/rockon_custom_config.py b/src/rockstor/storageadmin/views/rockon_custom_config.py index bc8b7873f..be0041d14 100644 --- a/src/rockstor/storageadmin/views/rockon_custom_config.py +++ b/src/rockstor/storageadmin/views/rockon_custom_config.py @@ -29,7 +29,7 @@ def get_queryset(self, *args, **kwargs): try: rockon = RockOn.objects.get(id=self.kwargs['rid']) except: - e_msg = ('Rockon(%s) does not exist' % self.kwargs['rid']) + e_msg = ('Rock-on(%s) does not exist' % self.kwargs['rid']) handle_exception(Exception(e_msg), self.request) return DCustomConfig.objects.filter(rockon=rockon) diff --git a/src/rockstor/storageadmin/views/rockon_id.py b/src/rockstor/storageadmin/views/rockon_id.py index a567c0818..e8b427715 100644 --- a/src/rockstor/storageadmin/views/rockon_id.py +++ b/src/rockstor/storageadmin/views/rockon_id.py @@ -41,8 +41,8 @@ def get_queryset(self, *args, **kwargs): @staticmethod def _pending_check(request): if (RockOn.objects.filter(state__contains='pending').exists()): - e_msg = ('Another Rockon is in state transition. Multiple ' - 'simultaneous Rockon transitions are not ' + e_msg = ('Another Rock-on is in state transition. Multiple ' + 'simultaneous Rock-on transitions are not ' 'supported. Please try again later.') handle_exception(Exception(e_msg), request) @@ -100,7 +100,7 @@ def post(self, request, rid, command): if (DPort.objects.filter(hostp=p).exists()): po = DPort.objects.get(hostp=p) if (po.container.rockon.id != rockon.id): - e_msg = ('Rockon port(%s) is dedicated to ' + e_msg = ('Rock-on port(%s) is dedicated to ' 'another Rock-On(%s) and cannot ' 'be changed. Choose a different name' % (p, po.container.rockon.name)) @@ -162,6 +162,10 @@ def post(self, request, rid, command): if (DVolume.objects.filter(container=co, dest_dir=s).exists()): e_msg = ('Directory(%s) is already mapped for this Rock-on' % s) handle_exception(Exception(e_msg), request) + if (not s.startswith('/')): + e_msg = ('Invalid Directory(%s). Must provide an ' + 'absolute path. Eg: /data/media' % s) + handle_exception(Exception(e_msg), request) do = DVolume(container=co, share=so, uservol=True, dest_dir=s) do.save() rockon.state = 'pending_update' diff --git a/src/rockstor/storageadmin/views/rockon_json.py b/src/rockstor/storageadmin/views/rockon_json.py index 84a6d4a21..3380153a8 100644 --- a/src/rockstor/storageadmin/views/rockon_json.py +++ b/src/rockstor/storageadmin/views/rockon_json.py @@ -23,7 +23,7 @@ 'description': 'Open Source VPN server', 'website': 'https://openvpn.net/', 'icon': 'https://openvpn.net/', - 'more_info': '

Additional steps are required by this Rockon.

Run these following commands as therootuser on your Rockstor system.

Initialize PKI    Rock-on will not start without it.

/opt/rockstor/bin/ovpn-initpki

Generate a client certificate    One for every client

/opt/rockstor/bin/ovpn-client-gen

Retrieve client configuration    For any one of your clients. The resulting .ovpn file can be used to connect.

/opt/rockstor/bin/ovpn-client-print

Configure firewall

If your Rockstor system is behind a firewall, you will need to configure it to allow OpenVPN traffic to forward to your Rockstor system.

',} + 'more_info': '

Additional steps are required by this Rock-on.

Run these following commands as therootuser on your Rockstor system, i.e., via a ssh console.

Initialize PKI    The OpenVPN Rock-on will not start without it.

/opt/rockstor/bin/ovpn-initpki

Generate a client certificate    One for every client

/opt/rockstor/bin/ovpn-client-gen

Retrieve client configuration    For any one of your clients. The resulting .ovpn file can be used to connect to this OpenVPN server.

/opt/rockstor/bin/ovpn-client-print

Configure firewall

If your Rockstor system is behind a firewall, you will need to configure it to allow OpenVPN traffic to forward to your Rockstor system.

',} owncloud = {'ui': {'slug': '', 'https': True,}, @@ -37,16 +37,16 @@ 'volumes': {'/var/www/owncloud/data': {'description': 'Choose a Share for OwnCloud data. Eg: create a Share called owncloud-data for this purpose alone.', 'min_size': 1073741824, - 'label': 'Data directory',}, + 'label': 'Data Storage',}, '/var/www/owncloud/config': {'description': 'Choose a Share for OwnCloud configuration. Eg: create a Share called owncloud-config for this purpose alone.', - 'label': 'Config directory', + 'label': 'Config Storage', 'min_size': 1073741824,},}, 'launch_order': 2,}, 'owncloud-postgres': {'image': 'postgres', 'volumes': {'/var/lib/postgresql/data': {'description': "Choose a Share for OwnCloud's postgresql database. Eg: create a Share called owncloud-db for this purpose alone.", - 'label': 'Database', + 'label': 'DB Storage', 'min_size': 1073741824, }, }, 'launch_order': 1}, }, 'container_links': {'owncloud': [{'source_container': 'owncloud-postgres', @@ -84,13 +84,14 @@ 'volumes': {'/home/syncthing/.config/syncthing': {'description': 'Choose a Share for configuration. Eg: create a Share called syncthing-config for this purpose alone.', 'min_size': ONE_GB, - 'label': 'Config directory',}, + 'label': 'Config Storage',}, '/home/syncthing/Sync': - {'label': 'Data directory', + {'label': 'Data Storage', 'description': 'Choose a Share for all incoming data. Eg: create a Share called syncthing-data for this purpose alone.',},}, 'launch_order': 1,},}, 'description': 'Continuous File Synchronization', - 'website': 'https://syncthing.net/',} + 'website': 'https://syncthing.net/', + 'volume_add_support': True, } transmission = {'ui': {'slug': '',}, 'containers': {'transmission': {'image': 'dperson/transmission', @@ -106,7 +107,7 @@ 'description': 'Port used to share the file being downloaded. You may need to open it(protocol: tcp and udp) on your firewall. Suggested default: 51413.',},}, 'volumes': {'/var/lib/transmission-daemon': {'description': "Choose a Share where Transmission will save all of it's files including your downloads. Eg: create a Share called transmission-rockon.", - 'label': 'Data directory',},}, + 'label': 'Data Storage',},}, 'launch_order': 1,},}, 'custom_config': {'TRUSER': {'label': 'WebUI username', @@ -132,12 +133,12 @@ 'description': 'Port for incoming data. You may need to open it(protocol: udp) on your firewall. Suggested default: 3369.',},}, 'volumes': {'/data': {'description': 'Choose a Share for all incoming data. Eg: create a Share called btsync-data for this purpose alone. It will be available as /data inside BTSync.', - 'label': 'Data directory',},}, + 'label': 'Data Storage',},}, 'launch_order': 1,},}, 'description': 'BitTorrent Sync', 'website': 'https://www.getsync.com/', 'volume_add_support': True, - 'more_info': '

Authentication

Default username for your BTSync UI isadminand password ispassword

Storage

We strongly recommend changing the Default folder location to/datain the UI preferences.

You can also assign additional Shares for custom organization of your data.

'} + 'more_info': '

Authentication

Default username for your BTSync UI isadminand password ispassword

Storage

You can also assign additional Shares for custom organization of your data.

'} plex = {'ui': {'slug': 'web',}, 'containers': {'plex': {'image': 'timhaak/plex', @@ -151,10 +152,10 @@ 'opts': [['--net=host', ''],], 'volumes': {'/config': {'description': 'Choose a Share for Plex configuration. Eg: create a Share called plex-config for this purpose alone.', - 'label': 'Config directory',}, + 'label': 'Config Storage',}, '/data': {'description': 'Choose a Share for Plex content(your media). Eg: create a Share called plex-data for this purpose alone. You can also assign other media Shares on the system after installation.', - 'label': 'Data directory',},},},}, + 'label': 'Data Storage',},},},}, 'description': 'Plex media server', 'website': 'https://plex.tv/', 'volume_add_support': True, diff --git a/src/rockstor/storageadmin/views/rockon_port.py b/src/rockstor/storageadmin/views/rockon_port.py index 1cf1f8e28..9be89ca2a 100644 --- a/src/rockstor/storageadmin/views/rockon_port.py +++ b/src/rockstor/storageadmin/views/rockon_port.py @@ -29,7 +29,7 @@ def get_queryset(self, *args, **kwargs): try: rockon = RockOn.objects.get(id=self.kwargs['rid']) except: - e_msg = ('Rockon(%s) does not exist' % self.kwargs['rid']) + e_msg = ('Rock-on(%s) does not exist' % self.kwargs['rid']) handle_exception(Exception(e_msg), self.request) containers = DContainer.objects.filter(rockon=rockon) diff --git a/src/rockstor/storageadmin/views/rockon_volume.py b/src/rockstor/storageadmin/views/rockon_volume.py index 2752c3680..98344e541 100644 --- a/src/rockstor/storageadmin/views/rockon_volume.py +++ b/src/rockstor/storageadmin/views/rockon_volume.py @@ -29,7 +29,7 @@ def get_queryset(self, *args, **kwargs): try: rockon = RockOn.objects.get(id=self.kwargs['rid']) except: - e_msg = ('Rockon(%s) does not exist' % self.kwargs['rid']) + e_msg = ('Rock-on(%s) does not exist' % self.kwargs['rid']) handle_exception(Exception(e_msg), self.request) containers = DContainer.objects.filter(rockon=rockon)