--- /dev/null
+#!/bin/bash
+rm -rf openmediavault-zfs/usr/share/openmediavault/engined/rpc
+mkdir -p openmediavault-zfs/usr/share/openmediavault/engined/rpc/zfs
+rm -rf openmediavault-zfs/var/www/openmediavault/js/omv/module/admin/service/zfs
+mkdir -p openmediavault-zfs/var/www/openmediavault/js/omv/module/admin/service/zfs
+rm -rf openmediavault-zfs/var/www/openmediavault/images
+mkdir -p openmediavault-zfs/var/www/openmediavault/images
+cp src/* openmediavault-zfs/usr/share/openmediavault/engined/rpc/zfs
+cp gui/rpc/zfs.inc openmediavault-zfs/usr/share/openmediavault/engined/rpc
+cp gui/js/omv/module/admin/service/zfs/* openmediavault-zfs/var/www/openmediavault/js/omv/module/admin/service/zfs
+cp images/* openmediavault-zfs/var/www/openmediavault/images
+cd openmediavault-zfs
+dpkg-buildpackage -us -uc
+
--- /dev/null
+// require("js/omv/tree/Panel.js")
+// require("js/omv/module/admin/service/zfs/TreePanel.js")
+// require("js/omv/workspace/window/Grid.js")
+
+Ext.define("OMV.module.admin.services.zfs.AddObject", {
+ extend: "OMV.workspace.window.Form",
+ uses: [
+ "OMV.data.Store",
+ "OMV.data.Model",
+ "OMV.data.proxy.Rpc",
+ "OMV.data.reader.RpcArray"
+ ],
+
+ rpcService: "ZFS",
+ rpcSetMethod: "addObject",
+ width: 420,
+
+ getFormItems: function() {
+ var me = this;
+ return [{
+ xtype: "combo",
+ name: "type",
+ fieldLabel: _("Object Type"),
+ queryMode: "local",
+ store: [
+ [ "filesystem", "Filesystem" ],
+ [ "snapshot", "Snapshot" ],
+ [ "volume", "Volume" ]
+ ],
+ allowBlank: true,
+ editable: false,
+ triggerAction: "all",
+ value: "filesystem",
+ listeners: {
+ scope: me,
+ change: function(combo, value) {
+ var sizeField = this.findField("size");
+ switch(value) {
+ case "volume":
+ sizeField.show();
+ sizeField.allowBlank = false;
+ break;
+ default:
+ sizeField.hide();
+ sizeField.allowBlank = true;
+ break;
+ }
+ sizeField.validate();
+ }
+ }
+ },{
+ xtype: "textfield",
+ name: "path",
+ fieldLabel: _("Prefix"),
+ allowBlank: false,
+ readOnly: true
+ },{
+ xtype: "textfield",
+ name: "name",
+ fieldLabel: _("Name"),
+ allowBlank: false,
+ plugins: [{
+ ptype: "fieldinfo",
+ text: _("Name of the new object. Prefix will prepend the name. Please omit leading /")
+ }]
+ },{
+ xtype: "textfield",
+ name: "size",
+ hidden: true,
+ fieldLabel: _("Size"),
+ allowBlank: true,
+ plugins: [{
+ ptype: "fieldinfo",
+ text: _("Size of the volume e.g. 5mb,100gb,1tb etc")
+ }]
+ }];
+ }
+});
+
+
+
+Ext.define("OMV.module.admin.service.zfs.EditProperties", {
+ extend: "OMV.workspace.window.Grid",
+ requires: [
+ "OMV.data.Store",
+ "OMV.data.Model",
+ "OMV.data.proxy.Rpc"
+ ],
+
+ rpcService: "ZFS",
+ rpcSetMethod: "setProperties",
+
+ title: _("Edit properties"),
+ width: 500,
+ height: 305,
+
+ getGridConfig: function() {
+ var me = this;
+
+ var rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
+ clicksToEdit: 1,
+ pluginId: 'rowEditing',
+ listeners: {
+ validateedit: function(editor, e, eOpts) {
+ e.record.set("modified", "true");
+ },
+ beforeedit: function(editor, e, eOpts) {
+ if (e.record.get("property") === "mountpoint") {
+ e.grid.getPlugin('rowEditing').editor.form.findField("value").disable();
+ e.grid.getPlugin('rowEditing').editor.form.findField("property").disable();
+ } else if (e.record.get("newproperty") === "false") {
+ e.grid.getPlugin('rowEditing').editor.form.findField("value").enable();
+ e.grid.getPlugin('rowEditing').editor.form.findField("property").disable();
+ } else {
+ e.grid.getPlugin('rowEditing').editor.form.findField("value").enable();
+ e.grid.getPlugin('rowEditing').editor.form.findField("property").enable();
+ }
+ }
+
+ }
+ });
+
+ var store = Ext.create("OMV.data.Store", {
+ autoLoad: true,
+ model: OMV.data.Model.createImplicit({
+ fields: [
+ { name: "property", type: "string" },
+ { name: "value", type: "string" },
+ { name: "source", type: "string" },
+ { name: "modified", type: "string" },
+ { name: "newproperty", type: "string", defaultValue: "false" }
+ ]
+ }),
+ proxy: {
+ type: "rpc",
+ rpcData: {
+ service: "ZFS",
+ method: "getProperties",
+ params: {
+ name: me.name,
+ type: me.type
+ }
+ }
+ }
+ });
+
+ return {
+ border: false,
+ stateful: true,
+ stateId: "8c3dc800-bdbb-11e3-b1b6-0800200c9a66",
+ selType: 'rowmodel',
+ plugins: [rowEditing],
+ store: store,
+ tbar: [{
+ text: "Add property",
+ icon: "images/add.png",
+ iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
+ handler: function(view) {
+ Ext.define('Property', {
+ extend: 'Ext.data.Model',
+ fields: [
+ "property",
+ "value",
+ "source",
+ "modified",
+ "newproperty"
+ ]
+ });
+ var newProperty = Ext.create("Property", {
+ property: "",
+ value: "",
+ source: "local",
+ modified: "true",
+ newproperty: "true"
+ });
+ rowEditing.cancelEdit();
+ store.insert(0, newProperty);
+ rowEditing.startEdit();
+ }
+ }],
+ columns: [{
+ text: _("Property"),
+ sortable: true,
+ dataIndex: "property",
+ stateId: "property",
+ editor: {
+ xtype: "textfield",
+ allowBlank: false,
+ }
+ },{
+ text: _("Value"),
+ sortable: true,
+ dataIndex: "value",
+ stateId: "value",
+ flex: 1,
+ readOnly: true,
+ editor: {
+ xtype: "textfield",
+ allowBlank: false,
+ }
+ },{
+ text: _("Source"),
+ sortable: true,
+ dataIndex: "source",
+ stateId: "source",
+ },{
+ xtype: 'actioncolumn',
+ header: 'Inherit',
+ icon: "images/checkmark.png",
+ tooltip: "Inherit",
+ handler: function(view, rowIndex, colIndex, item, e, record, row) {
+ OMV.RpcObserver.request({
+ msg : _("Updating property..."),
+ rpcData : {
+ service: "ZFS",
+ method: "inherit",
+ params: {
+ name: me.name,
+ type: me.type,
+ property: record.get("property")
+ }
+ },
+ finish : function() {
+ view.getStore().reload();
+ }
+ });
+ },
+ isDisabled: function(view, rowIdx, colIdx, item, record) {
+ var src = record.get("source");
+ if(src === "local") {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ },{
+ text: _("New"),
+ dataIndex: "newproperty",
+ stateId: "newproperty",
+ sortable: false,
+ hidden: true
+ },{
+ text: _("Modified"),
+ sortable: false,
+ dataIndex: "modified",
+ stateId: "modified",
+ hidden: true
+ }],
+ };
+ },
+
+ getRpcSetParams: function() {
+ var me = this;
+ var properties = [];
+ var values = me.getValues();
+ Ext.Array.each(values, function(value) {
+ if(value.modified === "false")
+ return;
+ properties.push({
+ "property": value.property,
+ "value": value.value,
+ });
+ });
+ return {
+ name: me.name,
+ type: me.type,
+ properties: properties
+ };
+ }
+
+});
+
+
+Ext.define("OMV.module.admin.services.zfs.CreateShare", {
+ extend: "OMV.workspace.window.Form",
+ uses: [
+ "OMV.data.Store",
+ "OMV.data.Model",
+ "OMV.data.proxy.Rpc",
+ "OMV.data.reader.RpcArray"
+ ],
+
+ rpcService: "ZFS",
+ rpcSetMethod: "createShare",
+ width: 500,
+
+ getFormItems: function() {
+ var me = this;
+ return [{
+ xtype: "textfield",
+ name: "sharename",
+ fieldLabel: _("Name"),
+ allowBlank: false,
+ },{
+ xtype: "textfield",
+ name: "mountpoint",
+ fieldLabel: _("Path"),
+ allowBlank: false,
+ readOnly: true
+ },{
+ xtype: "combo",
+ name: "mode",
+ fieldLabel: _("Permissions"),
+ queryMode: "local",
+ store: Ext.create("Ext.data.ArrayStore", {
+ fields: [ "value", "text" ],
+ data: [
+ [ "700", _("Administrator: read/write, Users: no access, Others: no access") ],
+ [ "750", _("Administrator: read/write, Users: read-only, Others: no access") ],
+ [ "770", _("Administrator: read/write, Users: read/write, Others: no access") ],
+ [ "755", _("Administrator: read/write, Users: read-only, Others: read-only") ],
+ [ "775", _("Administrator: read/write, Users: read/write, Others: read-only") ],
+ [ "777", _("Everyone: read/write") ]
+ ]
+ }),
+ displayField: "text",
+ valueField: "value",
+ allowBlank: false,
+ editable: false,
+ showItemTooltip: true,
+ triggerAction: "all",
+ value: "775",
+ plugins: [{
+ ptype: "fieldinfo",
+ text: _("The file mode of the shared folder path.")
+ }]
+ },{
+ xtype: "textarea",
+ name: "comment",
+ fieldLabel: _("Comment"),
+ allowBlank: true
+ },{
+ xtype: "textarea",
+ name: "name",
+ hidden: true
+ },{
+ xtype: "textarea",
+ name: "type",
+ hidden: true
+ }];
+ }
+});
+
+
+
+Ext.define("OMV.module.admin.service.zfs.Overview", {
+ extend: "OMV.module.admin.services.zfs.TreePanel",
+
+ rpcService: "ZFS",
+ rpcGetMethod: "getObjectTree",
+ requires: [
+ "OMV.data.Store",
+ "OMV.data.Model",
+ "OMV.data.proxy.Rpc"
+ ],
+
+ rootVisible: false,
+ stateful: true,
+ stateId: "cec54550-bc2a-11e3-a5e2-0800200c9a66",
+
+ columns: [{
+ text: _("Name"),
+ xtype: 'treecolumn',
+ dataIndex: 'name',
+ sortable: true,
+ flex: 2,
+ stateId: 'name'
+ },{
+ text: _("Type"),
+ dataIndex: 'type',
+ sortable: true,
+ flex: 1,
+ stateId: 'type'
+ },{
+ text: _("Share"),
+ xtype: 'actioncolumn',
+ tooltip: 'Create shared folder',
+ align: 'center',
+ icon: 'images/checkmark.png',
+ handler: function(view, rowIndex, colIndex, item, e, record, row) {
+ var me = this;
+ Ext.create("OMV.module.admin.services.zfs.CreateShare", {
+ title: _("Create shared folder"),
+ rpcGetMethod: "getSharedParams",
+ rpcGetParams: {
+ name: record.get('path'),
+ type: record.get('type')
+ }
+ }).show();
+ },
+ isDisabled: function(view, rowIdx, colIdx, item, record) {
+ var src = record.get("type");
+ if((src === "Filesystem") && (record.get("shared") === "false")) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+
+ },{
+ text: _("Details"),
+ xtype: 'actioncolumn',
+ tooltip: 'Details',
+ align: 'center',
+ icon: 'images/zfs_mag.png'
+ },{
+ text: _("Shared"),
+ dataIndex: 'shared',
+ sortable: false,
+ stateId: 'shared',
+ hidden: true
+ }],
+
+ initComponent: function() {
+ var me = this;
+ this.width = 600;
+ Ext.apply(me, {
+ store: Ext.create("Ext.data.TreeStore", {
+ autoLoad: true,
+ model: OMV.data.Model.createImplicit({
+ fields: [
+ { name: "name", type: "string" },
+ { name: "type", type: "string" },
+ { name: "id", type: "string" },
+ { name: "path", type: "string" },
+ { name: "origin", type: "string", defaultValue: "none" },
+ { name: "shared", type: "string", defaultValue: "false" }
+ ]
+ }),
+ proxy: {
+ type: "rpc",
+ rpcData: {
+ service: "ZFS",
+ method: "getObjectTree",
+ }
+ },
+ folderSort: true
+ })
+ });
+ me.callParent(arguments);
+ },
+
+ onAddObjButton: function() {
+ var me = this;
+ var sm = me.getSelectionModel();
+ var records = sm.getSelection();
+ var record = records[0];
+ Ext.create("OMV.module.admin.services.zfs.AddObject", {
+ title: _("Add Object"),
+ rpcGetMethod: "passParam",
+ rpcGetParams: {
+ key: "path",
+ value: record.get('path')
+ },
+ listeners: {
+ scope: me,
+ submit: function() {
+ this.doReload();
+ }
+ }
+ }).show();
+ },
+
+ onEditButton: function() {
+ var me = this;
+ var sm = me.getSelectionModel();
+ var records = sm.getSelection();
+ var record = records[0];
+ Ext.create("OMV.module.admin.service.zfs.EditProperties", {
+ name: record.get("path"),
+ type: record.get("type")
+ }).show();
+ },
+
+ doDeletion: function(record) {
+ var me = this;
+ OMV.Rpc.request({
+ scope: me,
+ callback: me.onDeletion,
+ rpcData: {
+ service: "ZFS",
+ method: "deleteObject",
+ params: {
+ name: record.get('path'),
+ type: record.get('type')
+ }
+ }
+ });
+ }
+
+});
+
+OMV.WorkspaceManager.registerPanel({
+ id: "overview",
+ path: "/service/zfs",
+ text: _("Overview"),
+ position: 10,
+ className: "OMV.module.admin.service.zfs.Overview"
+});
+
+
+
--- /dev/null
+/**
+ * This file is part of OpenMediaVault.
+ *
+ * @license http://www.gnu.org/licenses/gpl.html GPL Version 3
+ * @author Volker Theile <volker.theile@openmediavault.org>
+ * @copyright Copyright (c) 2009-2014 Volker Theile
+ *
+ * OpenMediaVault is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * OpenMediaVault is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with OpenMediaVault. If not, see <http://www.gnu.org/licenses/>.
+ */
+// require("js/omv/tree/Panel.js")
+// require("js/omv/grid/Panel.js")
+// require("js/omv/grid/column/BinaryUnit.js")
+// require("js/omv/grid/column/BooleanIcon.js")
+// require("js/omv/grid/column/BooleanText.js")
+// require("js/omv/grid/column/Empty.js")
+// require("js/omv/grid/column/Hyperlink.js")
+// require("js/omv/grid/column/UnixTimestamp.js")
+// require("js/omv/grid/column/WhiteSpace.js")
+// require("js/omv/window/MessageBox.js")
+
+/**
+ * @ingroup webgui
+ * @class OMV.workspace.grid.Panel
+ * @derived OMV.grid.Panel
+ * An enhanced grid panel. This grid provides 'Add', 'Edit' and 'Delete'
+ * buttons in the toolbar by default. The basic delete functionality is also
+ * implemented, simply overwrite the 'doDeletion' and 'afterDeletion'
+ * functions to implement fit your requirements. To implement the 'Add' and
+ * 'Edit' functionality overwrite the 'onAdd' and 'onEdit' callback
+ * functions. A paging toolbar which is displayed at the bottom of the grid
+ * can be displayed also. It is also possible to reload the grid
+ * automatically in a given interval.
+ * @param hideTopToolbar TRUE to hide the whole toolbar. Defaults to FALSE.
+ * @param hidePagingToolbar TRUE to hide the paging toolbar at the bottom of
+ * the grid. Defaults to TRUE.
+ * @param hideAddButton Hide the 'Add' button in the top toolbar.
+ * Defaults to FALSE.
+ * @param hideEditButton Hide the 'Edit' button in the top toolbar.
+ * Defaults to FALSE.
+ * @param hideDeleteButton Hide the 'Delete' button in the top toolbar.
+ * Defaults to FALSE.
+ * @param hideUpButton Hide the 'Up' button in the top toolbar.
+ * Defaults to TRUE.
+ * @param hideDownButton Hide the 'Down' button in the top toolbar.
+ * Defaults to TRUE.
+ * @param hideApplyButton Hide the 'Apply' button in the top toolbar.
+ * Defaults to TRUE.
+ * @param hideRefreshButton Hide the 'Refresh' button in the top toolbar.
+ * Defaults to TRUE.
+ * @param addButtonText The button text. Defaults to 'Add'.
+ * @param editButtonText The button text. Defaults to 'Edit'.
+ * @param deleteButtonText The button text. Defaults to 'Delete'.
+ * @param upButtonText The button text. Defaults to 'Up'.
+ * @param downButtonText The button text. Defaults to 'Down'.
+ * @param applyButtonText The button text. Defaults to 'Save'.
+ * @param refreshButtonText The button text. Defaults to 'Refresh'.
+ * @param deletionConfirmRequired Set to TRUE to force the user to confirm
+ * the deletion request. Defaults to TRUE.
+ * @param deletionWaitMsg The message displayed during the deletion process.
+ * @param mode The mode how to retrieve the data displayed in the grid panel.
+ * This can be 'local' or 'remote' which means the data is requested via
+ * RPC. Defaults to 'remote'.
+ * @param rememberSelected TRUE to reselect the previous selected rows
+ * after the grid content has been reloaded/refreshed. Defaults to FALSE.
+ */
+Ext.define("OMV.module.admin.services.zfs.TreePanel", {
+ extend: "OMV.tree.Panel",
+ requires: [
+ "OMV.window.MessageBox",
+ "OMV.grid.column.BinaryUnit",
+ "OMV.grid.column.BooleanIcon",
+ "OMV.grid.column.BooleanText",
+ "OMV.grid.column.Empty",
+ "OMV.grid.column.Hyperlink",
+ "OMV.grid.column.UnixTimestamp",
+ "OMV.grid.column.WhiteSpace"
+ ],
+
+ border: false,
+ rowLines: false,
+ columnLines: true,
+ selModel: {
+ allowDeselect: true,
+ mode: "SINGLE"
+ },
+
+ hideTopToolbar: false,
+ hidePagingToolbar: true,
+ hideAddButton: false,
+ hideAddObjButton: false,
+ hideEditButton: false,
+ hideDeleteButton: false,
+ hideUpButton: true,
+ hideDownButton: true,
+ hideApplyButton: true,
+ hideRefreshButton: true,
+ addButtonText: _("Add Pool"),
+ addObjButtonText: _("Add Object"),
+ editButtonText: _("Edit"),
+ deleteButtonText: _("Delete"),
+ upButtonText: _("Up"),
+ downButtonText: _("Down"),
+ applyButtonText: _("Save"),
+ refreshButtonText: _("Refresh"),
+ deletionConfirmRequired: true,
+ deletionWaitMsg: _("Deleting selected item(s)"),
+ mode: "remote",
+ rememberSelected: false,
+
+ initComponent: function() {
+ var me = this;
+ // Initialize toolbars.
+ me.dockedItems = [];
+ if(!me.hideTopToolbar) {
+ me.dockedItems.push(me.topToolbar = Ext.widget({
+ xtype: "toolbar",
+ dock: "top",
+ items: me.getTopToolbarItems(me)
+ }));
+ }
+ if(!me.hidePagingToolbar) {
+ me.dockedItems.push({
+ xtype: "toolbar",
+ dock: "bottom",
+ items: [ me.pagingToolbar = Ext.widget({
+ xtype: "pagingtoolbar",
+ store: me.store,
+ displayInfo: true,
+ displayMsg: _("Displaying items {0} - {1} of {2}"),
+ emptyMsg: _("No items to display")
+ }) ]
+ });
+ }
+ me.callParent(arguments);
+ // Register event handler.
+ // Process double clicks in grid.
+ me.on("itemdblclick", me.onItemDblClick, me);
+ // Process selections in grid, e.g. to update the toolbar.
+ var selModel = me.getSelectionModel();
+ selModel.on("selectionchange", me.onSelectionChange, me);
+ // Remember selection to restore it after the grid has been
+ // refreshed.
+ if(me.rememberSelected) {
+ me.getStore().on("beforeload", function() {
+ if(!me.rendered || Ext.isEmpty(me.getEl()))
+ return;
+ if(!selModel.hasSelection())
+ return;
+ me.previousSelected = selModel.getSelection();
+ });
+ me.getView().on("refresh", function(view) {
+ if(Ext.isEmpty(me.previousSelected))
+ return;
+ var select = [];
+ Ext.Array.each(me.previousSelected, function(r) {
+ var record = me.getStore().getById(r.getId());
+ if(!Ext.isEmpty(record))
+ select.push(record);
+ });
+ delete me.previousSelected;
+ if(select.length > 0) {
+ selModel.select(select, false, false);
+ selModel.view.focusNode(select[0]);
+ }
+ });
+ }
+ },
+
+ /**
+ * Returns the items displayed in the top toolbar.
+ * @param c This component object.
+ * @return An array of buttons displayed in the top toolbar.
+ */
+ getTopToolbarItems: function(c) {
+ var me = this;
+ return [{
+ id: me.getId() + "-add",
+ xtype: "button",
+ text: me.addButtonText,
+ icon: "images/add.png",
+ iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
+ hidden: me.hideAddButton,
+ handler: Ext.Function.bind(me.onAddButton, me, [ me ]),
+ scope: me
+ },{
+ id: me.getId() + "-addobj",
+ xtype: "button",
+ text: me.addObjButtonText,
+ icon: "images/add.png",
+ iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
+ hidden: me.hideAddObjButton,
+ handler: Ext.Function.bind(me.onAddObjButton, me, [ me ]),
+ scope: me,
+ disabled: true
+ },{
+ id: me.getId() + "-edit",
+ xtype: "button",
+ text: me.editButtonText,
+ icon: "images/edit.png",
+ iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
+ hidden: me.hideEditButton,
+ handler: Ext.Function.bind(me.onEditButton, me, [ me ]),
+ scope: me,
+ disabled: true
+ },{
+ id: me.getId() + "-delete",
+ xtype: "button",
+ text: me.deleteButtonText,
+ icon: "images/delete.png",
+ iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
+ hidden: me.hideDeleteButton,
+ handler: Ext.Function.bind(me.onDeleteButton, me, [ me ]),
+ scope: me,
+ disabled: true
+ },{
+ id: me.getId() + "-up",
+ xtype: "button",
+ text: me.upButtonText,
+ icon: "images/arrow-up.png",
+ iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
+ hidden: me.hideUpButton,
+ handler: Ext.Function.bind(me.onUpButton, me, [ me ]),
+ scope: me,
+ disabled: true
+ },{
+ id: me.getId() + "-down",
+ xtype: "button",
+ text: me.downButtonText,
+ icon: "images/arrow-down.png",
+ iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
+ hidden: me.hideDownButton,
+ handler: Ext.Function.bind(me.onDownButton, me, [ me ]),
+ scope: me,
+ disabled: true
+ },{
+ id: me.getId() + "-apply",
+ xtype: "button",
+ text: me.applyButtonText,
+ icon: "images/checkmark.png",
+ hidden: me.hideApplyButton,
+ handler: Ext.Function.bind(me.onApplyButton, me, [ me ]),
+ scope: me
+ },{
+ id: me.getId() + "-refresh",
+ xtype: "button",
+ text: me.refreshButtonText,
+ icon: "images/refresh.png",
+ iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
+ hidden: me.hideRefreshButton,
+ handler: Ext.Function.bind(me.onRefreshButton, me, [ me ]),
+ scope: me
+ }]
+ },
+
+ /**
+ * Handler that is called whenever the selection in the grid has
+ * been changed. The top toolbar buttons will be enabled/disabled
+ * depending on how much rows has been selected.
+ * @param model The selection model
+ */
+ onSelectionChange: function(model, records) {
+ var me = this;
+ if(me.hideTopToolbar)
+ return;
+ var tbarBtnName = [ "addobj", "edit", "delete", "up", "down" ];
+ var tbarBtnDisabled = {
+ "addobj": false,
+ "edit": false,
+ "delete": false,
+ "up": true,
+ "down": true
+ };
+ // Enable/disable buttons depending on the number of selected rows.
+ if(records.length <= 0) {
+ tbarBtnDisabled["addobj"] = true;
+ tbarBtnDisabled["edit"] = true;
+ tbarBtnDisabled["delete"] = true;
+ tbarBtnDisabled["up"] = true;
+ tbarBtnDisabled["down"] = true;
+ } else if(records.length == 1) {
+ tbarBtnDisabled["addobj"] = false;
+ tbarBtnDisabled["edit"] = false;
+ tbarBtnDisabled["delete"] = false;
+ tbarBtnDisabled["up"] = false;
+ tbarBtnDisabled["down"] = false;
+ } else {
+ tbarBtnDisabled["addobj"] = true;
+ tbarBtnDisabled["edit"] = true;
+ tbarBtnDisabled["delete"] = false;
+ tbarBtnDisabled["up"] = false;
+ tbarBtnDisabled["down"] = false;
+ }
+ //Disable 'AddObj' button if selected row is a Poool or a Snapshot
+ Ext.Array.each(records, function(record) {
+ if(("Pool" == record.get("type")) ||
+ ("Snapshot" == record.get("type"))) {
+ tbarBtnDisabled["addobj"] = true;
+ return false;
+ }
+ });
+
+ // Disable 'Delete' button if a selected node is not a leaf
+ Ext.Array.each(records, function(record) {
+ if((false == record.get("leaf"))) {
+ tbarBtnDisabled["delete"] = true;
+ return false;
+ }
+ });
+
+ // Update the button controls.
+ Ext.Array.each(tbarBtnName, function(name) {
+ var tbarBtnCtrl = me.queryById(me.getId() + "-" + name);
+ if(!Ext.isEmpty(tbarBtnCtrl)) {
+ if(true == tbarBtnDisabled[name]) {
+ tbarBtnCtrl.disable();
+ } else {
+ tbarBtnCtrl.enable();
+ }
+ }
+ });
+ },
+
+ onItemDblClick: function() {
+ var me = this;
+ if(!me.hideTopToolbar && !me.hideEditButton) {
+ me.onEditButton(me);
+ }
+ },
+
+ /**
+ * Load the grid content.
+ */
+ doLoad: function() {
+ var me = this;
+ if(me.mode === "remote") {
+ me.store.load();
+ }
+ },
+
+ /**
+ * Reload the grid content.
+ */
+ doReload: function() {
+ var me = this;
+ if(me.mode === "remote") {
+ me.store.reload();
+ }
+ },
+
+ /**
+ * Handler that is called when the 'Add' button in the top toolbar
+ * is pressed. Override this method to customize the default behaviour.
+ * @param this The grid itself.
+ */
+ onAddButton: function() {
+ // Nothing to do here
+ },
+
+ /**
+ * * Handler that is called when the 'AddObj' button in the top toolbar
+ * is pressed. Override this method to customize the default behaviour.
+ * @param this The grid itself.
+ */
+ onAddObjButton: function() {
+ // Nothing to do here
+ },
+
+
+ /**
+ * Handler that is called when the 'Edit' button in the top toolbar
+ * is pressed. Override this method to customize the default behaviour.
+ * @param this The grid itself.
+ */
+ onEditButton: function() {
+ // Nothing to do here
+ },
+
+ /**
+ * Handler that is called when the 'Up' button in the top toolbar
+ * is pressed. Override this method to customize the default behaviour.
+ * @param this The grid itself.
+ */
+ onUpButton: function() {
+ var me = this;
+ var sm = me.getSelectionModel();
+ var records = sm.getSelection();
+ if(records.length > 0) {
+ // Find the smallest index of the selected rows.
+ var ltIndex = me.store.indexOf(records[0]);
+ Ext.Array.each(records, function(record) {
+ var index = me.store.indexOf(record);
+ if(ltIndex > index)
+ ltIndex = index;
+ });
+ // Calculate the index where to insert the rows.
+ var index = ltIndex - 1;
+ if(index < 0)
+ index = 0;
+ me.doMoveRows(records, index);
+ }
+ },
+
+ /**
+ * Handler that is called when the 'Down' button in the top toolbar
+ * is pressed.
+ * @param this The grid itself.
+ */
+ onDownButton: function() {
+ var me = this;
+ var sm = me.getSelectionModel();
+ var records = sm.getSelection();
+ if(records.length > 0) {
+ // Find the smallest index of the selected rows.
+ var ltIndex = me.store.indexOf(records[0]);
+ Ext.Array.each(records, function(record) {
+ var index = me.store.indexOf(record);
+ if(ltIndex > index)
+ ltIndex = index;
+ });
+ // Calculate the index where to insert the rows.
+ var index = ltIndex + records.length;
+ var count = me.store.getCount() - 1;
+ if(index > count)
+ index = count;
+ me.doMoveRows(records, index);
+ }
+ },
+
+ /**
+ * Handler that is called when the 'Apply' button in the top toolbar
+ * is pressed. Override this method to customize the default behaviour.
+ * @param this The grid itself.
+ */
+ onApplyButton: function() {
+ // Nothing to do here
+ },
+
+ /**
+ * Handler that is called when the 'Refresh' button in the top toolbar
+ * is pressed. Override this method to customize the default behaviour.
+ * @param this The grid itself.
+ */
+ onRefreshButton: function() {
+ this.doReload();
+ },
+
+ /**
+ * Move the given rows to the given index.
+ * @param records The records to move.
+ * @param index The index where to insert the rows to be moved.
+ */
+ doMoveRows: function(records, index) {
+ var me = this;
+ if(!Ext.isNumber(index))
+ return;
+ records = Ext.Array.from(records);
+ me.store.suspendEvents();
+ Ext.Array.each(records, function(record) {
+ me.store.remove(record);
+ me.store.insert(index, record);
+ });
+ me.store.resumeEvents();
+ me.afterMoveRows(records, index);
+ me.getView().refresh();
+ },
+
+ /**
+ * Function that is called after the selected rows have been moved.
+ * Override this method to customize the default behaviour.
+ * @param records The records that have been move.
+ * @param index The index where the rows have been inserted.
+ */
+ afterMoveRows: function(records, index) {
+ var sm = this.getSelectionModel();
+ sm.select(records);
+ },
+
+ /**
+ * Handler that is called when the 'Delete' button in the top toolbar
+ * is pressed.
+ */
+ onDeleteButton: function() {
+ var me = this;
+ var sm = me.getSelectionModel();
+ var records = sm.getSelection();
+ if(me.deletionConfirmRequired === true) {
+ var msg = _("Do you really want to delete the selected item(s)?");
+ OMV.MessageBox.show({
+ title: _("Confirmation"),
+ msg: msg,
+ buttons: Ext.Msg.YESNO,
+ fn: function(answer) {
+ if(answer !== "yes")
+ return;
+ me.startDeletion(records);
+ },
+ scope: me,
+ icon: Ext.Msg.QUESTION
+ });
+ } else {
+ me.startDeletion(records);
+ }
+ },
+
+ /**
+ * @private
+ * Private method that is called when the deletion of the selected records
+ * has been aggreed.
+ * @param records The records to delete.
+ */
+ startDeletion: function(records) {
+ var me = this;
+ if(records.length <= 0)
+ return;
+ // Store selected records in a local variable
+ me.delActionInfo = {
+ records: records,
+ count: records.length
+ }
+ // Get first record to be deleted
+ var record = me.delActionInfo.records.pop();
+ // Display progress dialog
+ OMV.MessageBox.progress("", me.deletionWaitMsg, "");
+ me.updateDeletionProgress();
+ // Execute deletion function
+ me.doDeletion(record);
+ },
+
+ /**
+ * The method that is called to delete a selected record. Override this
+ * method to customize the default behaviour. This is necessary in
+ * 'remote' mode.
+ */
+ doDeletion: function(record) {
+ var me = this;
+ if(me.mode === "local") {
+ // Remove record from store
+ me.store.remove(record);
+ // Continue deletion process
+ me.onDeletion(null, true, null);
+ }
+ },
+
+ /**
+ * The method that is called by the 'doDeletion' method. The progress
+ * bar will be updated and the deletion progress will be continued if
+ * there are still records to delete.
+ */
+ onDeletion: function(id, success, response) {
+ var me = this;
+ if(!success) {
+ // Remove temporary local variables
+ delete me.delActionInfo;
+ // Hide progress dialog
+ OMV.MessageBox.hide();
+ // Display error message
+ OMV.MessageBox.error(null, response);
+ } else {
+ if(me.delActionInfo.records.length > 0) {
+ var record = me.delActionInfo.records.pop();
+ // Update progress dialog
+ me.updateDeletionProgress();
+ // Execute deletion function
+ me.doDeletion(record);
+ } else {
+ // Remove temporary local variables
+ delete me.delActionInfo;
+ // Update and hide progress dialog
+ OMV.MessageBox.updateProgress(1, _("100% completed ..."));
+ OMV.MessageBox.hide();
+ me.afterDeletion();
+ }
+ }
+ },
+
+ /**
+ * Function that is called after the deletion has been successful finished.
+ */
+ afterDeletion: function() {
+ var me = this;
+ if(me.mode === "remote") {
+ me.doReload();
+ }
+ },
+
+ /**
+ * @private
+ * Private helper function to update the progress dialog.
+ */
+ updateDeletionProgress: function() {
+ var me = this;
+ // Calculate percentage
+ var p = (me.delActionInfo.count - me.delActionInfo.records.length) /
+ me.delActionInfo.count;
+ // Create message text
+ var text = Math.round(100 * p) + _("% completed ...");
+ // Update progress dialog
+ OMV.MessageBox.updateProgress(p, text);
+ },
+
+ /**
+ * Convenience function for setting the given toolbar button
+ * disabled/enabled.
+ * @param name The name of the toolbar button.
+ * @param disabled TRUE to disable the button, FALSE to enable.
+ * @return The button component, otherwise FALSE.
+ */
+ setToolbarButtonDisabled: function(name, disabled) {
+ var me = this;
+ var result = false;
+ var btnCtrl = me.queryById(me.getId() + "-" + name);
+ if(!Ext.isEmpty(btnCtrl) && btnCtrl.isButton)
+ result = btnCtrl.setDisabled(disabled);
+ return result;
+ },
+
+ /**
+ * Helper function to get the top toolbar object.
+ * @return The paging toolbar object or NULL.
+ */
+ getTopToolbar: function() {
+ return this.topToolbar;
+ },
+
+ /**
+ * Helper function to get the paging toolbar object.
+ * @return The paging toolbar object or NULL.
+ */
+ getPagingToolbar: function() {
+ return this.pagingToolbar;
+ }
+});
+
+
+
--- /dev/null
+OMV.WorkspaceManager.registerNode({
+ id : "zfs",
+ path : "/service",
+ text : _("ZFS"),
+ icon16 : "images/zfs.png",
+ iconSvg : "images/zfs.svg"
+});
+
--- /dev/null
+<?php
+
+require_once("openmediavault/object.inc");
+require_once("openmediavault/config.inc");
+require_once("openmediavault/error.inc");
+require_once("openmediavault/util.inc");
+require_once("openmediavault/rpcservice.inc");
+require_once("openmediavault/notify.inc");
+require_once("zfs/Utils.php");
+require_once("zfs/Dataset.php");
+require_once("zfs/Snapshot.php");
+require_once("zfs/Zvol.php");
+
+class OMVRpcServiceZFS extends OMVRpcServiceAbstract {
+ public function getName() { return "ZFS";} // RPC Service name. Same as in .js files
+
+ /* Initialize the RPC service. Different methods of the RPC service are declared here*/
+ public function initialize() {
+ $this->registerMethod("getObjectTree");
+ $this->registermethod("passParam");
+ $this->registermethod("addObject");
+ $this->registermethod("deleteObject");
+ $this->registermethod("getProperties");
+ $this->registermethod("setProperties");
+ $this->registermethod("inherit");
+ $this->registermethod("getSharedParams");
+ $this->registermethod("createShare");
+ }
+
+ public function getObjectTree($params, $context) {
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+ $objects = OMVModuleZFSUtil::getZFSFlatArray();
+ $new = array();
+ foreach ($objects as $a){
+ $new[$a['parentid']][] = $a;
+ }
+ $tree = OMVModuleZFSUtil::createTree($new, $new['root']);
+ return $tree;
+ }
+
+ public function passParam($params, $context) {
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+ //$msg = "Key=" . $params['key'] . ";Value=" . $params['value'] . ";";
+ //throw new OMVModuleZFSException($msg);
+ return array($params['key'] => $params['value']);
+ }
+
+ public function addObject($params, $context) {
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+ switch ($params['type']) {
+ case "filesystem":
+ $name = $params['path'] . "/" . $params['name'];
+ $tmp = new OMVModuleZFSDataset($name);
+ break;
+ case "snapshot":
+ $name = $params['path'] . "@" . $params['name'];
+ $tmp = new OMVModuleZFSSnapshot($name);
+ break;
+ case "volume":
+ $name = $params['path'] . "/" . $params['name'];
+ $tmp = new OMVModuleZFSZvol($name);
+ $tmp->create($params['size']);
+ break;
+ default:
+ throw new OMVModuleZFSException("Illegal type provided: " . $params['type']);
+ break;
+ }
+ }
+
+ public function deleteObject($params, $context) {
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+ switch ($params['type']) {
+ case "Filesystem" || "Clone":
+ $name = $params['name'];
+ $tmp = new OMVModuleZFSDataset($name);
+ $tmp->destroy();
+ break;
+ case "Snapshot":
+ $name = $params['name'];
+ $tmp = new OMVModuleZFSSnapshot($name);
+ $tmp->destroy();
+ break;
+ case "Volume":
+ $name = $params['name'];
+ $tmp = new OMVModuleZFSZvol($name);
+ $tmp->destroy();
+ break;
+ default:
+ throw new OMVModuleZFSException("Illegal type provided: " . $params['type']);
+ break;
+ }
+ }
+
+ public function getProperties($params, $context) {
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+ $objects = array();
+ $name = $params['name'];
+ switch ($params['type']) {
+ case "Filesystem" || "Clone":
+ $tmp = new OMVModuleZFSDataset($name);
+ break;
+ case "Snapshot":
+ $tmp = new OMVModuleZFSSnapshot($name);
+ break;
+ case "Volume":
+ $tmp = new OMVModuleZFSZvol($name);
+ break;
+ default:
+ throw new OMVModuleZFSException("Illegal type provided: " . $params['type']);
+ break;
+ }
+ $properties = $tmp->getProperties();
+ foreach ($properties as $propertyk => $propertyv) {
+ if (!(strcmp($propertyv['source'], "-") == 0)) {
+ $objects[] = array('property' => $propertyk,
+ 'value' => $propertyv['value'],
+ 'source' => $propertyv['source'],
+ 'modified' => "false");
+ }
+ }
+ return $objects;
+ }
+
+ public function setProperties($params, $context) {
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+ $objects = array();
+ switch ($params['type']) {
+ case "Filesystem" || "Clone":
+ $tmp = new OMVModuleZFSDataset($params['name']);
+ break;
+ case "Snapshot":
+ $tmp = new OMVModuleZFSSnapshot($params['name']);
+ break;
+ case "Volume":
+ $tmp = new OMVModuleZFSZvol($params['name']);
+ break;
+ default:
+ throw new OMVModuleZFSException("Illegal type provided: " . $params['type']);
+ break;
+ }
+ foreach ($params['properties'] as $property) {
+ $objects[$property['property']] = $property['value'];
+ }
+ $tmp->setProperties($objects);
+ }
+
+ public function inherit($params, $context) {
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+ // Create a background process.
+ $bgStatusFilename = $this->createBgProcStatus();
+ $pid = $this->fork();
+ if($pid > 0) { // Parent process.
+ $this->initializeBgProcStatus($bgStatusFilename, $pid);
+ return $bgStatusFilename;
+ }
+ // Child process.
+ try {
+ $bgOutputFilename = $this->createBgProcOutput();
+ $this->updateBgProcStatus($bgStatusFilename, "outputfilename", $bgOutputFilename);
+ switch ($params['type']) {
+ case "Filesystem" || "Clone":
+ $tmp = new OMVModuleZFSDataset($params['name']);
+ break;
+ case "Snapshot":
+ $tmp = new OMVModuleZFSSnapshot($params['name']);
+ break;
+ case "Volume":
+ $tmp = new OMVModuleZFSZvol($params['name']);
+ break;
+ default:
+ throw new OMVModuleZFSException("Illegal type provided: " . $params['type']);
+ break;
+ }
+ $tmp->inherit($params['property']);
+ $this->finalizeBgProcStatus($bgStatusFilename, $output);
+ exit(0);
+ } catch(Exception $e) {
+ $this->finalizeBgProcStatus($bgStatusFilename, "", $e);
+ exit(1);
+ }
+ }
+
+ public function getSharedParams($params, $context) {
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+ $objects = array();
+ $ds = new OMVModuleZFSDataset($params['name']);
+ $mountpoint = $ds->getMountPoint();
+ return array(
+ "mountpoint" => $mountpoint,
+ "name" => $params['name'],
+ "type" => $params['type']);
+ }
+
+ public function createShare($params, $context) {
+ global $xmlConfig;
+ $this->validateMethodContext($context, array("role" => OMV_ROLE_ADMINISTRATOR));
+
+ //Get the UUID of the Pool
+ $pooluuid = OMVModuleZFSUtil::getUUIDbyName($params['name']);
+ preg_match('/^([A-Za-z0-9]+)\/?.*$/', $params['name'], $result);
+ $poolname = $result[1];
+ unset($result);
+
+ //Check if the UUID is already stored as an mntent object. If it isn't then create it.
+ $xpath = "//system/fstab/mntent[fsname=" . $pooluuid . "]";
+ $object = $xmlConfig->get($xpath);
+ if(is_null($object)) {
+ $uuid = OMVUtil::uuid();
+ $ds = new OMVModuleZFSDataset($poolname);
+ $dir = $ds->getMountPoint();
+ $object = array(
+ "uuid" => $uuid,
+ "fsname" => $pooluuid,
+ "dir" => $dir,
+ "type" => "zfs",
+ "opts" => "rw,relatime,xattr",
+ "freq" => "0",
+ "passno" => "2"
+ );
+ $xmlConfig->set("//system/fstab",array("mntent" => $object));
+ $dispatcher = &OMVNotifyDispatcher::getInstance();
+ $dispatcher->notify(OMV_NOTIFY_CREATE,"org.openmediavault.system.fstab.mntent", $object);
+ }
+
+ //Get the mntent object and fetch it's uuid.
+ $object = $xmlConfig->get($xpath);
+ $mntentref = $object['uuid'];
+
+ // Prepare the configuration object. Use the name of the shared
+ // folder as the relative directory name of the share.
+ switch ($params['type']) {
+ case "Filesystem" || "Clone":
+ $tmp = new OMVModuleZFSDataset($name);
+ break;
+ default:
+ throw new OMVModuleZFSException("Illegal type provided: " . $params['type']);
+ break;
+ }
+
+ $uuid = OMVUtil::uuid();
+ $pathName = $tmp->getMountPoint();
+ $subdirs = preg_split('/\//',$pathName);
+ $reldirpath = $subdirs[count($subdirs)-1];
+ $object = array(
+ "uuid" => $uuid,
+ "name" => $params['sharename'],
+ "comment" => $params['comment'],
+ "mntentref" => $mntentref,
+ "reldirpath" => $reldirpath
+ );
+
+ // Set the configuration object.
+ $success = FALSE;
+ // Check uniqueness. The share name must be global unique because
+ // the name is also used when exporting a shared folder via NFS for
+ // example.
+ $xpath = sprintf("//system/shares/sharedfolder[name='%s']",
+ $params['name']);
+ if(TRUE === $xmlConfig->exists($xpath)) {
+ throw new OMVException(OMVErrorMsg::E_CONFIG_OBJECT_UNIQUENESS,
+ gettext("A shared folder with the given name already exists"));
+ }
+
+ // Add empty list of privileges per default.
+ $object['privileges'] = array();
+
+ // Append object to configuration.
+ $success = $xmlConfig->set("//system/shares",
+ array("sharedfolder" => $object));
+ if(FALSE === $success) {
+ throw new OMVException(OMVErrorMsg::E_CONFIG_SET_OBJECT_FAILED);
+ }
+
+ // Append the file mode field to the notification object if set.
+ // Defaults to 775.
+ $object['mode'] = "775";
+ if(array_key_exists("mode", $params)) {
+ $object['mode'] = $params['mode'];
+ }
+
+ // Change group owner of directory to configured default group,
+ // e.g. 'users'.
+ if(FALSE === chgrp($pathName, $GLOBALS['OMV_USERMGMT_DEFAULT_GROUP'])) {
+ throw new OMVException(OMVErrorMsg::E_MISC_FAILURE,
+ sprintf("Failed to set file group to '%s' for '%s'",
+ $GLOBALS['OMV_USERMGMT_DEFAULT_GROUP'], $pathName));
+ }
+
+ // Set the setgid bit. Setting this permission means that all files
+ // created in the folder will inherit the group of the folder rather
+ // than the primary group of the user who creates the file.
+ $mode = fileperms($pathName) | 02000;
+ if(FALSE === chmod($pathName, $mode)) {
+ throw new OMVException(OMVErrorMsg::E_MISC_FAILURE,
+ sprintf("Failed to set file mode to '%o' for '%s'",
+ $mode, $pathName));
+ }
+
+ // Notify configuration changes.
+ $dispatcher = &OMVNotifyDispatcher::getInstance();
+ $dispatcher->notify(OMV_NOTIFY_CREATE,"org.openmediavault.system.shares.sharedfolder", $object);
+ // Return the configuration object.
+ return $object;
+
+ }
+
+}
+
+// Register the RPC service.
+$rpcServiceMgr = &OMVRpcServiceMgr::getInstance(); // Get the "root" instance for the Services
+$rpcServiceMgr->registerService(new OMVRpcServiceZFS()); // Register a new instance of the RPC service described above
+?>
+
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="32.000000pt" height="32.000000pt" viewBox="0 0 32.000000 32.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,32.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M0 160 l0 -160 160 0 160 0 0 160 0 160 -160 0 -160 0 0 -160z m300
+0 l0 -140 -140 0 -140 0 0 140 0 140 140 0 140 0 0 -140z"/>
+<path d="M64 219 c-29 -30 -29 -30 -4 -21 19 7 42 5 89 -11 l63 -20 -81 -25
+c-44 -14 -82 -31 -85 -39 -3 -8 -2 -12 3 -9 4 3 33 0 63 -6 63 -13 103 -6 118
+23 10 19 10 20 -9 10 -23 -13 -80 -14 -99 -3 -8 6 6 13 39 20 29 7 68 22 87
+34 l36 22 -50 11 c-27 7 -66 19 -88 28 -50 22 -47 22 -82 -14z"/>
+</g>
+</svg>
--- /dev/null
+openmediavault-zfs
+==================
--- /dev/null
+# -*- mode: makefile; coding: utf-8 -*-
+#
+# This file is part of OpenMediaVault.
+#
+# @license http://www.gnu.org/licenses/gpl.html GPL Version 3
+# @author Volker Theile <volker.theile@openmediavault.org>
+# @copyright Copyright (c) 2009-2013 Volker Theile
+#
+# OpenMediaVault is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# any later version.
+#
+# OpenMediaVault is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OpenMediaVault. If not, see <http://www.gnu.org/licenses/>.
+
+OMV_PACKAGE := $(shell pwd | sed 's|.*/||')
+OMV_POT_DIR := $(CURDIR)/usr/share/openmediavault/locale
+OMV_POT_FILE := $(OMV_PACKAGE).pot
+OMV_TRANSIFEX_SLUG := $(OMV_PACKAGE)pot
+
+omv_pull_po:
+ tx --root="$(CURDIR)/../" pull --all \
+ --resource=$(OMV_PACKAGE).$(OMV_TRANSIFEX_SLUG)
+
+omv_push_pot:
+ tx --root="$(CURDIR)/../" push --source \
+ --resource=$(OMV_PACKAGE).$(OMV_TRANSIFEX_SLUG)
+
+omv_build_pot:
+ dh_testdir
+ dh_clean
+ echo "Building PO template file ..." >&2
+ mkdir -p $(OMV_POT_DIR)
+ find $(CURDIR) \( -iname *.js -o -iname *.php -o -iname *.inc \) \
+ -type f -print0 | xargs -0r xgettext --keyword=_ \
+ --output-dir=$(OMV_POT_DIR) --output=$(OMV_POT_FILE) \
+ --force-po --no-location --no-wrap --sort-output \
+ --package-name=$(OMV_PACKAGE) -
+ # Remove '#, c-format' comments, otherwise manuall upload of translation
+ # files confuses Transifex.
+ sed --in-place '/^#, c-format/d' $(OMV_POT_DIR)/$(OMV_POT_FILE)
+
+omv_clean_scm:
+ dh_testdir
+ echo "Removing SCM files ..." >&2
+ find $(CURDIR)/debian/$(OMV_PACKAGE) \( -name .svn -o -name .git \) \
+ -type d -print0 -prune | xargs -0r rm -rf
+
+omv_build_doc: debian/doxygen.conf
+ mkdir -p debian/doxygen
+ doxygen $<
+
+source: clean
+ dpkg-buildpackage -S -us -uc
+
+.PHONY: omv_pull_po omv_push_pot omv_build_pot omv_clean_scm omv_build_doc
+.PHONY: source