First checkin of Bacula module
authorJamie Cameron <jcameron@webmin.com>
Wed, 27 Jun 2007 21:04:56 +0000 (21:04 +0000)
committerJamie Cameron <jcameron@webmin.com>
Wed, 27 Jun 2007 21:04:56 +0000 (21:04 +0000)
182 files changed:
bacula-backup/BaculaNode.class [new file with mode: 0644]
bacula-backup/BorderPanel.class [new file with mode: 0644]
bacula-backup/BorderPanel.java [new symlink]
bacula-backup/CHANGELOG [new file with mode: 0644]
bacula-backup/CbButton.class [new file with mode: 0644]
bacula-backup/CbButton.java [new symlink]
bacula-backup/CbButtonCallback.class [new file with mode: 0644]
bacula-backup/CbButtonGroup.class [new file with mode: 0644]
bacula-backup/CbScrollbar.class [new file with mode: 0644]
bacula-backup/CbScrollbar.java [new symlink]
bacula-backup/CbScrollbarArrow.class [new file with mode: 0644]
bacula-backup/CbScrollbarCallback.class [new file with mode: 0644]
bacula-backup/ErrorWindow.class [new file with mode: 0644]
bacula-backup/ErrorWindow.java [new symlink]
bacula-backup/FixedFrame.class [new file with mode: 0644]
bacula-backup/FixedFrame.java [new symlink]
bacula-backup/GrayPanel.class [new file with mode: 0644]
bacula-backup/GrayPanel.java [new symlink]
bacula-backup/Hierarchy.class [new file with mode: 0644]
bacula-backup/Hierarchy.java [new symlink]
bacula-backup/HierarchyCallback.class [new file with mode: 0644]
bacula-backup/HierarchyNode.class [new file with mode: 0644]
bacula-backup/Makefile [new file with mode: 0644]
bacula-backup/TreeChooser.class [new file with mode: 0644]
bacula-backup/TreeChooser.java [new file with mode: 0644]
bacula-backup/Util.class [new file with mode: 0644]
bacula-backup/Util.java [new symlink]
bacula-backup/apply.cgi [new file with mode: 0755]
bacula-backup/backup.cgi [new file with mode: 0755]
bacula-backup/backup_form.cgi [new file with mode: 0755]
bacula-backup/bacula-backup-lib.pl [new file with mode: 0644]
bacula-backup/bootup.cgi [new file with mode: 0755]
bacula-backup/cancel_jobs.cgi [new file with mode: 0755]
bacula-backup/clientstatus_form.cgi [new file with mode: 0755]
bacula-backup/config [new file with mode: 0644]
bacula-backup/config-windows [new file with mode: 0644]
bacula-backup/config.info [new file with mode: 0644]
bacula-backup/delete_clients.cgi [new file with mode: 0755]
bacula-backup/delete_fdirectors.cgi [new file with mode: 0755]
bacula-backup/delete_filesets.cgi [new file with mode: 0755]
bacula-backup/delete_gjobs.cgi [new file with mode: 0755]
bacula-backup/delete_groups.cgi [new file with mode: 0755]
bacula-backup/delete_jobs.cgi [new file with mode: 0755]
bacula-backup/delete_pools.cgi [new file with mode: 0755]
bacula-backup/delete_schedules.cgi [new file with mode: 0755]
bacula-backup/delete_sdirectors.cgi [new file with mode: 0755]
bacula-backup/delete_storages.cgi [new file with mode: 0755]
bacula-backup/delete_volumes.cgi [new file with mode: 0755]
bacula-backup/dirstatus_form.cgi [new file with mode: 0755]
bacula-backup/edit_client.cgi [new file with mode: 0755]
bacula-backup/edit_device.cgi [new file with mode: 0755]
bacula-backup/edit_director.cgi [new file with mode: 0755]
bacula-backup/edit_fdirector.cgi [new file with mode: 0755]
bacula-backup/edit_file.cgi [new file with mode: 0755]
bacula-backup/edit_fileset.cgi [new file with mode: 0755]
bacula-backup/edit_gjob.cgi [new file with mode: 0755]
bacula-backup/edit_group.cgi [new file with mode: 0755]
bacula-backup/edit_job.cgi [new file with mode: 0755]
bacula-backup/edit_pool.cgi [new file with mode: 0755]
bacula-backup/edit_schedule.cgi [new file with mode: 0755]
bacula-backup/edit_sdirector.cgi [new file with mode: 0755]
bacula-backup/edit_storage.cgi [new file with mode: 0755]
bacula-backup/edit_storagec.cgi [new file with mode: 0755]
bacula-backup/fixaddr.cgi [new file with mode: 0755]
bacula-backup/fixpass.cgi [new file with mode: 0755]
bacula-backup/gbackup.cgi [new file with mode: 0755]
bacula-backup/help/backup.html [new file with mode: 0644]
bacula-backup/help/clients.html [new file with mode: 0644]
bacula-backup/help/clientstatus.html [new file with mode: 0644]
bacula-backup/help/devices.html [new file with mode: 0644]
bacula-backup/help/director.html [new file with mode: 0644]
bacula-backup/help/dirstatus.html [new file with mode: 0644]
bacula-backup/help/fdirectors.html [new file with mode: 0644]
bacula-backup/help/file.html [new file with mode: 0644]
bacula-backup/help/filesets.html [new file with mode: 0644]
bacula-backup/help/gbackup.html [new file with mode: 0644]
bacula-backup/help/gjobs.html [new file with mode: 0644]
bacula-backup/help/groups.html [new file with mode: 0644]
bacula-backup/help/intro.html [new file with mode: 0644]
bacula-backup/help/jobs.html [new file with mode: 0644]
bacula-backup/help/label.html [new file with mode: 0644]
bacula-backup/help/mount.html [new file with mode: 0644]
bacula-backup/help/pools.html [new file with mode: 0644]
bacula-backup/help/poolstatus.html [new file with mode: 0644]
bacula-backup/help/schedules.html [new file with mode: 0644]
bacula-backup/help/sdirectors.html [new file with mode: 0644]
bacula-backup/help/storagec.html [new file with mode: 0644]
bacula-backup/help/storages.html [new file with mode: 0644]
bacula-backup/help/storagestatus.html [new file with mode: 0644]
bacula-backup/help/sync.html [new file with mode: 0644]
bacula-backup/images/.xvpics/backup.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/clientstatus.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/dirstatus.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/gbackup.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/gjobs.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/jobs.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/label.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/mount.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/restore.gif [new file with mode: 0644]
bacula-backup/images/.xvpics/storagestatus.gif [new file with mode: 0644]
bacula-backup/images/Thumbs.db [new file with mode: 0755]
bacula-backup/images/backup.gif [new file with mode: 0644]
bacula-backup/images/clients.gif [new file with mode: 0644]
bacula-backup/images/clientstatus.gif [new file with mode: 0644]
bacula-backup/images/devices.gif [new file with mode: 0644]
bacula-backup/images/dir.gif [new file with mode: 0644]
bacula-backup/images/director.gif [new file with mode: 0755]
bacula-backup/images/dirstatus.gif [new file with mode: 0755]
bacula-backup/images/fdirectors.gif [new file with mode: 0755]
bacula-backup/images/file.gif [new file with mode: 0644]
bacula-backup/images/filesets.gif [new file with mode: 0644]
bacula-backup/images/gbackup.gif [new file with mode: 0644]
bacula-backup/images/gjobs.gif [new file with mode: 0644]
bacula-backup/images/grestore.gif [new file with mode: 0644]
bacula-backup/images/groups.gif [new file with mode: 0644]
bacula-backup/images/icon.gif [new file with mode: 0644]
bacula-backup/images/jobs.gif [new file with mode: 0644]
bacula-backup/images/label.gif [new file with mode: 0644]
bacula-backup/images/mount.gif [new file with mode: 0644]
bacula-backup/images/pools.gif [new file with mode: 0644]
bacula-backup/images/poolstatus.gif [new file with mode: 0755]
bacula-backup/images/restore.gif [new file with mode: 0644]
bacula-backup/images/rfile.gif [new file with mode: 0644]
bacula-backup/images/schedules.gif [new file with mode: 0644]
bacula-backup/images/sdir.gif [new file with mode: 0644]
bacula-backup/images/sdirectors.gif [new file with mode: 0755]
bacula-backup/images/smallicon.gif [new file with mode: 0644]
bacula-backup/images/srfile.gif [new file with mode: 0644]
bacula-backup/images/storagec.gif [new file with mode: 0644]
bacula-backup/images/storages.gif [new file with mode: 0755]
bacula-backup/images/storagestatus.gif [new file with mode: 0644]
bacula-backup/images/sync.gif [new file with mode: 0644]
bacula-backup/index.cgi [new file with mode: 0755]
bacula-backup/label.cgi [new file with mode: 0755]
bacula-backup/label_form.cgi [new file with mode: 0755]
bacula-backup/lang/en [new file with mode: 0644]
bacula-backup/list.cgi [new file with mode: 0755]
bacula-backup/list_clients.cgi [new file with mode: 0755]
bacula-backup/list_devices.cgi [new file with mode: 0755]
bacula-backup/list_fdirectors.cgi [new file with mode: 0755]
bacula-backup/list_filesets.cgi [new file with mode: 0755]
bacula-backup/list_gbackup.cgi [new file with mode: 0755]
bacula-backup/list_gjobs.cgi [new file with mode: 0755]
bacula-backup/list_grestore.cgi [new file with mode: 0755]
bacula-backup/list_groups.cgi [new file with mode: 0755]
bacula-backup/list_jobs.cgi [new file with mode: 0755]
bacula-backup/list_pools.cgi [new file with mode: 0755]
bacula-backup/list_schedules.cgi [new file with mode: 0755]
bacula-backup/list_sdirectors.cgi [new file with mode: 0755]
bacula-backup/list_storages.cgi [new file with mode: 0755]
bacula-backup/list_sync.cgi [new file with mode: 0755]
bacula-backup/log_parser.pl [new file with mode: 0644]
bacula-backup/manual.sxw [new file with mode: 0755]
bacula-backup/module.info [new file with mode: 0644]
bacula-backup/mount.cgi [new file with mode: 0755]
bacula-backup/mount_form.cgi [new file with mode: 0755]
bacula-backup/poolstatus_form.cgi [new file with mode: 0755]
bacula-backup/restart.cgi [new file with mode: 0755]
bacula-backup/restore.cgi [new file with mode: 0755]
bacula-backup/restore_form.cgi [new file with mode: 0755]
bacula-backup/save_client.cgi [new file with mode: 0755]
bacula-backup/save_device.cgi [new file with mode: 0755]
bacula-backup/save_director.cgi [new file with mode: 0755]
bacula-backup/save_fdirector.cgi [new file with mode: 0755]
bacula-backup/save_file.cgi [new file with mode: 0755]
bacula-backup/save_fileset.cgi [new file with mode: 0755]
bacula-backup/save_gjob.cgi [new file with mode: 0755]
bacula-backup/save_group.cgi [new file with mode: 0755]
bacula-backup/save_job.cgi [new file with mode: 0755]
bacula-backup/save_pool.cgi [new file with mode: 0755]
bacula-backup/save_schedule.cgi [new file with mode: 0755]
bacula-backup/save_sdirector.cgi [new file with mode: 0755]
bacula-backup/save_storage.cgi [new file with mode: 0755]
bacula-backup/save_storagec.cgi [new file with mode: 0755]
bacula-backup/save_sync.cgi [new file with mode: 0755]
bacula-backup/schedule_chooser.cgi [new file with mode: 0755]
bacula-backup/schedule_select.cgi [new file with mode: 0755]
bacula-backup/start.cgi [new file with mode: 0755]
bacula-backup/stop.cgi [new file with mode: 0755]
bacula-backup/storagestatus_form.cgi [new file with mode: 0755]
bacula-backup/sync.pl [new file with mode: 0755]
bacula-backup/treechooser.cgi [new file with mode: 0755]

diff --git a/bacula-backup/BaculaNode.class b/bacula-backup/BaculaNode.class
new file mode 100644 (file)
index 0000000..894e13b
Binary files /dev/null and b/bacula-backup/BaculaNode.class differ
diff --git a/bacula-backup/BorderPanel.class b/bacula-backup/BorderPanel.class
new file mode 100644 (file)
index 0000000..9444caa
Binary files /dev/null and b/bacula-backup/BorderPanel.class differ
diff --git a/bacula-backup/BorderPanel.java b/bacula-backup/BorderPanel.java
new file mode 120000 (symlink)
index 0000000..2a2a302
--- /dev/null
@@ -0,0 +1 @@
+../file/BorderPanel.java
\ No newline at end of file
diff --git a/bacula-backup/CHANGELOG b/bacula-backup/CHANGELOG
new file mode 100644 (file)
index 0000000..9dbe888
--- /dev/null
@@ -0,0 +1,2 @@
+---- Changes since 1.350 ----
+First version of this module, which allows Bacula to be configured and both backups and restores to be executed.
diff --git a/bacula-backup/CbButton.class b/bacula-backup/CbButton.class
new file mode 100644 (file)
index 0000000..6223866
Binary files /dev/null and b/bacula-backup/CbButton.class differ
diff --git a/bacula-backup/CbButton.java b/bacula-backup/CbButton.java
new file mode 120000 (symlink)
index 0000000..7c7c7d0
--- /dev/null
@@ -0,0 +1 @@
+../file/CbButton.java
\ No newline at end of file
diff --git a/bacula-backup/CbButtonCallback.class b/bacula-backup/CbButtonCallback.class
new file mode 100644 (file)
index 0000000..c86fabc
Binary files /dev/null and b/bacula-backup/CbButtonCallback.class differ
diff --git a/bacula-backup/CbButtonGroup.class b/bacula-backup/CbButtonGroup.class
new file mode 100644 (file)
index 0000000..f88eac3
Binary files /dev/null and b/bacula-backup/CbButtonGroup.class differ
diff --git a/bacula-backup/CbScrollbar.class b/bacula-backup/CbScrollbar.class
new file mode 100644 (file)
index 0000000..726106f
Binary files /dev/null and b/bacula-backup/CbScrollbar.class differ
diff --git a/bacula-backup/CbScrollbar.java b/bacula-backup/CbScrollbar.java
new file mode 120000 (symlink)
index 0000000..ff9b1eb
--- /dev/null
@@ -0,0 +1 @@
+../file/CbScrollbar.java
\ No newline at end of file
diff --git a/bacula-backup/CbScrollbarArrow.class b/bacula-backup/CbScrollbarArrow.class
new file mode 100644 (file)
index 0000000..1d814ec
Binary files /dev/null and b/bacula-backup/CbScrollbarArrow.class differ
diff --git a/bacula-backup/CbScrollbarCallback.class b/bacula-backup/CbScrollbarCallback.class
new file mode 100644 (file)
index 0000000..ffa8927
Binary files /dev/null and b/bacula-backup/CbScrollbarCallback.class differ
diff --git a/bacula-backup/ErrorWindow.class b/bacula-backup/ErrorWindow.class
new file mode 100644 (file)
index 0000000..bbeba2e
Binary files /dev/null and b/bacula-backup/ErrorWindow.class differ
diff --git a/bacula-backup/ErrorWindow.java b/bacula-backup/ErrorWindow.java
new file mode 120000 (symlink)
index 0000000..4d9d78e
--- /dev/null
@@ -0,0 +1 @@
+../file/ErrorWindow.java
\ No newline at end of file
diff --git a/bacula-backup/FixedFrame.class b/bacula-backup/FixedFrame.class
new file mode 100644 (file)
index 0000000..80aab47
Binary files /dev/null and b/bacula-backup/FixedFrame.class differ
diff --git a/bacula-backup/FixedFrame.java b/bacula-backup/FixedFrame.java
new file mode 120000 (symlink)
index 0000000..ba13f38
--- /dev/null
@@ -0,0 +1 @@
+../file/FixedFrame.java
\ No newline at end of file
diff --git a/bacula-backup/GrayPanel.class b/bacula-backup/GrayPanel.class
new file mode 100644 (file)
index 0000000..5e0cdb7
Binary files /dev/null and b/bacula-backup/GrayPanel.class differ
diff --git a/bacula-backup/GrayPanel.java b/bacula-backup/GrayPanel.java
new file mode 120000 (symlink)
index 0000000..1e29a7b
--- /dev/null
@@ -0,0 +1 @@
+../file/GrayPanel.java
\ No newline at end of file
diff --git a/bacula-backup/Hierarchy.class b/bacula-backup/Hierarchy.class
new file mode 100644 (file)
index 0000000..8991774
Binary files /dev/null and b/bacula-backup/Hierarchy.class differ
diff --git a/bacula-backup/Hierarchy.java b/bacula-backup/Hierarchy.java
new file mode 120000 (symlink)
index 0000000..ed58e55
--- /dev/null
@@ -0,0 +1 @@
+../file/Hierarchy.java
\ No newline at end of file
diff --git a/bacula-backup/HierarchyCallback.class b/bacula-backup/HierarchyCallback.class
new file mode 100644 (file)
index 0000000..ca85e39
Binary files /dev/null and b/bacula-backup/HierarchyCallback.class differ
diff --git a/bacula-backup/HierarchyNode.class b/bacula-backup/HierarchyNode.class
new file mode 100644 (file)
index 0000000..3622b93
Binary files /dev/null and b/bacula-backup/HierarchyNode.class differ
diff --git a/bacula-backup/Makefile b/bacula-backup/Makefile
new file mode 100644 (file)
index 0000000..37119ee
--- /dev/null
@@ -0,0 +1,2 @@
+TreeChooser.class:     TreeChooser.java
+                       CLASSPATH=/usr/local/netscape7/plugins/java2/lib/javaplugin.jar:. javac -target 1.1 *.java
diff --git a/bacula-backup/TreeChooser.class b/bacula-backup/TreeChooser.class
new file mode 100644 (file)
index 0000000..3a08be2
Binary files /dev/null and b/bacula-backup/TreeChooser.class differ
diff --git a/bacula-backup/TreeChooser.java b/bacula-backup/TreeChooser.java
new file mode 100644 (file)
index 0000000..51fdf28
--- /dev/null
@@ -0,0 +1,295 @@
+import java.awt.*;
+import java.io.*;
+import java.applet.*;
+import java.net.*;
+import java.util.*;
+import netscape.javascript.JSObject;
+
+public class TreeChooser extends Applet
+       implements CbButtonCallback, HierarchyCallback
+{
+       CbButton add_b, remove_b, close_b;
+       Hierarchy tree;
+       BaculaNode root;
+       String volume;
+       String session;
+       String job;
+       Vector added = new Vector();
+
+       public void init()
+       {
+       // Create the root
+       String rpath = getParameter("root");
+       root = new BaculaNode(this, rpath, true, null);
+       volume = getParameter("volume");
+       session = getParameter("session");
+       job = getParameter("job");
+
+       // Build the UI
+       setLayout(new BorderLayout());
+       BorderPanel top = new BorderPanel(2);
+       top.setLayout(new FlowLayout(FlowLayout.LEFT));
+       top.add(add_b = new CbButton("Add", this));
+       top.add(remove_b = new CbButton("Remove", this));
+       top.add(close_b = new CbButton("Close", this));
+       add("North", top);
+       add("Center", tree = new Hierarchy(root, this));
+       }
+
+        Image get_image(String img)
+        {
+        return getImage(getDocumentBase(), "images/"+img);
+        }
+
+        String[] get_text(String url)
+        {
+       Cursor orig = getCursor();
+        try {
+               Cursor busy = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
+               setCursor(busy);
+                long now = System.currentTimeMillis();
+                if (url.indexOf('?') > 0) url += "&rand="+now;
+                else url += "?rand="+now;
+                URL u = new URL(getDocumentBase(), url);
+                URLConnection uc = u.openConnection();
+               set_cookie(uc);
+                String charset = get_charset(uc.getContentType());
+                BufferedReader is = new BufferedReader(
+                        (charset == null) ?
+                        new InputStreamReader(uc.getInputStream()) :
+                        new InputStreamReader(uc.getInputStream(), charset));
+                Vector lv = new Vector();
+                while(true) {
+                        String l = is.readLine();
+                        if (l == null) { break; }
+                        lv.addElement(l);
+                        }
+                is.close();
+                String rv[] = new String[lv.size()];
+                lv.copyInto(rv);
+                return rv;
+                }
+        catch(Exception e) {
+                e.printStackTrace();
+                //return null;
+                String err[] = { e.getMessage() };
+                return err;
+                }
+       finally {
+               setCursor(orig);
+               }
+        }
+
+       void set_cookie(URLConnection conn)
+       {
+       if (session != null)
+               conn.setRequestProperty("Cookie", session);
+       }
+
+        // Gets charset parameter from Content-Type: header
+        String get_charset(String ct)
+        {
+        if (ct == null)
+                return null;
+        StringTokenizer st = new StringTokenizer(ct, ";");
+        while (st.hasMoreTokens()) {
+                String l = st.nextToken().trim().toLowerCase();
+                if (l.startsWith("charset=")) {
+                        // get the value of charset= param.
+                        return l.substring(8);
+                        }
+                }
+        return null;
+        }
+
+       public void openNode(Hierarchy h, HierarchyNode n)
+       {
+       // Get the files under this directory, and expand the tree
+       BaculaNode bn = (BaculaNode)n;
+       bn.fill();
+       }
+
+       public void closeNode(Hierarchy h, HierarchyNode n)
+       {
+       // No need to do anything
+       }
+
+       public void clickNode(Hierarchy h, HierarchyNode n)
+       {
+       // Also no need to do anything
+       }
+
+       public void doubleNode(Hierarchy h, HierarchyNode n)
+       {
+       // add or remove a file
+       BaculaNode sel = (BaculaNode)n;
+       if (sel.added) remove_node(sel);
+       else add_node(sel);
+       }
+
+       public void click(CbButton b)
+       {
+       BaculaNode sel = (BaculaNode)tree.selected();
+       if (b == close_b) {
+               // Close the window, and update the text box
+               try {
+                       JSObject win = JSObject.getWindow(this);
+                       String params1[] = { "" };
+                       win.call("clear_files", params1);
+                       for(int i=0; i<added.size(); i++) {
+                               BaculaNode n = (BaculaNode)added.elementAt(i);
+                               String params2[] = { n.path };
+                               if (n.isdir && !n.path.equals("/"))
+                                       params2[0] = n.path+"/";
+                               win.call("add_file", params2);
+                               }
+                       String params3[] = { "" };
+                       win.call("finished", params3);
+                       }
+               catch(Exception e) {
+                       e.printStackTrace();
+                       new ErrorWindow("Failed to set files : "+
+                                       e.getMessage());
+                       }
+               }
+       else if (b == add_b) {
+               // Flag the selected file as added
+               if (sel != null) {
+                       add_node(sel);
+                       }
+               }
+       else if (b == remove_b) {
+               // Un-flag the selected file
+               if (sel != null) {
+                       remove_node(sel);
+                       }
+               }
+       }
+
+       void add_node(BaculaNode n)
+       {
+       if (!n.added) {
+               n.added = true;
+               n.set_all_icons();
+               tree.redraw();
+               added.addElement(n);
+               }
+       }
+
+       void remove_node(BaculaNode n)
+       {
+       if (n.added) {
+               n.added = false;
+               n.set_all_icons();
+               tree.redraw();
+               added.removeElement(n);
+               }
+       }
+
+       static String urlize(String s)
+       {
+       StringBuffer rv = new StringBuffer();
+       for(int i=0; i<s.length(); i++) {
+               char c = s.charAt(i);
+               if (c < 16)
+                       rv.append("%0"+Integer.toString(c, 16));
+               else if (!Character.isLetterOrDigit(c) && c != '/' &&
+                   c != '.' && c != '_' && c != '-')
+                       rv.append("%"+Integer.toString(c, 16));
+               else
+                       rv.append(c);
+               }
+       return rv.toString();
+       }
+}
+
+class BaculaNode extends HierarchyNode
+{
+       TreeChooser parent;
+       String path;
+       boolean isdir;
+       boolean known = false;
+       boolean added = false;
+       BaculaNode dir;
+
+       BaculaNode(TreeChooser parent, String path, boolean isdir, BaculaNode dir)
+       {
+       this.parent = parent;
+       this.path = path;
+       this.isdir = isdir;
+       this.dir = dir;
+       open = false;
+       set_icon();
+       ch = isdir ? new Vector() : null;
+       if (path.equals("/"))
+               text = "/";
+       else {
+               String ns = path.endsWith("/") ?
+                               path.substring(0, path.length() - 1) : path;
+               int slash = ns.lastIndexOf("/");
+               text = path.substring(slash+1);
+               }
+       }
+
+       void set_icon()
+       {
+       String imname = isdir ? "dir.gif" : "rfile.gif";
+       if (selected()) imname = "s"+imname;
+       im = parent.get_image(imname);
+       }
+
+       void set_all_icons()
+       {
+       set_icon();
+       if (ch != null) {
+               for(int i=0; i<ch.size(); i++) {
+                       BaculaNode c = (BaculaNode)ch.elementAt(i);
+                       c.set_all_icons();
+                       }
+               }
+       }
+
+       void fill()
+       {
+       if (!known && isdir) {
+               ch.removeAllElements();
+               String l[] = parent.get_text("list.cgi?dir="+
+                                            parent.urlize(path)+
+                                            "&volume="+
+                                            parent.urlize(parent.volume)+
+                                            "&job="+
+                                            parent.urlize(parent.job));
+               if (l[0].length() > 0) {
+                       new ErrorWindow("Failed to get files under "+path+
+                                       " : "+l[0]);
+                       return;
+                       }
+               for(int i=1; i<l.length; i++) {
+                       if (l[i].endsWith("/")) {
+                               ch.addElement(
+                                   new BaculaNode(
+                                           parent, l[i].substring(0, l[i].length()-1),
+                                           true, this));
+                               }
+                       else {
+                               ch.addElement(
+                                   new BaculaNode(
+                                       parent, l[i], false, this));
+                               }
+                       }
+               parent.tree.redraw();
+               known = true;
+               }
+       }
+
+       boolean selected()
+       {
+       BaculaNode n = this;
+       while(n != null) {
+               if (n.added) return true;
+               n = n.dir;
+               }
+       return false;
+       }
+}
+
diff --git a/bacula-backup/Util.class b/bacula-backup/Util.class
new file mode 100644 (file)
index 0000000..10a91a7
Binary files /dev/null and b/bacula-backup/Util.class differ
diff --git a/bacula-backup/Util.java b/bacula-backup/Util.java
new file mode 120000 (symlink)
index 0000000..1ba3fa0
--- /dev/null
@@ -0,0 +1 @@
+../file/Util.java
\ No newline at end of file
diff --git a/bacula-backup/apply.cgi b/bacula-backup/apply.cgi
new file mode 100755 (executable)
index 0000000..dc80269
--- /dev/null
@@ -0,0 +1,11 @@
+#!/usr/local/bin/perl
+# Apply the Bacula configuration
+
+require './bacula-backup-lib.pl';
+&error_setup($text{'apply_err'});
+$err = &apply_configuration();
+&error($err) if ($err);
+&webmin_log("restart");
+&redirect("");
+
+
diff --git a/bacula-backup/backup.cgi b/bacula-backup/backup.cgi
new file mode 100755 (executable)
index 0000000..65d6cef
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/local/bin/perl
+# Actually execute a backup
+
+require './bacula-backup-lib.pl';
+&ui_print_unbuffered_header(undef,  $text{'backup_title'}, "");
+&ReadParse();
+
+print "<b>",&text('backup_run', "<tt>$in{'job'}</tt>"),"</b>\n";
+print "<pre>";
+$h = &open_console();
+
+# Clear messages
+&console_cmd($h, "messages");
+
+# Select the job to run
+&sysprint($h->{'infh'}, "run\n");
+&wait_for($h->{'outfh'}, 'run\\n');
+$rv = &wait_for($h->{'outfh'}, 'Select Job.*:');
+print $wait_for_input;
+if ($rv == 0 && $wait_for_input =~ /(\d+):\s+\Q$in{'job'}\E/) {
+       &sysprint($h->{'infh'}, "$1\n");
+       }
+else {
+       &job_error($text{'backup_ejob'});
+       }
+
+# Say that it is OK
+$rv = &wait_for($h->{'outfh'}, 'OK to run.*:');
+print $wait_for_input;
+if ($rv == 0) {
+       &sysprint($h->{'infh'}, "yes\n");
+       }
+else {
+       &job_error($text{'backup_eok'});
+       }
+
+print "</pre>";
+
+if ($in{'wait'}) {
+       # Wait till we have a status
+       print "</pre>\n";
+       print "<b>",$text{'backup_running'},"</b>\n";
+       print "<pre>";
+       while(1) {
+               $out = &console_cmd($h, "messages");
+               if ($out !~ /You\s+have\s+no\s+messages/i) {
+                       print $out;
+                       }
+               if ($out =~ /Termination:\s+(.*)/) {
+                       $status = $1;
+                       last;
+                       }
+               sleep(1);
+               }
+       print "</pre>\n";
+       if ($status =~ /Backup\s+OK/i && $status !~ /warning/i) {
+               print "<b>",$text{'backup_done'},"</b><p>\n";
+               }
+       else {
+               print "<b>",$text{'backup_failed'},"</b><p>\n";
+               }
+       }
+else {
+       # Let it fly
+       print "<b>",$text{'backup_running2'},"</b><p>\n";
+       }
+
+&close_console($h);
+&webmin_log("backup", $in{'job'});
+
+&ui_print_footer("backup_form.cgi", $text{'backup_return'});
+
+sub job_error
+{
+&close_console($h);
+print "</pre>\n";
+print "<b>",@_,"</b><p>\n";
+&ui_print_footer("backup_form.cgi", $text{'backup_return'});
+exit;
+}
+
diff --git a/bacula-backup/backup_form.cgi b/bacula-backup/backup_form.cgi
new file mode 100755 (executable)
index 0000000..17d6638
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/local/bin/perl
+# Show a form for running one backup job
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'backup_title'}, "", "backup");
+
+print &ui_form_start("backup.cgi", "post");
+print &ui_table_start($text{'backup_header'}, undef, 2);
+
+# Job to run
+@jobs = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+            grep { !&is_oc_object($_) } &get_bacula_jobs();
+print &ui_table_row($text{'backup_job'},
+    &ui_select("job", undef,
+              [ map { [ $_->{'name'}, &text('backup_jd', $_->{'name'}, $_->{'fileset'}, $_->{'client'}) ] } @jobs ]));
+
+# Wait for completion?
+print &ui_table_row($text{'backup_wait'},
+                   &ui_yesno_radio("wait", $config{'wait'}));
+
+print &ui_table_end();
+print &ui_form_end([ [ "backup", $text{'backup_ok'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
diff --git a/bacula-backup/bacula-backup-lib.pl b/bacula-backup/bacula-backup-lib.pl
new file mode 100644 (file)
index 0000000..133de45
--- /dev/null
@@ -0,0 +1,1532 @@
+# Common functions for the bacula config file
+# XXX other schedule-level overrides, like pool, storage, etc..
+# XXX schedule chooser on IE
+
+do '../web-lib.pl';
+&init_config();
+do '../ui-lib.pl';
+use Time::Local;
+if (&foreign_check("node-groups")) {
+       &foreign_require("node-groups", "node-groups-lib.pl");
+       }
+
+$dir_conf_file = "$config{'bacula_dir'}/bacula-dir.conf";
+$fd_conf_file = "$config{'bacula_dir'}/bacula-fd.conf";
+$sd_conf_file = "$config{'bacula_dir'}/bacula-sd.conf";
+$bconsole_conf_file = "$config{'bacula_dir'}/bconsole.conf";
+$console_conf_file = "$config{'bacula_dir'}/console.conf";
+$console_cmd = -r "$config{'bacula_dir'}/bconsole" ?
+               "$config{'bacula_dir'}/bconsole" :
+               "$config{'bacula_dir'}/console";
+$bacula_cmd = "$config{'bacula_dir'}/bacula";
+
+@backup_levels = ( "Full", "Incremental", "Differential",
+          "InitCatalog", "Catalog", "VolumeToCatalog", "DiskToCatalog" );
+@pool_types = ( "Backup",
+               "*Archive", "*Cloned", "*Migration", "*Copy", "*Save" );
+
+$cron_cmd = "$module_config_directory/sync.pl";
+
+# connect_to_database()
+# Connects to the Bacula database, and returns the DBI handle
+sub connect_to_database
+{
+local $drh;
+eval <<EOF;
+use DBI;
+\$drh = DBI->install_driver(\$config{'driver'} || "mysql");
+EOF
+if ($@) {
+        die $text{'connect_emysql'}."\n";
+        }
+local $dbistr = &make_dbistr($config{'driver'}, $config{'db'}, $config{'host'});
+local $dbh = $drh->connect($dbistr,
+                           $config{'user'}, $config{'pass'}, { });
+$dbh || die &text('connect_elogin', "<tt>$config{'db'}</tt>",$drh->errstr)."\n";
+local $testcmd = $dbh->prepare("select count(*) from job");
+if (!$testcmd) {
+       die &text('connect_equery', "<tt>$config{'db'}</tt>")."\n".
+           ($config{'driver'} eq "SQLite" ? $text{'connect_equery2'} : "");
+       }
+$testcmd->finish();
+return $dbh;
+}
+
+# read_config_file(file)
+# Parses a bacula config file
+sub read_config_file
+{
+local ($file) = @_;
+if (!defined($config_file_cache{$file})) {
+       local @rv = ( );
+       local $parent = { 'members' => \@rv };
+       local $lnum = 0;
+       open(CONF, $_[0]) || return undef;
+       while(<CONF>) {
+               s/\r|\n//g;
+               s/#.*$//;
+               if (/^\s*\@(.*\S)/) {
+                       # An include file reference .. parse it
+                       local $incfile = $1;
+                       if ($incfile !~ /^\//) {
+                               $incfile = "$config{'bacula_dir'}/$incfile";
+                               }
+                       if (-d $incfile) {
+                               # Read a whole directory of files
+                               opendir(INCDIR, $incfile);
+                               foreach my $f (readdir(INCDIR)) {
+                                       next if ($f eq "." || $f eq "..");
+                                       local $inc = &read_config_file(
+                                               "$incfile/$f");
+                                       push(@{$parent->{'members'}}, @$inc);
+                                       }
+                               closedir(INCDIR);
+                               }
+                       else {
+                               # Read just one file
+                               local $inc = &read_config_file($incfile);
+                               push(@{$parent->{'members'}}, @$inc);
+                               }
+                       }
+               elsif (/^\s*}\s*$/) {
+                       # End of a section
+                       $parent->{'eline'} = $lnum;
+                       $parent = $parent->{'parent'};
+                       $parent ||
+                           die "Too many section ends at line ".($lnum+1);
+                       }
+               elsif (/^\s*(\S+)\s*{\s*(\S[^=]*\S)\s*=\s*"(.*)"(.*)$/ ||
+                      /^\s*(\S+)\s*{\s*(\S[^=]*\S)\s*=\s*([^\{]*\S)(.*)$/) {
+                       # Start of a section, with a name=value record on
+                       # the same line!
+                       local $dir = { 'name' => $1,
+                                      'parent' => $parent,
+                                      'line' => $lnum,
+                                      'eline' => $lnum,
+                                      'file' => $file,
+                                      'type' => 1,
+                                      'members' => [ ] };
+                       push(@{$parent->{'members'}}, $dir);
+                       $parent = $dir;
+                       local $dir = { 'name' => $2,
+                                      'value' => $3,
+                                      'line' => $lnum,
+                                      'eline' => $lnum,
+                                      'file' => $file,
+                                      'type' => 0,
+                                      'parent' => $parent };
+                       push(@{$parent->{'members'}}, $dir);
+                       }
+               elsif (/^\s*(\S[^=]*\S)\s*=\s*"(.*)"(.*)$/ ||
+                   /^\s*(\S[^=]*\S)\s*=\s*([^\{]*\S)(.*)$/) {
+                       # A name=value record
+                       local $rest = $3;
+                       local $dir = { 'name' => $1,
+                                      'value' => $2,
+                                      'line' => $lnum,
+                                      'eline' => $lnum,
+                                      'file' => $file,
+                                      'type' => 0,
+                                      'parent' => $parent };
+                       push(@{$parent->{'members'}}, $dir);
+
+                       if ($rest =~ /\s*{\s*$/) {
+                               # Also start of a section!
+                               $dir->{'type'} = 2;
+                               $dir->{'members'} = [ ];
+                               $parent = $dir;
+                               }
+                       }
+               elsif (/^\s*(\S+)\s*{\s*$/) {
+                       # Start of a section
+                       local $dir = { 'name' => $1,
+                                      'parent' => $parent,
+                                      'line' => $lnum,
+                                      'eline' => $lnum,
+                                      'file' => $file,
+                                      'type' => 1,
+                                      'members' => [ ] };
+                       push(@{$parent->{'members'}}, $dir);
+                       $parent = $dir;
+                       }
+               $lnum++;
+               }
+       close(CONF);
+       $config_file_cache{$file} = \@rv;
+       }
+return $config_file_cache{$file};
+}
+
+# read_config_file_parent(file)
+sub read_config_file_parent
+{
+local ($file) = @_;
+if (!$config_file_parent_cache{$file}) {
+       local $conf = &read_config_file($file);
+       return undef if (!$conf);
+       local $lref = &read_file_lines($file);
+       $config_file_parent_cache{$file} =
+              { 'members' => $conf,
+                'type' => 2,
+                'file' => $file,
+                'line' => 0,
+                'eline' => scalar(@$lref) };
+       }
+return $config_file_parent_cache{$file};
+}
+
+# find(name, &conf)
+sub find
+{
+local ($name, $conf) = @_;
+local @rv = grep { lc($_->{'name'}) eq lc($name) } @$conf;
+return wantarray ? @rv : $rv[0];
+}
+
+sub find_value
+{
+local ($name, $conf) = @_;
+local @rv = map { $_->{'value'} } &find(@_);
+return wantarray ? @rv : $rv[0];
+}
+
+sub find_by
+{
+local ($field, $value, $conf) = @_;
+foreach my $f (@$conf) {
+       my $name = &find_value($field, $f->{'members'});
+       return $f if ($name eq $value);
+       }
+return undef;
+}
+
+sub get_director_config
+{
+return &read_config_file($dir_conf_file);
+}
+
+sub get_director_config_parent
+{
+return &read_config_file_parent($dir_conf_file);
+}
+
+sub get_storage_config
+{
+return &read_config_file($sd_conf_file);
+}
+
+sub get_storage_config_parent
+{
+return &read_config_file_parent($sd_conf_file);
+}
+
+sub get_file_config
+{
+return &read_config_file($fd_conf_file);
+}
+
+sub get_file_config_parent
+{
+return &read_config_file_parent($fd_conf_file);
+}
+
+sub get_bconsole_config
+{
+return &read_config_file($bconsole_conf_file);
+}
+
+sub get_bconsole_config_parent
+{
+return &read_config_file_parent($bconsole_conf_file);
+}
+
+# save_directive(&conf, &parent, name|&old, &new, indent)
+# Updates a section or value in the Bacula config file
+sub save_directive
+{
+local ($conf, $parent, $name, $new, $indent) = @_;
+local $old;
+if (ref($name)) {
+       $old = $name;
+       $name = $old->{'name'};
+       }
+else {
+       $old = &find($name, $parent->{'members'});
+       }
+local $lref = $old && $old->{'file'} ? &read_file_lines($old->{'file'}) :
+             $parent->{'file'} ? &read_file_lines($parent->{'file'}) : undef;
+if (defined($new) && !ref($new)) {
+       $new = { 'name' => $name,
+                'value' => $new };
+       }
+
+local @lines = $new ? &directive_lines($new, $indent) : ( );
+local $len = $old ? $old->{'eline'} - $old->{'line'} + 1 : undef;
+if ($old && $new) {
+       # Update this object
+       if ($lref) {
+               splice(@$lref, $old->{'line'}, $len, @lines);
+               &renumber($conf, $old->{'line'}, scalar(@lines)-$len,
+                         $old->{'file'});
+               }
+       $old->{'value'} = $new->{'value'};
+       $old->{'members'} = $new->{'members'};
+       $old->{'type'} = $new->{'type'};
+       $old->{'eline'} = $old->{'line'} + scalar(@lines) - 1;
+       }
+elsif (!$old && $new) {
+       # Add to the parent
+       $new->{'line'} = $parent->{'eline'};
+       $new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
+       $new->{'file'} = $parent->{'file'};
+       if ($lref) {
+               splice(@$lref, $parent->{'eline'}, 0, @lines);
+               &renumber($conf, $new->{'line'}-1, scalar(@lines),
+                         $parent->{'file'});
+               }
+       push(@{$parent->{'members'}}, $new);
+       }
+elsif ($old && !$new) {
+       # Delete from the parent
+       if ($lref) {
+               splice(@$lref, $old->{'line'}, $len);
+               &renumber($conf, $old->{'line'}, -$len, $old->{'file'});
+               }
+       @{$parent->{'members'}} = grep { $_ ne $old } @{$parent->{'members'}};
+       }
+}
+
+# save_directives(&conf, &parent, name, &newvalues, indent)
+# Updates multiple directives in a section
+sub save_directives
+{
+local ($conf, $parent, $name, $news, $indent) = @_;
+local @news = map { ref($_) ? $_ : { 'name' => $name, 'value' => $_ } } @$news;
+local @olds = &find($name, $parent->{'members'});
+for(my $i=0; $i<@news || $i<@olds; $i++) {
+       &save_directive($conf, $parent, $olds[$i], $news[$i], $indent);
+       }
+}
+
+# renumber(&conf, start, offset, file)
+sub renumber
+{
+local ($conf, $line, $offset, $file) = @_;
+foreach my $c (@$conf) {
+       $c->{'line'} += $offset if ($c->{'line'} > $line &&
+                                   $c->{'file'} eq $file);
+       $c->{'eline'} += $offset if ($c->{'eline'} > $line &&
+                                    $c->{'file'} eq $file);
+       if ($c->{'type'}) {
+               &renumber($c->{'members'}, $line, $offset);
+               }
+       }
+local $parent = $config_file_parent_cache{$file};
+if ($conf eq $parent->{'members'}) {
+       # Update parent lines too
+       $parent->{'line'} += $offset if ($parent->{'line'} > $line);
+       $parent->{'eline'} += $offset if ($parent->{'eline'} > $line);
+       }
+}
+
+# directive_lines(&object, indent)
+# Returns the text lines of a Bacula directive
+sub directive_lines
+{
+local ($dir, $indent) = @_;
+local $istr = "  " x $indent;
+local @rv;
+if ($dir->{'type'}) {
+       # A section
+       push(@rv, $istr.$dir->{'name'}.
+                 ($dir->{'value'} ? " $dir->{'value'}" : "")." {");
+       foreach my $m (@{$dir->{'members'}}) {
+               push(@rv, &directive_lines($m, $indent+1));
+               }
+       push(@rv, $istr."}");
+       }
+else {
+       # A single line
+       local $qstr = $dir->{'value'} =~ /^\S+$/ ||
+                      $dir->{'value'} =~ /^\d+\s+(secs|mins|hours|days|weeks|months|years)$/i ||
+                      $dir->{'name'} eq 'Run' ? $dir->{'value'} :
+                     $dir->{'value'} =~ /"/ ? "'$dir->{'value'}'" :
+                                              "\"$dir->{'value'}\"";
+       push(@rv, $istr.$dir->{'name'}." = ".$qstr);
+       }
+return @rv;
+}
+
+# bacula_file_button(filesfield, [jobfield], [volume])
+# Pops up a window for selecting multiple files, using a tree-like view
+sub bacula_file_button
+{
+return "<input type=button onClick='ifield = form.$_[0]; jfield = form.$_[1]; chooser = window.open(\"treechooser.cgi?volume=".&urlize($_[2])."&files=\"+escape(ifield.value)+\"&job=\"+escape(jfield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbar=no,width=500,height=400\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
+}
+
+sub tape_select
+{
+local $t;
+print "<select name=tape>\n";
+foreach $t (split(/\s+/, $config{'tape_device'})) {
+       print "<option>",&text('index_tapedev', $t),"\n";
+       }
+print "<option value=''>$text{'index_other'}\n";
+print "</select>\n";
+print "<input name=other size=40> ",&file_chooser_button("other", 1),"\n";
+}
+
+# job_select(&dbh, [volumne])
+# XXX needs value input?
+# XXX needs flag for use of 'any' field?
+sub job_select
+{
+local $cmd;
+if ($_[1]) {
+       $cmd = $_[0]->prepare("select Job.JobId,Job.Name,Job.SchedTime ".
+                             "from Job,JobMedia,Media ".
+                             "where Job.JobId = JobMedia.JobId ".
+                             "and Media.MediaId = JobMedia.MediaId ".
+                             "and Media.VolumeName = '$_[1]'") ||
+               &error("prepare failed : ",$dbh->errstr);
+       }
+else {
+       $cmd = $_[0]->prepare("select JobId,Name,SchedTime from Job") ||
+               &error("prepare failed : ",$dbh->errstr);
+       }
+$cmd->execute();
+print "<select name=job>\n";
+print "<option value=''>$text{'job_any'}\n";
+while(my ($id, $name, $when) = $cmd->fetchrow()) {
+       $when =~ s/ .*$//;
+       print "<option value=$id>$name ($id) ($when)\n";
+       }
+print "</select>\n";
+}
+
+# client_select(&dbh)
+sub client_select
+{
+local $cmd = $_[0]->prepare("select ClientId,Name from Client order by ClientId asc");
+$cmd->execute();
+print "<select name=client>\n";
+while(my ($id, $name) = $cmd->fetchrow()) {
+       print "<option value=$name>$name ($id)\n";
+       }
+print "</select>\n";
+}
+
+sub unix_to_dos
+{
+local $rv = $_[0];
+$rv =~ s/^\/([a-zA-Z]):/$1:/;
+return $rv;
+}
+
+sub dos_to_unix
+{
+local $rv = $_[0];
+$rv =~ s/^([a-zA-Z]):/\/$1:/;
+return $rv;
+}
+
+# check_bacula()
+# Returns an error message if bacula is not installed, or undef if OK
+sub check_bacula
+{
+if (!-d $config{'bacula_dir'}) {
+       return &text('check_edir', "<tt>$config{'bacula_dir'}</tt>");
+       }
+local $got = 0;
+if (-r $dir_conf_file) {
+       if (!-x $bacula_cmd) {
+               return &text('check_ebacula', "<tt>$bacula_cmd</tt>");
+               }
+       if (!-x $console_cmd) {
+               return &text('check_econsole', "<tt>$console_cmd</tt>");
+               }
+       $got++;
+       }
+elsif (-r $fd_conf_file) {
+       $got++;
+       }
+elsif (-r $sd_conf_file) {
+       $got++;
+       }
+return &text('check_econfigs', "<tt>$config{'bacula_dir'}</tt>") if (!$got);
+return undef;
+}
+
+# Returns 1 if this system is a Bacula director
+sub has_bacula_dir
+{
+return -r $dir_conf_file;
+}
+
+# Returns 1 if this system is a Bacula file daemon
+sub has_bacula_fd
+{
+return -r $fd_conf_file;
+}
+
+# Returns 1 if this system is a Bacula storage daemon
+sub has_bacula_sd
+{
+return -r $sd_conf_file;
+}
+
+# Names of the Bacula programs
+@bacula_processes = ( &has_bacula_dir() ? ( "bacula-dir" ) : ( ),
+                     &has_bacula_sd() ? ( "bacula-sd" ) : ( ),
+                     &has_bacula_fd() ? ( "bacula-fd" ) : ( ),
+                   );
+if ($gconfig{'os_type'} eq 'windows') {
+       # On Windows, the bootup action is just called Bacula (for the FD)
+       @bacula_inits = ( "Bacula" );
+       }
+else {
+       # On Unix, each daemon has an action
+       @bacula_inits = @bacula_processes;
+       }
+
+# is_bacula_running(process)
+# Returns 1 if the specified Bacula process is running, 0 of not
+sub is_bacula_running
+{
+local ($proc) = @_;
+if (&has_command($bacula_cmd)) {
+       # Get status from bacula status command
+       $bacula_status_cache ||= `$bacula_cmd status 2>&1 </dev/null`;
+       return $bacula_status_cache =~ /\Q$proc\E\s+\(pid\s+([0-9 ]+)\)\s+is\s+running/i ||
+              $bacula_status_cache =~ /\Q$proc\E\s+is\s+running/i;
+       }
+else {
+       # Look for running process
+       local @pids = &find_byname($proc);
+       return @pids ? 1 : 0;
+       }
+}
+
+# start_bacula()
+# Attempts to start the Bacula processes, return undef on success or an
+# error message on failure
+sub start_bacula
+{
+undef($bacula_status_cache);
+if (&has_command($bacula_cmd)) {
+       local $out = &backquote_logged("$bacula_cmd start 2>&1 </dev/null");
+       return $? || $out =~ /failed|error/i ? "<pre>$out</pre>" : undef;
+       }
+else {
+       return &run_all_inits("start");
+       }
+}
+
+# stop_bacula()
+# Attempts to stop the Bacula processes, return undef on success or an
+# error message on failure
+sub stop_bacula
+{
+undef($bacula_status_cache);
+if (&has_command($bacula_cmd)) {
+       local $out = &backquote_logged("$bacula_cmd stop 2>&1 </dev/null");
+       return $? || $out =~ /failed|error/i ? "<pre>$out</pre>" : undef;
+       }
+else {
+       return &run_all_inits("stop");
+       }
+}
+
+# restart_bacula()
+# Attempts to re-start the Bacula processes, return undef on success or an
+# error message on failure
+sub restart_bacula
+{
+undef($bacula_status_cache);
+if (&has_command($bacula_cmd)) {
+       local $out = &backquote_logged("$bacula_cmd restart 2>&1 </dev/null");
+       return $? || $out =~ /failed|error/i ? "<pre>$out</pre>" : undef;
+       }
+else {
+       return &run_all_inits("restart");
+       }
+}
+
+# run_all_inits(action)
+# Runs all the Bacula init script with some action
+sub run_all_inits
+{
+local ($action) = @_;
+&foreign_require("init", "init-lib.pl");
+foreach my $i (@bacula_inits) {
+       local $st = &init::action_status($i);
+       return &text('start_einit', "<tt>$i</tt>") if (!$st);
+       }
+foreach my $i (@bacula_inits) {
+       local $func = $action eq "start" ? \&init::start_action :
+                     $action eq "stop" ? \&init::stop_action :
+                     $action eq "restart" ? \&init::restart_action :
+                                            undef;
+       $func || return "Unknown init action $action";
+       local $err = &$func($i);
+       if ($err) {
+               return &text('start_erun', "<tt>$i</tt>", "<pre>$err</pre>");
+               }
+       }
+return undef;
+}
+
+# apply_configuration()
+# Tells Bacula to re-read it's config files
+sub apply_configuration
+{
+if (&has_bacula_dir()) {
+       # Call console reload
+       local $h = &open_console();
+       local $out = &console_cmd($h, "reload");
+       &close_console($h);
+       return defined($out) ? undef : $text{'apply_failed'}."<pre>$out</pre>";
+       }
+else {
+       # Need to do a restart
+       return &restart_bacula();
+       }
+}
+
+# auto_apply_configuration()
+# Apply the configuration if automatic apply is enabled
+sub auto_apply_configuration
+{
+if ($config{'apply'} && &is_bacula_running($bacula_processes[0])) {
+       local $err = &apply_configuration();
+       &error(&text('apply_problem', $err)) if ($err);
+       }
+}
+
+# show_period_input(name, value)
+# Returns HTML for selection a retention period
+sub show_period_input
+{
+local ($name, $value) = @_;
+local ($t, $u) = split(/\s+/, $value);
+$u ||= "days";
+$u .= "s" if ($u !~ /s$/);
+return &ui_textbox($name."_t", $t, 5)." ".
+       &ui_select($name."_u", $u,
+         [ [ "seconds" ], [ "minutes" ], [ "hours" ], [ "days" ],
+           [ "weeks" ], [ "months" ], [ "years" ] ], 1, 0, 1);
+}
+
+# parse_period_input(name)
+sub parse_period_input
+{
+local ($name) = @_;
+$in{$name."_t"} =~ /^\d+$/ || return undef;
+return $in{$name."_t"}." ".$in{$name."_u"};
+}
+
+# find_dependency(field, value, &types, &conf)
+# Checks if any of the given object types have the specified field, and returns
+# the name of the dependent object
+sub find_dependency
+{
+local ($field, $value, $types, $conf) = @_;
+foreach my $name (@$types) {
+       local @children = &find($name, $conf);
+       local $child = &find_by($field, $value, \@children);
+       if ($child) {
+               local $cname = &find_value("Name", $child->{'members'});
+               return $cname;
+               }
+       }
+return undef;
+}
+
+# open_console()
+# Starts the Bacula console process, and returns a handle object for it
+sub open_console
+{
+##&foreign_require("proc", "proc-lib.pl");
+#$ENV{'TERM'} = "dumb";
+#local ($fh, $fpid) = &proc::pty_process_exec($console_cmd);
+#&wait_for($fh, '\\*');                # skip first prompt
+#return { 'fh' => $fh,
+#       'fpid' => $fpid };
+
+pipe(INr, INw);
+pipe(OUTr, OUTw);
+local $pid;
+if (!($pid = fork())) {
+        untie(*STDIN);
+        untie(*STDOUT);
+        untie(*STDERR);
+        close(STDIN);
+        close(STDOUT);
+        close(STDERR);
+        open(STDIN, "<&INr");
+        open(STDOUT, ">&OUTw");
+        open(STDERR, ">&OUTw");
+        $| = 1;
+        close(INw);
+       close(OUTr);
+        chdir($config{'bacula_dir'});
+        exec($console_cmd);
+        print STDERR "exec failed : $!\n";
+        exit(1);
+        }
+close(INr);
+close(OUTw);
+local $infh = \*INw;
+local $outfh = \*OUTr;
+local $old = select($infh); $| = 1;
+select($outfh); $| = 1; select($old);
+return { 'infh' => $infh,
+        'outfh' => $outfh,
+        'fpid' => $pid };
+}
+
+# console_cmd(&handle, command)
+# Runs one Bacula command, and returns the output
+sub console_cmd
+{
+local ($h, $cmd) = @_;
+&sysprint($h->{'infh'}, $cmd."\n");
+&sysprint($h->{'infh'}, "time\n");
+local $rv = &wait_for($h->{'outfh'}, 'time\n(\d+\-\S+\-\d+ \d+:\d+:\d+)\n',
+                                    'Unable to connect to Director');
+return undef if ($rv == 1);
+$wait_for_input =~ s/time\n(\d+\-\S+\-\d+ \d+:\d+:\d+)\n//;
+$wait_for_input =~ s/^\Q$cmd\E\n//;
+return $rv == 0 ? $wait_for_input : undef;
+}
+
+# close_console(&handle)
+sub close_console
+{
+local ($h) = @_;
+&console_cmd($h, "quit");
+close($h->{'infh'});
+close($h->{'outfh'});
+kill('TERM', $h->{'fpid'});
+waitpid($h->{'pid'}, -1);
+}
+
+# get_bacula_version()
+# Get the Bacula version, either from one of the command-line programs, or
+# from the console
+sub get_bacula_version
+{
+foreach my $p (@bacula_processes) {
+       if (&has_command($p)) {
+               local $out = `$p -\? 2>&1`;
+               if ($out =~ /Version:\s+(\S+)/) {
+                       return $1;
+                       }
+               }
+       }
+local $h = &open_console();
+local $out = &console_cmd($h, "version");
+&close_console($h);
+if ($out =~ /Version:\s+(\S+)/) {
+       &open_tempfile(CACHE, ">$module_config_directory/version");
+       &print_tempfile(CACHE, $1,"\n");
+       &close_tempfile(CACHE);
+       return $1;
+       }
+return undef;
+}
+
+sub get_bacula_version_cached
+{
+open(CACHE, "$module_config_directory/version");
+chop($version = <CACHE>);
+close(CACHE);
+return $version || &get_bacula_version();
+}
+
+# get_bacula_jobs()
+# Returns a list of all jobs known to Bacula
+sub get_bacula_jobs
+{
+local $h = &open_console();
+local $jobs = &console_cmd($h, "show jobs");
+&close_console($h);
+local @rv;
+local $job;
+foreach my $l (split(/\r?\n/, $jobs)) {
+       if ($l =~ /^Job:\s+name=([^=]*\S)\s/) {
+               $job = { 'name' => $1 };
+               push(@rv, $job);
+               }
+       elsif ($l =~ /Client:\s+name=([^=]*\S)\s/ && $job) {
+               $job->{'client'} = $1;
+               }
+       elsif ($l =~ /FileSet:\s+name=([^=]*\S)\s/ && $job) {
+               $job->{'fileset'} = $1;
+               }
+       }
+return @rv;
+}
+
+# get_bacula_clients()
+# Returns a list of all clients known to Bacula
+sub get_bacula_clients
+{
+local $h = &open_console();
+local $clients = &console_cmd($h, "show clients");
+&close_console($h);
+local @rv;
+local $client;
+foreach my $l (split(/\r?\n/, $clients)) {
+       if ($l =~ /^Client:\s+name=([^=]*\S)\s/) {
+               $client = { 'name' => $1 };
+               if ($l =~ /address=(\S+)/ && $client) {
+                       $client->{'address'} = $1;
+                       }
+               if ($l =~ /FDport=(\d+)/ && $client) {
+                       $client->{'port'} = $1;
+                       }
+               push(@rv, $client);
+               }
+       }
+return @rv;
+}
+
+# get_bacula_storages()
+# Returns a list of all storage daemons known to Bacula
+sub get_bacula_storages
+{
+local $h = &open_console();
+local $storages = &console_cmd($h, "show storages");
+&close_console($h);
+local @rv;
+local $storage;
+foreach my $l (split(/\r?\n/, $storages)) {
+       if ($l =~ /^Storage:\s+name=([^=]*\S)\s/) {
+               $storage = { 'name' => $1 };
+               if ($l =~ /address=(\S+)/) {
+                       $storage->{'address'} = $1;
+                       }
+               if ($l =~ /SDport=(\d+)/) {
+                       $storage->{'port'} = $1;
+                       }
+               push(@rv, $storage);
+               }
+       }
+return @rv;
+}
+
+# get_bacula_pools()
+# Returns a list of all pools known to Bacula
+sub get_bacula_pools
+{
+local $h = &open_console();
+local $pools = &console_cmd($h, "show pools");
+&close_console($h);
+local @rv;
+local $pool;
+foreach my $l (split(/\r?\n/, $pools)) {
+       if ($l =~ /^Pool:\s+name=([^=]*\S)\s/) {
+               $pool = { 'name' => $1 };
+               if ($l =~ /PoolType=(\S+)/) {
+                       $pool->{'type'} = $1;
+                       }
+               push(@rv, $pool);
+               }
+       }
+return @rv;
+}
+
+# get_director_status()
+# Returns three arrays, containing the status of scheduled, running and finished
+# jobs respectively
+sub get_director_status
+{
+local $h = &open_console();
+local $status = &console_cmd($h, "status dir");
+&close_console($h);
+local $sect = 0;
+local (@sched, @run, @done);
+foreach my $l (split(/\r?\n/, $status)) {
+       if ($l =~ /^Scheduled\s+Jobs/i) { $sect = 1; }
+       elsif ($l =~ /^Running\s+Jobs/i) { $sect = 2; }
+       elsif ($l =~ /^Terminated\s+Jobs/i) { $sect = 3; }
+
+       if ($sect == 1 && $l =~ /^\s*(\S+)\s+(\S+)\s+(\d+)\s+(\S+\s+\S+)\s+(\S+)\s+(\S+)\s*$/) {
+               push(@sched, { 'level' => &full_level($1),
+                              'type' => $2,
+                              'pri' => $3,
+                              'date' => $4,
+                              'name' => $5,
+                              'volume' => $6 });
+               }
+       elsif ($sect == 2 && $l =~ /^\s*(\d+)\s+(\S+)\s+(\S+)\.(\d+\-\d+\-\S+)\s+(.*)/) {
+               push(@run, { 'id' => $1,
+                            'level' => &full_level($2),
+                            'name' => &job_name($3),
+                            'status' => $5 });
+               }
+       elsif ($sect == 2 && $l =~ /^\s*(\d+)\s+(\S+)\.(\d+\-\d+\-\S+)\s+(.*)/) {
+               push(@run, { 'id' => $1,
+                            'level' => "Restore",
+                            'name' => &job_name($2),
+                            'status' => $4 });
+               }
+       elsif ($sect == 3 && $l =~ /^\s*(\d+)\s+(\S+)\s+([0-9,]+)\s+([0-9,]+)\s+(\S+)\s+(\S+\s+\S+)\s+(\S+)\s*$/) {
+               push(@done, { 'id' => $1,
+                             'level' => &full_level($2),
+                             'files' => &remove_comma($3),
+                             'bytes' => &remove_comma($4),
+                             'status' => $5,
+                             'date' => $6,
+                             'name' => &job_name($7) });
+               }
+       }
+return (\@sched, \@run, \@done);
+}
+
+# get_client_status(client)
+# Returns a status message, OK flag, running jobs and done jobs for some client
+sub get_client_status
+{
+local ($client) = @_;
+local $h = &open_console();
+local $status = &console_cmd($h, "status client=$client");
+&close_console($h);
+local $msg;
+if ($status =~ /Connecting\s+to\s+Client.*\n(\n?)(.*)\n/i) {
+       $msg = $2;
+       $msg =~ s/^\s*$client\s//;
+       }
+local $sect = 0;
+local (@run, @done);
+foreach my $l (split(/\r?\n/, $status)) {
+       if ($l =~ /^Running\s+Jobs/i) { $sect = 2; }
+       elsif ($l =~ /^Terminated\s+Jobs/i) { $sect = 3; }
+
+       if ($sect == 2 && $l =~ /^\s*JobID\s+(\d+)\s+Job\s+(\S+)\.(\d+\-\d+\-\S+)\s+(.*)/i) {
+               push(@run, { 'id' => $1,
+                            'name' => &job_name($2),
+                            'status' => $4 });
+               }
+       elsif ($sect == 2 && $l =~ /^\s*Backup\s+Job\s+started:\s+(\S+\s+\S+)/) {
+               $run[$#run]->{'date'} = $1;
+               }
+       elsif ($sect == 3 && $l =~ /^\s*(\d+)\s+(\S+)\s+([0-9,]+)\s+([0-9,]+)\s+(\S+)\s+(\S+\s+\S+)\s+(\S+)\s*$/) {
+               push(@done, { 'id' => $1,
+                             'level' => &full_level($2),
+                             'files' => &remove_comma($3),
+                             'bytes' => &remove_comma($4),
+                             'status' => $5,
+                             'date' => $6,
+                             'name' => &job_name($7) });
+               }
+       }
+return ($msg, $msg =~ /failed|error/i ? 0 : 1, \@run, \@done);
+}
+
+# get_storage_status(storage)
+# Returns a status message, OK flag, running jobs and done jobs for some
+# storage daemon
+sub get_storage_status
+{
+local ($storage) = @_;
+local $h = &open_console();
+local $status = &console_cmd($h, "status storage=$storage");
+&close_console($h);
+local $msg;
+if ($status =~ /Connecting\s+to\s+Storage.*\n(\n?)(.*)\n/i) {
+       $msg = $2;
+       }
+local $sect = 0;
+local (@run, @done);
+local $old_style = 0;
+foreach my $l (split(/\r?\n/, $status)) {
+       if ($l =~ /^Running\s+Jobs/i) { $sect = 2; }
+       elsif ($l =~ /^Terminated\s+Jobs/i) { $sect = 3; }
+
+       if ($sect == 2 && $l =~ /^\s*Backup\s+Job\s+(\S+)\.(\d+\-\d+\-\S+)\s+(.*)/) {
+               push(@run, { 'name' => &job_name($1),
+                            'status' => $3 });
+               }
+       elsif ($sect == 2 && $l =~ /^\s*(\S+)\s+Backup\s+job\s+(\S+)\s+JobId=(\d+)\s+Volume="(.*)"(\s+device="(.*)")?/i) {
+               if (!@run || $old_style) {
+                       push(@run, { 'name' => $2 });
+                       $old_style = 1;
+                       }
+               $run[$#run]->{'level'} = $1;
+               $run[$#run]->{'id'} = $3;
+               $run[$#run]->{'volume'} = $4;
+               $run[$#run]->{'device'} = $6;
+               }
+       elsif ($sect == 3 && $l =~ /^\s*(\d+)\s+(\S+)\s+([0-9,]+)\s+([0-9,]+)\s+(\S+)\s+(\S+\s+\S+)\s+(\S+)\s*$/) {
+               push(@done, { 'id' => $1,
+                             'level' => &full_level($2),
+                             'files' => &remove_comma($3),
+                             'bytes' => &remove_comma($4),
+                             'status' => $5,
+                             'date' => $6,
+                             'name' => &job_name($7) });
+               }
+       }
+return ($msg, $msg =~ /failed|error/i ? 0 : 1, \@run, \@done);
+}
+
+# get_pool_volumes(pool)
+# Returns a list of volumes in some pool
+sub get_pool_volumes
+{
+local ($pool) = @_;
+local $h = &open_console();
+local $volumes = &console_cmd($h, "llist volumes pool=$pool");
+&close_console($h);
+local @volumes;
+local $volume;
+foreach my $l (split(/\r?\n/, $volumes)) {
+       if ($l =~ /^\s*(\S+):\s*(.*)/) {
+               # A setting in this volume
+               local ($n, $v) = (lc($1), $2);
+               $volume ||= { };
+               if ($v =~ /^[0-9,]+$/) {
+                       $v = &remove_comma($v);
+                       }
+               elsif ($v eq "0000-00-00 00:00:00") {
+                       $v = undef;
+                       }
+               $volume->{$n} = $v;
+               }
+       elsif ($l =~ /^\s*$/) {
+               # End of this volume
+               push(@volumes, $volume);
+               $volume = undef;
+               }
+       }
+push(@volumes, $volume) if ($volume && &indexof($volume, @volumes) < 0);
+return @volumes;
+}
+
+# full_level(level)
+# Converts a shortened backup level to a long one
+sub full_level
+{
+local ($level) = @_;
+foreach my $l (@backup_levels) {
+       return $l if ($l =~ /^\Q$level\E/i);
+       }
+return $level;
+}
+
+sub remove_comma
+{
+local ($n) = @_;
+$n =~ s/,//g;
+return $n;
+}
+
+# job_name(name)
+# Converts a job name that has had spaces replaced with _ to the real name
+sub job_name
+{
+local ($name) = @_;
+$name =~ s/_/./g;
+local $conf = &get_director_config();
+foreach my $j (&find("Job", $conf)) {
+       local $n = &find_value("Name", $j->{'members'});
+       if ($n =~ /^$name$/) {
+               return $n;
+               }
+       }
+return $name;
+}
+
+sub bacula_yesno
+{
+local ($id, $name, $mems) = @_;
+local $v = &find_value($name, $mems);
+return &ui_radio($id, $v =~ /^yes/i ? "yes" : $v =~ /^no/i ? "no" : "",
+                [ [ "yes", $text{'yes'} ],
+                  [ "no", $text{'no'} ],
+                  [ "", $text{'default'} ] ]);
+}
+
+# has_node_groups()
+# Returns 1 if the system supports OC-Manager node groups
+sub has_node_groups
+{
+return $config{'groupmode'} && &foreign_check("node-groups");
+}
+
+# check_node_groups()
+# Returns an error message if the node group database could not be contacted
+sub check_node_groups
+{
+if ($config{'groupmode'} eq 'oc') {
+       return $text{'check_engmod'} if (!&foreign_check("node-groups"));
+       return &node_groups::check_node_groups();
+       }
+elsif ($config{'groupmode'} eq 'webmin') {
+       &foreign_require("servers", "servers-lib.pl");
+       local @groups = &servers::list_all_groups();
+       return @groups ? undef : $text{'check_eservers'};
+       }
+else {
+       return undef;
+       }
+}
+
+# list_node_groups()
+# Returns a list of groups, each of which is a hash containing a name and
+# a list of members
+sub list_node_groups
+{
+if ($config{'groupmode'} eq 'webmin') {
+       # Get list of groups from Webmin
+       &foreign_require("servers", "servers-lib.pl");
+       return &servers::list_all_groups();
+       }
+elsif ($config{'groupmode'} eq 'oc') {
+       # Get list from OC database
+       return &node_groups::list_node_groups();
+       }
+else {
+       &error("Node groups not enabled!");
+       }
+}
+
+sub make_dbistr
+{
+local ($driver, $db, $host) = @_;
+local $rv;
+if ($driver eq "mysql") {
+       $rv = "database=$db";
+       }
+elsif ($driver eq "Pg") {
+       $rv = "dbname=$db";
+       }
+else {
+       $rv = $db;
+       }
+if ($host) {
+       $rv .= ";host=$host";
+       }
+return $rv;
+}
+
+# is_oc_object(&client|&job|name, [force-scalar])
+# Returns the group name if the given object is associated with an OC group.
+# In an array context, returns the job or client name too
+sub is_oc_object
+{
+local ($object, $scalar) = @_;
+local $name = ref($object) && defined($object->{'members'}) ?
+               &find_value("Name", $object->{'members'}) :
+             ref($object) ? $object->{'name'}
+                          : $object;
+local @rv = $name =~ /^ocgroup[_\.](.*)$/ ? ( $1 ) :
+                   $name =~ /^occlientjob[_\.]([^_\.]*)[_\.](.*)$/ ? ( $1, $2 ) :
+                   $name =~ /^ocjob[_\.](.*)$/ ? ( $1 ) :
+                   $name =~ /^occlient[_\.]([^_\.]*)[_\.](.*)$/ ? ( $1, $2 ) : ( );
+return wantarray && !$scalar ? @rv : $rv[0];
+}
+
+# sync_group_clients(&nodegroup)
+# Update or delete all clients created from the given node group 
+sub sync_group_clients
+{
+local ($group) = @_;
+local $conf = &get_director_config();
+local $parent = &get_director_config_parent();
+
+# First delete old clients and jobs
+local $gclient;
+local %doneclient;
+foreach my $client (&find("Client", $conf)) {
+       local ($g, $c) = &is_oc_object($client);
+       if ($g eq $group->{'name'} && $c) {
+               # Delete this client which was generated from the group
+               &save_directive($conf, $parent, $client, undef);
+               $doneclient{$c} = 1;
+               }
+       elsif ($g eq $group->{'name'} && !$c) {
+               # Found the special group definition client
+               $gclient = $client;
+               }
+       }
+foreach my $job (&find("Job", $conf)) {
+       local ($j, $c) = &is_oc_object($job);
+       if ($j && $c && $doneclient{$c}) {
+               # Delete this job which is associated with a group's client
+               &save_directive($conf, $parent, $job, undef);
+               }
+       }
+
+if ($gclient) {
+       # Create one client for each group
+       foreach my $m (@{$group->{'members'}}) {
+               local $newclient = &clone_object($gclient);
+               &save_directive($conf, $newclient,
+                       "Name", "occlient_".$group->{'name'}."_".$m);
+               &save_directive($conf, $newclient, "Address", $m);
+               &save_directive($conf, $parent, undef, $newclient, 0);
+               }
+
+       # Create one real job for each group job and for each client in it!
+       foreach my $job (&find_by("Client", "ocgroup_".$group->{'name'}, $conf)) {
+               local $name = &is_oc_object($job);
+               next if (!$name);
+               foreach my $m (@{$group->{'members'}}) {
+                       local $newjob = { 'name' => 'Job',
+                                         'type' => 1,
+                                         'members' => [
+                               { 'name' => 'Name',
+                                 'value' => "occlientjob_".$name."_".$m },
+                               { 'name' => 'JobDefs',
+                                 'value' => "ocjob_".$name },
+                               { 'name' => 'Client',
+                                 'value' => "occlient_".
+                                            $group->{'name'}."_".$m },
+                                       ] };
+                       &save_directive($conf, $parent, undef, $newjob, 0);
+                       }
+               }
+       }
+}
+
+# clone_object(&object)
+# Deep-clones a Bacula object, minus any file or line details
+sub clone_object
+{
+local ($src) = @_;
+local %dest = %$src;
+delete($dest{'file'});
+delete($dest{'line'});
+delete($dest{'eline'});
+$dest{'members'} = [ ];
+foreach my $sm (@{$src->{'members'}}) {
+       push(@{$dest{'members'}}, &clone_object($sm));
+       }
+return \%dest;
+}
+
+sub find_cron_job
+{
+&foreign_require("cron", "cron-lib.pl");
+local ($job) = grep { $_->{'command'} eq $cron_cmd } &cron::list_cron_jobs();
+return $job;
+}
+
+# joblink(jobname)
+# Returns a link for editing some job, if possible
+sub joblink
+{
+if (!defined(%joblink_jobs)) {
+       local $conf = &get_director_config();
+       %joblink_jobs = map { $n=&find_value("Name", $_->{'members'}), 1 }
+                       &find("Job", $conf);
+       }
+local ($name) = @_;
+local $job = $joblink_jobs{$name};
+local ($j, $c) = &is_oc_object($name);
+if (!$job) {
+       return $j ? "$j ($c)" : $name;
+       }
+else {
+       if ($j) {
+               return "<a href='edit_gjob.cgi?name=".&urlize($j)."'>$j ($c)</a>";
+               }
+       else {
+               return "<a href='edit_job.cgi?name=".&urlize($name)."'>$name</a>";
+               }
+       }
+}
+
+sub sort_by_name
+{
+local ($list) = @_;
+@$list = sort { $na = &find_value("Name", $a->{'members'});
+               $nb = &find_value("Name", $b->{'members'});
+               return lc($na) cmp lc($nb) } @$list;
+}
+
+# show_tls_directives(&object)
+# Print inputs for TLS directives for a director, client or storage
+sub show_tls_directives
+{
+local ($object) = @_;
+local $mems = $object->{'members'};
+return if (&get_bacula_version_cached() < 1.38);
+print &ui_table_hr();
+
+print &ui_table_row($text{'tls_enable'},
+                   &bacula_yesno("tls_enable", "TLS Enable", $mems));
+
+print &ui_table_row($text{'tls_require'},
+                   &bacula_yesno("tls_require", "TLS Require", $mems));
+
+print &ui_table_row($text{'tls_verify'},
+                   &bacula_yesno("tls_verify", "TLS Verify Peer", $mems));
+
+local $cert = &find_value("TLS Certificate", $mems);
+print &ui_table_row($text{'tls_cert'},
+           &ui_opt_textbox("tls_cert", $cert, 60, $text{'tls_none'})." ".
+           &file_chooser_button("tls_cert", 0), 3);
+
+local $key = &find_value("TLS Key", $mems);
+print &ui_table_row($text{'tls_key'},
+           &ui_opt_textbox("tls_key", $key, 60, $text{'tls_none'})." ".
+           &file_chooser_button("tls_key", 0), 3);
+
+local $cacert = &find_value("TLS CA Certificate File", $mems);
+print &ui_table_row($text{'tls_cacert'},
+           &ui_opt_textbox("tls_cacert", $cacert, 60, $text{'tls_none'})." ".
+           &file_chooser_button("tls_cacert", 0), 3);
+}
+
+# parse_tls_directives(&config, &object, indent)
+sub parse_tls_directives
+{
+local ($conf, $object, $indent) = @_;
+return if (&get_bacula_version_cached() < 1.38);
+
+&save_directive($conf, $object, "TLS Enable", $in{'tls_enable'} || undef,
+               $indent);
+&save_directive($conf, $object, "TLS Require", $in{'tls_require'} || undef,
+               $indent);
+&save_directive($conf, $object, "TLS Verify Peer", $in{'tls_verify'} || undef,
+               $indent);
+
+$in{'tls_cert_def'} || -r $in{'tls_cert'} || &error($text{'tls_ecert'});
+&save_directive($conf, $object, "TLS Certificate",
+               $text{'tls_ecert_def'} ? undef : $in{'tls_cert'}, $indent);
+
+$in{'tls_key_def'} || -r $in{'tls_key'} || &error($text{'tls_ekey'});
+&save_directive($conf, $object, "TLS Key",
+               $text{'tls_ekey_def'} ? undef : $in{'tls_key'}, $indent);
+
+$in{'tls_cacert_def'} || -r $in{'tls_cacert'} || &error($text{'tls_ecacert'});
+&save_directive($conf, $object, "TLS CA Certificate File",
+               $text{'tls_ecacert_def'} ? undef : $in{'tls_cacert'}, $indent);
+
+if ($in{'tls_enable'} eq 'yes' &&
+    ($in{'tls_cert_def'} || $in{'tls_key_def'} || $in{'tls_cacert_def'})) {
+       &error($text{'tls_ecerts'});
+       }
+
+if (!$in{'tls_cert_def'}) {
+       &foreign_require("webmin", "webmin-lib.pl");
+       &webmin::validate_key_cert($in{'tls_cert'},
+                       $in{'tls_key_def'} ? undef : $in{'tls_key'});
+       }
+
+}
+
+# schedule_chooser_button(name)
+# Returns a button for choosing a Bacula schedule in a popup window
+sub schedule_chooser_button
+{
+local ($name) = @_;
+return "<input type=button onClick='ifield = form.$name; schedule = window.open(\"schedule_chooser.cgi?schedule=\"+escape(ifield.value), \"schedule\", \"toolbar=no,menubar=no,scrollbars=no,width=600,height=600\"); exclude.ifield = ifield; window.ifield = ifield;' value=\"...\">\n";
+}
+
+# parse_schedule(string)
+# Returns an object containing details of a schedule, or undef if not parseable
+# XXX hourly at mins
+sub parse_schedule
+{
+local ($str) = @_;
+local @w = split(/\s+/, $str);
+local $rv = { };
+
+# Look for month spec
+if ($w[0] eq "monthly") {
+       # Monthyl
+       $rv->{'months_all'} = 1;
+       shift(@w);
+       }
+elsif ($w[0] =~ /^(\S+)\-(\S+)$/ &&
+       defined(&is_month($1)) && defined(&is_month($2))) {
+       # A month range
+       $rv->{'months'} = [ &is_month($1) .. &is_month($2) ];
+       shift(@w);
+       }
+elsif (defined(&is_month($w[0]))) {
+       # One month
+       $rv->{'months'} = [ &is_month($w[0]) ];
+       shift(@w);
+       }
+else {
+       $rv->{'months_all'} = 2;
+       }
+
+# Look for days of month spec
+if ($w[0] eq "on") {
+       shift(@w);
+       }
+if ($w[0] =~ /^(\d+)\-(\d+)$/) {
+       $rv->{'days'} = [ $1 .. $2 ];
+       shift(@w);
+       }
+elsif ($w[0] =~ /^\d+$/) {
+       $rv->{'days'} = [ $w[0] ];
+       shift(@w);
+       }
+else {
+       $rv->{'days_all'} = 1;
+       }
+
+# Look for days of week
+if ($w[0] =~ /^(\S+)\-(\S+)$/ &&
+       defined(&is_nth($1)) && defined(&is_nth($2))) {
+       # nth weekday range
+       $rv->{'weekdaynums'} = [ &is_nth($1) .. &is_nth($2) ];
+       shift(@w);
+       }
+elsif (defined(&is_nth($w[0]))) {
+       # nth weekday of month
+       $rv->{'weekdaynums'} = [ &is_nth($w[0]) ];
+       shift(@w);
+       }
+else {
+       # Any weekday num
+       $rv->{'weekdaynums_all'} = 1;
+       }
+if ($w[0] =~ /^(\S+)\-(\S+)$/ &&
+    defined(&is_weekday($1)) && defined(&is_weekday($2))) {
+       # Day or week range
+       $rv->{'weekdays'} = [ &is_weekday($1) .. &is_weekday($2) ];
+       shift(@w);
+       }
+elsif (defined(&is_weekday($w[0]))) {
+       # One day of week
+       $rv->{'weekdays'} = [ &is_weekday($w[0]) ];
+       shift(@w);
+       }
+else {
+       # Any weekday
+       return "Missing weekday when weekday number was specified"
+               if (!$rv->{'weekdaynums_all'});
+       $rv->{'weekdays_all'} = 1;
+       }
+
+# Look for time of day
+if ($w[0] eq "at") {
+       shift(@w);
+       }
+if ($w[0] =~ /^(\d+):(\d+)$/) {
+       $rv->{'hour'} = $1;
+       $rv->{'minute'} = $2;
+       }
+elsif ($w[0] =~ /^(\d+):(\d+)(am|pm)$/i) {
+       $rv->{'hour'} = $1;
+       $rv->{'minute'} = $2;
+       $rv->{'hour'} += 12 if (lc($3) eq 'pm');
+       }
+else {
+       return "Missing hour:minute spec";
+       }
+
+return $rv;
+}
+
+sub is_month
+{
+local $m = lc(substr($_[0], 0, 3));
+return $month_to_number_map{$m};
+}
+
+sub is_nth
+{
+local $n = lc($_[0]);
+return $n eq "1st" || $n eq "first" ? 1 :
+       $n eq "2nd" || $n eq "second" ? 2 :
+       $n eq "3rd" || $n eq "third" ? 3 :
+       $n eq "4th" || $n eq "fourth" ? 4 :
+       $n eq "5th" || $n eq "fifth" ? 5 : undef;
+}
+
+sub is_weekday
+{
+local $w = lc(substr($_[0], 0, 3));
+return $w eq "sun" ? 0 :
+       $w eq "mon" ? 1 :
+       $w eq "tue" ? 2 :
+       $w eq "wed" ? 3 :
+       $w eq "thu" ? 4 :
+       $w eq "fri" ? 5 :
+       $w eq "sat" ? 6 : undef;
+}
+
+# join_schedule(&sched)
+# Converts a schedule object into a string
+sub join_schedule
+{
+local ($sched) = @_;
+local @w;
+
+if (!$sched->{'months_all'}) {
+       local $r = &make_range($sched->{'months'}, \%number_to_month_map);
+       defined($r) || &error($text{'chooser_emonthsrange'});
+       push(@w, $r);
+       }
+
+if (!$sched->{'days_all'}) {
+       local %days_map = map { $_, $_ } (1 .. 31);
+       local $r = &make_range($sched->{'days'}, \%days_map);
+       defined($r) || &error($text{'chooser_edaysrange'});
+       push(@w, "on", $r);
+       }
+
+if (!$sched->{'weekdaynums_all'}) {
+       local %weekdaynums_map = ( 1 => '1st', 2 => '2nd', 3 => '3rd',
+                                  4 => '4th', 5 => '5th' );
+       local $r = &make_range($sched->{'weekdaynums'}, \%weekdaynums_map);
+       defined($r) || &error($text{'chooser_eweekdaynumsrange'});
+       push(@w, $r);
+       }
+
+if (!$sched->{'weekdays_all'}) {
+       local %weekdays_map = ( 0 => 'sun', 1 => 'mon', 2 => 'tue',
+                          3 => 'wed', 4 => 'thu', 5 => 'fri', 6 => 'sat' );
+       local $r = &make_range($sched->{'weekdays'}, \%weekdays_map);
+       defined($r) || &error($text{'chooser_eweekdaysrange'});
+       push(@w, $r);
+       }
+
+push(@w, "at");
+push(@w, $sched->{'hour'}.":".$sched->{'minute'});
+
+return join(" ", @w);
+}
+
+# make_range(&nums, &map)
+sub make_range
+{
+local ($nums, $map) = @_;
+if (scalar(@$nums) == 1) {
+       return $map->{$nums->[0]};
+       }
+@$nums = sort { $a <=> $b } @$nums;
+$prev = undef;
+foreach my $n (@$nums) {
+       if (defined($prev) && $prev != $n-1) {
+               return undef;
+               }
+       $prev = $n;
+       }
+return $map->{$nums->[0]}."-".$map->{$nums->[@$nums-1]};
+}
+
+# date_to_unix(string)
+# Converts a MySQL date string to a Unix time_t
+sub date_to_unix
+{
+local ($str) = @_;
+if ($str =~ /^(\d{4})\-(\d\d)\-(\d\d)\s+(\d\d):(\d\d):(\d\d)$/) {
+       # MySQL time
+       return timelocal($6, $5, $4, $3, $2-1, $1-1900);
+       }
+return undef;
+}
+
+1;
+
diff --git a/bacula-backup/bootup.cgi b/bacula-backup/bootup.cgi
new file mode 100755 (executable)
index 0000000..d0cc6b3
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/local/bin/perl
+# Start or stop Bacula at boot
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+&foreign_require("init", "init-lib.pl");
+
+if ($in{'boot'}) {
+       foreach $p (@bacula_inits) {
+               &init::enable_at_boot($p);
+               }
+       &webmin_log("bootup");
+       }
+else {
+       foreach $p (@bacula_inits) {
+               &init::disable_at_boot($p);
+               }
+       &webmin_log("bootdown");
+       }
+&redirect("");
+
diff --git a/bacula-backup/cancel_jobs.cgi b/bacula-backup/cancel_jobs.cgi
new file mode 100755 (executable)
index 0000000..752bf6c
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/local/bin/perl
+# Cancel running jobs
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+if (!$in{'refresh'}) {
+       # Cancel jobs if not refreshing
+       &error_setup($text{'cancel_err'});
+       @d = split(/\0/, $in{'d'});
+       @d || &error($text{'cancel_enone'});
+
+       $h = &open_console();
+       foreach $d (@d) {
+               $out = &console_cmd($h, "cancel JobId=$d");
+               if ($out =~ /failed|error/i) {
+                       &error(&text('dvolumes_ebacula', "<tt>$out</tt>"));
+                       }
+               }
+       &close_console($h);
+       }
+
+if ($in{'client'}) {
+       &redirect("clientstatus_form.cgi?client=$in{'client'}");
+       }
+elsif ($in{'storage'}) {
+       &redirect("storagestatus_form.cgi?storage=$in{'storage'}");
+       }
+else {
+       &redirect("dirstatus_form.cgi");
+       }
+
diff --git a/bacula-backup/clientstatus_form.cgi b/bacula-backup/clientstatus_form.cgi
new file mode 100755 (executable)
index 0000000..88b5f95
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/local/bin/perl
+# Show a form for displaying the status of one client
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'clientstatus_title'}, "", "clientstatus");
+&ReadParse();
+
+# Client selector
+@clients = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+               grep { !&is_oc_object($_, 1) } &get_bacula_clients();
+if (@clients == 1) {
+       $in{'client'} ||= $clients[0]->{'name'};
+       }
+print &ui_form_start("clientstatus_form.cgi");
+print "<b>$text{'clientstatus_show'}</b>\n";
+print &ui_select("client", $in{'client'},
+        [ map { [ $_->{'name'},
+                  &text('clientstatus_on', $_->{'name'}, $_->{'address'}) ] }
+          @clients ]);
+print &ui_submit($text{'clientstatus_ok'}),"<br>\n";
+print &ui_form_end();
+
+if ($in{'client'}) {
+       # Show this client
+       ($msg, $ok, $run, $done) = &get_client_status($in{'client'});
+
+       if ($ok) {
+               print &text('clientstatus_msg', $in{'client'}, $msg),"<p>\n";
+
+               # Running jobs
+               print &ui_subheading($text{'dirstatus_run'});
+               if (@$run) {
+                       print &ui_form_start("cancel_jobs.cgi", "post");
+                       print &ui_hidden("client", $in{'client'}),"\n";
+                       @links = ( &select_all_link("d", 1),
+                                  &select_invert_link("d", 1) );
+                       print &ui_links_row(\@links);
+                       @tds = ( "width=5" );
+                       print &ui_columns_start([ "", $text{'dirstatus_name'},
+                                                 $text{'dirstatus_id'},
+                                                 $text{'dirstatus_date2'} ],
+                                               "100%", 0, \@tds);
+                       foreach $j (@$run) {
+                               print &ui_checked_columns_row([
+                                       &joblink($j->{'name'}),
+                                       $j->{'id'},
+                                       $j->{'date'} ], \@tds, "d", $j->{'id'});
+                               }
+                       print &ui_columns_end();
+                       print &ui_links_row(\@links);
+                       print &ui_form_end([ [ "cancel", $text{'dirstatus_cancel'} ] ]);
+                       }
+               else {
+                       print "<b>$text{'dirstatus_runnone'}</b><p>\n";
+                       }
+
+               # Completed jobs
+               print &ui_subheading($text{'dirstatus_done'});
+               if (@$done) {
+                       print &ui_columns_start([ $text{'dirstatus_name'},
+                                                 $text{'dirstatus_id'},
+                                                 $text{'dirstatus_level'},
+                                                 $text{'dirstatus_date'},
+                                                 $text{'dirstatus_bytes'},
+                                                 $text{'dirstatus_files'},
+                                                 $text{'dirstatus_status2'} ],
+                                               "100%");
+                       foreach $j (@$done) {
+                               print &ui_columns_row([
+                                       &joblink($j->{'name'}),
+                                       $j->{'id'},
+                                       $j->{'level'},
+                                       $j->{'date'},
+                                       &nice_size($j->{'bytes'}),
+                                       $j->{'files'},
+                                       $j->{'status'} ]);
+                               }
+                       print &ui_columns_end();
+                       }
+               else {
+                       print "<b>$text{'dirstatus_donenone'}</b><p>\n";
+                       }
+               }
+       else {
+               # Couldn't connect!
+               print "<b>",&text('clientstatus_err', $in{'client'}, $msg),
+                     "</b><p>\n";
+               }
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
+
diff --git a/bacula-backup/config b/bacula-backup/config
new file mode 100644 (file)
index 0000000..5ee523e
--- /dev/null
@@ -0,0 +1,11 @@
+driver=Pg
+user=bacula
+pass=
+db=bacula
+bacula_dir=/etc/bacula
+bextract=bextract
+bls=bls
+btape=btape
+wait=1
+apply=1
+showdirs=0
diff --git a/bacula-backup/config-windows b/bacula-backup/config-windows
new file mode 100644 (file)
index 0000000..ead3506
--- /dev/null
@@ -0,0 +1,11 @@
+driver=Pg
+user=bacula
+pass=opencountry
+db=bacula
+bacula_dir=c:/bacula/bin
+bextract=bextract
+bls=bls
+btape=btape
+wait=1
+apply=1
+showdirs=0
diff --git a/bacula-backup/config.info b/bacula-backup/config.info
new file mode 100644 (file)
index 0000000..6d5cc1c
--- /dev/null
@@ -0,0 +1,15 @@
+line0=Configurable options,11
+wait=Default backup wait mode,1,1-Wait for completion,0-Run in background
+apply=Automatically apply director configuration?,1,1-Yes,0-No
+groupmode=Get node group information from,1,webmin-Webmin Servers Index module,oc-OCM Manager database,-Nowhere
+showdirs=Always show remote directors?,1,1-Yes,0-No
+line1=Bacula database settings,11
+driver=Database type,1,Pg-PostgreSQL,mysql-MySQL,SQLite-SQLite
+user=User to login to database as,0
+pass=Password to login with,0
+db=Database or file containing Bacula information,0
+line2=File settings,11
+bacula_dir=Bacula configuration directory,0
+bextract=Full path to <tt>bextract</tt> command,0
+bls=Full path to <tt>bls</tt> command,0
+btape=Full path to <tt>btape</tt> command,0
diff --git a/bacula-backup/delete_clients.cgi b/bacula-backup/delete_clients.cgi
new file mode 100755 (executable)
index 0000000..e93ab19
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl
+# Delete multiple clients
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@clients = &find("Client", $conf);
+
+&error_setup($text{'clients_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $client = &find_by("Name", $d, \@clients);
+       if ($client) {
+               $child = &find_dependency("Client", $d, [ "Job", "JobDefs" ], $conf);
+               $child && &error(&text('client_echild', $child));
+               &save_directive($conf, $parent, $client, undef, 0);
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "clients", scalar(@d));
+&redirect("list_clients.cgi");
+
diff --git a/bacula-backup/delete_fdirectors.cgi b/bacula-backup/delete_fdirectors.cgi
new file mode 100755 (executable)
index 0000000..6e15ad8
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/local/bin/perl
+# Delete multiple fdirector devices
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_file_config();
+$parent = &get_file_config_parent();
+@fdirectors = &find("Director", $conf);
+
+&error_setup($text{'fdirectors_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $fdirector = &find_by("Name", $d, \@fdirectors);
+       if ($fdirector) {
+               &save_directive($conf, $parent, $fdirector, undef, 0);
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "fdirectors", scalar(@d));
+&redirect("list_fdirectors.cgi");
+
diff --git a/bacula-backup/delete_filesets.cgi b/bacula-backup/delete_filesets.cgi
new file mode 100755 (executable)
index 0000000..3ff3fee
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl
+# Delete multiple filesets
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@filesets = &find("FileSet", $conf);
+
+&error_setup($text{'filesets_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $fileset = &find_by("Name", $d, \@filesets);
+       if ($fileset) {
+               $child = &find_dependency("Client", $d, [ "Job", "JobDefs" ], $conf);
+               $child && &error(&text('fileset_echild', $child));
+               &save_directive($conf, $parent, $fileset, undef, 0);
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "filesets", scalar(@d));
+&redirect("list_filesets.cgi");
+
diff --git a/bacula-backup/delete_gjobs.cgi b/bacula-backup/delete_gjobs.cgi
new file mode 100755 (executable)
index 0000000..89d6743
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/local/bin/perl
+# Delete group backup jobs
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@jobs = &find("JobDefs", $conf);
+
+@nodegroups = &list_node_groups();
+
+&error_setup($text{'gjobs_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $job = &find_by("Name", "ocjob_".$d, \@jobs);
+       if ($job) {
+               $client = &find_value("Client", $job->{'members'});
+               &save_directive($conf, $parent, $job, undef, 0);
+
+               ($nodegroup) = grep { $_->{'name'} eq $client } @nodegroups;
+               if ($nodegroup) {
+                       &sync_group_clients($nodegroup);
+                       }
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "gjobs", scalar(@d));
+&redirect("list_gjobs.cgi");
+
diff --git a/bacula-backup/delete_groups.cgi b/bacula-backup/delete_groups.cgi
new file mode 100755 (executable)
index 0000000..1f30b16
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/local/bin/perl
+# Delete multiple node groups
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@clients = &find("Client", $conf);
+
+@nodegroups = &list_node_groups();
+
+&error_setup($text{'groups_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $client = &find_by("Name", "ocgroup_".$d, \@clients);
+       if ($client) {
+               $child = &find_dependency("Client", $d, [ "Job", "JobDefs" ], $conf);
+               $child && &error(&text('client_echild', $child));
+               &save_directive($conf, $parent, $client, undef, 0);
+
+               ($nodegroup) = grep { $_->{'name'} eq $d } @nodegroups;
+               if ($nodegroup) {
+                       &sync_group_clients($nodegroup);
+                       }
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "groups", scalar(@d));
+&redirect("list_groups.cgi");
+
diff --git a/bacula-backup/delete_jobs.cgi b/bacula-backup/delete_jobs.cgi
new file mode 100755 (executable)
index 0000000..dfd1845
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl
+# Delete multiple jobs
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@jobs = ( &find("JobDefs", $conf), &find("Job", $conf) );
+
+&error_setup($text{'jobs_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $job = &find_by("Name", $d, \@jobs);
+       if ($job) {
+               $child = &find_dependency("JobDefs", $d, [ "Job" ], $conf);
+               $child && &error(&text('job_echild', $child));
+               &save_directive($conf, $parent, $job, undef, 0);
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "jobs", scalar(@d));
+&redirect("list_jobs.cgi");
+
diff --git a/bacula-backup/delete_pools.cgi b/bacula-backup/delete_pools.cgi
new file mode 100755 (executable)
index 0000000..c2dd473
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl
+# Delete multiple pool devices
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@pools = &find("Pool", $conf);
+
+&error_setup($text{'pools_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $pool = &find_by("Name", $d, \@pools);
+       if ($pool) {
+               $child = &find_dependency("Pool", $d, [ "Job", "JobDefs" ], $conf);
+               $child && &error(&text('pool_echild', $child));
+               &save_directive($conf, $parent, $pool, undef, 0);
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "pools", scalar(@d));
+&redirect("list_pools.cgi");
+
diff --git a/bacula-backup/delete_schedules.cgi b/bacula-backup/delete_schedules.cgi
new file mode 100755 (executable)
index 0000000..6fc384e
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl
+# Delete multiple schedules
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@schedules = &find("Schedule", $conf);
+
+&error_setup($text{'schedules_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $schedule = &find_by("Name", $d, \@schedules);
+       if ($schedule) {
+               $child = &find_dependency("Schedule", $d, [ "Job", "JobDefs" ], $conf);
+               $child && &error(&text('schedule_echild', $child));
+               &save_directive($conf, $parent, $schedule, undef, 0);
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "schedules", scalar(@d));
+&redirect("list_schedules.cgi");
+
diff --git a/bacula-backup/delete_sdirectors.cgi b/bacula-backup/delete_sdirectors.cgi
new file mode 100755 (executable)
index 0000000..8c6dd6b
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/local/bin/perl
+# Delete multiple storage daemon directors
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_storage_config();
+$parent = &get_storage_config_parent();
+@sdirectors = &find("Director", $conf);
+
+&error_setup($text{'sdirectors_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $sdirector = &find_by("Name", $d, \@sdirectors);
+       if ($sdirector) {
+               &save_directive($conf, $parent, $sdirector, undef, 0);
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "sdirectors", scalar(@d));
+&redirect("list_sdirectors.cgi");
+
diff --git a/bacula-backup/delete_storages.cgi b/bacula-backup/delete_storages.cgi
new file mode 100755 (executable)
index 0000000..4468819
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl
+# Delete multiple storage devices
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@storages = &find("Storage", $conf);
+
+&error_setup($text{'storages_derr'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'filesets_ednone'});
+
+&lock_file($parent->{'file'});
+foreach $d (@d) {
+       $storage = &find_by("Name", $d, \@storages);
+       if ($storage) {
+               $child = &find_dependency("Storage", $d, [ "Job", "JobDefs" ], $conf);
+               $child && &error(&text('storage_echild', $child));
+               &save_directive($conf, $parent, $storage, undef, 0);
+               }
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+&webmin_log("delete", "storages", scalar(@d));
+&redirect("list_storages.cgi");
+
diff --git a/bacula-backup/delete_volumes.cgi b/bacula-backup/delete_volumes.cgi
new file mode 100755 (executable)
index 0000000..79f2318
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/local/bin/perl
+# Delete a bunch of volumes from a pool
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+&error_setup($text{'dvolumes_err'});
+@d = split(/\0/, $in{'d'});
+@d || &error($text{'dvolumes_enone'});
+
+$h = &open_console();
+foreach $d (@d) {
+       &sysprint($h->{'infh'}, "delete media volume=$d\n");
+       $rv = &wait_for($h->{'outfh'}, "Are you sure.*:");
+       if ($rv == 0) {
+               &sysprint($h->{'infh'}, "yes\n");
+               }
+       else {
+               &error(&text('dvolumes_ebacula', "<tt>$wait_for_input</tt>"));
+               }
+       }
+&close_console($h);
+
+&redirect("poolstatus_form.cgi?pool=$in{'pool'}");
+
diff --git a/bacula-backup/dirstatus_form.cgi b/bacula-backup/dirstatus_form.cgi
new file mode 100755 (executable)
index 0000000..87afb88
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/local/bin/perl
+# Show the status of the director, including recent jobs
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'dirstatus_title'}, "", "dirstatus");
+
+($sched, $run, $done) = &get_director_status();
+
+# Running jobs
+print &ui_subheading($text{'dirstatus_run'});
+if (@$run) {
+       print &ui_form_start("cancel_jobs.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d") );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5" );
+       print &ui_columns_start([ "",
+                                 $text{'dirstatus_name'},
+                                 $text{'dirstatus_id'},
+                                 $text{'dirstatus_level'},
+                                 $text{'dirstatus_status'} ], "100%",
+                               0, \@tds);
+       foreach $j (@$run) {
+               print &ui_checked_columns_row([
+                       &joblink($j->{'name'}),
+                       $j->{'id'},
+                       $j->{'level'},
+                       $j->{'status'} ], \@tds, "d", $j->{'id'});
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "cancel", $text{'dirstatus_cancel'} ],
+                            [ "refresh", $text{'dirstatus_refresh'} ] ]);
+       }
+else {
+       print "<b>$text{'dirstatus_runnone'}</b><p>\n";
+       print &ui_form_start("cancel_jobs.cgi");
+       print &ui_form_end([ [ "refresh", $text{'dirstatus_refresh'} ] ]);
+       }
+
+# Completed jobs
+print &ui_subheading($text{'dirstatus_done'});
+if (@$done) {
+       print &ui_columns_start([ $text{'dirstatus_name'},
+                                 $text{'dirstatus_id'},
+                                 $text{'dirstatus_level'},
+                                 $text{'dirstatus_date'},
+                                 $text{'dirstatus_bytes'},
+                                 $text{'dirstatus_files'},
+                                 $text{'dirstatus_status2'} ], "100%");
+       foreach $j (@$done) {
+               print &ui_columns_row([
+                       &joblink($j->{'name'}),
+                       $j->{'id'},
+                       $j->{'level'},
+                       $j->{'date'},
+                       &nice_size($j->{'bytes'}),
+                       $j->{'files'},
+                       $j->{'status'} ]);
+               }
+       print &ui_columns_end();
+       }
+else {
+       print "<b>$text{'dirstatus_donenone'}</b><p>\n";
+       }
+
+
+
+# Scheduled jobs
+print &ui_subheading($text{'dirstatus_sched'});
+if (@$sched) {
+       print &ui_columns_start([ $text{'dirstatus_name'},
+                                 $text{'dirstatus_level'},
+                                 $text{'dirstatus_type'},
+                                 $text{'dirstatus_date'},
+                                 $text{'dirstatus_volume'} ], "100%");
+       foreach $j (@$sched) {
+               print &ui_columns_row([
+                       &joblink($j->{'name'}),
+                       $j->{'level'},
+                       $j->{'type'},
+                       $j->{'date'},
+                       $j->{'volume'} ]);
+               }
+       print &ui_columns_end();
+       }
+else {
+       print "<b>$text{'dirstatus_schednone'}</b><p>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/edit_client.cgi b/bacula-backup/edit_client.cgi
new file mode 100755 (executable)
index 0000000..075a991
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/local/bin/perl
+# Show the details of one client
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+@clients = &find("Client", $conf);
+@catalogs = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Catalog", $conf);
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'client_title1'}, "");
+       $mems = [ { 'name' => 'FDPort',
+                   'value' => 9102 },
+                 { 'name' => 'Catalog',
+                   'value' => $catalogs[0] },
+                 { 'name' => 'File Retention',
+                   'value' => '30 days' },
+                 { 'name' => 'Job Retention',
+                   'value' => '6 months' },
+               ];
+       $client = { 'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'client_title2'}, "");
+       $client = &find_by("Name", $in{'name'}, \@clients);
+       $client || &error($text{'client_egone'});
+       $mems = $client->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_client.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'client_header'}, "width=100%", 4);
+
+# Client name
+print &ui_table_row($text{'client_name'},
+       &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Password for remote
+print &ui_table_row($text{'client_pass'},
+       &ui_textbox("pass", $pass=&find_value("Password", $mems), 60), 3);
+
+# Connection details
+print &ui_table_row($text{'client_address'},
+       &ui_textbox("address", $address=&find_value("Address", $mems), 20));
+print &ui_table_row($text{'client_port'},
+       &ui_textbox("port", $port=&find_value("FDPort", $mems), 6));
+
+# Catalog
+print &ui_table_row($text{'client_catalog'},
+       &ui_select("catalog", $catalog=&find_value("Catalog", $mems),
+                  [ map { [ $_ ] } @catalogs ], 1, 0, 1));
+
+# Prune option
+$prune = &find_value("AutoPrune", $mems);
+print &ui_table_row($text{'client_prune'},
+       &ui_radio("prune", $prune,
+                 [ [ "yes", $text{'yes'} ], [ "no", $text{'no'} ],
+                   [ "", $text{'default'} ] ]));
+
+# Retention options
+$fileret = &find_value("File Retention", $mems);
+print &ui_table_row($text{'client_fileret'},
+                   &show_period_input("fileret", $fileret));
+$jobret = &find_value("Job Retention", $mems);
+print &ui_table_row($text{'client_jobret'},
+                   &show_period_input("jobret", $jobret));
+
+# SSL options
+&show_tls_directives($client);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "status", $text{'client_status'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_clients.cgi", $text{'clients_return'});
+
diff --git a/bacula-backup/edit_device.cgi b/bacula-backup/edit_device.cgi
new file mode 100755 (executable)
index 0000000..32013e0
--- /dev/null
@@ -0,0 +1,74 @@
+#!/usr/local/bin/perl
+# Show the details of one device device
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_storage_config();
+@devices = &find("Device", $conf);
+
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'device_title1'}, "");
+       $mems = [ { 'name' => 'Media Type',
+                   'value' => 'File' },
+                 { 'name' => 'LabelMedia',
+                   'value' => 'yes' },
+                 { 'name' => 'Random Access',
+                   'value' => 'yes' },
+                 { 'name' => 'AutomaticMount',
+                   'value' => 'yes' },
+                 { 'name' => 'RemovableMedia',
+                   'value' => 'no' },
+                 { 'name' => 'AlwaysOpen',
+                   'value' => 'no' },
+               ];
+       $device = { 'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'device_title2'}, "");
+       $device = &find_by("Name", $in{'name'}, \@devices);
+       $device || &error($text{'device_egone'});
+       $mems = $device->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_device.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'device_header'}, "width=100%", 4);
+
+# Device name
+print &ui_table_row($text{'device_name'},
+       &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Archive device or file
+print &ui_table_row($text{'device_device'},
+       &ui_textbox("device", $device=&find_value("Archive Device", $mems), 40)." ".
+       &file_chooser_button("device", 0), 3);
+
+# Media type
+print &ui_table_row($text{'device_media'},
+       &ui_textbox("media", $media=&find_value("Media Type", $mems), 20));
+
+# Various yes/no options
+print &ui_table_row($text{'device_label'},
+       &bacula_yesno("label", "LabelMedia", $mems));
+print &ui_table_row($text{'device_random'},
+       &bacula_yesno("random", "Random Access", $mems));
+print &ui_table_row($text{'device_auto'},
+       &bacula_yesno("auto", "AutomaticMount", $mems));
+print &ui_table_row($text{'device_removable'},
+       &bacula_yesno("removable", "RemovableMedia", $mems));
+print &ui_table_row($text{'device_always'},
+       &bacula_yesno("always", "AlwaysOpen", $mems));
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_devices.cgi", $text{'devices_return'});
+
diff --git a/bacula-backup/edit_director.cgi b/bacula-backup/edit_director.cgi
new file mode 100755 (executable)
index 0000000..c9b7507
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/local/bin/perl
+# Show the global director configuration
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$director = &find("Director", $conf);
+$director || &error($text{'director_enone'});
+$mems = $director->{'members'};
+
+@messages = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Messages", $conf);
+
+&ui_print_header(undef, $text{'director_title'}, "", "director");
+
+print &ui_form_start("save_director.cgi", "post");
+print &ui_table_start($text{'director_header'}, "width=100%", 4);
+
+$name = &find_value("Name", $mems);
+print &ui_table_row($text{'director_name'},
+                   &ui_textbox("name", $name, 20));
+
+$port = &find_value("DIRport", $mems);
+print &ui_table_row($text{'director_port'},
+                   &ui_textbox("port", $port, 6));
+
+$jobs = &find_value("Maximum Concurrent Jobs", $mems);
+print &ui_table_row($text{'director_jobs'},
+                   &ui_opt_textbox("jobs", $jobs, 6, $text{'default'}));
+
+$messages = &find_value("Messages", $mems);
+print &ui_table_row($text{'director_messages'},
+       &ui_select("messages", $messages,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @messages ], 1, 0, 1));
+
+$dir = &find_value("WorkingDirectory", $mems);
+print &ui_table_row($text{'director_dir'},
+                   &ui_textbox("dir", $dir, 60)." ".
+                   &file_chooser_button("dir", 1), 3);
+
+# SSL options
+&show_tls_directives($director);
+
+print &ui_table_end();
+print &ui_form_end([ [ "save", $text{'save'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/edit_fdirector.cgi b/bacula-backup/edit_fdirector.cgi
new file mode 100755 (executable)
index 0000000..11a7107
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/local/bin/perl
+# Show the details of one file fdirector daemon
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_file_config();
+@fdirectors = &find("Director", $conf);
+
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'fdirector_title1'}, "");
+       $mems = [ ];
+       $fdirector = { 'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'fdirector_title2'}, "");
+       $fdirector = &find_by("Name", $in{'name'}, \@fdirectors);
+       $fdirector || &error($text{'fdirector_egone'});
+       $mems = $fdirector->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_fdirector.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'fdirector_header'}, "width=100%", 4);
+
+# Director name
+print &ui_table_row($text{'fdirector_name'},
+       &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Password for remote
+print &ui_table_row($text{'fdirector_pass'},
+       &ui_textbox("pass", $pass=&find_value("Password", $mems), 60), 3);
+
+# Monitor mode
+print &ui_table_row($text{'fdirector_monitor'},
+       &bacula_yesno("monitor", "Monitor", $mems));
+
+&show_tls_directives($fdirector);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_fdirectors.cgi", $text{'fdirectors_return'});
+
diff --git a/bacula-backup/edit_file.cgi b/bacula-backup/edit_file.cgi
new file mode 100755 (executable)
index 0000000..560a793
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/local/bin/perl
+# Show the global file daemon configuration
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_file_config();
+$file = &find("FileDaemon", $conf);
+$file || &error($text{'file_enone'});
+$mems = $file->{'members'};
+
+&ui_print_header(undef, $text{'file_title'}, "", "file");
+
+print &ui_form_start("save_file.cgi", "post");
+print &ui_table_start($text{'file_header'}, "width=100%", 4);
+
+$name = &find_value("Name", $mems);
+print &ui_table_row($text{'file_name'},
+                   &ui_textbox("name", $name, 20));
+
+$port = &find_value("FDport", $mems);
+print &ui_table_row($text{'file_port'},
+                   &ui_textbox("port", $port, 6));
+
+$jobs = &find_value("Maximum Concurrent Jobs", $mems);
+print &ui_table_row($text{'file_jobs'},
+                   &ui_opt_textbox("jobs", $jobs, 6, $text{'default'}));
+
+$dir = &find_value("WorkingDirectory", $mems);
+print &ui_table_row($text{'file_dir'},
+                   &ui_textbox("dir", $dir, 60)." ".
+                   &file_chooser_button("dir", 1), 3);
+
+# SSL options
+&show_tls_directives($file);
+
+print &ui_table_end();
+print &ui_form_end([ [ "save", $text{'save'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/edit_fileset.cgi b/bacula-backup/edit_fileset.cgi
new file mode 100755 (executable)
index 0000000..7093b9d
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/local/bin/perl
+# Show the details of one fileset
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+@filesets = &find("FileSet", $conf);
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'fileset_title1'}, "");
+       $mems = [ ];
+       $fileset = { };
+       }
+else {
+       &ui_print_header(undef, $text{'fileset_title2'}, "");
+       $fileset = &find_by("Name", $in{'name'}, \@filesets);
+       $fileset || &error($text{'fileset_egone'});
+       $mems = $fileset->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_fileset.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'fileset_header'}, "width=100%", 4);
+
+# File set name
+print &ui_table_row($text{'fileset_name'},
+           &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Included files
+$inc = &find("Include", $mems);
+@files = $inc ? &find_value("File", $inc->{'members'}) : ( );
+print &ui_table_row($text{'fileset_include'},
+                   &ui_textarea("include", join("\n", @files), 5, 60)."\n".
+                   &file_chooser_button("include", 0, 0, undef, 1), 3);
+
+# Options
+$opts = $inc ? &find("Options", $inc->{'members'}) : undef;
+$sig = $opts ? &find_value("signature", $opts->{'members'}) : undef;
+print &ui_table_row($text{'fileset_sig'},
+                   &ui_select("signature", $sig,
+                       [ [ "", $text{'fileset_none'} ],
+                         [ "MD5" ], [ "SHA1" ] ], 1, 0, 1));
+
+# Excluded files
+$exc = &find("Exclude", $mems);
+@files = $exc ? &find_value("File", $exc->{'members'}) : ( );
+print &ui_table_row($text{'fileset_exclude'},
+                   &ui_textarea("exclude", join("\n", @files), 5, 60)."\n".
+                   &file_chooser_button("exclude", 0, 0, undef, 1), 3);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_filesets.cgi", $text{'filesets_return'});
+
diff --git a/bacula-backup/edit_gjob.cgi b/bacula-backup/edit_gjob.cgi
new file mode 100755 (executable)
index 0000000..2251900
--- /dev/null
@@ -0,0 +1,144 @@
+#!/usr/local/bin/perl
+# Show the details of one backup job
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+@jobs = &find("JobDefs", $conf);
+@clients = map { $n=&find_value("Name", $_->{'members'}); }
+               grep { ($g, $c) = &is_oc_object($_); $g && !$c }
+                  &find("Client", $conf);
+@filesets = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("FileSet", $conf);
+@schedules = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Schedule", $conf);
+@storages = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Storage", $conf);
+@pools = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Pool", $conf);
+@messages = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Messages", $conf);
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'gjob_title1'}, "");
+       $mems = [ { 'name' => 'Type',
+                   'value' => 'Backup' },
+                 { 'name' => 'Level',
+                   'value' => 'Incremental' },
+                 { 'name' => 'Client',
+                   'value' => $clients[0] },
+                 { 'name' => 'FileSet',
+                   'value' => $filesets[0] },
+                 { 'name' => 'Schedule',
+                   'value' => $schedules[0] },
+                 { 'name' => 'Storage',
+                   'value' => $storages[0] },
+                 { 'name' => 'Messages',
+                   'value' => $messages[0] },
+                 { 'name' => 'Pool',
+                   'value' => $pools[0] },
+               ];
+       $job = { 'name' => 'Job',
+                'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'gjob_title2'}, "");
+       $job = &find_by("Name", "ocjob_".$in{'name'}, \@jobs);
+       $job || &error($text{'job_egone'});
+       $mems = $job->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_gjob.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'gjob_header'}, "width=100%", 4);
+
+# Job name
+print &ui_table_row($text{'job_name'},
+                   &ui_textbox("name", $in{'name'}, 40), 3);
+
+# Job type
+$type = &find_value("Type", $mems);
+print &ui_table_row($text{'job_type'},
+       &ui_select("type", $type,
+               [ [ "Backup" ], [ "Restore" ], [ "Verify" ], [ "Admin" ] ],
+               1, 0, 1));
+
+# Backup level
+$level = &find_value("Level", $mems);
+print &ui_table_row($text{'job_level'},
+       &ui_select("level", $level,
+               [ map { [ $_ ] } @backup_levels ],
+               1, 0, 1));
+
+# Client being backed up
+$client = &find_value("Client", $mems);
+print &ui_table_row($text{'gjob_client'},
+       &ui_select("client", $client,
+               [ map { [ $_, &is_oc_object($_) ] } @clients ], 1, 0, 1));
+
+# Files to be backed up
+$fileset = &find_value("FileSet", $mems);
+print &ui_table_row($text{'job_fileset'},
+       &ui_select("fileset", $fileset,
+               [ map { [ $_ ] } @filesets ], 1, 0, 1));
+
+# Backup schedule
+$schedule = &find_value("Schedule", $mems);
+print &ui_table_row($text{'job_schedule'},
+       &ui_select("schedule", $schedule,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @schedules ], 1, 0, 1));
+
+# Storage device
+$storage = &find_value("Storage", $mems);
+print &ui_table_row($text{'job_storage'},
+       &ui_select("storage", $storage,
+               [ map { [ $_ ] } @storages ], 1, 0, 1));
+
+# Backup pool
+$pool = &find_value("Pool", $mems);
+print &ui_table_row($text{'job_pool'},
+       &ui_select("pool", $pool,
+               [ map { [ $_ ] } @pools ], 1, 0, 1));
+
+# Backup messages
+$messages = &find_value("Messages", $mems);
+print &ui_table_row($text{'job_messages'},
+       &ui_select("messages", $messages,
+               [ map { [ $_ ] } @messages ], 1, 0, 1));
+
+# Priority level
+$prority = &find_value("Priority", $mems);
+print &ui_table_row($text{'job_prority'},
+       &ui_opt_textbox("priority", $priority, 4, $text{'default'}));
+
+# Before and after commands
+print &ui_table_hr();
+
+$before = &find_value("Run Before Job", $mems);
+print &ui_table_row($text{'job_before'},
+       &ui_opt_textbox("before", $before, 60, $text{'default'}), 3);
+$after = &find_value("Run After Job", $mems);
+print &ui_table_row($text{'job_after'},
+       &ui_opt_textbox("after", $after, 60, $text{'default'}), 3);
+
+$cbefore = &find_value("Client Run Before Job", $mems);
+print &ui_table_row($text{'job_cbefore'},
+       &ui_opt_textbox("cbefore", $cbefore, 60, $text{'default'}), 3);
+$cafter = &find_value("Client Run After Job", $mems);
+print &ui_table_row($text{'job_cafter'},
+       &ui_opt_textbox("cafter", $cafter, 60, $text{'default'}), 3);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "run", $text{'job_run'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_gjobs.cgi", $text{'jobs_return'});
+
diff --git a/bacula-backup/edit_group.cgi b/bacula-backup/edit_group.cgi
new file mode 100755 (executable)
index 0000000..a4f00f4
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/local/bin/perl
+# Show the details of one node group, which is actually a special client
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+@groups = &find("Client", $conf);
+@catalogs = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Catalog", $conf);
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'group_title1'}, "");
+       $mems = [ { 'name' => 'FDPort',
+                   'value' => 9102 },
+                 { 'name' => 'Catalog',
+                   'value' => $catalogs[0] },
+                 { 'name' => 'File Retention',
+                   'value' => '30 days' },
+                 { 'name' => 'Job Retention',
+                   'value' => '6 months' },
+               ];
+       $group = { 'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'group_title2'}, "");
+       $group = &find_by("Name", "ocgroup_".$in{'name'}, \@groups);
+       $group || &error($text{'group_egone'});
+       $mems = $group->{'members'};
+       }
+
+# Get node group
+@nodegroups = &list_node_groups();
+$ngname = $in{'name'} || $in{'new'};
+($nodegroup) = grep { $_->{'name'} eq $ngname } @nodegroups;
+
+# Show details
+print &ui_form_start("save_group.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'group_header'}, "width=100%", 4);
+
+# Group name
+print &ui_table_row($text{'group_name'},
+                   $in{'new'} || $in{'name'});
+
+# Password for remote
+print &ui_table_row($text{'client_pass'},
+       &ui_textbox("pass", $pass=&find_value("Password", $mems), 60), 3);
+
+# FD port
+print &ui_table_row($text{'client_port'},
+       &ui_textbox("port", $port=&find_value("FDPort", $mems), 6), 3);
+
+# Catalog
+print &ui_table_row($text{'client_catalog'},
+       &ui_select("catalog", $catalog=&find_value("Catalog", $mems),
+                  [ map { [ $_ ] } @catalogs ], 1, 0, 1));
+
+# Prune option
+$prune = &find_value("AutoPrune", $mems);
+print &ui_table_row($text{'client_prune'},
+       &ui_radio("prune", $prune,
+                 [ [ "yes", $text{'yes'} ], [ "no", $text{'no'} ],
+                   [ "", $text{'default'} ] ]));
+
+# Retention options
+$fileret = &find_value("File Retention", $mems);
+print &ui_table_row($text{'client_fileret'},
+                   &show_period_input("fileret", $fileret));
+$jobret = &find_value("Job Retention", $mems);
+print &ui_table_row($text{'client_jobret'},
+                   &show_period_input("jobret", $jobret));
+
+# Members
+print &ui_table_row($text{'group_members'},
+                   join(", ", @{$nodegroup->{'members'}}), 3);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_groups.cgi", $text{'groups_return'});
+
diff --git a/bacula-backup/edit_job.cgi b/bacula-backup/edit_job.cgi
new file mode 100755 (executable)
index 0000000..2d56b61
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/local/bin/perl
+# Show the details of one backup job
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+@jobs = ( &find("JobDefs", $conf), &find("Job", $conf) );
+@clients = map { $n=&find_value("Name", $_->{'members'}) }
+               grep { !&is_oc_object($_) } &find("Client", $conf);
+@filesets = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("FileSet", $conf);
+@schedules = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Schedule", $conf);
+@storages = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Storage", $conf);
+@pools = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Pool", $conf);
+@messages = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Messages", $conf);
+@defs = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("JobDefs", $conf);
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'job_title1'}, "");
+       $mems = [ { 'name' => 'Type',
+                   'value' => 'Backup' },
+                 { 'name' => 'Level',
+                   'value' => 'Incremental' },
+                 { 'name' => 'Client',
+                   'value' => $clients[0] },
+                 { 'name' => 'FileSet',
+                   'value' => $filesets[0] },
+                 { 'name' => 'Schedule',
+                   'value' => $schedules[0] },
+                 { 'name' => 'Storage',
+                   'value' => $storages[0] },
+                 { 'name' => 'Messages',
+                   'value' => $messages[0] },
+                 { 'name' => 'Pool',
+                   'value' => $pools[0] },
+               ];
+       $job = { 'name' => 'Job',
+                'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'job_title2'}, "");
+       $job = &find_by("Name", $in{'name'}, \@jobs);
+       $job || &error($text{'job_egone'});
+       $mems = $job->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_job.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'job_header'}, "width=100%", 4);
+
+# Job name
+print &ui_table_row($text{'job_name'},
+       &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Default or source
+$defs = &find_value("JobDefs", $mems);
+$dmode = $defs ? 2 : $job->{'name'} eq 'Job' ? 1 : 0;
+print &ui_table_row($text{'job_def'},
+    &ui_radio("dmode", $dmode,
+       [ [ 0, $text{'job_def0'} ],
+         [ 1, $text{'job_def1'} ],
+         [ 2, &text('job_def2', 
+               &ui_select("defs", $defs, [ map { [ $_ ] } @defs ])) ] ]), 3);
+
+# Job type
+$type = &find_value("Type", $mems);
+print &ui_table_row($text{'job_type'},
+       &ui_select("type", $type,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 [ "Backup" ], [ "Restore" ], [ "Verify" ], [ "Admin" ] ],
+               1, 0, 1));
+
+# Backup level
+$level = &find_value("Level", $mems);
+print &ui_table_row($text{'job_level'},
+       &ui_select("level", $level,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @backup_levels ],
+               1, 0, 1));
+
+# Client being backed up
+$client = &find_value("Client", $mems);
+print &ui_table_row($text{'job_client'},
+       &ui_select("client", $client,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @clients ], 1, 0, 1));
+
+# Files to be backed up
+$fileset = &find_value("FileSet", $mems);
+print &ui_table_row($text{'job_fileset'},
+       &ui_select("fileset", $fileset,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @filesets ], 1, 0, 1));
+
+# Backup schedule
+$schedule = &find_value("Schedule", $mems);
+print &ui_table_row($text{'job_schedule'},
+       &ui_select("schedule", $schedule,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @schedules ], 1, 0, 1));
+
+# Storage device
+$storage = &find_value("Storage", $mems);
+print &ui_table_row($text{'job_storage'},
+       &ui_select("storage", $storage,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @storages ], 1, 0, 1));
+
+# Backup pool
+$pool = &find_value("Pool", $mems);
+print &ui_table_row($text{'job_pool'},
+       &ui_select("pool", $pool,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @pools ], 1, 0, 1));
+
+# Backup messages
+$messages = &find_value("Messages", $mems);
+print &ui_table_row($text{'job_messages'},
+       &ui_select("messages", $messages,
+               [ [ "", "&lt;$text{'default'}&gt;" ],
+                 map { [ $_ ] } @messages ], 1, 0, 1));
+
+# Priority level
+$prority = &find_value("Priority", $mems);
+print &ui_table_row($text{'job_prority'},
+       &ui_opt_textbox("priority", $priority, 4, $text{'default'}));
+
+# Before and after commands
+print &ui_table_hr();
+
+$before = &find_value("Run Before Job", $mems);
+print &ui_table_row($text{'job_before'},
+       &ui_opt_textbox("before", $before, 60, $text{'default'}), 3);
+$after = &find_value("Run After Job", $mems);
+print &ui_table_row($text{'job_after'},
+       &ui_opt_textbox("after", $after, 60, $text{'default'}), 3);
+
+$cbefore = &find_value("Client Run Before Job", $mems);
+print &ui_table_row($text{'job_cbefore'},
+       &ui_opt_textbox("cbefore", $cbefore, 60, $text{'default'}), 3);
+$cafter = &find_value("Client Run After Job", $mems);
+print &ui_table_row($text{'job_cafter'},
+       &ui_opt_textbox("cafter", $cafter, 60, $text{'default'}), 3);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       ($bjob) = grep { $_->{'name'} eq $in{'name'} } &get_bacula_jobs();
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            ( $job->{'name'} eq 'Job' && $bjob ?
+                               ( [ "run", $text{'job_run'} ] ) : ( ) ),
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_jobs.cgi", $text{'jobs_return'});
+
diff --git a/bacula-backup/edit_pool.cgi b/bacula-backup/edit_pool.cgi
new file mode 100755 (executable)
index 0000000..f2804bb
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/local/bin/perl
+# Show the details of one file pool daemon
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+@pools = &find("Pool", $conf);
+
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'pool_title1'}, "");
+       $mems = [ { 'name' => 'Pool Type',
+                   'value' => 'Backup' },
+                 { 'name' => 'Recycle',
+                   'value' => 'yes' },
+                 { 'name' => 'AutoPrune',
+                   'value' => 'yes' },
+                 { 'name' => 'Accept Any Volume',
+                   'value' => 'yes' },
+                 { 'name' => 'Volume Retention',
+                   'value' => '365 days' },
+               ];
+       $pool = { 'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'pool_title2'}, "");
+       $pool = &find_by("Name", $in{'name'}, \@pools);
+       $pool || &error($text{'pool_egone'});
+       $mems = $pool->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_pool.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'pool_header'}, "width=100%", 4);
+
+# Pool name
+print &ui_table_row($text{'pool_name'},
+       &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Pool type
+print &ui_table_row($text{'pool_type'},
+       &ui_select("type", $type=&find_value("Pool Type", $mems),
+                  [ map { [ $_, $_ =~ /^\*(.*)$/ ? $1 : $_ ] }
+                        @pool_types ], 1, 0, 1));
+
+# Maximum Volume Jobs
+$max = &find_value("Maximum Volume Jobs", $mems);
+print &ui_table_row($text{'pool_max'},
+           &ui_radio("maxmode", $max == 0 ? 0 : 1,
+                     [ [ 0, $text{'pool_unlimited'} ],
+                       [ 1, &ui_textbox('max', $max == 0 ? "" : $max, 6) ] ]));
+
+# Retention period
+$reten = &find_value("Volume Retention", $mems);
+print &ui_table_row($text{'pool_reten'},
+                   &show_period_input("reten", $reten));
+
+# Various yes/no options
+print &ui_table_row($text{'pool_recycle'},
+                   &bacula_yesno("recycle", "Recycle", $mems));
+print &ui_table_row($text{'pool_auto'},
+                   &bacula_yesno("auto", "AutoPrune", $mems));
+print &ui_table_row($text{'pool_any'},
+                   &bacula_yesno("any", "Accept Any Volume", $mems));
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "status", $text{'pool_status'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_pools.cgi", $text{'pools_return'});
+
diff --git a/bacula-backup/edit_schedule.cgi b/bacula-backup/edit_schedule.cgi
new file mode 100755 (executable)
index 0000000..1477194
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/local/bin/perl
+# Show the details of one schedule
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+@schedules = &find("Schedule", $conf);
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'schedule_title1'}, "");
+       $mems = [ ];
+       $schedule = { };
+       }
+else {
+       &ui_print_header(undef, $text{'schedule_title2'}, "");
+       $schedule = &find_by("Name", $in{'name'}, \@schedules);
+       $schedule || &error($text{'schedule_egone'});
+       $mems = $schedule->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_schedule.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'schedule_header'}, "width=100%", 4);
+
+# Schedule
+print &ui_table_row($text{'schedule_name'},
+           &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Run files
+@runs = &find_value("Run", $schedule->{'members'});
+$rtable = &ui_columns_start([ $text{'schedule_level'},
+                             $text{'schedule_times'} ], "width=100%");
+$i = 0;
+foreach $r (@runs, undef, undef, undef) {
+       ($level, $times) = split(/\s+/, $r, 2);
+       $level =~ s/^Level\s*=\s*//;
+       $sched = &parse_schedule($times);
+       $rtable .= &ui_columns_row([
+               &ui_select("level_$i", $level,
+                          [ [ "", "&nbsp;" ], [ "Full" ],
+                            [ "Incremental" ], [ "Differential" ] ],
+                          1, 0, 1),
+               &ui_textbox("times_$i", $times,
+                           $sched || !$r ? "40 readonly" : 40)." ".
+               &schedule_chooser_button("times_$i") ]);
+       $i++;
+       }
+$rtable .= &ui_columns_end();
+print &ui_table_row($text{'schedule_runs'}, $rtable);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_schedules.cgi", $text{'schedules_return'});
+
diff --git a/bacula-backup/edit_sdirector.cgi b/bacula-backup/edit_sdirector.cgi
new file mode 100755 (executable)
index 0000000..54ed443
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/local/bin/perl
+# Show the details of one file daemon director
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_storage_config();
+@sdirectors = &find("Director", $conf);
+
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'sdirector_title1'}, "");
+       $mems = [ ];
+       $sdirector = { 'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'sdirector_title2'}, "");
+       $sdirector = &find_by("Name", $in{'name'}, \@sdirectors);
+       $sdirector || &error($text{'sdirector_egone'});
+       $mems = $sdirector->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_sdirector.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'sdirector_header'}, "width=100%", 4);
+
+# Director name
+print &ui_table_row($text{'sdirector_name'},
+       &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Password for remote
+print &ui_table_row($text{'sdirector_pass'},
+       &ui_textbox("pass", $pass=&find_value("Password", $mems), 60), 3);
+
+# Monitor mode
+print &ui_table_row($text{'sdirector_monitor'},
+       &bacula_yesno("monitor", "Monitor", $mems));
+
+&show_tls_directives($sdirector);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_sdirectors.cgi", $text{'sdirectors_return'});
+
diff --git a/bacula-backup/edit_storage.cgi b/bacula-backup/edit_storage.cgi
new file mode 100755 (executable)
index 0000000..bc328fd
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/local/bin/perl
+# Show the details of one file storage daemon
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+@storages = &find("Storage", $conf);
+$sconf = &get_storage_config();
+if ($sconf) {
+       @devices = map { $n=&find_value("Name", $_->{'members'}) }
+                       &find("Device", $sconf);
+       }
+
+if ($in{'new'}) {
+       &ui_print_header(undef, $text{'storage_title1'}, "");
+       $mems = [ { 'name' => 'SDPort',
+                   'value' => 9103 },
+                 { 'name' => 'Address',
+                   'value' => &get_system_hostname() },
+                 { 'name' => 'Media Type',
+                   'value' => 'File' },
+                 { 'name' => 'Device',
+                   'value' => $devices[0] },
+               ];
+       if (@storages) {
+               push(@$mems,
+                       { 'name' => 'Password',
+                         'value' => &find_value("Password",
+                                       $storages[0]->{'members'})
+                       });
+               }
+       $storage = { 'members' => $mems };
+       }
+else {
+       &ui_print_header(undef, $text{'storage_title2'}, "");
+       $storage = &find_by("Name", $in{'name'}, \@storages);
+       $storage || &error($text{'storage_egone'});
+       $mems = $storage->{'members'};
+       }
+
+# Show details
+print &ui_form_start("save_storage.cgi", "post");
+print &ui_hidden("new", $in{'new'}),"\n";
+print &ui_hidden("old", $in{'name'}),"\n";
+print &ui_table_start($text{'storage_header'}, "width=100%", 4);
+
+# Storage name
+print &ui_table_row($text{'storage_name'},
+       &ui_textbox("name", $name=&find_value("Name", $mems), 40), 3);
+
+# Password for remote
+print &ui_table_row($text{'storage_pass'},
+       &ui_textbox("pass", $pass=&find_value("Password", $mems), 60), 3);
+
+# Connection details
+print &ui_table_row($text{'storage_address'},
+       &ui_textbox("address", $address=&find_value("Address", $mems), 20));
+print &ui_table_row($text{'storage_port'},
+       &ui_textbox("port", $port=&find_value("SDPort", $mems), 6));
+
+# Device name
+if (@devices) {
+       $device=&find_value("Device", $mems);
+       $found = &indexof($device, @devices) >= 0;
+       print &ui_table_row($text{'storage_device'},
+               &ui_select("device", $found ? $device : "",
+                          [ (map { [ $_ ] } @devices),
+                            [ "", $text{'storage_other'} ] ])."\n".
+               &ui_textbox("other", $found ? "" : $device, 10));
+       }
+else {
+       print &ui_table_row($text{'storage_device'},
+               &ui_textbox("device", $device=&find_value("Device",$mems), 20));
+       }
+
+# Media type
+print &ui_table_row($text{'storage_media'},
+       &ui_textbox("media", $media=&find_value("Media Type", $mems), 20));
+
+# SSL options
+&show_tls_directives($storage);
+
+# All done
+print &ui_table_end();
+if ($in{'new'}) {
+       print &ui_form_end([ [ "create", $text{'create'} ] ]);
+       }
+else {
+       print &ui_form_end([ [ "save", $text{'save'} ],
+                            [ "status", $text{'storage_status'} ],
+                            [ "delete", $text{'delete'} ] ]);
+       }
+&ui_print_footer("list_storages.cgi", $text{'storages_return'});
+
diff --git a/bacula-backup/edit_storagec.cgi b/bacula-backup/edit_storagec.cgi
new file mode 100755 (executable)
index 0000000..18456b8
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/local/bin/perl
+# Show the global storage daemon configuration
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_storage_config();
+$storagec = &find("Storage", $conf);
+$storagec || &error($text{'storagec_enone'});
+$mems = $storagec->{'members'};
+
+@messages = map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Messages", $conf);
+
+&ui_print_header(undef, $text{'storagec_title'}, "", "storagec");
+
+print &ui_form_start("save_storagec.cgi", "post");
+print &ui_table_start($text{'storagec_header'}, "width=100%", 4);
+
+$name = &find_value("Name", $mems);
+print &ui_table_row($text{'storagec_name'},
+                   &ui_textbox("name", $name, 20));
+
+$port = &find_value("SDport", $mems);
+print &ui_table_row($text{'storagec_port'},
+                   &ui_textbox("port", $port, 6));
+
+$jobs = &find_value("Maximum Concurrent Jobs", $mems);
+print &ui_table_row($text{'storagec_jobs'},
+                   &ui_opt_textbox("jobs", $jobs, 6, $text{'default'}));
+
+$dir = &find_value("WorkingDirectory", $mems);
+print &ui_table_row($text{'storagec_dir'},
+                   &ui_textbox("dir", $dir, 60)." ".
+                   &file_chooser_button("dir", 1), 3);
+
+# SSL options
+&show_tls_directives($storagec);
+
+print &ui_table_end();
+print &ui_form_end([ [ "save", $text{'save'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/fixaddr.cgi b/bacula-backup/fixaddr.cgi
new file mode 100755 (executable)
index 0000000..02145df
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/local/bin/perl
+# Update the host in bconsole.conf to match this system
+
+require './bacula-backup-lib.pl';
+
+&lock_file($bconsole_conf_file);
+$conconf = &get_bconsole_config();
+$condir = &find("Director", $conconf);
+$addr = &get_system_hostname();
+if (!gethostbyname($addr)) {
+       $addr = "localhost";
+       }
+&save_directive($conconf, $condir, "Address", $addr, 1);
+&flush_file_lines();
+&unlock_file($bconsole_conf_file);
+
+&webmin_log("fixaddr");
+&redirect("");
+
diff --git a/bacula-backup/fixpass.cgi b/bacula-backup/fixpass.cgi
new file mode 100755 (executable)
index 0000000..019e624
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/local/bin/perl
+# Update the password in bconsole.conf to match bacula-dir.conf
+
+require './bacula-backup-lib.pl';
+
+&lock_file($bconsole_conf_file);
+$dirconf = &get_director_config();
+$dirdir = &find("Director", $dirconf);
+$conconf = &get_bconsole_config();
+$condir = &find("Director", $conconf);
+$dirpass = &find_value("Password", $dirdir->{'members'});
+&save_directive($conconf, $condir, "Password", $dirpass, 1);
+&flush_file_lines();
+&unlock_file($bconsole_conf_file);
+
+&webmin_log("fixpass");
+&redirect("");
+
diff --git a/bacula-backup/gbackup.cgi b/bacula-backup/gbackup.cgi
new file mode 100755 (executable)
index 0000000..3a1f10f
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/local/bin/perl
+# Execute multiple backup jobs, one for each client
+
+require './bacula-backup-lib.pl';
+&ui_print_unbuffered_header(undef,  $text{'gbackup_title'}, "");
+&ReadParse();
+
+# Get the backup job def and real jobs
+$conf = &get_director_config();
+@jobdefs = &find("JobDefs", $conf);
+$jobdef = &find_by("Name", "ocjob_".$in{'job'}, \@jobdefs);
+foreach $job (&get_bacula_jobs()) {
+       ($j, $c) = &is_oc_object($job);
+       if ($j eq $in{'job'} && $c) {
+               push(@jobs, $job);
+               }
+       }
+
+print "<b>",&text('gbackup_run', "<tt>$in{'job'}</tt>",
+                                scalar(@jobs)),"</b>\n";
+
+# Clear messages
+$h = &open_console();
+&console_cmd($h, "messages");
+
+# Run the real jobs
+print "<dl>\n";
+foreach $job (@jobs) {
+       ($j, $c) = &is_oc_object($job);
+       print "<dt>",&text('gbackup_on', "<tt>$c</tt>"),"\n"; 
+       print "<dd><pre>";
+
+       # Select the job to run
+       &sysprint($h->{'infh'}, "run\n");
+       &wait_for($h->{'outfh'}, 'run\\n');
+       $rv = &wait_for($h->{'outfh'}, 'Select Job.*:');
+       print $wait_for_input;
+       if ($rv == 0 && $wait_for_input =~ /(\d+):\s+\Q$job->{'name'}\E/) {
+               &sysprint($h->{'infh'}, "$1\n");
+               }
+       else {
+               &job_error($text{'backup_ejob'});
+               }
+
+       # Say that it is OK
+       $rv = &wait_for($h->{'outfh'}, 'OK to run.*:');
+       print $wait_for_input;
+       if ($rv == 0) {
+               &sysprint($h->{'infh'}, "yes\n");
+               }
+       else {
+               &job_error($text{'backup_eok'});
+               }
+
+       print "</pre>";
+       }
+print "</dl>\n";
+&close_console($h);
+&webmin_log("gbackup", $in{'job'});
+
+&ui_print_footer("", $text{'index_return'});
+
+sub job_error
+{
+print "</pre>\n";
+print "<b>",@_,"</b><p>\n";
+&close_console($h);
+&ui_print_footer("backup_form.cgi", $text{'backup_return'});
+exit;
+}
+
diff --git a/bacula-backup/help/backup.html b/bacula-backup/help/backup.html
new file mode 100644 (file)
index 0000000..e501ed2
--- /dev/null
@@ -0,0 +1,8 @@
+<header>Run Backup Job</header>
+
+This form can be used to start the immediate execution of a Bacula backup
+job. To run a job, you only need to select it from the list, and select whether
+or not Webmin should wait for its final status to be displayed. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/clients.html b/bacula-backup/help/clients.html
new file mode 100644 (file)
index 0000000..b2c521c
--- /dev/null
@@ -0,0 +1,18 @@
+<header>Backup Clients</header>
+
+A Bacula client is a system whose files can be backed up. All the clients
+that you wish to backup files on must be listed on this page, and each must
+have the Bacula file daemon running on it. In a typical single-system setup,
+on this server needs to be listed. <p>
+
+When adding remote clients, in addition to the hostname you also need to know
+the name of it's Bacula file daemon, and the daemon's password. These are set
+in the <tt>/etc/bacula/bacula-fd.conf</tt> file on the client system. <p>
+
+If shown, the TLS options on the client page determine if encryption is used
+when this director communicates with the client. Before TLS can be enabled,
+you must generate an SSL certificate and key, and enter their paths into the
+appropriate fields on this form. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/clientstatus.html b/bacula-backup/help/clientstatus.html
new file mode 100644 (file)
index 0000000..5b4d69a
--- /dev/null
@@ -0,0 +1,8 @@
+<header>Client Status</header>
+
+This page displays backup jobs that are currently running and the 10 most
+recently run, on a selected Bacula client system. Those running or run on
+different clients will not be displayed. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/devices.html b/bacula-backup/help/devices.html
new file mode 100644 (file)
index 0000000..6f9024c
--- /dev/null
@@ -0,0 +1,19 @@
+<header>Storage Devices</header>
+
+Unlike all of the other pages in this module, this one configures the Bacula
+storage daemon rather than the director. It allows you to control which tape
+devices and directories are used for backups. Each entry on the list defines
+a storage device, each of which must have a unique name, a device file or 
+directory (like <i>/dev/st0</i> or <i>/backup</i>), and a media type name. <p>
+
+Devices defined here can be referenced on the <b>Storage Daemons</b> page,
+which can in turn be used in backup jobs. This if you want to create a new
+directory to backup to, it must be added both here and to the daemons list.
+In addition, any new directory must be labelled first (using the <b>Label
+Volume</b> page) before Bacula will write to it. <p>
+
+Be aware that changes made to this page will only be activated when the
+<b>Restart Bacula</b> button is clicked on the module's main page. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/director.html b/bacula-backup/help/director.html
new file mode 100644 (file)
index 0000000..8c883cf
--- /dev/null
@@ -0,0 +1,13 @@
+<header>Director Configuration</header>
+
+This page allows you to configure the Bacula director process, which is
+responsible for actually controlling and scheduling all backup jobs. Most of
+the options here do not generally need to be adjusted, as the defaults are
+typically correct for your system. <p>
+
+The TLS options here can be used to enable secure encypted communication
+between the director and the command-line <tt>bconsole</tt> program. Because
+these typically run on the same system, TLS does not usually need to be enabled.<p>
+
+<footer>
+
diff --git a/bacula-backup/help/dirstatus.html b/bacula-backup/help/dirstatus.html
new file mode 100644 (file)
index 0000000..61805a8
--- /dev/null
@@ -0,0 +1,8 @@
+<header>Director Status</header>
+
+This page displays all backup jobs that are currently running, the 10 most
+recently run, and those that are scheduled to run in future. All jobs known
+to the Bacula director on this system will be included. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/fdirectors.html b/bacula-backup/help/fdirectors.html
new file mode 100644 (file)
index 0000000..7659607
--- /dev/null
@@ -0,0 +1,12 @@
+<header>File Daemon Directors</header>
+
+This page lists all directors that are allowed to connect to this file
+daemon. If you are adding this file daemon to a remote director, the password
+in its <b>Backup Client</b> entry must match the password listed here. <p>
+
+If TLS is enabled for a director, communication between that director and
+this file daemon will be encrypted and verified using the selected certificate
+and key. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/file.html b/bacula-backup/help/file.html
new file mode 100644 (file)
index 0000000..0e1716d
--- /dev/null
@@ -0,0 +1,10 @@
+<header>File Daemon Configuration</header>
+
+This form can be used to adjust settings for the Bacula file daemon running
+on the system. The defaults will typically be correct, although you can adjust
+the daemon name and number of concurrent jobs. If your Bacula system supports
+TLS security, the TLS options on this page can be used to secure communication
+between the file daemon, director and storage daemons. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/filesets.html b/bacula-backup/help/filesets.html
new file mode 100644 (file)
index 0000000..649c3d4
--- /dev/null
@@ -0,0 +1,10 @@
+<header>File Sets</header>
+
+A file set is a list of files and directories that Bacula can back up as part
+of a job. Each set must have a unique name, a list of files to include and an
+optional list of files to exclude. This latter feature can be useful for
+skipping non-critical files or directories under one of the directories to
+exclude. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/gbackup.html b/bacula-backup/help/gbackup.html
new file mode 100644 (file)
index 0000000..e2e37be
--- /dev/null
@@ -0,0 +1,9 @@
+<header>Run Bacula Group Job</header>
+
+This form can be used to start the immediate execution of a Bacula backup
+job that runs on all hosts in a Bacula group. To start it, just select it from
+the list and click the <b>Backup Now</b> button. Because the job runs on 
+multiple hosts, its final status will not be displayed. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/gjobs.html b/bacula-backup/help/gjobs.html
new file mode 100644 (file)
index 0000000..e57dc06
--- /dev/null
@@ -0,0 +1,10 @@
+<header>Bacula Group Backup Jobs</header>
+
+This page lists backup jobs that are set up to run on all hosts in a Bacula
+group at once. They have the same settings as standard backup jobs, except
+that you cannot define default jobs to share settings between jobs. Also,
+instead of selecting a client to backup, you must instead select a previously
+defined Bacula group. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/groups.html b/bacula-backup/help/groups.html
new file mode 100644 (file)
index 0000000..eed04fa
--- /dev/null
@@ -0,0 +1,13 @@
+<header>Bacula Groups</header>
+
+This page lists host groups, each of which is a set of hosts that can be
+simultaneously backed by a single <b>Bacula Group Backup Job</b>. The hosts
+in a group are taken from either an OCM Manager node group database or the
+Webmin Servers Index module, depending on the configuration of this module.<p>
+
+For bacula group backups to work, each must have at least the Bacula file daemon
+installed, and all hosts in the group must have the same password for this 
+director. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/intro.html b/bacula-backup/help/intro.html
new file mode 100644 (file)
index 0000000..d10ce34
--- /dev/null
@@ -0,0 +1,29 @@
+<header>Bacula Backup</header>
+
+Bacula is a set of computer programs that permits you (or the system administrator) to manage backup, recovery, and verification of computer data across a network of computers of different kinds. Bacula can also run entirely upon a single computer, and can backup to various types of media, including tape and disk.<p>
+
+In technical terms, it is a network Client/Server based backup program. Bacula is relatively easy to use and efficient, while offering many advanced storage management features that make it easy to find and recover lost or damaged files. Due to its modular design, Bacula is scalable from small single computer systems to systems consisting of hundreds of computers located over a large network.<p>
+
+The Bacula system is divided into three separate daemons, which can theoretically run on different hosts. These are:<br>
+<dl>
+<dt>Director
+<dd>The director stores jobs, clients and most other configuration setttings,
+    and is responsible for initiating backup jobs. This Webmin module must be
+    run on the system that has the director installed. 
+<dt>File Daemon
+<dd>This daemon is responsible for reading the actual files to be backed up.
+    A Bacula configuration can have more that one file daemon, each run on
+    a system to be backed up.
+<dt>Storage Daemon
+<dd>The storage daemon is responsible for writing data to the final backup
+    media, such as a tape drive or file. Typically you will only need one
+    such daemon (usually run on the same host as the director), but a more
+    complex Bacula setup may have multiple systems with different tape drives,
+    to spread the backup load.
+</dl>
+
+This Webmin module can manage a system that has one or more of the Bacula
+daemons installed. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/jobs.html b/bacula-backup/help/jobs.html
new file mode 100644 (file)
index 0000000..11e20ca
--- /dev/null
@@ -0,0 +1,31 @@
+<header>Backup Jobs</header>
+
+A job is the most important configurable object in Bacula, as it brings togethera client, fileset and other settings to control exactly what is backed up. For this reason, it is generally best to define your file sets and clients first
+before creating a job. <p>
+
+Each job has the following important attributes :<br>
+
+<dl>
+<dt>Job name
+<dd>A unique name for this job.
+<dt>Job type
+<dd>This determines what kind of action the job will perform. In almost all cases this should be set to <b>Backup</b>.
+<dt>Backup level
+<dd>Determines if the job will do a complete or partial backup of the selected files.
+<dt>Client to backup
+<dd>If your Bacula system has multiple clients, this option determines which one the files are read from by this backup job.
+<dt>File set to backup
+<dd>The selected file set determines which actual files and directories are included in the backup.
+<dt>Backup on schedule
+<dd>This optional option determines if the job is automatically run on schedule, and if so when.
+<dt>Destination storage device
+<dd>If your Bacula configuration is set up with more than one storage daemon, this option determines which one the backup is written to.
+</dl><p>
+
+To simplify the job-creation process, Bacula lets you create <b>Default definition</b> jobs that do not actually run themselves, but rather specify settings to
+be inherited by real jobs. When editing a job, the <b>Default type</b> field
+determines if it is a default definition, if it inherits settings from some
+default, or neither. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/label.html b/bacula-backup/help/label.html
new file mode 100644 (file)
index 0000000..c2f2163
--- /dev/null
@@ -0,0 +1,13 @@
+<header>Label Volume</header>
+
+Labelling marks a volume (such as a loaded tape or backup directory) as being
+part of a selected volume pool, and thus usable for backups. You must label
+at least one volume before any backups done via Bacula will work - failure to
+do so will cause the backup to halt until a suitable volume is labelled. <p>
+
+For backups to files, the volume name determines the name of the file in
+the destination directory that will actually be written to. For tapes, the
+name will be the tape label. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/mount.html b/bacula-backup/help/mount.html
new file mode 100644 (file)
index 0000000..1574073
--- /dev/null
@@ -0,0 +1,8 @@
+<header>Mount or Unmount</header>
+
+When using Bacula to backup to a tape drive, you must use this page to mount the
+inserted tape before it can be used, so that Bacula knows which volume it
+contains. Mounting is not generally needed for backups to files. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/pools.html b/bacula-backup/help/pools.html
new file mode 100644 (file)
index 0000000..6a9105d
--- /dev/null
@@ -0,0 +1,13 @@
+<header>Volume Pools</header>
+
+In the Bacula context, a pool is a set of volumes that are dedicated to some
+purpose. A volume is typically a specific tape or destination directory, each
+of which is labelled with a unique name. When creating a backup job you can
+select which pool will be used for destination files, and thus which volumes.
+<p>
+
+A volume can be added to a pool using the <b>Label Volume</b> page, which
+marks an inserted tape or destination directory with a unique name. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/poolstatus.html b/bacula-backup/help/poolstatus.html
new file mode 100644 (file)
index 0000000..ed220ab
--- /dev/null
@@ -0,0 +1,8 @@
+<header>Volumes In Pool</header>
+
+This page can be used to list all volumes that have been marked as members
+of a selected backup pool. Volumes are added to a pool using the <b>Label
+Volume</b> page. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/schedules.html b/bacula-backup/help/schedules.html
new file mode 100644 (file)
index 0000000..4bf5a7c
--- /dev/null
@@ -0,0 +1,12 @@
+<header>Backup Schedules</header>
+
+This page lists schedules that can be applied to backup jobs, in order to have
+them automatically executed by Bacula on a regular basis. Each schedule has
+a unique name, and a list of backup levels and run times. Each run time must
+be formatted like <i>mon-fri at 9:00</i> or <i>sat at 23:00</i>, indicating the
+days of the week and time to execute. Days can also be specified as days 
+of the month, like <i>1st sun</i> or <i>on 5</i>. See the Bacula online
+documentation for the full details of allowed formats. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/sdirectors.html b/bacula-backup/help/sdirectors.html
new file mode 100644 (file)
index 0000000..fe68eb7
--- /dev/null
@@ -0,0 +1,12 @@
+<header>Storage Daemon Directors</header>
+
+This page lists all directors that are allowed to connect to this storage
+daemon. If you are adding this storage daemon to a remote director, the password
+in its <b>Storage Daemon</b> entry must match the password listed here. <p>
+
+If TLS is enabled for a director, communication between that director and
+this storage daemon will be encrypted and verified using the selected certificate
+and key. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/storagec.html b/bacula-backup/help/storagec.html
new file mode 100644 (file)
index 0000000..fd26019
--- /dev/null
@@ -0,0 +1,10 @@
+<header>Storage Daemon Configuration</header>
+
+This form can be used to adjust settings for the Bacula storage daemon running
+on the system. The defaults will typically be correct, although you can adjust
+the daemon name and number of concurrent jobs. If your Bacula system supports
+TLS security, the TLS options on this page can be used to secure communication
+between the storage and file daemons. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/storages.html b/bacula-backup/help/storages.html
new file mode 100644 (file)
index 0000000..4dd5c40
--- /dev/null
@@ -0,0 +1,32 @@
+<header>Storage Daemons</header>
+
+Each entry on this page defines a connection to a particular device on a 
+Bacula storage daemon. Typically the daemon runs on the same system, but it
+is possible to define more than one daemon, some of which are on remote hosts
+with different tape drive types or more disk space. <p>
+
+The important attributes for each daemon are :<br>
+<dl>
+<dt>Storage name
+<dd>A unique name used on this system to identify the daemon.
+<dt>Bacula SD password
+<dd>This field must contain the password used by the storage daemon on the remote system, which can be found in the <tt>/etc/bacula/bacula-sd.conf</tt> file.
+<dt>Hostname or IP address
+<dd>The fully qualified hostname or IP of the system running the storage daemon.
+<dt>Storage device name
+<dd>Each storage daemon can define one or more devices, each of which corresponds to a different directory or tape drive. This field determines which device this particular daemon connection uses.
+<dt>Media type name
+<dd>This field must contain a short name for the type of media used by the storage device, such as <i>File</i> or <i>DDS-2</i>. It must be the same as the media type in the <b>Storage Devices</b> section on the storage server.
+</dl>
+
+Often it is useful to have more than one entry for the same host on this page,
+each of which has a different storage device. This allows you to create backups
+that write to different directories or tape drives. <p>
+
+If the TLS options are shown on the storage daemon form, they can be used
+to enable secure communication between the director and the remote storage
+server. This is typically not necessary in a simple Bacula configuration, as
+the storage and director daemons usually run on the same system. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/storagestatus.html b/bacula-backup/help/storagestatus.html
new file mode 100644 (file)
index 0000000..31bf62e
--- /dev/null
@@ -0,0 +1,8 @@
+<header>Storage Daemon Status</header>
+
+This page displays backup jobs that are currently running and the 10 most
+recently run, on a selected Bacula storage daemon system. Those running or run
+on different daemons will not be displayed. <p>
+
+<footer>
+
diff --git a/bacula-backup/help/sync.html b/bacula-backup/help/sync.html
new file mode 100644 (file)
index 0000000..426f21d
--- /dev/null
@@ -0,0 +1,11 @@
+<header>Bacula Group Synchronization</header>
+
+Because the hosts in a Bacula group can be changed after they have been created
+in this module, this page can be used to set up the automatic updating of
+group membership information. This Webmin module caches the member hosts of
+each group in the Bacula configuration, and so will not automatically detect
+changes to membership unless synchronization is set up.<p>
+
+<footer>
+
+
diff --git a/bacula-backup/images/.xvpics/backup.gif b/bacula-backup/images/.xvpics/backup.gif
new file mode 100644 (file)
index 0000000..99b28cb
Binary files /dev/null and b/bacula-backup/images/.xvpics/backup.gif differ
diff --git a/bacula-backup/images/.xvpics/clientstatus.gif b/bacula-backup/images/.xvpics/clientstatus.gif
new file mode 100644 (file)
index 0000000..3bce7d8
Binary files /dev/null and b/bacula-backup/images/.xvpics/clientstatus.gif differ
diff --git a/bacula-backup/images/.xvpics/dirstatus.gif b/bacula-backup/images/.xvpics/dirstatus.gif
new file mode 100644 (file)
index 0000000..8a80aaf
Binary files /dev/null and b/bacula-backup/images/.xvpics/dirstatus.gif differ
diff --git a/bacula-backup/images/.xvpics/gbackup.gif b/bacula-backup/images/.xvpics/gbackup.gif
new file mode 100644 (file)
index 0000000..c3672f4
Binary files /dev/null and b/bacula-backup/images/.xvpics/gbackup.gif differ
diff --git a/bacula-backup/images/.xvpics/gjobs.gif b/bacula-backup/images/.xvpics/gjobs.gif
new file mode 100644 (file)
index 0000000..7bffebd
Binary files /dev/null and b/bacula-backup/images/.xvpics/gjobs.gif differ
diff --git a/bacula-backup/images/.xvpics/jobs.gif b/bacula-backup/images/.xvpics/jobs.gif
new file mode 100644 (file)
index 0000000..e9fc68c
Binary files /dev/null and b/bacula-backup/images/.xvpics/jobs.gif differ
diff --git a/bacula-backup/images/.xvpics/label.gif b/bacula-backup/images/.xvpics/label.gif
new file mode 100644 (file)
index 0000000..2961dbb
--- /dev/null
@@ -0,0 +1,5 @@
+P7 332
+#IMGINFO:48x48 Indexed (2313 bytes)
+#END_OF_COMMENTS
+48 48 255
+I$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%Û¶Ú¶%HII·Ú¶¶%HIIÛ¶¶¶I$IIÛ¶¶ÛI$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%Û¶Ú¶%HII·Ú¶¶%HIIÛ¶¶¶I$IIÛ¶¶ÛI$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%Û¶ÚnmmImmnmmÛÛ$IÚ·Ú¶$II%Ú·Ú¶I$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%ÛµI\92ÛÚ·ÚÛµ\93µnmnÚ¶Û¶¶$IIIÚ·¶¶Û¶¶¶I$IIÛ¶¶Ú%H%IÛ¶Ú¶·H¶·Ú·\91\92¶¶¶·\91\92¶mm·HI·Ú¶¶%HIIÛ¶¶¶I$IIÛ¶¶Ú%H%IÛ¶ÚÛH\92ÛÚ·µ·¶\92¶m¶·¶¶Ú·H\92ÛÚ¶·¶H%IHÛ¶¶¶I$IIÛ¶¶Ú%H%IÛ¶ÚnlÛ·ÚÛ¶ÚÛ¶Û¶¶\92\91\92¶Û¶mmnµÛ·$IHIÛ¶¶¶I$IIÛ¶¶Ú%H%IÛ¶Ú%¶ÚÛÚn¶¶ÛÚÛÚ·¶¶¶\91\92ÛÚnHnm\92Ú%H%I$IIÛ¶¶¶IH%IÛ¶¶Ú%H·HÛÛÚÛÚ·\91¶\92¶ÛÚ·µ·¶¶ÛÚÛH\92¶nmmÛ¶I$IIÛ¶¶¶IH%IÛ¶¶Ú%ÛH\92Ûµ\93ÚÛÚÛÚÛ¶¶m\92Û¶ÚÛÚ·\91nÚ\92Ú·HÛ¶I$IIÛ¶¶¶IH%IÛ¶¶Ú%mm·Ú\92¶ÚÛÛÚ·þ·ÚÛµn¶ÛÚÛ¶mn¶¶Ú·mÚ·I$IIÛ¶¶¶IH%IÛ¶¶Ú\92I¶ÛÚÛ¶ÛÚÛ¶Ú·¶ÚÛÚÛÚ·þ·\91In¶¶Ú·mÛ¶Û¶¶¶I$IIÛ¶¶Ú%H%ÿI\92ÚÛÚÛÚÛ¶\92¶¶·Ú¶Û¶¶Ú·Ú·ln$·µ·¶mÿ$Û¶¶¶I$IIÛ¶¶Ú%H%\92I¶\92ÚÛÚ·Ú¶\92\92\91ÿ·Ú¶\92¶\91·ÚnmmIm·Ú¶IH%Û¶¶¶I$IIÛ¶¶Ú%HÛ%\91\92m\92\92¶¶ÛÚ¶·þ·ÚÛ\91\92¶\92¶\92mmnmIÚÛ¶IIHÛ¶¶¶I$IIÛ¶¶Ú%HnIÚ·µ·\91\92nµ\93Ú¶Û¶ÚÛ¶\92\91n¶mmnmmm\92Û¶mI$I$IIÛ¶¶¶IH%IÛÚ$\93µ\92m¶\92¶·\91\92\91\92¶\92·Ú¶¶·HImnmmIm\92Ûmÿ¶¶I$IIÛ¶¶¶IH%IÛmH·\92¶Ú\92\91\92\92\92¶Ún\92\91\92¶\92ÚnHIII$mIn\91Û¶Û¶¶I$IIÛ¶¶¶IH%IÛH\92\92\91\92\92\92¶¶\92\91\92\91\93¶¶\91\92mnHnI$IIHnI¶ÛÚ¶¶·I$IIÛ¶¶¶IH%ImIÛ¶¶Ú\92\92\91n¶\92Ú\92m\92\92\92ÚmImII$IIImI¶%Ú·Ú¶Û¶¶¶I$IIÛ¶¶¶Hn¶\92¶\92Ú·¶¶mm·\91Û\91n\91\93HmmII%HIImJÚ¶I$IIÛ¶¶¶I$IIÛ¶ÚIm·¶Ú\92¶\92¶\92Ú¶·\91n\91\92I¶HJmIm$I%HnImÿ¶$IIIÛ¶¶¶I$IIÛ¶IH·\91\92¶¶·µ·µ\93\91¶·Ú\92\91\92IIImIIH%IImmÛ¶Ú%H%IÛ¶¶¶I$IIÛm$mnÚ·µ\93µ\92·µ·¶\91\92¶¶·HJlIImII%Hn\92ÿ¶¶Ú%H%II$IIÛ¶¶¶¶$\92IImm\92¶¶\93µ\92\92Ú¶·\91\92m%mIImIImm\92Ú·$IIH·Ú·¶I$IIÛ¶¶Ú%Hn\92¶\91nImm\92¶¶¶\92\92\92µJHIIm%mI\91·ÚÛ¶¶I$IIÛ¶¶ÚI$IIÛ¶ÚII¶ImIm\92\92\91nm$\93µ·¶\91m%mIIIIm\92Úÿ¶·¶Ú$I%IÚ·Ú¶I$IIÛ¶m$n\91\92¶n\91I%\91\92\91\93HIm\92\92\91%mImIm\92ÿ$I·Ú¶·H%HIÛ¶¶·Û¶¶¶I\92Hn\91Inmm¶\92mnHnm\92¶IHIIIImI\92¶Û¶Ú¶%HI%ÛÚ¶¶%HIIÛ¶¶¶Û$mnm¶¶I\92Hn\91\92\92\91%Im\92\92m$nlJ\91Û$Û¶¶Û$I%HÛ¶·Ú$II$Û¶¶ÛHI¶ImII¶\92\91\92%mm\92\92¶$Im%mIm\92¶I$ÛÚ·¶$IIH·Ú·¶H%IIÛ¶¶\91%\92¶\91·\91nHnm\92\91\92ImI\92\92\91n$mnmÛþ%H·Ú·¶$IIHÛ¶·¶H%IHI$ÿII\92HJl·Ú\92m\92I$·\91\92mII\92HII\92¶ÛÚ¶·$IHI·Ú¶·H%HIÛ¶¶ÛI$\92mImn\92\91$In\91·\91nHnm\92\91nmHJm\92ÿ¶¶Ú·$IH%ÛÚ¶·$IHI·Ú¶·IÚImIII\92¶m%mmI¶Û\91nHnH\93$Immÿ$Û¶·¶H%IIÚ·¶Ú$I%IÚ·Ú¶I¶mIImIIm·¶mH%$\93l\92\92mnHIImÛÛ$Û¶Ú¶%HI%ÛÚ¶¶%HII·Ú¶¶Û¶mmmII%Im\92Ú¶¶n¶$%¶¶m%Hn\91ÿ·¶$IIH·Ú·¶H%IHÛ¶·¶HI%HÛ¶mmnlJ$IIHJµ·Ú·¶¶¶\92$IImÛ¶Ú·$IH%ÛÚ¶·$IHI·Ú¶·H%HIÛ¶¶mImmI%IH%IHn\92Ú·µ%$Im·ÚÛ¶¶$IIIÚ·¶¶I$IIÛ¶¶Ú%H%IÛ¶¶\91JmmIH%IH%%H%HJHI%m\91Û·¶Ú¶%HII·Ú¶¶I$IIÛ¶¶¶IH%II$IÛmmmJHI$I%$I$%$%HJmÿ¶$I%HÛ¶Û¶$II$ÛÚ·¶$IIH·Ú·¶I$II¶mnHJl%I$%$$%$%Im¶ÿ¶%IHIÛ¶¶·H%HIÛ¶¶Û$I$IÛ¶Ú·I$IIÛ\91mJlIIII$I%$%Hn\91ÿ·¶$IIH·Ú·¶H%IHÛ¶·¶HI%HÛ¶·ÚI$IIÛ¶¶mmIIIIH%II$I\92ÛÚ¶¶%HII·Ú¶¶I$IIÛ¶¶Ú%H%IÛ¶¶ÚÛ¶¶¶I$Iÿ\92mmIIIHI%ImÛ$I$IÛ¶Ú·$IH%ÛÚ·¶$IIH·Ú·¶H%IHÛ¶¶¶I$IIÛÚ\92mImIIIm¶Û$IIH·Ú·¶H%IHÛ¶·Ú$I%IÚ·¶Ú$II%Û¶¶¶I$IIÛ¶¶ÿ¶\92mm\92\92ÿ¶$II$ÛÚ·¶$IIH·Ú·¶H%IHÛ¶·Ú$I%IÛ¶¶¶I$IIÛ¶¶Ú%H%ÿÿ¶·Ú$I%HÛ¶·Ú$II$Û¶Û¶$IIH·Ú·¶$IIH
\ No newline at end of file
diff --git a/bacula-backup/images/.xvpics/mount.gif b/bacula-backup/images/.xvpics/mount.gif
new file mode 100644 (file)
index 0000000..e4df1e6
Binary files /dev/null and b/bacula-backup/images/.xvpics/mount.gif differ
diff --git a/bacula-backup/images/.xvpics/restore.gif b/bacula-backup/images/.xvpics/restore.gif
new file mode 100644 (file)
index 0000000..8bbdae5
--- /dev/null
@@ -0,0 +1,5 @@
+P7 332
+#IMGINFO:48x48 Indexed (2154 bytes)
+#END_OF_COMMENTS
+48 48 255
+I$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%Û¶Ú¶%HII·Ú¶¶%HIIÛ¶¶¶I$IIÛ¶¶ÛI$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%Û¶Ú¶%HII·Ú¶¶%HIIÛ¶¶¶I$IIÛ¶¶ÛI$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%Û¶Ú¶%HII·Ú¶¶%HIIÛ¶¶¶I$IIÛ¶¶ÛI$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%Û¶Ú¶%HII·Ú¶¶%HIIÛ¶¶¶I$IIÛ¶¶ÛÛ¶¶¶I$IE»Öº¶%HII׺¶¶I$IIÛ¶¶Ö)H%IÛ¶Öº%HI%Û¶Ú·$IHIÛ¶¶¶I$%E»Öº¶E(II׺¶Ö%H)IÛ¶¶Ö)Dÿ»\96²r\92ÿÛ$IÖ»¶¶I$IIÛ¶¶¶%D)%Û¶Ö¶)HEI»Ö¶º%HIEÛ¶º¶·\92·\92¶·\93¶¶N\8eÛ¶ºÖ·(EH)Û¶¶$%E(%۶׶(EIH»Ö·º$IEHÛ¶ß×\97¶\93\92\96\93\92·\92·\92M\8eÛ¶ºD)IEI$I%$E)%D%I%$E)×(EH)Û¶Öß×»\92·\92\92\92r\92\8es\91\8fv\92³\92)Û$Û¶ÖºI$%E(%E%(E%%H%E»D)HEÛ¶»\92³\96\92\93\91\93\92\92\8er\92n\8err\8f\96ni(۶׶I$%E(%E%(E%%H%E»D)HEÿ»³\92²\97\92\8e\92\92\92\93q\8fq\93nmsn\92\97$۶׶ºI$I%$E)%D%I%$E)×(EÿÛ\97²\97²\92\92r\8e\93r\91\93m\93\8dsmrnns¶%\92Ú·¶ºÛ¶¶$%E(%E%(E%E(E»û»\92·\92²s\91\8frr\8ers\8eq\8fqnnnmsN·IIÿ$IIÛ¶¶¶%D)%Û¶Ö¶)HEÿ»·\92³\96³r\92NiJMnnrn\92n\92mnnnNr·ME»DI%Û¶¶¶I$%E»Öº¶E(IÛ\93¶\92\93\92\92nIII*I%msnr\8emN\8eNmoR×M%¶E(IÛ¶¶¶I$I%Û¶¶Ú%ÿ·²\97\92\92n\92JmNI\8eRiNirNnm\8eJmnmOn»mE·$IHI$IIÛ¶¶¶IH%Iÿ·\92\93¶\8esmJMjIMnjr\92JrjqnnMjnmN·\92\92%Û¶ÖºI$IIÛ¶¶¶IH%ÿ··\92\92\92\8fq&MIIjI)JIMjRnnmnnmjmNÛ\93\91Eÿº·ÖI$IIÛ¶¶¶IH%Û\93\92\92\93\92nIIIJIIII*InnmnnmnmnJm·¶\93(³Ú¶·¶I$IIÛ¶¶¶IHÛ\93\92\92\92s\8eq&iMJIIIIJrJmnrinmnmnnû\96\8e%߶ַºÛ¶¶¶I$IIÛÚ\97\92\8e\93q\8frNIIIJ)IEnrnIrnmn\8dNjmnÿ\97²I\92Ö(E)IÛ¶¶¶I$IIÛ¶\93r\8e\92n\92N\92%MFMENr\8ern\8dnnmnmnmiÛ»·\92Eÿ¶(EI)Û¶¶¶I$IIÿ\93\92r\8e\92ms\8eN\91Oinmrnnn\91nn\8dnmnmnÛ×»¶%Û¶¶(EIIÛ¶¶¶I$II·\96\8ern\92n\8enSnnNnmN\8emr\8emnn\8dnmnÛÛÛÛ$·¶Ú¶%HI)I$IIÛ¶¶Ûr³mrn\92n\8emnnmnnmnr\8dn\8emr\8aqn\8eÛÞÛÛn\8d)HE)ÛÖ¶ºI$IIÛ¶Û\92\96\93nr\8em\92\8d\92\91®\92mn\91nn\8dn\92mn\8em\92ÿÛÛÛm\92¶%HII·Ú¶¶I$IIÛ¶\92\97\92\93Mn\8e\91\92­\91\8e\91²mnnmn\8dn\8eq\8enÖÿÛÛß\8e\91·ºDI%IÚ·ºÖI$IIÛ¶\93m\97oqn\92\8d±\8e\91\8dÖMj\8dnmrn\8dn\8d\92ÛÿÛ»úN²º¶·DI(I׺·ÖÛ¶¶¶I\92sm\93nrN\8d²±\8d\8e±±nmnmn\8dnn\8d×ßÿÛß×q%ÛD)EÚ·Ú¶$II)Û¶¶¶ÛnRj\92\96OnMn\8d\8e\91­rinmn\8dnnÖÿÛßû߶nn\91%IHEÛ¶º·D)HIÛ¶¶¶\93\92MnJ\92\97JRJnmJmN\8emjm\92ÛÛÿÛÛþ»·\92\92Eß$IID»Ú·¶D)IHÛ¶¶¶\93rInmN\92³rnNNjmnm\93ÖÛÿÛÛÛþÛ\92\96·\93l\93¶DI)IÖ»¶Ö(EI)I$IÿoRinMnmn\93·Ú\97×ÚÛÛÛÛÛÛÿÚÛ\92M\8eÛ·riÿ$»Ö·ºD)IDÛ¶»ÖI$IßnnNinMJims²»·×ºÛÛÛÿ·¶\92II\92Û·\92IÛH%Û¶Ú·$IHI·Ú¶·I$IÿNomIJImJmmJ\8e\96²\97\92\92\92niInI\92Û»Ö)·DI)Ö»Ö¶$IIIº×¶¶I$IÿnSIIJiMIJIIIIIIJiInmjM²Û»·H·D)IEÚ·ºÖ$I)IÖ»¶ÖÛ¶¶¶\97NNi*HjIIIJIMiJmIniMjÚÛ··IºD»Ö·¶(IEIÚ·¶ºD)IEÛ¶¶¶ÛNOMJIIIIIJiMIjImJi¶ÛÛ·\96iÛ$IÛ¶¶»D)HEÛ¶º×$I(IÛ¶¶¶I\97NNNJIImJImIJmIn\92ú·»×r\92ÛD)HÛ¶·Ö(IE(۶׺$IIDÛ¶¶¶I$·rN/JMJIiNIin²ÛÛ»·¶\92Ú·D)HI׺·¶DI)HÛ¶·Ö(EI(I$IIÛ¶ûr\92rsnsNn\92·¶Û·»·¶×Û$I)×Ú¶¶)DII»Ö¶ºE(EIÛ¶¶ÚI$IIÛ¶¶¶¶jr³¶·¶·\97·¶··¶ÿ¶$IIEº×º¶%HII׺¶¶E(IIÛ¶¶ÖI$IIÛ¶¶¶Iÿ\92\93\92n²\97²¶ÛÛÖ»¶¶H%IIÚ·¶Ö(I%IÚ·Öº$II%Ú·Ú¶I$IIÛ¶¶¶IH%IÛ¶¶Ú%HI%Û¶Ú¶%HII·Ú¶¶%HIIÛ¶¶¶I$IIÛ¶¶ÛÛ¶¶¶I$IIÛ¶¶Ú%H%IÛ¶Ú¶%HI%Û¶Ú¶%HII·Ú¶¶I$IIÛ¶¶·HI$IÛ¶¶¶I$IIÛ¶¶Ú%H%IÛ¶Ú¶%HI%Û¶Ú¶%HII·Ú¶¶I$IIÛ¶¶·HI$IÛ¶¶¶I$IIÛ¶¶Ú%H%IÛ¶Ú¶%HI%Û¶Ú¶%HII·Ú¶¶I$IIÛ¶¶·HI$IÛ¶¶¶I$IIÛ¶¶Ú%H%IÛ¶Ú¶%HI%Û¶Ú¶%HII·Ú¶¶I$IIÛ¶¶·HI$I
\ No newline at end of file
diff --git a/bacula-backup/images/.xvpics/storagestatus.gif b/bacula-backup/images/.xvpics/storagestatus.gif
new file mode 100644 (file)
index 0000000..c31c7df
Binary files /dev/null and b/bacula-backup/images/.xvpics/storagestatus.gif differ
diff --git a/bacula-backup/images/Thumbs.db b/bacula-backup/images/Thumbs.db
new file mode 100755 (executable)
index 0000000..9ed3aee
Binary files /dev/null and b/bacula-backup/images/Thumbs.db differ
diff --git a/bacula-backup/images/backup.gif b/bacula-backup/images/backup.gif
new file mode 100644 (file)
index 0000000..0ff8693
Binary files /dev/null and b/bacula-backup/images/backup.gif differ
diff --git a/bacula-backup/images/clients.gif b/bacula-backup/images/clients.gif
new file mode 100644 (file)
index 0000000..458f860
Binary files /dev/null and b/bacula-backup/images/clients.gif differ
diff --git a/bacula-backup/images/clientstatus.gif b/bacula-backup/images/clientstatus.gif
new file mode 100644 (file)
index 0000000..a877707
Binary files /dev/null and b/bacula-backup/images/clientstatus.gif differ
diff --git a/bacula-backup/images/devices.gif b/bacula-backup/images/devices.gif
new file mode 100644 (file)
index 0000000..8ef6431
Binary files /dev/null and b/bacula-backup/images/devices.gif differ
diff --git a/bacula-backup/images/dir.gif b/bacula-backup/images/dir.gif
new file mode 100644 (file)
index 0000000..d9ef302
Binary files /dev/null and b/bacula-backup/images/dir.gif differ
diff --git a/bacula-backup/images/director.gif b/bacula-backup/images/director.gif
new file mode 100755 (executable)
index 0000000..01b965c
Binary files /dev/null and b/bacula-backup/images/director.gif differ
diff --git a/bacula-backup/images/dirstatus.gif b/bacula-backup/images/dirstatus.gif
new file mode 100755 (executable)
index 0000000..9e1ca0c
Binary files /dev/null and b/bacula-backup/images/dirstatus.gif differ
diff --git a/bacula-backup/images/fdirectors.gif b/bacula-backup/images/fdirectors.gif
new file mode 100755 (executable)
index 0000000..85d3cb0
Binary files /dev/null and b/bacula-backup/images/fdirectors.gif differ
diff --git a/bacula-backup/images/file.gif b/bacula-backup/images/file.gif
new file mode 100644 (file)
index 0000000..458f860
Binary files /dev/null and b/bacula-backup/images/file.gif differ
diff --git a/bacula-backup/images/filesets.gif b/bacula-backup/images/filesets.gif
new file mode 100644 (file)
index 0000000..4b51c79
Binary files /dev/null and b/bacula-backup/images/filesets.gif differ
diff --git a/bacula-backup/images/gbackup.gif b/bacula-backup/images/gbackup.gif
new file mode 100644 (file)
index 0000000..44cd8d7
Binary files /dev/null and b/bacula-backup/images/gbackup.gif differ
diff --git a/bacula-backup/images/gjobs.gif b/bacula-backup/images/gjobs.gif
new file mode 100644 (file)
index 0000000..a46e9f4
Binary files /dev/null and b/bacula-backup/images/gjobs.gif differ
diff --git a/bacula-backup/images/grestore.gif b/bacula-backup/images/grestore.gif
new file mode 100644 (file)
index 0000000..43caf97
Binary files /dev/null and b/bacula-backup/images/grestore.gif differ
diff --git a/bacula-backup/images/groups.gif b/bacula-backup/images/groups.gif
new file mode 100644 (file)
index 0000000..2455653
Binary files /dev/null and b/bacula-backup/images/groups.gif differ
diff --git a/bacula-backup/images/icon.gif b/bacula-backup/images/icon.gif
new file mode 100644 (file)
index 0000000..259e6b5
Binary files /dev/null and b/bacula-backup/images/icon.gif differ
diff --git a/bacula-backup/images/jobs.gif b/bacula-backup/images/jobs.gif
new file mode 100644 (file)
index 0000000..985ace8
Binary files /dev/null and b/bacula-backup/images/jobs.gif differ
diff --git a/bacula-backup/images/label.gif b/bacula-backup/images/label.gif
new file mode 100644 (file)
index 0000000..b469b05
Binary files /dev/null and b/bacula-backup/images/label.gif differ
diff --git a/bacula-backup/images/mount.gif b/bacula-backup/images/mount.gif
new file mode 100644 (file)
index 0000000..0eeb5aa
Binary files /dev/null and b/bacula-backup/images/mount.gif differ
diff --git a/bacula-backup/images/pools.gif b/bacula-backup/images/pools.gif
new file mode 100644 (file)
index 0000000..f8f4e91
Binary files /dev/null and b/bacula-backup/images/pools.gif differ
diff --git a/bacula-backup/images/poolstatus.gif b/bacula-backup/images/poolstatus.gif
new file mode 100755 (executable)
index 0000000..2b5eac4
Binary files /dev/null and b/bacula-backup/images/poolstatus.gif differ
diff --git a/bacula-backup/images/restore.gif b/bacula-backup/images/restore.gif
new file mode 100644 (file)
index 0000000..43caf97
Binary files /dev/null and b/bacula-backup/images/restore.gif differ
diff --git a/bacula-backup/images/rfile.gif b/bacula-backup/images/rfile.gif
new file mode 100644 (file)
index 0000000..c878641
Binary files /dev/null and b/bacula-backup/images/rfile.gif differ
diff --git a/bacula-backup/images/schedules.gif b/bacula-backup/images/schedules.gif
new file mode 100644 (file)
index 0000000..3929e52
Binary files /dev/null and b/bacula-backup/images/schedules.gif differ
diff --git a/bacula-backup/images/sdir.gif b/bacula-backup/images/sdir.gif
new file mode 100644 (file)
index 0000000..379ef1e
Binary files /dev/null and b/bacula-backup/images/sdir.gif differ
diff --git a/bacula-backup/images/sdirectors.gif b/bacula-backup/images/sdirectors.gif
new file mode 100755 (executable)
index 0000000..85d3cb0
Binary files /dev/null and b/bacula-backup/images/sdirectors.gif differ
diff --git a/bacula-backup/images/smallicon.gif b/bacula-backup/images/smallicon.gif
new file mode 100644 (file)
index 0000000..1dbb66c
Binary files /dev/null and b/bacula-backup/images/smallicon.gif differ
diff --git a/bacula-backup/images/srfile.gif b/bacula-backup/images/srfile.gif
new file mode 100644 (file)
index 0000000..3af62aa
Binary files /dev/null and b/bacula-backup/images/srfile.gif differ
diff --git a/bacula-backup/images/storagec.gif b/bacula-backup/images/storagec.gif
new file mode 100644 (file)
index 0000000..94e92bb
Binary files /dev/null and b/bacula-backup/images/storagec.gif differ
diff --git a/bacula-backup/images/storages.gif b/bacula-backup/images/storages.gif
new file mode 100755 (executable)
index 0000000..407971f
Binary files /dev/null and b/bacula-backup/images/storages.gif differ
diff --git a/bacula-backup/images/storagestatus.gif b/bacula-backup/images/storagestatus.gif
new file mode 100644 (file)
index 0000000..812c3e6
Binary files /dev/null and b/bacula-backup/images/storagestatus.gif differ
diff --git a/bacula-backup/images/sync.gif b/bacula-backup/images/sync.gif
new file mode 100644 (file)
index 0000000..d18b4a2
Binary files /dev/null and b/bacula-backup/images/sync.gif differ
diff --git a/bacula-backup/index.cgi b/bacula-backup/index.cgi
new file mode 100755 (executable)
index 0000000..306e1a5
--- /dev/null
@@ -0,0 +1,212 @@
+#!/usr/local/bin/perl
+# Show the Bacula main menu
+
+require './bacula-backup-lib.pl';
+$hsl = &help_search_link("bacula", "man", "doc", "google");
+
+# Make sure it is installed
+$err = &check_bacula();
+if ($err) {
+       &ui_print_header(undef, $module_info{'desc'}, "", "intro", 1, 1, 0,
+                        $hsl);
+       print &ui_config_link('index_echeck', [ $err, undef ]),"<p>\n";
+       &ui_print_footer("/", $text{'index'});
+       exit;
+       }
+
+if (&has_bacula_dir()) {
+       # Test DB connection
+       eval { $dbh = &connect_to_database(); };
+       if ($@) {
+               &ui_print_header(undef, $module_info{'desc'}, "", "intro", 1, 1, 0,
+                                $hsl);
+               print &ui_config_link('index_edb', [ $@, undef ]),"<p>\n";
+               &ui_print_footer("/", $text{'index'});
+               exit;
+               }
+       $dbh->disconnect();
+       }
+
+# Test node group DB
+if (&has_bacula_dir() && &has_node_groups()) {
+       $err = &check_node_groups();
+       if ($err) {
+               &ui_print_header(undef, $module_info{'desc'}, "", "intro",
+                                1, 1, 0, $hsl);
+               print &ui_config_link('index_eng', [ $err, undef ]),"<p>\n";
+               &ui_print_footer("/", $text{'index'});
+               exit;
+               }
+       }
+
+# Get the Bacula version, and check it
+$ver = &get_bacula_version();
+&ui_print_header(undef, $module_info{'desc'}, "", "intro", 1, 1, 0,
+                $hsl, undef, undef,
+                ($ver ? &text('index_version', $ver)."<br>" : undef).
+                $text{'index_opencountry'});
+if ($ver && $ver < 1.36) {
+       print &text('index_eversion', 1.36, $ver),"<p>\n";
+       &ui_print_footer("/", $text{'index'});
+       exit;
+       }
+
+# Make sure bconsole works
+if (&is_bacula_running("bacula-dir")) {
+       # Check hostname in console config
+       $conconf = &get_bconsole_config();
+       $condir = &find("Director", $conconf);
+       $conaddr = &find_value("Address", $condir->{'members'});
+       if (!gethostbyname($conaddr)) {
+               # Offer to fix hostname
+               print &text('index_econsole2',
+                       "<tt>$console_cmd</tt>", "<tt>$conaddr</tt>"),"<p>\n";
+               print &ui_form_start("fixaddr.cgi");
+               print &ui_form_end([ [ "fix", $text{'index_fixaddr'} ] ]);
+               &ui_print_footer("/", $text{'index'});
+               exit;
+               }
+
+       # Test run bconsole
+       local $status;
+       eval {
+               local $h = &open_console();
+               $status = &console_cmd($h, "version");
+               &close_console($h);
+               };
+       if ($status !~ /Version/i) {
+               # Nope .. check if there is a password mismatch we can fix
+               print &text('index_econsole',
+                       "<tt>$console_cmd</tt>",
+                       "<tt>$config{'bacula_dir'}/bconsole.conf</tt>"),"<p>\n";
+               $dirconf = &get_director_config();
+               $dirdir = &find("Director", $dirconf);
+               $dirpass = &find_value("Password", $dirdir->{'members'});
+               $conpass = &find_value("Password", $condir->{'members'});
+               if ($dirpass && $conpass && $dirpass ne $conpass) {
+                       # Can fix!
+                       print &ui_form_start("fixpass.cgi");
+                       print &ui_form_end([ [ "fix",
+                                              $text{'index_fixpass'} ] ]);
+                       }
+               &ui_print_footer("/", $text{'index'});
+               exit;
+               }
+       }
+
+# Show director, storage and file daemon icons
+if (&has_bacula_dir()) {
+       print &ui_subheading($text{'index_dir'});
+       @pages = ( "director", "clients", "filesets", "schedules",
+                  "jobs", "pools", "storages" );
+       &show_icons_from_pages(\@pages);
+       }
+if (&has_bacula_sd()) {
+       print &ui_subheading($text{'index_sd'});
+       @pages = ( "storagec", "devices" );
+       if (!&has_bacula_dir() || $config{'showdirs'}) {
+               push(@pages, "sdirectors");
+               }
+       &show_icons_from_pages(\@pages);
+       }
+if (&has_bacula_fd()) {
+       print &ui_subheading($text{'index_fd'});
+       @pages = ( "file" );
+       if (!&has_bacula_dir() || $config{'showdirs'}) {
+               push(@pages, "fdirectors");
+               }
+       &show_icons_from_pages(\@pages);
+       }
+
+# Show icons for node group operations
+if (&has_bacula_dir() && &has_node_groups()) {
+       print &ui_subheading($text{'index_groups'});
+       @pages = ( "groups", "gjobs", "gbackup", "sync" );
+       @links = map { "list_${_}.cgi" } @pages;
+       @titles = map { $text{"${_}_title"} } @pages;
+       @icons = map { "images/${_}.gif" } @pages;
+       &icons_table(\@links, \@titles, \@icons);
+       }
+
+if (&has_bacula_dir()) {
+       # Show icons for actions
+       print "<hr>\n";
+       print &ui_subheading($text{'index_actions'});
+       if (&is_bacula_running("bacula-dir")) {
+               @actions = ( "backup", "dirstatus", "clientstatus",
+                            "storagestatus", "label", "poolstatus", "mount",
+                            "restore" );
+               @links = map { "${_}_form.cgi" } @actions;
+               @titles = map { $text{"${_}_title"} } @actions;
+               @icons = map { "images/${_}.gif" } @actions;
+               &icons_table(\@links, \@titles, \@icons);
+               }
+       else {
+               print "<b>$text{'index_notrun'}</b><p>\n";
+               }
+       }
+
+print "<hr>\n";
+
+# See what processes are running
+print "<b>$text{'index_status'}</b>\n";
+foreach $p (@bacula_processes) {
+       print "&nbsp;|&nbsp;\n" if ($p ne $bacula_processes[0]);
+       print $text{'proc_'.$p}," - ";
+       if (&is_bacula_running($p)) {
+               print "<font color=#00aa00><b>",$text{'index_up'},
+                     "</b></font>\n";
+               $pcount++;
+               }
+       else {
+               print "<font color=#ff0000><b>",$text{'index_down'},
+                     "</b></font>\n";
+               }
+       }
+print "<p>\n";
+print &ui_buttons_start();
+if ($pcount > 0) {
+       if (!$config{'apply'}) {
+               print &ui_buttons_row("apply.cgi",
+                             $text{'index_apply'}, $text{'index_applydesc'});
+               }
+       if (&has_bacula_dir()) {
+               # Only show restart button if we are running the director, as
+               # for others the apply does a restart
+               print &ui_buttons_row("restart.cgi",
+                     $text{'index_restart'}, $text{'index_restartdesc'});
+               }
+       print &ui_buttons_row("stop.cgi",
+                     $text{'index_stop'}, $text{'index_stopdesc'});
+       }
+if ($pcount < scalar(@bacula_processes)) {
+       print &ui_buttons_row("start.cgi",
+                     $text{'index_start'}, $text{'index_startdesc'});
+       }
+
+# See what is started at boot
+if (&foreign_installed("init")) {
+       &foreign_require("init", "init-lib.pl");
+       $status = &init::action_status($bacula_inits[0]);
+       if ($status) {
+               print &ui_buttons_row("bootup.cgi",
+                             $text{'index_boot'}, $text{'index_bootdesc'},
+                             undef,
+                             &ui_yesno_radio("boot", $status == 2 ? 1 : 0));
+               }
+       }
+
+print &ui_buttons_end();
+
+&ui_print_footer("/", $text{'index'});
+
+sub show_icons_from_pages
+{
+local ($pages) = @_;
+local @links = map { $_ eq "director" || $_ eq "file" || $_ eq "storagec" ?
+                       "edit_${_}.cgi" : "list_${_}.cgi" } @$pages;
+local @titles = map { $text{"${_}_title"} } @$pages;
+local @icons = map { "images/${_}.gif" } @$pages;
+&icons_table(\@links, \@titles, \@icons);
+}
+
diff --git a/bacula-backup/label.cgi b/bacula-backup/label.cgi
new file mode 100755 (executable)
index 0000000..59389af
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/local/bin/perl
+# Actually label a volume
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+# Validate inputs
+&error_setup($text{'label_err'});
+$in{'label'} =~ /\S/ || &error($text{'label_elabel'});
+
+# Do it
+&ui_print_unbuffered_header(undef,  $text{'label_title'}, "");
+
+print "<b>",&text('label_run', "<tt>$in{'storage'}</tt>", $in{'label'}),"</b>\n";
+print "<pre>";
+$h = &open_console();
+
+# Do the label
+&sysprint($h->{'infh'}, "label storage=$in{'storage'}\n");
+$rv = &wait_for($h->{'outfh'}, 'name:',
+                              'not found');
+print $wait_for_input;
+if ($rv == 1) {
+       &job_error($text{'label_estorage'});
+       }
+&sysprint($h->{'infh'}, $in{'label'}."\n");
+$rv = &wait_for($h->{'outfh'}, 'already exists',
+                              'Connecting to Storage daemon',
+                              '((.*\n)*)Select the Pool.*:');
+print $wait_for_input;
+if ($rv == 0) {
+       &job_error($text{'label_eexists'});
+       }
+elsif ($rv == 2) {
+       # Need to choose a pool
+       if ($matches[1] =~ /(\d+):\s+\Q$in{'pool'}\E/) {
+               &sysprint($h->{'infh'}, "$1\n");
+               }
+       else {
+               &job_error($text{'label_epool'});
+               }
+       }
+
+$rv = &wait_for($h->{'outfh'}, 'success.*\\n', 'failed.*\\n');
+print $wait_for_input;
+if ($rv == 1) {
+       &job_error($text{'label_efailed'});
+       }
+
+print "</pre>";
+print "<b>$text{'label_done'}</b><p>\n";
+
+&close_console($h);
+&webmin_log("label", $in{'storage'});
+
+&ui_print_footer("label_form.cgi", $text{'label_return'});
+
+sub job_error
+{
+&close_console($h);
+print "</pre>\n";
+print "<b>",@_,"</b><p>\n";
+&ui_print_footer("label_form.cgi", $text{'label_return'});
+exit;
+}
+
diff --git a/bacula-backup/label_form.cgi b/bacula-backup/label_form.cgi
new file mode 100755 (executable)
index 0000000..3e674de
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/local/bin/perl
+# Show a form for labelling one volume
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'label_title'}, "", "label");
+
+print &ui_form_start("label.cgi", "post");
+print &ui_table_start($text{'label_header'}, undef, 2);
+
+# Daemon to label
+@storages = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+                &get_bacula_storages();
+print &ui_table_row($text{'label_storage'},
+       &ui_select("storage", $in{'storage'},
+        [ map { [ $_->{'name'},
+                  &text('clientstatus_on', $_->{'name'}, $_->{'address'}) ] }
+          @storages ]));
+
+@pools = sort { lc($a->{'name'}) cmp lc($b->{'name'}) } &get_bacula_pools();
+print &ui_table_row($text{'label_pool'},
+       &ui_select("pool", $pools[0]->{'name'},
+               [ map { [ $_->{'name'} ] } @pools ]));
+
+# Label to write
+print &ui_table_row($text{'label_label'},
+                   &ui_textbox("label", undef, 20));
+
+print &ui_table_end();
+print &ui_form_end([ [ undef, $text{'label_ok'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
diff --git a/bacula-backup/lang/en b/bacula-backup/lang/en
new file mode 100644 (file)
index 0000000..7a93c28
--- /dev/null
@@ -0,0 +1,640 @@
+index_echeck=$1 Maybe it is not installed, or the <a href='$2'>module configuration</a> is incorrect.
+index_edb=Failed to connect to the Bacula database : $1. Maybe it is not set up, or the <a href='$2'>module configuration</a> is incorrect.
+index_eng=Failed to connect to the Bacula groups database : $1. Maybe it does not exist, or the <a href='$2'>module configuration</a> is incorrect.
+index_econsole=The Bacula console command $1 could not communicate with the Bacula director. Make sure the password in $2 is correct.
+index_econsole2=The Bacula console command $1 is not configured with a valid Bacula director host. It is currently using $2, which does not exist.
+index_fixpass=Click Here to Fix The Console Password
+index_fixaddr=Click Here to Fix The Bacula Director Host
+index_stop=Stop Bacula
+index_stopdesc=Click this button to shut down the Bacula daemon processes listed above.
+index_start=Start Bacula
+index_startdesc=Click this button to start up the Bacula daemon processes listed above.
+index_restart=Restart Bacula
+index_restartdesc=Click this button to stop and re-start the Bacula daemon processes listed above. This may be needed to activate storage device configurations.
+index_apply=Apply Configuration
+index_applydesc=Click this button to activate the Bacula director configuration shown above.
+index_boot=Start At Boot
+index_bootdesc=Change this option to control whether or not Bacula is started at system boot time.
+index_status=Process statuses:
+index_up=Up
+index_down=Down
+index_return=module index
+index_version=Bacula $1
+index_notrun=Backups and other operations cannot be performed as the Bacula Directory daemon is down.
+index_eversion=Your system is using Bacula version $2, but this Webmin module only supports versions $1 and above.
+index_dir=Director Configuration
+index_sd=Storage Daemon Configuration
+index_fd=File Daemon Configuration
+index_groups=Bacula Group Configuration
+index_actions=Backup and Restore Actions
+index_opencountry=Contributed by Laurent Gharda
+
+connect_emysql=Failed to load the database DBI driver.
+connect_elogin=Failed to login to the database $1 : $2.
+connect_equery=The database $1 does not appear to contain Bacula tables.
+connect_equery2=This may be because the SQLite Perl module installed is too new, and does not support the older SQLite database format used by Bacula.
+connect_equery3=The database $1 does not appear to contain OC Bacula group tables.
+esql=SQL error : $1
+
+check_edir=The Bacula configuration directory $1 was not found on your system.
+check_ebacula=The Bacula control command $1 was not found.
+check_econsole=The Bacula console command $1 was not found.
+check_edirector=The Bacula director configuration file $1 was not found.
+check_eclient=This system appears to be a <a href='$2'>Bacula client</a> rather than a director.
+check_econfigs=No Bacula configuration files were found in $1
+check_eservers=No Webmin server groups have been defined
+check_engmod=The OpenCountry Bacula groups module is not installed
+
+proc_bacula-sd=Storage daemon
+proc_bacula-fd=File daemon
+proc_bacula-dir=Bacula Director daemon
+
+stop_err=Failed to stop Bacula
+start_err=Failed to start Bacula
+start_einit=No init script found for $1
+start_erun=Failed to start $1 : $2
+restart_err=Failed to re-start Bacula
+apply_err=Failed to apply configuration
+apply_failed=A configuration error was detected
+apply_problem=Failed to apply configuration : $1
+
+jobs_title=Backup Jobs
+jobs_none=No backup jobs have been defined yet.
+jobs_name=Job name
+jobs_deftype=Defaults?
+jobs_type=Job type
+jobs_client=Client to backup
+jobs_fileset=File set to backup
+jobs_schedule=Backup schedule
+jobs_add=Add a new backup job.
+jobs_delete=Delete Selected Jobs
+jobs_return=list of jobs
+jobs_derr=Failed to delete jobs
+
+filesets_title=File Sets
+filesets_none=No backup file sets have been defined yet.
+filesets_name=File set name
+filesets_files=Included files
+filesets_add=Add a new backup file set.
+filesets_delete=Delete Selected File Sets
+filesets_return=list of file sets
+filesets_derr=Failed to delete file sets
+filesets_ednone=None selected
+
+fileset_title1=Create File Set
+fileset_title2=Edit File Set
+fileset_header=Backup file set details
+fileset_egone=File set no longer exists!
+fileset_name=File set name
+fileset_include=Files and directories to backup
+fileset_exclude=Files and directories to skip
+fileset_sig=File signature type
+fileset_none=None
+fileset_md5=MD5
+fileset_err=Failed to save file set
+fileset_ename=Missing file set name
+fileset_eclash=A file set with the same name already exists
+fileset_echild=This file set cannot be deleted as it is used by $1
+
+clients_title=Backup Clients
+clients_none=No backup clients have been defined yet.
+clients_name=Client name
+clients_address=Hostname or address
+clients_catalog=Catalog
+clients_add=Add a new backup client.
+clients_delete=Delete Selected Clients
+clients_return=list of clients
+clients_derr=Failed to delete clients
+
+client_title1=Create Backup Client
+client_title2=Edit Backup Client
+client_header=Details of client to be backed up
+client_egone=Client no longer exists!
+client_name=Client FD name
+client_address=Hostname or IP address
+client_port=Bacula FD port
+client_pass=Bacula FD password
+client_catalog=Catalog to use
+client_prune=Prune expired jobs and files?
+client_fileret=Keep backup files for
+client_jobret=Keep backup jobs for
+client_err=Failed to save backup client
+client_ename=Missing or invalid client name
+client_eclash=A client with the same name already exists
+client_epass=Missing password
+client_eaddress=Missing or invalid hostname or address
+client_eport=Missing or invalid FD port
+client_efileret=Missing or invalid file retention period
+client_ejobret=Missing or invalid job retention period
+client_echild=This client cannot be deleted as it is used by $1
+client_status=Show Status
+
+job_title1=Create Backup Job
+job_title2=Edit Backup Job
+job_header=Backup job details
+job_name=Backup job name
+job_def=Default type
+job_def0=Default definiton
+job_def1=Stand-alone job
+job_def2=Inherit defaults from $1
+job_type=Job type
+job_level=Backup level
+job_client=Client to backup
+job_fileset=File set to backup
+job_schedule=Backup on schedule
+job_storage=Destination storage device
+job_pool=Volume pool
+job_messages=Destination for messages
+job_prority=Backup priority
+job_err=Failed to save backup job
+job_ename=Missing or invalid job name
+job_eclash=A job with the same name already exists
+job_epriority=Missing or invalid priority number
+job_echild=This default job definition cannot be deleted as it is used by $1
+job_run=Run Now
+job_before=Command before job
+job_after=Command after job
+job_cbefore=Command before job (on client)
+job_cafter=Command after job (on client)
+
+schedules_title=Backup Schedules
+schedules_none=No backup schedules have been defined yet.
+schedules_name=Schedule name
+schedules_sched=Run levels and times
+schedules_add=Add a new backup schedule.
+schedules_delete=Delete Selected Schedules
+schedules_return=list of schedules
+schedules_derr=Failed to delete schedules
+
+schedule_title1=Create Backup Schedule
+schedule_title2=Edit Backup Schedule
+schedule_header=Backup schedule details
+schedule_name=Backup schedule name
+schedule_runs=Run levels and times
+schedule_level=Backup level
+schedule_times=Run at times
+schedule_err=Failed to save backup schedule
+schedule_ename=Missing or invalid schedule name
+schedule_eclash=A schedule with the same name already exists
+schedule_etimes=Missing backup times in row $1
+schedule_echild=This schedule cannot be deleted as it is used by $1
+
+backup_title=Run Backup Job
+backup_header=Backup job details
+backup_job=Job to run
+backup_jd=$1 (File set $2 on $3)
+backup_wait=Wait for results?
+backup_ok=Backup Now
+backup_run=Starting backup job $1 ..
+backup_return=backup form
+backup_ejob=.. failed to find job!
+backup_eok=.. job could not be started
+backup_running=.. the backup job is now running. When complete, the results will be shown below ..
+backup_running2=.. the backup job has been started in the background.
+backup_done=.. backup complete.
+backup_failed=.. the backup did not complete successfully. Check the error message above for details.
+
+gbackup_title=Run Bacula Group Backup Job
+gbackup_run=Starting backup job $1 on $2 clients ..
+gbackup_on=Running backup job on client $1 :
+gbackup_header=Bacula group backup job details
+gbackup_jd=$1 (File set $2 on group $3)
+
+dirstatus_title=Director Status
+dirstatus_sched=Scheduled Backup Jobs
+dirstatus_name=Job name
+dirstatus_type=Type
+dirstatus_level=Level
+dirstatus_date=Run at
+dirstatus_date2=Started at
+dirstatus_volume=Volume
+dirstatus_schednone=No backup jobs are currently scheduled.
+dirstatus_id=Run ID
+dirstatus_status=Current status
+dirstatus_run=Running Backup Jobs
+dirstatus_runnone=No backup jobs are currently running.
+dirstatus_done=Completed Backup Jobs
+dirstatus_bytes=Size
+dirstatus_files=Files
+dirstatus_status2=Status
+dirstatus_donenone=No backup jobs have been run.
+dirstatus_cancel=Cancel Selected Jobs
+dirstatus_refresh=Refresh List
+
+clientstatus_title=Client Status
+clientstatus_err=Failed to fetch status from $1 : $2
+clientstatus_msg=Status from $1 : $2
+clientstatus_show=Show status of client:
+clientstatus_ok=OK
+clientstatus_on=$1 (on $2)
+
+storages_title=Storage Daemons
+storages_none=No storage daemons have been defined yet.
+storages_name=Storage name
+storages_address=Hostname or address
+storages_device=Storage device
+storages_type=Media type
+storages_add=Add a new storage daemon.
+storages_delete=Delete Selected Storage Daemons
+storages_return=list of storage daemons
+storages_derr=Failed to delete storage daemons
+
+storage_title1=Create Storage Daemon
+storage_title2=Edit Storage Daemon
+storage_header=Details of remote storage daemon
+storage_egone=Storage daemon no longer exists!
+storage_name=Storage daemon name
+storage_address=Hostname or IP address
+storage_port=Bacula SD port
+storage_pass=Bacula SD password
+storage_device=Storage device name
+storage_media=Media type name
+storage_other=Other..
+storage_err=Failed to save storage daemon
+storage_ename=Missing storage daemon name
+storage_eclash=A storage daemon with the same name already exists
+storage_epass=Missing password
+storage_eaddress=Missing or invalid hostname or address
+storage_eport=Missing or invalid SD port
+storage_edevice=Missing storage device name
+storage_emedia=Missing media type name
+storage_echild=This client cannot be deleted as it is used by $1
+storage_status=Show Status
+
+devices_title=Storage Devices
+devices_none=No storage devices have been defined yet.
+devices_name=Device name
+devices_device=Device file or directory
+devices_type=Media type
+devices_add=Add a new storage device.
+devices_delete=Delete Selected Storage Devices 
+devices_return=list of storage devices
+devices_derr=Failed to delete storage devices
+
+device_title1=Create Storage Device
+device_title2=Edit Storage Device
+device_header=Details of file storage device
+device_egone=Storage device no longer exists!
+device_name=Storage device name
+device_device=Archive device or directory
+device_media=Media type name
+device_label=Automatically label media?
+device_random=Random access medium?
+device_auto=Mount automatically?
+device_removable=Removable media?
+device_always=Always keep open?
+device_err=Failed to save storage device
+device_ename=Missing storage device name
+device_eclash=A storage device with the same name already exists
+device_emedia=Missing media type name
+device_edevice=Missing or invalid archive device or directory
+device_echild=This client cannot be deleted as it is used by $1
+
+storagestatus_title=Storage Daemon Status
+storagestatus_err=Failed to fetch status from $1 : $2
+storagestatus_msg=Status from $1 : $2
+storagestatus_show=Show status of storage daemon:
+storagestatus_ok=OK
+
+label_title=Label Volume
+label_header=Details of volume to label
+label_storage=Storage daemon to label
+label_pool=Create in pool
+label_label=New label name
+label_ok=Label Now
+label_return=label form
+label_run=Labelling volume with $2 on storage daemon $1 ..
+label_estorage=.. storage daemon was not found!
+label_eexists=.. the specified label already exists.
+label_efailed=.. labelling failed! Check the error message above for the reason why.
+label_done=.. labelling successful.
+label_epool=.. could not find pool!
+label_err=Label failed
+label_elabel=No label entered
+
+pools_title=Volume Pools
+pools_none=No volume pools have been defined yet.
+pools_name=Pool name
+pools_type=Pool type
+pools_reten=Retention period
+pools_add=Add a new volume pool.
+pools_delete=Delete Selected Volume Pools
+pools_return=list of volume pools
+pools_derr=Failed to delete volume pools
+
+pool_title1=Create Volume Pool
+pool_title2=Edit Volume Pool
+pool_header=Details of backup volume pool
+pool_egone=Volume pool no longer exists!
+pool_name=Volume pool name
+pool_recycle=Automatically recycle volumes?
+pool_auto=Prune expired volumes?
+pool_any=Backup to any volume in pool?
+pool_reten=Volume retention period
+pool_type=Volume pool type
+pool_max=Maximum jobs per volume
+pool_unlimited=Unlimited
+pool_err=Failed to save storage device
+pool_ename=Missing storage device name
+pool_eclash=A storage device with the same name already exists
+pool_echild=This client cannot be deleted as it is used by $1
+pool_emax=Missing or invalid maximum number of jobs per volume
+pool_ereten=Missing or invalid retention period
+pool_status=Show Volumes
+
+poolstatus_title=Volumes In Pool
+poolstatus_show=Show volumes in pool:
+poolstatus_ok=OK
+poolstatus_volumes=Volumes In Selected Pool
+poolstatus_name=Volume name
+poolstatus_type=Media type
+poolstatus_first=First used
+poolstatus_last=Last used
+poolstatus_bytes=Bytes written
+poolstatus_status=Backup mode
+poolstatus_none=There are no volumes currently in this backup pool.
+poolstatus_never=Never
+poolstatus_delete=Delete Selected Volumes
+
+dvolumes_err=Failed to delete volumes
+dvolumes_enone=None selected
+dvolumes_ebacula=Bacula error : $1
+
+mount_title=Mount or Unmount
+mount_header=Storage mount or un-mount options
+mount_storage=Storge device
+mount_mount=Mount Storage
+mount_unmount=Un-Mount Storage
+mount_run=Mounting volume on storage device $1 ..
+unmount_run=Un-mounting volume on storage device $1 ..
+mount_done=.. mounted successfully.
+unmount_done=.. un-mounted successfully.
+mount_failed=.. mount failed! See the error message above for the reason why.
+unmount_failed=.. un-mount failed! See the error message above for the reason why.
+mount_return=mount form
+
+cancel_err=Failed to cancel jobs
+cancel_enone=None selected
+
+gjobs_title=Bacula Group Backup Jobs
+gjobs_none=No Bacula group backup jobs have been defined yet.
+gjobs_add=Add a new Bacula group backup job.
+gjobs_delete=Delete Selected Jobs
+gjobs_return=list of Bacula group jobs
+gjobs_derr=Failed to delete Bacula group jobs
+gjobs_client=Bacula group to backup
+
+gjob_title1=Create Bacula Group Backup Job
+gjob_title2=Edit Bacula Group Backup Job
+gjob_header=Bacula group backup job details
+gjob_client=Bacula group to backup
+
+groups_title=Bacula Groups
+groups_none=No Bacula groups have been selected for backup by Bacula yet.
+groups_name=Group name
+groups_port=FD port
+groups_add=Add Bacula group:
+groups_ok=Add
+groups_catalog=Catalog
+groups_delete=Delete Selected Bacula Groups
+groups_return=list of groups
+groups_derr=Failed to delete groups
+groups_noadd=No Bacula groups exist to be selected for backups.
+groups_info=$1 ($2 members)
+groups_already=All Bacula groups have been already added.
+
+group_title1=Create Bacula Group
+group_title2=Edit Bacula Group
+group_header=Details of Bacula group to be backed up
+group_egone=Group no longer exists!
+group_egone2=Bacula group no longer exists!
+group_name=Bacula group name
+group_port=Bacula FD port
+group_err=Failed to save Bacula group
+group_eclash=A group with the same name already exists
+group_members=Hosts in Bacula group
+
+sync_title=Bacula Group Synchronization
+sync_header=Automatic Bacula group client synchronization options
+sync_sched=Synchronize on schedule?
+sync_schedyes=Yes, at times selected below ..
+sync_err=Failed to save Bacula group synchronization
+
+log_create_client=Created backup client $1
+log_modify_client=Modified backup client $1
+log_delete_client=Deleted backup client $1
+log_delete_clients=Deleted $1 backup clients
+log_create_fileset=Created file set $1
+log_modify_fileset=Modified file set $1
+log_delete_fileset=Deleted file set $1
+log_delete_filesets=Deleted $1 file sets
+log_create_job=Created backup job $1
+log_modify_job=Modified backup job $1
+log_delete_job=Deleted backup job $1
+log_delete_jobs=Deleted $1 backup jobs
+log_create_schedule=Created backup schedule $1
+log_modify_schedule=Modified backup schedule $1
+log_delete_schedule=Deleted backup schedule $1
+log_delete_schedules=Deleted $1 backup schedules
+log_create_pool=Created volume pool $1
+log_modify_pool=Modified volume pool $1
+log_delete_pool=Deleted volume pool $1
+log_delete_pools=Deleted $1 volume pools
+log_create_storage=Created storage daemon $1
+log_modify_storage=Modified storage daemon $1
+log_delete_storage=Deleted storage daemon $1
+log_delete_storages=Deleted $1 storage daemons
+log_create_device=Created storage device $1
+log_modify_device=Modified storage device $1
+log_delete_device=Deleted storage device $1
+log_delete_devices=Deleted $1 storage devices
+log_create_group=Created Bacula group $1
+log_modify_group=Modified Bacula group $1
+log_delete_group=Deleted Bacula group $1
+log_delete_groups=Deleted $1 Bacula groups
+log_create_gjob=Created Bacula group backup job $1
+log_modify_gjob=Modified Bacula group backup job $1
+log_delete_gjob=Deleted Bacula group backup job $1
+log_delete_gjobs=Deleted $1 Bacula group backup jobs
+log_create_fdirector=Created file daemon director $1
+log_modify_fdirector=Modified file daemon director $1
+log_delete_fdirector=Deleted file daemon director $1
+log_delete_fdirectors=Deleted $1 file daemon directors
+log_create_sdirector=Created storage daemon director $1
+log_modify_sdirector=Modified storage daemon director $1
+log_delete_sdirector=Deleted storage daemon director $1
+log_delete_sdirectors=Deleted $1 storage daemon directors
+log_stop=Stopped Bacula daemons
+log_start=Started Bacula daemons
+log_apply=Applied configuration
+log_restart=Restarted Bacula daemons
+log_backup=Started backup job $1
+log_gbackup=Started Bacula group backup job $1
+log_label=Labelled storage daemon $1
+log_mount=Mounted storage device $1
+log_unmount=Un-mounted storage device $1
+log_sync=Saved Bacula group synchronization
+log_director=Saved global Bacula director configuration
+log_file=Saved Bacula file daemon configuration
+log_storagec=Saved Bacula storage daemon configuration
+log_fixpass=Fixed Bacula console program password
+
+director_title=Director Configuration
+director_header=Global Bacula director options
+director_name=Director name
+director_port=Listen on port
+director_jobs=Maximum concurrent jobs
+director_messages=Destination for messages
+director_enone=No director configuration found!
+director_dir=Bacula working directory
+director_err=Failed to save director configuration
+director_ename=Missing or invalid director name
+director_eport=Missing or invalid port number
+director_ejobs=Missing or invalid number of concurrent jobs
+director_edir=Missing or non-existant working directory
+
+tls_enable=Enable TLS encryption?
+tls_require=Only accept TLS connections?
+tls_verify=Verify TLS clients?
+tls_cert=TLS PEM certificate file
+tls_key=TLS PEM key file
+tls_cacert=TLS PEM certificate authority file
+tls_none=None
+tls_ecert=Missing or non-existant TLS certificate file
+tls_ekey=Missing or non-existant TLS key file
+tls_ecacert=Missing or non-existant TLS CA certificate file
+tls_ecerts=For TLS to be enabled, certificate, key and CA files must be specified
+
+file_title=File Daemon Configuration
+file_header=Bacula file daemon options
+file_name=File daemon name
+file_port=Listen on port
+file_jobs=Maximum concurrent jobs
+file_dir=Bacula working directory
+file_enone=No file daemon configuration found!
+file_err=Failed to save file daemon configuration
+file_ename=Missing or invalid file daemon name
+file_eport=Missing or invalid port number
+file_ejobs=Missing or invalid number of concurrent jobs
+file_edir=Missing or non-existant working directory
+
+fdirectors_title=File Daemon Directors
+fdirectors_none=No directors have been defined yet.
+fdirectors_name=Director name
+fdirectors_pass=Accepted password
+fdirectors_add=Add a new director.
+fdirectors_delete=Delete Selected Directors
+fdirectors_return=list of directors
+fdirectors_derr=Failed to delete directors
+
+fdirector_title1=Create File Daemon Director
+fdirector_title2=Edit File Daemon Director
+fdirector_header=Details of controlling remote director
+fdirector_egone=Director no longer exists!
+fdirector_name=Director name
+fdirector_pass=Accepted password
+fdirector_monitor=Only allow monitoring connection?
+fdirector_err=Failed to save file daemon director
+fdirector_ename=Missing director name
+fdirector_eclash=A director with the same name already exists
+fdirector_epass=Missing password
+
+sdirectors_title=Storage Daemon Directors
+sdirectors_none=No directors have been defined yet.
+sdirectors_name=Director name
+sdirectors_pass=Accepted password
+sdirectors_add=Add a new director.
+sdirectors_delete=Delete Selected Directors
+sdirectors_return=list of directors
+sdirectors_derr=Failed to delete directors
+
+sdirector_title1=Create Storage Daemon Director
+sdirector_title2=Edit Storage Daemon Director
+sdirector_header=Details of controlling remote director
+sdirector_egone=Director no longer exists!
+sdirector_name=Director name
+sdirector_pass=Accepted password
+sdirector_monitor=Only allow monitoring connection?
+sdirector_err=Failed to save storage daemon director
+sdirector_ename=Missing director name
+sdirector_eclash=A director with the same name already exists
+sdirector_epass=Missing password
+
+storagec_title=Storage Daemon Configuration
+storagec_header=Bacula storage daemon options
+storagec_name=Daemon name
+storagec_port=Listen on port
+storagec_jobs=Maximum concurrent jobs
+storagec_enone=No storage daemon configuration found!
+storagec_dir=Bacula working directory
+storagec_err=Failed to save storage daemon configuration
+storagec_ename=Missing or invalid storage daemon name
+storagec_eport=Missing or invalid port number
+storagec_ejobs=Missing or invalid number of concurrent jobs
+storagec_edir=Missing or non-existant working directory
+
+chooser_title=Select Schedule
+chooser_monthsh=Months to execute
+chooser_months=Months of year
+chooser_all=All
+chooser_sel=Selected below ..
+chooser_ok=OK
+chooser_timeh=Time of day to execute
+chooser_time=Hour and minute
+chooser_weekdaysh=Days of week to execute
+chooser_weekdays=Days of week
+chooser_weekdaynums=Numbers in month
+chooser_daysh=Days of the month to execute
+chooser_days=Dates
+chooser_err=Failed to select schedule
+chooser_emonths=No months chosen
+chooser_eweekdays=No weekdays chosen
+chooser_eweekdaynums=No weekday numbers chosen
+chooser_edays=No days of the month chosen
+chooser_ehour=Missing or invalid hour of day
+chooser_eminute=Missing or invalid minute
+chooser_emonthsrange=Selected months must be contiguous
+chooser_eweekdaysrange=Selected weekdays must be contiguous
+chooser_eweekdaynumsrange=Selected weekdays numbers must be contiguous
+chooser_edaysrange=Selected days of the month must be contiguous
+
+weekdaynum_1=First
+weekdaynum_2=Second
+weekdaynum_3=Third
+weekdaynum_4=Fourth
+weekdaynum_5=Fifth
+
+restore_title=Restore Backup
+restore_title2=Restore Backup To Bacula Group
+restore_title3=Restore Bacula Group Backup
+restore_header=Options for restore of previous backup job
+restore_job=Job to restore
+restore_files=Files to restore
+restore_client=Restore to client or group
+restore_storage=Restore from storage device
+restore_where=Restore to directory
+restore_where2=Other root directory
+restore_ewhere=Missing directory to restore to
+restore_ok=Restore Now
+restore_err=Failed to restore backup
+restore_efiles=No files entered
+restore_ejob=Invalid job ID
+restore_ejobfiles=No files recorded for job
+restore_run=Starting restore of job $1 to client $2 from storage $3 ..
+restore_return=restore form
+restore_eok=.. job could not be started
+restore_running=.. the restore is now running. When complete, the results will be shown below ..
+restore_running2=.. the restore has been started in the background.
+restore_done=.. restore complete.
+restore_failed=.. the restore did not complete successfully. Check the error message above for details.
+restore_clist=--Clients--
+restore_glist=--Bacula Groups--
+restore_eclient=No client or Bacula group selected
+restore_egroup=Bacula group does not exist
+restore_jlist=--Single-System Jobs--
+restore_njlist=--Bacula Groups Jobs--
+restore_all=--All Clients In Group--
+restore_eclients=No members of Bacula group backup found!
+restore_eall1=The <b>All Clients in Backup</b> option must be selected for <b>Restore to client or group</b> when doing a Bacula group job restore
+restore_eall2=The <b>All Clients in Backup</b> option can only be selected for <b>Restore to client or group</b> when doing a Bacula group job restore
+restore_enofiles=None of the selected files are in the backup
diff --git a/bacula-backup/list.cgi b/bacula-backup/list.cgi
new file mode 100755 (executable)
index 0000000..459eb2d
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/local/bin/perl
+# Returns a list of files and directories under some directory
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+print "Content-type: text/plain\n\n";
+
+# Get the parent directory ID
+$dbh = &connect_to_database();
+$cmd = $dbh->prepare("select PathId from Path where Path = ?");
+$d = $in{'dir'} eq "/" ? "/" : $in{'dir'}."/";
+$wind = &unix_to_dos($d);
+$cmd->execute($wind);
+($pid) = $cmd->fetchrow();
+$cmd->finish();
+
+if ($in{'job'} ne "") {
+       $jobsql = "and Job.JobId = $in{'job'}";
+       }
+
+if ($in{'volume'}) {
+       # Search just within one volume
+       # Subdirectories of directory, that are on this volume
+       $cmd1 = $dbh->prepare("
+               select Path.Path
+               from Path, File, Job, JobMedia, Media
+               where File.PathId = Path.PathId
+               and File.JobId = Job.JobId
+               and Job.JobId = JobMedia.JobId
+               and JobMedia.MediaId = Media.MediaId
+               and Media.VolumeName = ?
+               $jobsql
+               ");
+       $cmd1->execute($in{'volume'}) || die "db error : ".$dbh->errstr;
+       while(($f) = $cmd1->fetchrow()) {
+               $f = &dos_to_unix($f);
+               if ($f =~ /^(\Q$d\E[^\/]+\/)/) {
+                       push(@rv, $1);
+                       }
+               }
+       $cmd1->finish();
+
+       # Files in directory, that are on this volume
+       $cmd2 = $dbh->prepare("
+               select Filename.Name
+               from File, Filename, Job, JobMedia, Media
+               where File.FilenameId = Filename.FilenameId
+               and File.PathId = ?
+               and File.JobId = Job.JobId
+               and Job.JobId = JobMedia.JobId
+               and JobMedia.MediaId = Media.MediaId
+               and Media.VolumeName = ?
+               $jobsql
+               ");
+       $cmd2->execute($pid, $in{'volume'}) || die "db error : ".$dbh->errstr;
+       while(($f) = $cmd2->fetchrow()) {
+               push(@rv, "$d$f") if ($f =~ /\S/);
+               }
+       $cmd2->finish();
+       }
+else {
+       # Search all files
+       # Subdirectories of directory
+       $cmd1 = $dbh->prepare("
+               select Path
+               from Path, File, Job
+               where File.PathId = Path.PathId
+               and File.JobId = Job.JobId
+               $jobsql
+               ");
+       $cmd1->execute() || die "db error : ".$dbh->errstr;
+       while(($f) = $cmd1->fetchrow()) {
+               $f = &dos_to_unix($f);
+               if ($f =~ /^(\Q$d\E[^\/]+\/)/) {
+                       push(@rv, $1);
+                       }
+               }
+       $cmd1->finish();
+
+       # Files in directory
+       $cmd2 = $dbh->prepare("
+               select Filename.Name
+               from File, Filename, Job
+               where File.FilenameId = Filename.FilenameId
+               and File.PathId = ?
+               and File.JobId = Job.JobId
+               $jobsql
+               ");
+       $cmd2->execute($pid) || die "db error : ".$dbh->errstr;
+       while(($f) = $cmd2->fetchrow()) {
+               push(@rv, "$d$f") if ($f =~ /\S/);
+               }
+       $cmd2->finish();
+       }
+
+# Return output
+@rv = &unique(@rv);
+print "\n";
+foreach $f (@rv) {
+       print $f,"\n";
+       }
+
diff --git a/bacula-backup/list_clients.cgi b/bacula-backup/list_clients.cgi
new file mode 100755 (executable)
index 0000000..f86c218
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/local/bin/perl
+# Show a list of all backup clients
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'clients_title'}, "", "clients");
+
+$conf = &get_director_config();
+@clients = grep { !&is_oc_object($_) } &find("Client", $conf);
+&sort_by_name(\@clients);
+if (@clients) {
+       print &ui_form_start("delete_clients.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d"),
+                  "<a href='edit_client.cgi?new=1'>$text{'clients_add'}</a>" );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=30%", "width=40%", "width=30%" );
+       print &ui_columns_start([ "", $text{'clients_name'},
+                                 $text{'clients_address'},
+                                 $text{'clients_catalog'} ], "100%", 0, \@tds);
+       foreach $f (@clients) {
+               $name = &find_value("Name", $f->{'members'});
+               $addr = &find_value("Address", $f->{'members'});
+               $cat = &find_value("Catalog", $f->{'members'});
+               print &ui_checked_columns_row([
+                       "<a href='edit_client.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $addr,
+                       $cat,
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'clients_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'clients_none'}</b><p>\n";
+       print "<a href='edit_client.cgi?new=1'>$text{'clients_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_devices.cgi b/bacula-backup/list_devices.cgi
new file mode 100755 (executable)
index 0000000..0ddfc9b
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/local/bin/perl
+# Show a list of all backup devices
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'devices_title'}, "", "devices");
+
+$conf = &get_storage_config();
+@devices = &find("Device", $conf);
+&sort_by_name(\@devices);
+if (@devices) {
+       print &ui_form_start("delete_devices.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d"),
+                  "<a href='edit_device.cgi?new=1'>$text{'devices_add'}</a>" );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=30%", "width=40%", "width=30%" );
+       print &ui_columns_start([ "", $text{'devices_name'},
+                                 $text{'devices_device'},
+                                 $text{'devices_type'} ], "100%", 0, \@tds);
+       foreach $f (@devices) {
+               $name = &find_value("Name", $f->{'members'});
+               $device = &find_value("Archive Device", $f->{'members'});
+               $type = &find_value("Media Type", $f->{'members'});
+               print &ui_checked_columns_row([
+                       "<a href='edit_device.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $device,
+                       $type,
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'devices_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'devices_none'}</b><p>\n";
+       print "<a href='edit_device.cgi?new=1'>$text{'devices_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_fdirectors.cgi b/bacula-backup/list_fdirectors.cgi
new file mode 100755 (executable)
index 0000000..40990f5
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/local/bin/perl
+# Show a list of all directors known to a file daemon
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'fdirectors_title'}, "", "fdirectors");
+
+$conf = &get_file_config();
+@fdirectors = &find("Director", $conf);
+&sort_by_name(\@fdirectors);
+if (@fdirectors) {
+       print &ui_form_start("delete_fdirectors.cgi", "post");
+       print &select_all_link("d"),"\n";
+       print &select_invert_link("d"),"\n";
+       print "<a href='edit_fdirector.cgi?new=1'>$text{'fdirectors_add'}</a><br>\n";
+       @tds = ( "width=5", "width=30%", "width=70%" );
+       print &ui_columns_start([ "", $text{'fdirectors_name'},
+                                 $text{'fdirectors_pass'} ], "100%", 0, \@tds);
+       foreach $f (@fdirectors) {
+               $name = &find_value("Name", $f->{'members'});
+               $pass = &find_value("Password", $f->{'members'});
+               print &ui_columns_row([
+                       &ui_checkbox("d", $name),
+                       "<a href='edit_fdirector.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $pass,
+                       ], \@tds);
+               }
+       print &ui_columns_end();
+       print &select_all_link("d"),"\n";
+       print &select_invert_link("d"),"\n";
+       print "<a href='edit_fdirector.cgi?new=1'>$text{'fdirectors_add'}</a><br>\n";
+       print &ui_form_end([ [ "delete", $text{'fdirectors_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'fdirectors_none'}</b><p>\n";
+       print "<a href='edit_fdirector.cgi?new=1'>$text{'fdirectors_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_filesets.cgi b/bacula-backup/list_filesets.cgi
new file mode 100755 (executable)
index 0000000..24e5b52
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/local/bin/perl
+# Show a list of all sets of files to backup
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'filesets_title'}, "", "filesets");
+
+$conf = &get_director_config();
+@filesets = &find("FileSet", $conf);
+&sort_by_name(\@filesets);
+if (@filesets) {
+       print &ui_form_start("delete_filesets.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d"),
+                  "<a href='edit_fileset.cgi?new=1'>$text{'filesets_add'}</a>",
+                );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=20%", "width=80%" );
+       print &ui_columns_start([ "", $text{'filesets_name'},
+                                 $text{'filesets_files'} ], "100%", 0, \@tds);
+       foreach $f (@filesets) {
+               $name = &find_value("Name", $f->{'members'});
+               $inc = &find("Include", $f->{'members'});
+               @files = $inc ? &find_value("File", $inc->{'members'}) : ( );
+               if (@files > 4) {
+                       @files = ( @files[0..3], "..." );
+                       }
+               print &ui_checked_columns_row([
+                       "<a href='edit_fileset.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       join(" , ", @files),
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'filesets_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'filesets_none'}</b><p>\n";
+       print "<a href='edit_fileset.cgi?new=1'>$text{'filesets_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_gbackup.cgi b/bacula-backup/list_gbackup.cgi
new file mode 100755 (executable)
index 0000000..09509f8
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl
+# Show a form for running a node group backup job
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'gbackup_title'}, "", "gbackup");
+
+print &ui_form_start("gbackup.cgi", "post");
+print &ui_table_start($text{'gbackup_header'}, undef, 2);
+
+# Job to run
+$conf = &get_director_config();
+foreach $job (&find("JobDefs", $conf)) {
+       if ($name = &is_oc_object($job)) {
+               $client = &is_oc_object(
+                       &find_value("Client", $job->{'members'}));
+               $fileset = &find_value("FileSet", $job->{'members'});
+               push(@sel, [ $name, 
+                       &text('gbackup_jd', $name, $fileset, $client) ]);
+               }
+       }
+print &ui_table_row($text{'backup_job'},
+                   &ui_select("job", undef, \@sel));
+
+print &ui_table_end();
+print &ui_form_end([ [ "backup", $text{'backup_ok'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
diff --git a/bacula-backup/list_gjobs.cgi b/bacula-backup/list_gjobs.cgi
new file mode 100755 (executable)
index 0000000..78ce721
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/local/bin/perl
+# Show a list of backup jobs that use node groups
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'gjobs_title'}, "", "gjobs");
+
+$conf = &get_director_config();
+@jobs = grep { &is_oc_object($_) } &find("JobDefs", $conf);
+&sort_by_name(\@jobs);
+if (@jobs) {
+       print &ui_form_start("delete_gjobs.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d"),
+                  "<a href='edit_gjob.cgi?new=1'>$text{'gjobs_add'}</a>" );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=40%", "width=20%", "width=20%",
+                "width=20%" );
+       print &ui_columns_start([ "", $text{'jobs_name'},
+                                 $text{'jobs_type'},
+                                 $text{'gjobs_client'},
+                                 $text{'jobs_fileset'} ], "100%", 0, \@tds);
+       foreach $f (@jobs) {
+               $name = &find_value("Name", $f->{'members'});
+               $name = &is_oc_object($name);
+               $type = &find_value("Type", $f->{'members'});
+               $client = &find_value("Client", $f->{'members'});
+               $client = &is_oc_object($client);
+               $fileset = &find_value("FileSet", $f->{'members'});
+               print &ui_checked_columns_row([
+                       "<a href='edit_gjob.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $type || "<i>$text{'default'}</i>",
+                       $client || "<i>$text{'default'}</i>",
+                       $fileset || "<i>$text{'default'}</i>",
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'gjobs_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'jobs_none'}</b><p>\n";
+       print "<a href='edit_gjob.cgi?new=1'>$text{'gjobs_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_grestore.cgi b/bacula-backup/list_grestore.cgi
new file mode 100755 (executable)
index 0000000..f64d109
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/local/bin/perl
+# Show a form for restoring an old node group backup job
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'grestore_title'}, "", "grestore");
+
+print &ui_form_start("grestore.cgi", "post");
+print &ui_table_start($text{'grestore_header'}, undef, 2);
+
+# Old job to restore
+$dbh = &connect_to_database();
+$cmd = $dbh->prepare("select JobId,Name,SchedTime from Job where Name not like 'Restore%' order by SchedTime desc") ||
+                &error("prepare failed : ",$dbh->errstr);
+$cmd->execute();
+while(my ($id, $name, $when) = $cmd->fetchrow()) {
+       if ($oc = &is_oc_object($name)) {
+               push(@opts, [ $id, "$oc ($id) ($when)" ]);
+               }
+       }
+$cmd->finish();
+print &ui_table_row($text{'restore_job'},
+                   &ui_select("job", undef, \@opts));
+
+# Files to restore
+print &ui_table_row($text{'restore_files'},
+                   &ui_textarea("files", undef, 8, 50)."\n".
+                   &bacula_file_button("files", "job"));
+
+# Destination client
+@clients = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+               grep { &is_oc_object($_, 1) } &get_bacula_clients();
+print &ui_table_row($text{'restore_client'},
+       &ui_select("client", undef,
+        [ map { [ $_->{'name'},
+                  &text('clientstatus_on', $_->{'name'}, $_->{'address'}) ] }
+          @clients ]));
+
+# Storage device
+@storages = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+               &get_bacula_storages();
+print &ui_table_row($text{'restore_storage'},
+       &ui_select("storage", undef,
+        [ map { [ $_->{'name'},
+                  &text('storagestatus_on', $_->{'name'}, $_->{'address'}) ] }
+          @storages ]));
+
+print &ui_table_end();
+print &ui_form_end([ [ "restore", $text{'restore_ok'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_groups.cgi b/bacula-backup/list_groups.cgi
new file mode 100755 (executable)
index 0000000..f046e30
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/local/bin/perl
+# Show all groups that are actually templates for OC-Host node groups
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'groups_title'}, "", "groups");
+
+$conf = &get_director_config();
+@groups = &find("Client", $conf);
+@groups = grep { $name = &find_value("Name", $_->{'members'});
+                $name =~ /^ocgroup_/ } @groups;
+&sort_by_name(\@groups);
+if (@groups) {
+       print &ui_form_start("delete_groups.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d") );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=30%", "width=40%", "width=30%" );
+       print &ui_columns_start([ "", $text{'groups_name'},
+                                 $text{'groups_port'},
+                                 $text{'groups_catalog'} ], "100%", 0, \@tds);
+       foreach $f (@groups) {
+               $name = &find_value("Name", $f->{'members'});
+               $name =~ s/^ocgroup_//g;
+               $port = &find_value("FDPort", $f->{'members'});
+               $cat = &find_value("Catalog", $f->{'members'});
+               $done{$name}++;
+               print &ui_checked_columns_row([
+                       "<a href='edit_group.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $port,
+                       $cat,
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'groups_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'groups_none'}</b><p>\n";
+       }
+
+@ng = &list_node_groups();
+@canng = grep { !$done{$_->{'name'}} } @ng;
+if (@canng) {
+       # Show adding form
+       print &ui_form_start("edit_group.cgi");
+       print "<b>$text{'groups_add'}</b>\n";
+       print &ui_select("new", undef,
+               [ map { [ $_->{'name'}, &text('groups_info', $_->{'name'}, scalar(@{$_->{'members'}})) ] } @canng ]);
+       print &ui_submit($text{'groups_ok'});
+       print &ui_form_end();
+       }
+elsif (@ng) {
+       print "<b>$text{'groups_already'}</b><p>\n";
+       }
+else {
+       print "<b>$text{'groups_noadd'}</b><p>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_jobs.cgi b/bacula-backup/list_jobs.cgi
new file mode 100755 (executable)
index 0000000..7ec3ca7
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/local/bin/perl
+# Show a list of all backup jobs
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'jobs_title'}, "", "jobs");
+
+$conf = &get_director_config();
+@jobs = grep { !&is_oc_object($_) }
+            ( &find("JobDefs", $conf), &find("Job", $conf) );
+&sort_by_name(\@jobs);
+if (@jobs) {
+       print &ui_form_start("delete_jobs.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d"),
+                  "<a href='edit_job.cgi?new=1'>$text{'jobs_add'}</a>" );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=30%", "width=10%", "width=20%", "width=20%",
+                "width=20%" );
+       print &ui_columns_start([ "", $text{'jobs_name'},
+                                 $text{'jobs_deftype'},
+                                 $text{'jobs_type'},
+                                 $text{'jobs_client'},
+                                 $text{'jobs_fileset'},
+                                 $text{'jobs_schedule'}, ], "100%", 0, \@tds);
+       foreach $f (@jobs) {
+               $name = &find_value("Name", $f->{'members'});
+               $type = &find_value("Type", $f->{'members'});
+               $client = &find_value("Client", $f->{'members'});
+               $fileset = &find_value("FileSet", $f->{'members'});
+               $schedule = &find_value("Schedule", $f->{'members'});
+               print &ui_checked_columns_row([
+                       "<a href='edit_job.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $f->{'name'} eq 'Job' ? $text{'no'} : $text{'yes'},
+                       $type || "<i>$text{'default'}</i>",
+                       $client || "<i>$text{'default'}</i>",
+                       $fileset || "<i>$text{'default'}</i>",
+                       $schedule || "<i>$text{'default'}</i>",
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'jobs_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'jobs_none'}</b><p>\n";
+       print "<a href='edit_job.cgi?new=1'>$text{'jobs_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_pools.cgi b/bacula-backup/list_pools.cgi
new file mode 100755 (executable)
index 0000000..cacc354
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/local/bin/perl
+# Show a list of all backup pools
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'pools_title'}, "", "pools");
+
+$conf = &get_director_config();
+@pools = &find("Pool", $conf);
+&sort_by_name(\@pools);
+if (@pools) {
+       print &ui_form_start("delete_pools.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d"),
+                  "<a href='edit_pool.cgi?new=1'>$text{'pools_add'}</a>" );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=30%", "width=40%", "width=30%" );
+       print &ui_columns_start([ "", $text{'pools_name'},
+                                 $text{'pools_type'},
+                                 $text{'pools_reten'} ], "100%", 0, \@tds);
+       foreach $f (@pools) {
+               $name = &find_value("Name", $f->{'members'});
+               $type = &find_value("Pool Type", $f->{'members'});
+               $reten = &find_value("Volume Retention", $f->{'members'});
+               print &ui_checked_columns_row([
+                       "<a href='edit_pool.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $type,
+                       $reten,
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'pools_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'pools_none'}</b><p>\n";
+       print "<a href='edit_pool.cgi?new=1'>$text{'pools_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_schedules.cgi b/bacula-backup/list_schedules.cgi
new file mode 100755 (executable)
index 0000000..cf371ee
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/local/bin/perl
+# Show a list of all backup schedules
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'schedules_title'}, "", "schedules");
+
+$conf = &get_director_config();
+@schedules = &find("Schedule", $conf);
+&sort_by_name(\@schedules);
+if (@schedules) {
+       print &ui_form_start("delete_schedules.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d"),
+                  "<a href='edit_schedule.cgi?new=1'>".
+                  "$text{'schedules_add'}</a>" );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=30%", "width=70%" );
+       print &ui_columns_start([ "", $text{'schedules_name'},
+                                 $text{'schedules_sched'} ], "100%", 0, \@tds);
+       foreach $f (@schedules) {
+               $name = &find_value("Name", $f->{'members'});
+               @runs = &find_value("Run", $f->{'members'});
+               if (@runs > 2) {
+                       @runs = ( @runs[0..1], "..." );
+                       }
+               print &ui_checked_columns_row([
+                       "<a href='edit_schedule.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       join(" , ", @runs),
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'schedules_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'schedules_none'}</b><p>\n";
+       print "<a href='edit_schedule.cgi?new=1'>$text{'schedules_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_sdirectors.cgi b/bacula-backup/list_sdirectors.cgi
new file mode 100755 (executable)
index 0000000..d8f1ee4
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/local/bin/perl
+# Show a list of all directors known to the storage daemon
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'sdirectors_title'}, "", "sdirectors");
+
+$conf = &get_storage_config();
+@sdirectors = &find("Director", $conf);
+&sort_by_name(\@sdirectors);
+if (@sdirectors) {
+       print &ui_form_start("delete_sdirectors.cgi", "post");
+       print &select_all_link("d"),"\n";
+       print &select_invert_link("d"),"\n";
+       print "<a href='edit_sdirector.cgi?new=1'>$text{'sdirectors_add'}</a><br>\n";
+       @tds = ( "width=5", "width=30%", "width=70%" );
+       print &ui_columns_start([ "", $text{'sdirectors_name'},
+                                 $text{'sdirectors_pass'} ], "100%", 0, \@tds);
+       foreach $f (@sdirectors) {
+               $name = &find_value("Name", $f->{'members'});
+               $pass = &find_value("Password", $f->{'members'});
+               print &ui_columns_row([
+                       &ui_checkbox("d", $name),
+                       "<a href='edit_sdirector.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $pass,
+                       ], \@tds);
+               }
+       print &ui_columns_end();
+       print &select_all_link("d"),"\n";
+       print &select_invert_link("d"),"\n";
+       print "<a href='edit_sdirector.cgi?new=1'>$text{'sdirectors_add'}</a><br>\n";
+       print &ui_form_end([ [ "delete", $text{'sdirectors_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'sdirectors_none'}</b><p>\n";
+       print "<a href='edit_sdirector.cgi?new=1'>$text{'sdirectors_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_storages.cgi b/bacula-backup/list_storages.cgi
new file mode 100755 (executable)
index 0000000..86a65ca
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/local/bin/perl
+# Show a list of all backup storages
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef, $text{'storages_title'}, "", "storages");
+
+$conf = &get_director_config();
+@storages = &find("Storage", $conf);
+&sort_by_name(\@storages);
+if (@storages) {
+       print &ui_form_start("delete_storages.cgi", "post");
+       @links = ( &select_all_link("d"),
+                  &select_invert_link("d"),
+                  "<a href='edit_storage.cgi?new=1'>$text{'storages_add'}</a>",
+                );
+       print &ui_links_row(\@links);
+       @tds = ( "width=5", "width=30%", "width=20%", "width=30%", "width=20%" );
+       print &ui_columns_start([ "", $text{'storages_name'},
+                                 $text{'storages_address'},
+                                 $text{'storages_device'},
+                                 $text{'storages_type'} ], "100%", 0, \@tds);
+       foreach $f (@storages) {
+               $name = &find_value("Name", $f->{'members'});
+               $addr = &find_value("Address", $f->{'members'});
+               $device = &find_value("Device", $f->{'members'});
+               $type = &find_value("Media Type", $f->{'members'});
+               print &ui_checked_columns_row([
+                       "<a href='edit_storage.cgi?name=".&urlize($name)."'>".
+                       $name."</a>",
+                       $addr,
+                       $device,
+                       $type,
+                       ], \@tds, "d", $name);
+               }
+       print &ui_columns_end();
+       print &ui_links_row(\@links);
+       print &ui_form_end([ [ "delete", $text{'storages_delete'} ] ]);
+       }
+else {
+       print "<b>$text{'storages_none'}</b><p>\n";
+       print "<a href='edit_storage.cgi?new=1'>$text{'storages_add'}</a><br>\n";
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/list_sync.cgi b/bacula-backup/list_sync.cgi
new file mode 100755 (executable)
index 0000000..4c693d9
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl
+# Show a form for setting up automatic node group updates
+
+require './bacula-backup-lib.pl';
+&foreign_require("cron", "cron-lib.pl");
+&ui_print_header(undef,  $text{'sync_title'}, "", "sync");
+
+print &ui_form_start("save_sync.cgi", "post");
+print &ui_table_start($text{'sync_header'}, undef, 2);
+
+# Sync enabled?
+$job = &find_cron_job();
+print &ui_table_row($text{'sync_sched'},
+                   &ui_radio("sched", $job ? 1 : 0,
+                             [ [ 0, $text{'no'} ],
+                               [ 1, $text{'sync_schedyes'} ] ]));
+
+# Cron times
+$job ||= { 'special' => 'hourly' };
+$cron = &capture_function_output(\&cron::show_times_input, $job);
+print &ui_table_span("<table border>$cron</table>");
+
+print &ui_table_end();
+print &ui_form_end([ [ "save", $text{'save'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/log_parser.pl b/bacula-backup/log_parser.pl
new file mode 100644 (file)
index 0000000..33cf0f5
--- /dev/null
@@ -0,0 +1,28 @@
+# log_parser.pl
+# Functions for parsing this module's logs
+
+do 'bacula-backup-lib.pl';
+
+# parse_webmin_log(user, script, action, type, object, &params)
+# Converts logged information from this module into human-readable form
+sub parse_webmin_log
+{
+local ($user, $script, $action, $type, $object, $p) = @_;
+if ($type eq "client" || $type eq "fileset" || $type eq "schedule" ||
+    $type eq "job" || $type eq "pool" || $type eq "storage" ||
+    $type eq "device" || $type eq "group" || $type eq "gjob") {
+       # Adding, modifying or deleting some object
+       return &text('log_'.$action.'_'.$type,
+                    "<tt>".&html_escape($object)."</tt>");
+       }
+elsif ($type eq "clients" || $type eq "filesets" || $type eq "schedules" ||
+    $type eq "jobs" || $type eq "pools" || $type eq "storages" ||
+    $type eq "devices" || $type eq "groups" || $type eq "gjobs") {
+       # Deleting several
+       return &text('log_'.$action.'_'.$type, $object);
+       }
+else {
+       return &text('log_'.$action, "<tt>".&html_escape($object)."</tt>");
+       }
+}
+
diff --git a/bacula-backup/manual.sxw b/bacula-backup/manual.sxw
new file mode 100755 (executable)
index 0000000..c8aded3
Binary files /dev/null and b/bacula-backup/manual.sxw differ
diff --git a/bacula-backup/module.info b/bacula-backup/module.info
new file mode 100644 (file)
index 0000000..a427d46
--- /dev/null
@@ -0,0 +1,4 @@
+desc=Bacula Backup System
+category=system
+version=0.4
+depends=proc cron 1.250
diff --git a/bacula-backup/mount.cgi b/bacula-backup/mount.cgi
new file mode 100755 (executable)
index 0000000..fd54485
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/local/bin/perl
+# Actually execute a backup
+
+require './bacula-backup-lib.pl';
+&ui_print_unbuffered_header(undef,  $text{'mount_title'}, "");
+&ReadParse();
+$mode = $in{'mount'} ? "mount" : "unmount";
+
+print "<b>",&text($mode.'_run', "<tt>$in{'storage'}</tt>"),"</b>\n";
+print "<pre>";
+$h = &open_console();
+&console_cmd($h, "messages");
+
+# Run the command
+$out = &console_cmd($h, "$mode storage=$in{'storage'}");
+print $out;
+
+print "</pre>";
+if ($out =~ /\sOK\s/i) {
+       # Worked
+       print "<b>",$text{$mode.'_done'},"</b><p>\n";
+       }
+else {
+       # Something went wrong
+       print "<b>",$text{$mode.'_failed'},"</b><p>\n";
+       }
+
+&close_console($h);
+&webmin_log($mode, $in{'storage'});
+
+&ui_print_footer("mount_form.cgi", $text{'mount_return'});
+
+
diff --git a/bacula-backup/mount_form.cgi b/bacula-backup/mount_form.cgi
new file mode 100755 (executable)
index 0000000..9605d38
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/local/bin/perl
+# Show a form for mounting or un-mounting one volume
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'mount_title'}, "", "mount");
+
+print &ui_form_start("mount.cgi", "post");
+print &ui_table_start($text{'mount_header'}, undef, 2);
+
+# Storage to mount
+@storages = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+                &get_bacula_storages();
+print &ui_table_row($text{'mount_storage'},
+       &ui_select("storage", $in{'storage'},
+        [ map { [ $_->{'name'},
+                  &text('clientstatus_on', $_->{'name'}, $_->{'address'}) ] }
+          @storages ]));
+
+print &ui_table_end();
+print &ui_form_end([ [ 'mount', $text{'mount_mount'} ],
+                    [ 'unmount', $text{'mount_unmount'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
diff --git a/bacula-backup/poolstatus_form.cgi b/bacula-backup/poolstatus_form.cgi
new file mode 100755 (executable)
index 0000000..26d1c30
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/local/bin/perl
+# Show a form for displaying the status of one pool
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'poolstatus_title'}, "", "poolstatus");
+&ReadParse();
+
+# Show pool selector
+$conf = &get_director_config();
+@pools =  map { $n=&find_value("Name", $_->{'members'}) }
+               &find("Pool", $conf);
+@pools = sort { lc($a) cmp lc($b) } @pools;
+if (@pools == 1) {
+       $in{'pool'} ||= $pools[0];
+       }
+print &ui_form_start("poolstatus_form.cgi");
+print "<b>$text{'poolstatus_show'}</b>\n";
+print &ui_select("pool", $in{'pool'},
+        [ map { [ $_ ] } @pools ]);
+print &ui_submit($text{'poolstatus_ok'}),"<br>\n";
+print &ui_form_end();
+
+if ($in{'pool'}) {
+       # Show volumes in this pool
+       @volumes = &get_pool_volumes($in{'pool'});
+
+       print &ui_subheading($text{'poolstatus_volumes'});
+       $never = "<i>$text{'poolstatus_never'}</i>";
+       if (@volumes) {
+               print &ui_form_start("delete_volumes.cgi", "post");
+               print &ui_hidden("pool", $in{'pool'}),"\n";
+               print &select_all_link("d", 1),"\n";
+               print &select_invert_link("d", 1),"<br>\n";
+               @tds = ( "width=5" );
+               print &ui_columns_start([ "",
+                                         $text{'poolstatus_name'},
+                                         $text{'poolstatus_type'},
+                                         $text{'poolstatus_first'},
+                                         $text{'poolstatus_last'},
+                                         $text{'poolstatus_bytes'},
+                                         $text{'poolstatus_status'} ],
+                                       "100%", 0, \@tds);
+               foreach $v (@volumes) {
+                       print &ui_columns_row([
+                               &ui_checkbox("d", $v->{'volumename'}),
+                               $v->{'volumename'},
+                               $v->{'mediatype'},
+                               $v->{'firstwritten'} || $never,
+                               $v->{'lastwritten'} || $never,
+                               $v->{'volbytes'},
+                               $v->{'volstatus'},
+                               ], \@tds);
+                       }
+               print &ui_columns_end();
+               print &select_all_link("d", 1),"\n";
+               print &select_invert_link("d", 1),"<br>\n";
+               print &ui_form_end([ [ "delete",$text{'poolstatus_delete'} ] ]);
+               }
+       else {
+               print "<b>$text{'poolstatus_none'}</b><p>\n";
+               }
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
+sub joblink
+{
+return $jobs{$_[0]} ? "<a href='edit_job.cgi?name=".&urlize($_[0])."'>$_[0]</a>" : $_[0];
+}
+
diff --git a/bacula-backup/restart.cgi b/bacula-backup/restart.cgi
new file mode 100755 (executable)
index 0000000..5a57d5b
--- /dev/null
@@ -0,0 +1,11 @@
+#!/usr/local/bin/perl
+# Re-start Bacula
+
+require './bacula-backup-lib.pl';
+&error_setup($text{'restart_err'});
+$err = &restart_bacula();
+&error($err) if ($err);
+&webmin_log("restart");
+&redirect("");
+
+
diff --git a/bacula-backup/restore.cgi b/bacula-backup/restore.cgi
new file mode 100755 (executable)
index 0000000..f7d2cd5
--- /dev/null
@@ -0,0 +1,174 @@
+#!/usr/local/bin/perl
+# Actually execute the restore
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+&error_setup($text{'restore_err'});
+$dbh = &connect_to_database();
+
+# Validate inputs
+@files = split(/\r?\n/, $in{'files'});
+@files || &error($text{'restore_efiles'});
+$in{'where'} =~ s/\\/\//g;
+$in{'where_def'} || $in{'where'} =~ /^([a-z]:)?\// ||
+       &error($text{'restore_ewhere'});
+$in{'client'} || &error($text{'restore_eclient'});
+if ($in{'job'} =~ /^nj_(.*)_(\d+)_(\d+)$/) {
+       # Node group job restore ..
+       $in{'client'} eq "*" || &error($text{'restore_eall1'});
+       $name = $1;
+       $time = $2;
+       }
+else {
+       # Single job restore
+       $in{'client'} eq "*" && &error($text{'restore_eall2'});
+
+       # Get the job name
+       $cmd = $dbh->prepare("select Name from Job where JobId = ?");
+       $cmd->execute($in{'job'});
+       ($name) = $cmd->fetchrow();
+       $cmd->finish();
+       }
+
+# Work out clients to restore to
+if ($in{'client'} eq "*") {
+       # Clients that were originally backed up
+       $cmd = $dbh->prepare("select Job.JobId,Job.Name,Job.SchedTime,Client.Name from Job,Client where Job.Name not like 'Restore%' and Job.ClientId = Client.ClientId order by SchedTime desc");
+       $cmd->execute();
+       while(my ($jid, $jname, $jwhen, $jclient) = $cmd->fetchrow()) {
+               ($j, $c) = &is_oc_object($jname);
+               $stime = &date_to_unix($jwhen);
+               if ($j && $c && $j eq $name) {
+                       if (abs($stime - $time) < 30) {
+                               # Found a member of the group
+                               push(@clients, [ $jclient, $jid ]);
+                               }
+                       }
+               }
+       $cmd->finish();
+       @clients || &error($text{'restore_eclients'});
+       &ui_print_unbuffered_header(undef,  $text{'restore_title3'}, "", "restore");
+       }
+elsif ($g = &is_oc_object($in{'client'})) {
+       # All clients in a node group
+       ($group) = grep { $_->{'name'} eq $g } &list_node_groups();
+       $group || &error($text{'restore_egroup'});
+       @clients = map { [ "occlient_${g}_".$_, $in{'job'} ] } @{$group->{'members'}};
+       &ui_print_unbuffered_header(undef,  $text{'restore_title2'}, "", "restore");
+       }
+else {
+       # Just one
+       &ui_print_unbuffered_header(undef,  $text{'restore_title'}, "", "restore");
+       @clients = ( [ $in{'client'}, $in{'job'} ] );
+       }
+
+foreach $clientjob (@clients) {
+       $client = $clientjob->[0];
+       $job = $clientjob->[1];
+       ($g, $c) = &is_oc_object($name);
+       ($gc, $cc) = &is_oc_object($client);
+       print "<b>",&text('restore_run', $c ? "<tt>$c</tt>" : "<tt>$name</tt>", $cc ? "<tt>$cc</tt>" : "<tt>$client</tt>", "<tt>$in{'storage'}</tt>"),"</b>\n";
+       print "<pre>";
+       $h = &open_console();
+
+       # Clear messages
+       &console_cmd($h, "messages");
+
+       # Start the restore process
+       &sysprint($h->{'infh'}, "restore client=$client jobid=$job storage=$in{'storage'}".($in{'where_def'} ? "" : " where=\"$in{'where'}\"")."\n");
+       &wait_for($h->{'outfh'}, 'restore.*\n');
+       print $wait_for_input;
+       $rv = &wait_for($h->{'outfh'}, 'Enter\s+"done".*\n',
+                                      'Unable to get Job record',
+                                      'all the files');
+       print $wait_for_input;
+       if ($rv == 1) {
+               &job_error($text{'restore_ejob'});
+               }
+       elsif ($rv == 2) {
+               &job_error($text{'restore_ejobfiles'});
+               }
+
+       # Select the files
+       foreach $f (@files) {
+               $f = &unix_to_dos($f);
+               if ($f eq "/") {
+                       &sysprint($h->{'infh'}, "cd /\n");
+                       &wait_for($h->{'outfh'}, "\\\$");
+                       print $wait_for_input;
+
+                       &sysprint($h->{'infh'}, "mark *\n");
+                       &wait_for($h->{'outfh'}, "\\\$");
+                       print $wait_for_input;
+                       }
+               elsif ($f =~ /^(.*)\/([^\/]+)\/?$/) {
+                       local ($fd, $ff) = ($1, $2);
+                       $fd ||= "/";
+                       &sysprint($h->{'infh'}, "cd \"$fd\"\n");
+                       &wait_for($h->{'outfh'}, "\\\$");
+                       print $wait_for_input;
+
+                       &sysprint($h->{'infh'}, "mark \"$ff\"\n");
+                       &wait_for($h->{'outfh'}, "\\\$");
+                       print $wait_for_input;
+                       }
+               }
+       &sysprint($h->{'infh'}, "done\n");
+       $rv = &wait_for($h->{'outfh'}, 'OK to run.*:', 'no files selected');
+       print $wait_for_input;
+       if ($rv == 0) {
+               &sysprint($h->{'infh'}, "yes\n");
+               }
+       elsif ($rv == 1) {
+               &job_error($text{'restore_enofiles'});
+               }
+       else {
+               &job_error($text{'backup_eok'});
+               }
+       print "</pre>\n";
+
+       if ($in{'wait'}) {
+               # Wait till we have a status
+               print "</pre>\n";
+               print "<b>",$text{'restore_running'},"</b>\n";
+               print "<pre>";
+               while(1) {
+                       $out = &console_cmd($h, "messages");
+                       if ($out !~ /You\s+have\s+no\s+messages/i) {
+                               print $out;
+                               }
+                       if ($out =~ /Termination:\s+(.*)/) {
+                               $status = $1;
+                               last;
+                               }
+                       sleep(1);
+                       }
+               print "</pre>\n";
+               if ($status =~ /Restore\s+OK/i && $status !~ /warning/i) {
+                       print "<b>",$text{'restore_done'},"</b><p>\n";
+                       }
+               else {
+                       print "<b>",$text{'restore_failed'},"</b><p>\n";
+                       }
+               }
+       else {
+               # Let it fly
+               print "<b>",$text{'restore_running2'},"</b><p>\n";
+               }
+
+       &close_console($h);
+       }
+
+&webmin_log("restore", $in{'job'});
+
+&ui_print_footer("restore_form.cgi", $text{'restore_return'});
+
+sub job_error
+{
+&close_console($h);
+print "</pre>\n";
+print "<b>",@_,"</b><p>\n";
+&ui_print_footer("backup_form.cgi", $text{'backup_return'});
+exit;
+}
+
diff --git a/bacula-backup/restore_form.cgi b/bacula-backup/restore_form.cgi
new file mode 100755 (executable)
index 0000000..fad83d5
--- /dev/null
@@ -0,0 +1,117 @@
+#!/usr/local/bin/perl
+# Show a form for restoring an old backup job
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'restore_title'}, "", "restore");
+
+$conf = &get_director_config();
+@jobs = &find("Job", $conf);
+$backup = &find_by("Type", "Restore", \@jobs);
+
+print &ui_form_start("restore.cgi", "post");
+print &ui_table_start($text{'restore_header'}, undef, 2);
+
+# Old job to restore
+$dbh = &connect_to_database();
+$cmd = $dbh->prepare("select JobId,Name,SchedTime from Job where Name not like 'Restore%' order by SchedTime desc") ||
+                &error("prepare failed : ",$dbh->errstr);
+$cmd->execute();
+while(my ($id, $name, $when) = $cmd->fetchrow()) {
+       ($j, $c) = &is_oc_object($name);
+       if (!$j) {
+               # Normal backup
+               push(@opts, [ $id, "$name ($when)" ]);
+               }
+       elsif ($j && $c) {
+               # Backup of one node
+               push(@opts, [ $id, "$j on $c ($when)" ]);
+
+               # Save the job ID to a list of those for this particular node
+               # group backup
+               $stime = &date_to_unix($when);
+               $found = 0;
+               foreach $nj (@nodejobs) {
+                       $diff = abs($stime - $nj->{'stime'});
+                       if ($nj->{'job'} eq $j && $diff < 30) {
+                               push(@{$nj->{'clients'}}, [ $id, $c ]);
+                               $found = 1;
+                               last;
+                               }
+                       }
+               if (!$found) {
+                       push(@nodejobs, { 'job' => $j,
+                                         'stime' => $stime,
+                                         'when' => $when,
+                                         'clients' => [ [ $id, $c ] ]});
+                       }
+               }
+       }
+# Add entries for entire node group restores
+if (@nodejobs) {
+       @opts = ( [ undef, $text{'restore_jlist'} ], @opts,
+                 [ undef, $text{'restore_njlist'} ] );
+       foreach $nj (@nodejobs) {
+               push(@opts, [ "nj_".$nj->{'job'}."_".$nj->{'stime'}."_".
+                               $nj->{'clients'}->[0]->[0],
+                             "$nj->{'job'} ($nj->{'when'}" ]);
+               }
+       }
+$cmd->finish();
+print &ui_table_row($text{'restore_job'},
+                   &ui_select("job", undef, \@opts));
+
+# Files to restore
+print &ui_table_row($text{'restore_files'},
+                   &ui_textarea("files", undef, 8, 50)."\n".
+                   &bacula_file_button("files", "job"));
+
+# Storage device
+@storages = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+                &get_bacula_storages();
+print &ui_table_row($text{'restore_storage'},
+       &ui_select("storage", undef,
+        [ map { [ $_->{'name'},
+                  &text('storagestatus_on', $_->{'name'}, $_->{'address'}) ] }
+          @storages ]));
+
+# Destination client or group
+@clients = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+               grep { !&is_oc_object($_, 1) } &get_bacula_clients();
+@groups = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+               grep { &is_oc_object($_, 1) } &get_bacula_clients();
+@opts = ( );
+if (@clients) {
+       push(@opts, [ undef, $text{'restore_clist'} ]) if (@groups);
+       push(@opts,
+          map { [ $_->{'name'},
+                  &text('clientstatus_on', $_->{'name'}, $_->{'address'}) ] }
+          @clients);
+       }
+if (@groups) {
+       push(@opts, [ undef, $text{'restore_glist'} ]) if (@clients);
+       push(@opts,
+          map { ($g, $c) = &is_oc_object($_);
+                $c ? ( ) : ( [ $_->{'name'}, $g ] ) } @groups);
+       }
+if (@nodejobs) {
+       push(@opts, [ "*", $text{'restore_all'} ]);
+       }
+print &ui_table_row($text{'restore_client'},
+                   &ui_select("client", undef, \@opts));
+
+# Destination directory
+$where = &find_value("Where", $backup->{'members'});
+print &ui_table_row($text{'restore_where'},
+                   &ui_opt_textbox("where", undef, 40,
+                                   $text{'default'}." (<tt>$where</tt>)<br>",
+                                   $text{'restore_where2'}));
+
+# Wait for completion?
+print &ui_table_row($text{'backup_wait'},
+                   &ui_yesno_radio("wait", $config{'wait'}));
+
+print &ui_table_end();
+print &ui_form_end([ [ "restore", $text{'restore_ok'} ] ]);
+
+&ui_print_footer("", $text{'index_return'});
+
diff --git a/bacula-backup/save_client.cgi b/bacula-backup/save_client.cgi
new file mode 100755 (executable)
index 0000000..abecfd5
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/local/bin/perl
+# Create, update or delete a client
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+if ($in{'status'}) {
+       # Go to status page
+       &redirect("clientstatus_form.cgi?client=".&urlize($in{'old'}));
+       exit;
+       }
+
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@clients = &find("Client", $conf);
+
+if (!$in{'new'}) {
+       $client = &find_by("Name", $in{'old'}, \@clients);
+        $client || &error($text{'client_egone'});
+       }
+else {
+       $client = { 'type' => 1,
+                    'name' => 'Client',
+                    'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       $name = &find_value("Name", $client->{'members'});
+       $child = &find_dependency("Client", $name, [ "Job", "JobDefs" ], $conf);
+       $child && &error(&text('client_echild', $child));
+       &save_directive($conf, $parent, $client, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'client_err'});
+       $in{'name'} =~ /^\S+$/ || &error($text{'client_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@clients);
+               $clash && &error($text{'client_eclash'});
+               }
+       &save_directive($conf, $client, "Name", $in{'name'}, 1);
+
+       $in{'pass'} || &error($text{'client_epass'});
+       &save_directive($conf, $client, "Password", $in{'pass'}, 1);
+
+       gethostbyname($in{'address'}) || &error($text{'client_eaddress'});
+       &save_directive($conf, $client, "Address", $in{'address'}, 1);
+
+       $in{'port'} =~ /^\d+$/ && $in{'port'} > 0 && $in{'port'} < 65536 ||
+               &error($text{'client_eport'});
+       &save_directive($conf, $client, "FDPort", $in{'port'}, 1);
+
+       &save_directive($conf, $client, "Catalog", $in{'catalog'}, 1);
+
+       &save_directive($conf, $client, "AutoPrune", $in{'prune'} || undef, 1);
+
+       $fileret = &parse_period_input("fileret");
+       $fileret || &error($text{'client_efileret'});
+       &save_directive($conf, $client, "File Retention", $fileret, 1);
+
+       $jobret = &parse_period_input("jobret");
+       $jobret || &error($text{'client_ejobret'});
+       &save_directive($conf, $client, "Job Retention", $jobret, 1);
+
+       # Save SSL options
+       &parse_tls_directives($conf, $client, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $client, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "client", $in{'old'} || $in{'name'});
+&redirect("list_clients.cgi");
+
diff --git a/bacula-backup/save_device.cgi b/bacula-backup/save_device.cgi
new file mode 100755 (executable)
index 0000000..1e3df21
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/local/bin/perl
+# Create, update or delete a device device
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+$conf = &get_storage_config();
+$parent = &get_storage_config_parent();
+@devices = &find("Device", $conf);
+
+if (!$in{'new'}) {
+       $device = &find_by("Name", $in{'old'}, \@devices);
+        $device || &error($text{'device_egone'});
+       }
+else {
+       $device = { 'type' => 1,
+                    'name' => 'Device',
+                    'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       $name = &find_value("Name", $device->{'members'});
+       $child = &find_dependency("Device", $name, [ "Job", "JobDefs" ], $conf);
+       $child && &error(&text('device_echild', $child));
+       &save_directive($conf, $parent, $device, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'device_err'});
+       $in{'name'} =~ /\S/ || &error($text{'device_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@devices);
+               $clash && &error($text{'device_eclash'});
+               }
+       &save_directive($conf, $device, "Name", $in{'name'}, 1);
+
+       -r $in{'device'} || -d $in{'device'} || &error($text{'device_edevice'});
+       &save_directive($conf, $device, "Archive Device", $in{'device'}, 1);
+
+       $in{'media'} =~ /\S/ || &error($text{'device_emedia'});
+       &save_directive($conf, $device, "Media Type", $in{'media'}, 1);
+
+       # Save yes/no options
+       &save_directive($conf, $device, "LabelMedia", $in{'label'} || undef, 1);
+       &save_directive($conf, $device, "Random Access", $in{'random'} || undef, 1);
+       &save_directive($conf, $device, "AutomaticMount", $in{'auto'} || undef, 1);
+       &save_directive($conf, $device, "RemovableMedia", $in{'removable'} || undef, 1);
+       &save_directive($conf, $device, "AlwaysOpen", $in{'always'} || undef, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $device, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "device", $in{'old'} || $in{'name'});
+&redirect("list_devices.cgi");
+
diff --git a/bacula-backup/save_director.cgi b/bacula-backup/save_director.cgi
new file mode 100755 (executable)
index 0000000..5641daa
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/local/bin/perl
+# Update the Director section
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+&error_setup($text{'director_err'});
+
+$conf = &get_director_config();
+$director = &find("Director", $conf);
+$director || &error($text{'director_enone'});
+$mems = $director->{'members'};
+&lock_file($director->{'file'});
+
+# Validate and store inputs
+$in{'name'} =~ /^[a-z0-9\.\-\_]+$/ || &error($text{'director_ename'});
+&save_directive($conf, $director, "Name", $in{'name'}, 1);
+
+$in{'port'} =~ /^\d+$/ && $in{'port'} > 0 && $in{'port'} < 65536 ||
+       &error($text{'director_eport'});
+&save_directive($conf, $director, "DIRport", $in{'port'}, 1);
+
+$in{'jobs'} =~ /^\d+$/ || &error($text{'director_ejobs'});
+&save_directive($conf, $director, "Maximum Concurrent Jobs", $in{'jobs'}, 1);
+
+&save_directive($conf, $director, "Messages", $in{'messages'} || undef, 1);
+
+-d $in{'dir'} || &error($text{'director_edir'});
+&save_directive($conf, $director, "WorkingDirectory", $in{'dir'}, 1);
+
+# Validate and store TLS inputs
+&parse_tls_directives($conf, $director, 1);
+
+&flush_file_lines();
+&unlock_file($director->{'file'});
+&auto_apply_configuration();
+&webmin_log("director");
+&redirect("");
+
diff --git a/bacula-backup/save_fdirector.cgi b/bacula-backup/save_fdirector.cgi
new file mode 100755 (executable)
index 0000000..8e64794
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/local/bin/perl
+# Create, update or delete a file daemon director
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+$conf = &get_file_config();
+$parent = &get_file_config_parent();
+@fdirectors = &find("Director", $conf);
+
+if (!$in{'new'}) {
+       $fdirector = &find_by("Name", $in{'old'}, \@fdirectors);
+        $fdirector || &error($text{'fdirector_egone'});
+       }
+else {
+       $fdirector = { 'type' => 1,
+                    'name' => 'Director',
+                    'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       $name = &find_value("Name", $fdirector->{'members'});
+       &save_directive($conf, $parent, $fdirector, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'fdirector_err'});
+       $in{'name'} =~ /\S/ || &error($text{'fdirector_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@fdirectors);
+               $clash && &error($text{'fdirector_eclash'});
+               }
+       &save_directive($conf, $fdirector, "Name", $in{'name'}, 1);
+
+       $in{'pass'} || &error($text{'fdirector_epass'});
+       &save_directive($conf, $fdirector, "Password", $in{'pass'}, 1);
+
+       &save_directive($conf, $fdirector, "Monitor",
+                       $in{'monitor'} || undef, 1);
+
+        # Save SSL options
+        &parse_tls_directives($conf, $fdirector, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $fdirector, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "fdirector", $in{'old'} || $in{'name'});
+&redirect("list_fdirectors.cgi");
+
diff --git a/bacula-backup/save_file.cgi b/bacula-backup/save_file.cgi
new file mode 100755 (executable)
index 0000000..127ee59
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/local/bin/perl
+# Update the FileDaemon section
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+&error_setup($text{'file_err'});
+
+$conf = &get_file_config();
+$file = &find("FileDaemon", $conf);
+$file || &error($text{'file_enone'});
+$mems = $file->{'members'};
+&lock_file($file->{'file'});
+
+# Validate and store inputs
+$in{'name'} =~ /^[a-z0-9\.\-\_]+$/ || &error($text{'file_ename'});
+&save_directive($conf, $file, "Name", $in{'name'}, 1);
+
+$in{'port'} =~ /^\d+$/ && $in{'port'} > 0 && $in{'port'} < 65536 ||
+       &error($text{'file_eport'});
+&save_directive($conf, $file, "FDport", $in{'port'}, 1);
+
+$in{'jobs'} =~ /^\d+$/ || &error($text{'file_ejobs'});
+&save_directive($conf, $file, "Maximum Concurrent Jobs", $in{'jobs'}, 1);
+
+-d $in{'dir'} || &error($text{'file_edir'});
+&save_directive($conf, $file, "WorkingDirectory", $in{'dir'}, 1);
+
+# Validate and store TLS inputs
+&parse_tls_directives($conf, $file, 1);
+
+&flush_file_lines();
+&unlock_file($file->{'file'});
+&auto_apply_configuration();
+&webmin_log("file");
+&redirect("");
+
diff --git a/bacula-backup/save_fileset.cgi b/bacula-backup/save_fileset.cgi
new file mode 100755 (executable)
index 0000000..62b145c
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/local/bin/perl
+# Create, update or delete a file set
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@filesets = &find("FileSet", $conf);
+
+if (!$in{'new'}) {
+       $fileset = &find_by("Name", $in{'old'}, \@filesets);
+        $fileset || &error($text{'fileset_egone'});
+       }
+else {
+       $fileset = { 'type' => 1,
+                    'name' => 'FileSet',
+                    'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       $name = &find_value("Name", $client->{'members'});
+       $child = &find_dependency("Client", $name, [ "Job", "JobDefs" ], $conf);
+       $child && &error(&text('fileset_echild', $child));
+       &save_directive($conf, $parent, $fileset, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'fileset_err'});
+       $in{'name'} =~ /\S/ || &error($text{'fileset_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@filesets);
+               $clash && &error($text{'fileset_eclash'});
+               }
+       &save_directive($conf, $fileset, "Name", $in{'name'}, 1);
+
+       # Save included files
+       $in{'include'} =~ s/\r//g;
+       $in{'include'} =~ s/\\/\//g;
+       @include = split(/\n+/, $in{'include'});
+       $inc = &find("Include", $fileset->{'members'});
+       if (!$inc) {
+               $inc = { 'name' => 'Include', 'type' => 1, 'members' => [ ] };
+               &save_directive($conf, $fileset, undef, $inc, 1);
+               }
+       &save_directives($conf, $inc, "File", \@include, 2);
+       $opts = &find("Options", $inc->{'members'});
+       if (!$opts) {
+               $opts = { 'name' => 'Options', 'type' => 1, 'members' => [ ] };
+               &save_directive($conf, $inc, undef, $opts, 2);
+               }
+       &save_directive($conf, $opts, "signature",
+                       $in{'signature'} || undef, 3);
+
+       # Save excluded files
+       $in{'exclude'} =~ s/\r//g;
+       $in{'exclude'} =~ s/\\/\//g;
+       @exclude = split(/\n+/, $in{'exclude'});
+       $exc = &find("Exclude", $fileset->{'members'});
+       if (!$exc && @exclude) {
+               $exc = { 'name' => 'Exclude', 'type' => 1, 'members' => [ ] };
+               &save_directive($conf, $fileset, undef, $exc, 1);
+               }
+       if ($exc) {
+               &save_directives($conf, $exc, "File", \@exclude, 2);
+               }
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $fileset, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "fileset", $in{'old'} || $in{'name'});
+&redirect("list_filesets.cgi");
+
diff --git a/bacula-backup/save_gjob.cgi b/bacula-backup/save_gjob.cgi
new file mode 100755 (executable)
index 0000000..a5727ac
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/local/bin/perl
+# Create, update or delete a job
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+if ($in{'run'}) {
+       # Just run this job
+       &redirect("gbackup.cgi?job=".&urlize($in{'old'}).
+                 "&wait=$config{'wait'}");
+       exit;
+       }
+
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@jobs = &find("JobDefs", $conf);
+@nodegroups = &list_node_groups();
+
+if (!$in{'new'}) {
+       $job = &find_by("Name", "ocjob_".$in{'old'}, \@jobs);
+        $job || &error($text{'job_egone'});
+       $oldclient = &find_value("Client", $job->{'members'});
+       $oldclient = &is_oc_object($oldclient);
+       }
+else {
+       $job = { 'type' => 1,
+                'name' => 'JobDefs',
+                'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       &save_directive($conf, $parent, $job, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'job_err'});
+       $in{'name'} =~ /\S/ || &error($text{'job_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", "ocjob_".$in{'name'}, \@jobs);
+               $clash && &error($text{'job_eclash'});
+               }
+       &save_directive($conf, $job, "Name", "ocjob_".$in{'name'}, 1);
+
+       &save_directive($conf, $job, "Type", $in{'type'} || undef, 1);
+       &save_directive($conf, $job, "Level", $in{'level'} || undef, 1);
+       &save_directive($conf, $job, "Client", $in{'client'} || undef, 1);
+       &save_directive($conf, $job, "FileSet", $in{'fileset'} || undef, 1);
+       &save_directive($conf, $job, "Schedule", $in{'schedule'} || undef, 1);
+       &save_directive($conf, $job, "Storage", $in{'storage'} || undef, 1);
+       &save_directive($conf, $job, "Pool", $in{'pool'} || undef, 1);
+       &save_directive($conf, $job, "Messages", $in{'messages'} || undef, 1);
+       $in{'priority_def'} || $in{'priority'} =~ /^\d+$/ ||
+               &error($text{'job_epriority'});
+       &save_directive($conf, $job, "Priority",
+               $in{'priority_def'} ? undef : $in{'priority'}, 1);
+
+       &save_directive($conf, $job, "Run Before Job",
+                       $in{'before_def'} ? undef : $in{'before'}, 1);
+       &save_directive($conf, $job, "Run After Job",
+                       $in{'after_def'} ? undef : $in{'after'}, 1);
+       &save_directive($conf, $job, "Client Run Before Job",
+                       $in{'cbefore_def'} ? undef : $in{'cbefore'}, 1);
+       &save_directive($conf, $job, "Client Run After Job",
+                       $in{'cafter_def'} ? undef : $in{'cafter'}, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $job, 0);
+               }
+       }
+
+# Sync the old and new node groups
+if ($oldclient) {
+       ($oldnodegroup) = grep { $_->{'name'} eq $oldclient } @nodegroups;
+       &sync_group_clients($oldnodegroup) if ($oldnodegroup);
+       }
+if (!$in{'delete'}) {
+       $client = &is_oc_object($in{'client'});
+       ($nodegroup) = grep { $_->{'name'} eq $client } @nodegroups;
+       if ($nodegroup && $nodegroup ne $oldnodegroup) {
+               &sync_group_clients($nodegroup);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "gjob", $in{'old'} || $in{'name'});
+&redirect("list_gjobs.cgi");
+
diff --git a/bacula-backup/save_group.cgi b/bacula-backup/save_group.cgi
new file mode 100755 (executable)
index 0000000..3e1bf12
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/local/bin/perl
+# Create, update or delete a special client used as a group
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@clients = &find("Client", $conf);
+
+# Get node group
+@nodegroups = &list_node_groups();
+$ngname = $in{'old'} || $in{'new'};
+($nodegroup) = grep { $_->{'name'} eq $ngname } @nodegroups;
+
+if (!$in{'new'}) {
+       $client = &find_by("Name", "ocgroup_".$in{'old'}, \@clients);
+        $client || &error($text{'group_egone'});
+       }
+else {
+       $client = { 'type' => 1,
+                    'name' => 'Client',
+                    'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       # XXX
+       #$name = &find_value("Name", $client->{'members'});
+       #$child = &find_dependency("Client", $name, [ "Job", "JobDefs" ], $conf);
+       #$child && &error(&text('client_echild', $child));
+       &save_directive($conf, $parent, $client, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'group_err'});
+       if ($in{'new'}) {
+               &save_directive($conf, $client, "Name", "ocgroup_".$in{'new'}, 1);
+               $clash = &find_by("Name", "ocgroup_".$in{'name'}, \@clients);
+               $clash && &error($text{'group_eclash'});
+               }
+
+       &save_directive($conf, $client, "Address", "localhost", 1);
+
+       $in{'pass'} || &error($text{'client_epass'});
+       &save_directive($conf, $client, "Password", $in{'pass'}, 1);
+
+       $in{'port'} =~ /^\d+$/ && $in{'port'} > 0 && $in{'port'} < 65536 ||
+               &error($text{'client_eport'});
+       &save_directive($conf, $client, "FDPort", $in{'port'}, 1);
+
+       &save_directive($conf, $client, "Catalog", $in{'catalog'}, 1);
+
+       &save_directive($conf, $client, "AutoPrune", $in{'prune'} || undef, 1);
+
+       $fileret = &parse_period_input("fileret");
+       $fileret || &error($text{'client_efileret'});
+       &save_directive($conf, $client, "File Retention", $fileret, 1);
+
+       $jobret = &parse_period_input("jobret");
+       $jobret || &error($text{'client_ejobret'});
+       &save_directive($conf, $client, "Job Retention", $jobret, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $client, 0);
+               }
+       }
+
+if ($nodegroup) {
+       # Force update to all dependent clients
+       &sync_group_clients($nodegroup);
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "group", $ngname);
+&redirect("list_groups.cgi");
+
diff --git a/bacula-backup/save_job.cgi b/bacula-backup/save_job.cgi
new file mode 100755 (executable)
index 0000000..7e51f9b
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/local/bin/perl
+# Create, update or delete a job
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+if ($in{'run'}) {
+       # Just run this job
+       &redirect("backup.cgi?job=".&urlize($in{'old'}).
+                 "&wait=$config{'wait'}");
+       exit;
+       }
+
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@jobs = ( &find("JobDefs", $conf), &find("Job", $conf) );
+
+if (!$in{'new'}) {
+       $job = &find_by("Name", $in{'old'}, \@jobs);
+        $job || &error($text{'job_egone'});
+       }
+else {
+       $job = { 'type' => 1,
+                'name' => 'Job',
+                'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       if ($job->{'name'} eq 'JobDefs') {
+               # Cannot delete if anything inherits from it
+               $name = &find_value("Name", $job->{'members'});
+               $child = &find_dependency("JobDefs", $name, [ "Job" ], $conf);
+               $child && &error(&text('job_echild', $child));
+               }
+       &save_directive($conf, $parent, $job, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'job_err'});
+       $in{'name'} =~ /\S/ || &error($text{'job_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@jobs);
+               $clash && &error($text{'job_eclash'});
+               }
+       &save_directive($conf, $job, "Name", $in{'name'}, 1);
+
+       if ($in{'dmode'} == 0) {
+               $job->{'name'} = "JobDefs";
+               }
+       else {
+               $job->{'name'} = "Job";
+               &save_directive($conf, $job, "JobDefs",
+                       $in{'dmode'} == 1 ? undef : $in{'defs'}, 1);
+               }
+
+       &save_directive($conf, $job, "Type", $in{'type'} || undef, 1);
+       &save_directive($conf, $job, "Level", $in{'level'} || undef, 1);
+       &save_directive($conf, $job, "Client", $in{'client'} || undef, 1);
+       &save_directive($conf, $job, "FileSet", $in{'fileset'} || undef, 1);
+       &save_directive($conf, $job, "Schedule", $in{'schedule'} || undef, 1);
+       &save_directive($conf, $job, "Storage", $in{'storage'} || undef, 1);
+       &save_directive($conf, $job, "Pool", $in{'pool'} || undef, 1);
+       &save_directive($conf, $job, "Messages", $in{'messages'} || undef, 1);
+       $in{'priority_def'} || $in{'priority'} =~ /^\d+$/ ||
+               &error($text{'job_epriority'});
+       &save_directive($conf, $job, "Priority",
+               $in{'priority_def'} ? undef : $in{'priority'}, 1);
+
+       &save_directive($conf, $job, "Run Before Job",
+                       $in{'before_def'} ? undef : $in{'before'}, 1);
+       &save_directive($conf, $job, "Run After Job",
+                       $in{'after_def'} ? undef : $in{'after'}, 1);
+       &save_directive($conf, $job, "Client Run Before Job",
+                       $in{'cbefore_def'} ? undef : $in{'cbefore'}, 1);
+       &save_directive($conf, $job, "Client Run After Job",
+                       $in{'cafter_def'} ? undef : $in{'cafter'}, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $job, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "job", $in{'old'} || $in{'name'});
+&redirect("list_jobs.cgi");
+
diff --git a/bacula-backup/save_pool.cgi b/bacula-backup/save_pool.cgi
new file mode 100755 (executable)
index 0000000..3d5b099
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/local/bin/perl
+# Create, update or delete a volume pool
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+if ($in{'status'}) {
+       # Go to status page
+       &redirect("poolstatus_form.cgi?pool=".&urlize($in{'old'}));
+       exit;
+       }
+
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@pools = &find("Pool", $conf);
+
+if (!$in{'new'}) {
+       $pool = &find_by("Name", $in{'old'}, \@pools);
+        $pool || &error($text{'pool_egone'});
+       }
+else {
+       $pool = { 'type' => 1,
+                 'name' => 'Pool',
+                 'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       $name = &find_value("Name", $pool->{'members'});
+       $child = &find_dependency("Pool", $name, [ "Job", "JobDefs" ], $conf);
+       $child && &error(&text('pool_echild', $child));
+       &save_directive($conf, $parent, $pool, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'pool_err'});
+       $in{'name'} =~ /\S/ || &error($text{'pool_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@pools);
+               $clash && &error($text{'pool_eclash'});
+               }
+       &save_directive($conf, $pool, "Name", $in{'name'}, 1);
+       &save_directive($conf, $pool, "Pool Type", $in{'type'}, 1);
+
+       # Max volume jobs
+       if ($in{'maxmode'} == 0) {
+               &save_directive($conf, $pool, "Maximum Volume Jobs", undef, 1);
+               }
+       else {
+               $in{'max'} =~ /^\d+$/ || &error($text{'pool_emax'});
+               &save_directive($conf, $pool, "Maximum Volume Jobs", $in{'max'}, 1);
+               }
+
+       # Retention period
+       $reten = &parse_period_input("reten");
+       $reten || &error($text{'pool_ereten'});
+       &save_directive($conf, $pool, "Volume Retention", $reten, 1);
+
+       # Save yes/no options
+       &save_directive($conf, $pool, "Recycle", $in{'recycle'} || undef, 1);
+       &save_directive($conf, $pool, "AutoPrune", $in{'auto'} || undef, 1);
+       &save_directive($conf, $pool, "Accept Any Volume", $in{'any'} || undef, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $pool, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "pool", $in{'old'} || $in{'name'});
+&redirect("list_pools.cgi");
+
diff --git a/bacula-backup/save_schedule.cgi b/bacula-backup/save_schedule.cgi
new file mode 100755 (executable)
index 0000000..4328ae3
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/local/bin/perl
+# Create, update or delete a backup schedule
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@schedules = &find("Schedule", $conf);
+
+if (!$in{'new'}) {
+       $schedule = &find_by("Name", $in{'old'}, \@schedules);
+        $schedule || &error($text{'schedule_egone'});
+       }
+else {
+       $schedule = { 'type' => 1,
+                    'name' => 'Schedule',
+                    'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       $name = &find_value("Name", $schedule->{'members'});
+       $child = &find_dependency("Schedule", $name, [ "Job", "JobDefs" ], $conf);
+       $child && &error(&text('schedule_echild', $child));
+       &save_directive($conf, $parent, $schedule, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'schedule_err'});
+       $in{'name'} =~ /\S/ || &error($text{'schedule_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@schedules);
+               $clash && &error($text{'schedule_eclash'});
+               }
+       &save_directive($conf, $schedule, "Name", $in{'name'}, 1);
+
+       # Parse and save run times
+       for($i=0; defined($level = $in{"level_$i"}); $i++) {
+               next if (!$level);
+               $times = $in{"times_$i"};
+               $times =~ /\S/ || &error(&text('schedule_etimes', $i+1));
+               push(@runs, "$level $times");
+               }
+       &save_directives($conf, $schedule, "Run", \@runs, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $schedule, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "schedule", $in{'old'} || $in{'name'});
+&redirect("list_schedules.cgi");
+
diff --git a/bacula-backup/save_sdirector.cgi b/bacula-backup/save_sdirector.cgi
new file mode 100755 (executable)
index 0000000..516c4a2
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/local/bin/perl
+# Create, update or delete a storage daemon director
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+$conf = &get_storage_config();
+$parent = &get_storage_config_parent();
+@sdirectors = &find("Director", $conf);
+
+if (!$in{'new'}) {
+       $sdirector = &find_by("Name", $in{'old'}, \@sdirectors);
+        $sdirector || &error($text{'sdirector_egone'});
+       }
+else {
+       $sdirector = { 'type' => 1,
+                    'name' => 'Director',
+                    'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       $name = &find_value("Name", $sdirector->{'members'});
+       &save_directive($conf, $parent, $sdirector, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'sdirector_err'});
+       $in{'name'} =~ /\S/ || &error($text{'sdirector_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@sdirectors);
+               $clash && &error($text{'sdirector_eclash'});
+               }
+       &save_directive($conf, $sdirector, "Name", $in{'name'}, 1);
+
+       $in{'pass'} || &error($text{'sdirector_epass'});
+       &save_directive($conf, $sdirector, "Password", $in{'pass'}, 1);
+
+       &save_directive($conf, $sdirector, "Monitor",
+                       $in{'monitor'} || undef, 1);
+
+       # Save SSL options
+       &parse_tls_directives($conf, $sdirector, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $sdirector, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "sdirector", $in{'old'} || $in{'name'});
+&redirect("list_sdirectors.cgi");
+
diff --git a/bacula-backup/save_storage.cgi b/bacula-backup/save_storage.cgi
new file mode 100755 (executable)
index 0000000..4f3a575
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/local/bin/perl
+# Create, update or delete a storage daemon
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+
+if ($in{'status'}) {
+       # Go to status page
+       &redirect("storagestatus_form.cgi?storage=".&urlize($in{'old'}));
+       exit;
+       }
+
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@storages = &find("Storage", $conf);
+
+if (!$in{'new'}) {
+       $storage = &find_by("Name", $in{'old'}, \@storages);
+        $storage || &error($text{'storage_egone'});
+       }
+else {
+       $storage = { 'type' => 1,
+                    'name' => 'Storage',
+                    'members' => [ ] };
+       }
+
+&lock_file($parent->{'file'});
+if ($in{'delete'}) {
+       # Just delete this one
+       $name = &find_value("Name", $storage->{'members'});
+       $child = &find_dependency("Storage", $name, [ "Job", "JobDefs" ], $conf);
+       $child && &error(&text('storage_echild', $child));
+       &save_directive($conf, $parent, $storage, undef, 0);
+       }
+else {
+       # Validate and store inputs
+       &error_setup($text{'storage_err'});
+       $in{'name'} =~ /\S/ || &error($text{'storage_ename'});
+       if ($in{'new'} || $in{'name'} ne $in{'old'}) {
+               $clash = &find_by("Name", $in{'name'}, \@storages);
+               $clash && &error($text{'storage_eclash'});
+               }
+       &save_directive($conf, $storage, "Name", $in{'name'}, 1);
+
+       $in{'pass'} || &error($text{'storage_epass'});
+       &save_directive($conf, $storage, "Password", $in{'pass'}, 1);
+
+       gethostbyname($in{'address'}) || &error($text{'storage_eaddress'});
+       &save_directive($conf, $storage, "Address", $in{'address'}, 1);
+
+       $in{'port'} =~ /^\d+$/ && $in{'port'} > 0 && $in{'port'} < 65536 ||
+               &error($text{'storage_eport'});
+       &save_directive($conf, $storage, "SDPort", $in{'port'}, 1);
+
+       $in{'device'} ||= $in{'other'};
+       $in{'device'} =~ /\S/ || &error($text{'storage_edevice'});
+       &save_directive($conf, $storage, "Device", $in{'device'}, 1);
+
+       $in{'media'} =~ /\S/ || &error($text{'storage_emedia'});
+       &save_directive($conf, $storage, "Media Type", $in{'media'}, 1);
+
+       # SSL directives
+       &parse_tls_directives($conf, $storage, 1);
+
+       # Create or update
+       if ($in{'new'}) {
+               &save_directive($conf, $parent, undef, $storage, 0);
+               }
+       }
+
+&flush_file_lines();
+&unlock_file($parent->{'file'});
+&auto_apply_configuration();
+&webmin_log($in{'new'} ? "create" : $in{'delete'} ? "delete" : "modify",
+           "storage", $in{'old'} || $in{'name'});
+&redirect("list_storages.cgi");
+
diff --git a/bacula-backup/save_storagec.cgi b/bacula-backup/save_storagec.cgi
new file mode 100755 (executable)
index 0000000..19f5d2e
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/local/bin/perl
+# Update the Storage section
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+&error_setup($text{'storagec_err'});
+
+$conf = &get_storage_config();
+$storagec = &find("Storage", $conf);
+$storagec || &error($text{'storagec_enone'});
+$mems = $storagec->{'members'};
+&lock_file($storagec->{'file'});
+
+# Validate and store inputs
+$in{'name'} =~ /^[a-z0-9\.\-\_]+$/ || &error($text{'storagec_ename'});
+&save_directive($conf, $storagec, "Name", $in{'name'}, 1);
+
+$in{'port'} =~ /^\d+$/ && $in{'port'} > 0 && $in{'port'} < 65536 ||
+       &error($text{'storagec_eport'});
+&save_directive($conf, $storagec, "SDport", $in{'port'}, 1);
+
+$in{'jobs'} =~ /^\d+$/ || &error($text{'storagec_ejobs'});
+&save_directive($conf, $storagec, "Maximum Concurrent Jobs", $in{'jobs'}, 1);
+
+-d $in{'dir'} || &error($text{'storagec_edir'});
+&save_directive($conf, $storagec, "WorkingDirectory", $in{'dir'}, 1);
+
+# Validate and store TLS inputs
+&parse_tls_directives($conf, $storagec, 1);
+
+&flush_file_lines();
+&unlock_file($storagec->{'file'});
+&auto_apply_configuration();
+&webmin_log("storagec");
+&redirect("");
+
diff --git a/bacula-backup/save_sync.cgi b/bacula-backup/save_sync.cgi
new file mode 100755 (executable)
index 0000000..5380b37
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/local/bin/perl
+# Turn syncing on or off
+
+require './bacula-backup-lib.pl';
+&foreign_require("cron", "cron-lib.pl");
+&ReadParse();
+&error_setup($text{'sync_err'});
+
+$job = $oldjob = &find_cron_job();
+if ($in{'sched'}) {
+       $job ||= { 'command' => $cron_cmd,
+                  'user' => 'root',
+                  'active' => 1 };
+       &lock_file(&cron::cron_file($job));
+       &cron::parse_times_input($job, \%in);
+       &cron::create_wrapper($cron_cmd, $module_name, "sync.pl");
+       if ($oldjob) {
+               &cron::change_cron_job($job);
+               }
+       else {
+               &cron::create_cron_job($job);
+               }
+       &unlock_file(&cron::cron_file($job));
+       }
+elsif ($job) {
+       &lock_file(&cron::cron_file($job));
+       &cron::delete_cron_job($job);
+       &unlock_file(&cron::cron_file($job));
+       }
+&webmin_log("sync");
+&redirect("");
+
diff --git a/bacula-backup/schedule_chooser.cgi b/bacula-backup/schedule_chooser.cgi
new file mode 100755 (executable)
index 0000000..a516850
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/local/bin/perl
+# Show a popup window for selecting a Bacula schedule
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+&popup_header($text{'chooser_title'});
+
+# Parse into month, day and hour parts
+if ($in{'schedule'}) {
+       $sched = &parse_schedule($in{'schedule'});
+       }
+else {
+       $sched = { 'months_all' => 1,
+                  'weekdays_all' => 1,
+                  'weekdaynums_all' => 1,
+                  'days_all' => 1,
+                  'hour' => '00',
+                  'minute' => '00',
+                };
+       }
+ref($sched) || &error($sched);
+print &ui_form_start("schedule_select.cgi", "post");
+@tds = ( "width=30%", "width=70%" );
+
+# Show months section
+@months = map { [ $_-1, $text{'month_'.$_} ] } (1 .. 12);
+print &ui_table_start($text{'chooser_monthsh'}, "width=100%", 2);
+print &ui_table_row($text{'chooser_months'},
+       &ui_radio("months_all", $sched->{'months_all'} ? 1 : 0,
+                 [ [ 1, $text{'chooser_all'} ],
+                   [ 0, $text{'chooser_sel'} ] ])."<br>".
+       &select_chooser("months", \@months, $sched->{'months'}),
+       1, \@tds);
+print &ui_table_end();
+
+# Show days of month section
+@days = map { [ $_, $_ ] } (1 .. 31);
+print &ui_table_start($text{'chooser_daysh'}, "width=100%", 2);
+print &ui_table_row($text{'chooser_days'},
+       &ui_radio("days_all", $sched->{'days_all'} ? 1 : 0,
+                 [ [ 1, $text{'chooser_all'} ],
+                   [ 0, $text{'chooser_sel'} ] ])."<br>".
+       &select_chooser("days", \@days, $sched->{'days'}, 8),
+       1, \@tds);
+print &ui_table_end();
+
+# Show days of week section
+@weekdays = map { [ $_, $text{'day_'.$_} ] } (0 .. 6);
+@weekdaynums = map { [ $_, $text{'weekdaynum_'.$_} ] } (1 .. 5);
+print &ui_table_start($text{'chooser_weekdaysh'}, "width=100%", 2);
+print &ui_table_row($text{'chooser_weekdays'},
+       &ui_radio("weekdays_all", $sched->{'weekdays_all'} ? 1 : 0,
+                 [ [ 1, $text{'chooser_all'} ],
+                   [ 0, $text{'chooser_sel'} ] ])."<br>".
+       &select_chooser("weekdays", \@weekdays, $sched->{'weekdays'}),
+       1, \@tds);
+print &ui_table_row($text{'chooser_weekdaynums'},
+       &ui_radio("weekdaynums_all", $sched->{'weekdaynums_all'} ? 1 : 0,
+                 [ [ 1, $text{'chooser_all'} ],
+                   [ 0, $text{'chooser_sel'} ] ])."<br>".
+       &select_chooser("weekdaynums", \@weekdaynums,$sched->{'weekdaynums'},5),
+       1, \@tds);
+print &ui_table_end();
+
+# Show time section
+print &ui_table_start($text{'chooser_timeh'}, "width=100%", 2);
+print &ui_table_row($text{'chooser_time'},
+                   &ui_textbox("hour", $sched->{'hour'}, 3).":".
+                   &ui_textbox("minute", $sched->{'minute'}, 3),
+                   1, \@tds);
+print &ui_table_end();
+
+print &ui_form_end([ [ "ok", $text{'chooser_ok'} ] ]);
+
+&popup_footer();
+
+# select_chooser(name, &opts, &selected, [cols])
+sub select_chooser
+{
+local ($name, $opts, $sel, $cols) = @_;
+$cols ||= 4;
+local %sel = map { $_, 1 } @$sel;
+local $rv = "<table>\n";
+for(my $i=0; $i<@$opts; $i++) {
+       $rv .= "<tr>\n" if ($i%$cols == 0);
+       $rv .= "<td>".&ui_checkbox($name, $opts->[$i]->[0], $opts->[$i]->[1],
+                                 $sel{$opts->[$i]->[0]})."</td>\n";
+       $rv .= "</tr>\n" if ($i%$cols == $cols-1);
+       }
+$rv .= "</table>\n";
+return $rv;
+}
diff --git a/bacula-backup/schedule_select.cgi b/bacula-backup/schedule_select.cgi
new file mode 100755 (executable)
index 0000000..03974c9
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/local/bin/perl
+# Convert inputs into a schedule string, and update the original field
+
+require './bacula-backup-lib.pl';
+&ReadParse();
+&error_setup($text{'chooser_err'});
+
+# Validate inputs and make the object
+$sched = { };
+foreach $f ("months", "weekdays", "weekdaynums", "days") {
+       if ($in{$f."_all"}) {
+               $sched->{$f."_all"} = 1;
+               }
+       else {
+               defined($in{$f}) || &error($text{'chooser_e'.$f});
+               $sched->{$f} = [ split(/\0/, $in{$f}) ];
+               }
+       }
+$in{'hour'} =~ /^\d+$/ && $in{'hour'} >= 0 && $in{'hour'} < 24 ||
+       &error($text{'chooser_ehour'});
+$sched->{'hour'} = $in{'hour'};
+$in{'minute'} =~ /^\d+$/ && $in{'minute'} >= 0 && $in{'minute'} < 60 ||
+       &error($text{'chooser_eminute'});
+$sched->{'minute'} = $in{'minute'};
+
+# Update the original field
+$str = &join_schedule($sched);
+&popup_header($text{'chooser_title'});
+
+print <<EOF;
+<script>
+top.opener.ifield.value = "$str";
+window.close();
+</script>
+EOF
+
+&popup_footer();
+
diff --git a/bacula-backup/start.cgi b/bacula-backup/start.cgi
new file mode 100755 (executable)
index 0000000..1492752
--- /dev/null
@@ -0,0 +1,11 @@
+#!/usr/local/bin/perl
+# Start Bacula
+
+require './bacula-backup-lib.pl';
+&error_setup($text{'start_err'});
+$err = &start_bacula();
+&error($err) if ($err);
+&webmin_log("start");
+&redirect("");
+
+
diff --git a/bacula-backup/stop.cgi b/bacula-backup/stop.cgi
new file mode 100755 (executable)
index 0000000..d94b4d9
--- /dev/null
@@ -0,0 +1,11 @@
+#!/usr/local/bin/perl
+# Stop Bacula
+
+require './bacula-backup-lib.pl';
+&error_setup($text{'stop_err'});
+$err = &stop_bacula();
+&error($err) if ($err);
+&webmin_log("stop");
+&redirect("");
+
+
diff --git a/bacula-backup/storagestatus_form.cgi b/bacula-backup/storagestatus_form.cgi
new file mode 100755 (executable)
index 0000000..52fc490
--- /dev/null
@@ -0,0 +1,96 @@
+#!/usr/local/bin/perl
+# Show a form for displaying the status of one storage daemon
+
+require './bacula-backup-lib.pl';
+&ui_print_header(undef,  $text{'storagestatus_title'}, "", "storagestatus");
+&ReadParse();
+
+# Storage selector
+@storages = sort { lc($a->{'name'}) cmp lc($b->{'name'}) }
+                &get_bacula_storages();
+if (@storages == 1) {
+       $in{'storage'} ||= $storages[0]->{'name'};
+       }
+print &ui_form_start("storagestatus_form.cgi");
+print "<b>$text{'storagestatus_show'}</b>\n";
+print &ui_select("storage", $in{'storage'},
+        [ map { [ $_->{'name'},
+                  &text('storagestatus_on', $_->{'name'}, $_->{'address'}) ] }
+          @storages ]);
+print &ui_submit($text{'storagestatus_ok'}),"<br>\n";
+print &ui_form_end();
+
+if ($in{'storage'}) {
+       # Show this storage
+       ($msg, $ok, $run, $done) = &get_storage_status($in{'storage'});
+
+       if ($ok) {
+               print &text('storagestatus_msg', $in{'storage'}, $msg),"<p>\n";
+
+               # Running jobs
+               print &ui_subheading($text{'dirstatus_run'});
+               if (@$run) {
+                       print &ui_form_start("cancel_jobs.cgi", "post");
+                       print &ui_hidden("storage", $in{'storage'}),"\n";
+                       @links = ( &select_all_link("d", 1),
+                                  &select_invert_link("d", 1) );
+                       print &ui_links_row(\@links);
+                       @tds = ( "width=5" );
+                       print &ui_columns_start([ "", $text{'dirstatus_name'},
+                                                 $text{'dirstatus_id'},
+                                                 $text{'dirstatus_level'},
+                                                 $text{'dirstatus_status'} ],
+                                               "100%", 0, \@tds);
+                       foreach $j (@$run) {
+                               print &ui_checked_columns_row([
+                                       &joblink($j->{'name'}),
+                                       $j->{'id'},
+                                       $j->{'level'},
+                                       $j->{'status'} ], \@tds,
+                                       "d", $j->{'id'});
+                               }
+                       print &ui_columns_end();
+                       print &ui_links_row(\@links);
+                       print &ui_form_end([ [ "cancel", $text{'dirstatus_cancel'} ] ]);
+                       }
+               else {
+                       print "<b>$text{'dirstatus_runnone'}</b><p>\n";
+                       }
+
+               # Completed jobs
+               print &ui_subheading($text{'dirstatus_done'});
+               if (@$done) {
+                       print &ui_columns_start([ $text{'dirstatus_name'},
+                                                 $text{'dirstatus_id'},
+                                                 $text{'dirstatus_level'},
+                                                 $text{'dirstatus_date'},
+                                                 $text{'dirstatus_bytes'},
+                                                 $text{'dirstatus_files'},
+                                                 $text{'dirstatus_status2'} ],
+                                               "100%");
+                       foreach $j (@$done) {
+                               print &ui_columns_row([
+                                       &joblink($j->{'name'}),
+                                       $j->{'id'},
+                                       $j->{'level'},
+                                       $j->{'date'},
+                                       &nice_size($j->{'bytes'}),
+                                       $j->{'files'},
+                                       $j->{'status'} ]);
+                               }
+                       print &ui_columns_end();
+                       }
+               else {
+                       print "<b>$text{'dirstatus_donenone'}</b><p>\n";
+                       }
+               }
+       else {
+               # Couldn't connect!
+               print "<b>",&text('storagestatus_err', $in{'storage'}, $msg),
+                     "</b><p>\n";
+               }
+       }
+
+&ui_print_footer("", $text{'index_return'});
+
+
diff --git a/bacula-backup/sync.pl b/bacula-backup/sync.pl
new file mode 100755 (executable)
index 0000000..60958e2
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/local/bin/perl
+# Update all node groups
+
+$no_acl_check++;
+require './bacula-backup-lib.pl';
+exit if (!&has_node_groups());
+$conf = &get_director_config();
+$parent = &get_director_config_parent();
+@nodegroups = &list_node_groups();
+
+&lock_file($parent->{'file'});
+foreach $nodegroup (@nodegroups) {
+       &sync_group_clients($nodegroup);
+       }
+&flush_file_lines($parent->{'file'});
+&unlock_file($parent->{'file'});
+
diff --git a/bacula-backup/treechooser.cgi b/bacula-backup/treechooser.cgi
new file mode 100755 (executable)
index 0000000..ebbcb7e
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/local/bin/perl
+# treechooser.cgi
+# Outputs HTML for a java file-chooser tree
+
+require './bacula-backup-lib.pl';
+&PrintHeader();
+&ReadParse();
+
+$shortest = "/";
+if ($session_id) {
+       $session = "<param name=session value=\"sid=$session_id\">";
+       }
+
+$in{'job'} =~ s/^(.*)_(\d+)$/$2/g;
+print <<EOF;
+<html><head><title>$text{'tree_title'}</title><body>
+
+<script>
+function clear_files()
+{
+top.ifield.value = "";
+}
+
+function add_file(file)
+{
+top.ifield.value = top.ifield.value + file + "\\n";
+}
+
+function finished()
+{
+window.close();
+}
+</script>
+
+<applet code=TreeChooser name=TreeChooser width=100% height=100% MAYSCRIPT>
+<param name=volume value="$in{'volume'}">
+<param name=root value="$shortest">
+<param name=job value="$in{'job'}">
+$session
+</applet>
+</body></html>
+EOF
+