mirror of https://github.com/borgbase/vorta
Compare commits
24 Commits
a362ec3227
...
b8b5667157
Author | SHA1 | Date |
---|---|---|
![]() |
b8b5667157 | |
![]() |
ee8f805577 | |
![]() |
9cabbbd193 | |
![]() |
3268bf1599 | |
![]() |
7642002573 | |
![]() |
58137f004d | |
![]() |
e070fda084 | |
![]() |
dae0ce153f | |
![]() |
c5fc7c9c3f | |
![]() |
2b55cd9866 | |
![]() |
384c57af57 | |
![]() |
0c332780f8 | |
![]() |
e6faa54dee | |
![]() |
2f40b7c405 | |
![]() |
a5c6729064 | |
![]() |
1d76358404 | |
![]() |
767dcc126a | |
![]() |
4f03af6675 | |
![]() |
d21316b920 | |
![]() |
4b299f9a4c | |
![]() |
b4f2e64662 | |
![]() |
5f07abd8d0 | |
![]() |
7b800661c2 | |
![]() |
6abd0f5862 |
|
@ -3,17 +3,21 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
branch:
|
branch:
|
||||||
description: 'Branch to use for building macOS release'
|
description: 'Branch to use for building release'
|
||||||
required: true
|
required: true
|
||||||
default: 'master'
|
default: 'master'
|
||||||
borg_version:
|
borg_version:
|
||||||
description: 'Borg version to package'
|
description: 'Borg version to package'
|
||||||
required: true
|
required: true
|
||||||
default: '1.2.1'
|
default: '1.2.8'
|
||||||
|
macos_version:
|
||||||
|
description: 'macOS version for building'
|
||||||
|
required: true
|
||||||
|
default: 'macos-11'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-11
|
runs-on: ${{ github.event.inputs.macos_version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out selected branch
|
- name: Check out selected branch
|
||||||
|
|
|
@ -6,10 +6,13 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
days-before-issue-stale: 60
|
days-before-issue-stale: 90
|
||||||
days-before-pr-stale: -1
|
days-before-pr-stale: -1
|
||||||
days-before-issue-close: 7
|
days-before-issue-close: 7
|
||||||
# days-before-pr-close: 10
|
# days-before-pr-close: 10
|
|
@ -725,6 +725,20 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="preBackupScriptEditorButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string> Open Pre-backup script editor</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>:/icons/edit.svg</normaloff>:/icons/edit.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -764,6 +778,20 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="postBackupScriptEditorButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string> Open Post-backup script editor</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>:/icons/edit.svg</normaloff>:/icons/edit.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ScriptEdit</class>
|
||||||
|
<widget class="QDialog" name="ScriptEdit">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>440</width>
|
||||||
|
<height>260</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Edit Script</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>10</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Pre/Post-Backup Script:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>5</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QTextEdit" name="scriptEdit">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Monospace</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="cursor" stdset="0">
|
||||||
|
<cursorShape>IBeamCursor</cursorShape>
|
||||||
|
</property>
|
||||||
|
<property name="lineWrapMode">
|
||||||
|
<enum>QTextEdit::NoWrap</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -56,5 +56,44 @@
|
||||||
],
|
],
|
||||||
"tags": ["type:dev", "editor:android-studio", "os:linux"],
|
"tags": ["type:dev", "editor:android-studio", "os:linux"],
|
||||||
"author": "shivansh02"
|
"author": "shivansh02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jetbrains IDEs cache, config, path and logs",
|
||||||
|
"slug": "jetbrains",
|
||||||
|
"patterns": [
|
||||||
|
"fm:*/.config/JetBrains",
|
||||||
|
"fm:*/.cache/JetBrains",
|
||||||
|
"fm:*/.local/share/JetBrains"
|
||||||
|
],
|
||||||
|
"tags": ["type:dev", "editor:jetbrains", "os:linux"],
|
||||||
|
"author": "SAMAD101"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AWS artefacts",
|
||||||
|
"slug": "aws-artefacts",
|
||||||
|
"patterns": [
|
||||||
|
"fm:*/.aws"
|
||||||
|
],
|
||||||
|
"tags": ["type:dev", "cloud:aws", "os:linux", "os:darwin"],
|
||||||
|
"author": "SAMAD101"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Spotify cache and config files",
|
||||||
|
"slug": "spotify",
|
||||||
|
"patterns": [
|
||||||
|
"fm:*/.cache/spotify",
|
||||||
|
"fm:*/.config/spotify"
|
||||||
|
],
|
||||||
|
"tags": ["type:media", "media:spotify", "os:linux"],
|
||||||
|
"author": "SAMAD101"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Docker artefacts",
|
||||||
|
"slug": "docker-artefacts",
|
||||||
|
"patterns": [
|
||||||
|
"fm:*/.docker"
|
||||||
|
],
|
||||||
|
"tags": ["type:dev", "cloud:docker", "os:linux", "os:darwin"],
|
||||||
|
"author": "SAMAD101"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -459,6 +459,7 @@ class VortaScheduler(QtCore.QObject):
|
||||||
Pruning and checking after successful backup.
|
Pruning and checking after successful backup.
|
||||||
"""
|
"""
|
||||||
profile = BackupProfileModel.get(id=profile_id)
|
profile = BackupProfileModel.get(id=profile_id)
|
||||||
|
notifier = VortaNotifications.pick()
|
||||||
logger.info('Doing post-backup jobs for %s', profile.name)
|
logger.info('Doing post-backup jobs for %s', profile.name)
|
||||||
if profile.prune_on:
|
if profile.prune_on:
|
||||||
msg = BorgPruneJob.prepare(profile)
|
msg = BorgPruneJob.prepare(profile)
|
||||||
|
@ -489,6 +490,11 @@ class VortaScheduler(QtCore.QObject):
|
||||||
self.app.jobs_manager.add_job(job)
|
self.app.jobs_manager.add_job(job)
|
||||||
|
|
||||||
logger.info('Finished background task for profile %s', profile.name)
|
logger.info('Finished background task for profile %s', profile.name)
|
||||||
|
notifier.deliver(
|
||||||
|
self.tr('Vorta Backup'),
|
||||||
|
self.tr('Post Backup Tasks successful for %s' % profile.name),
|
||||||
|
level='info',
|
||||||
|
)
|
||||||
|
|
||||||
def remove_job(self, profile_id):
|
def remove_job(self, profile_id):
|
||||||
if profile_id in self.timers:
|
if profile_id in self.timers:
|
||||||
|
|
|
@ -15,7 +15,13 @@ from typing import Any, Callable, Iterable, List, Optional, Tuple, TypeVar
|
||||||
import psutil
|
import psutil
|
||||||
from PyQt6 import QtCore
|
from PyQt6 import QtCore
|
||||||
from PyQt6.QtCore import QFileInfo, QThread, pyqtSignal
|
from PyQt6.QtCore import QFileInfo, QThread, pyqtSignal
|
||||||
from PyQt6.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon
|
from PyQt6.QtWidgets import (
|
||||||
|
QAbstractItemView,
|
||||||
|
QApplication,
|
||||||
|
QFileDialog,
|
||||||
|
QSystemTrayIcon,
|
||||||
|
QTreeView,
|
||||||
|
)
|
||||||
|
|
||||||
from vorta.borg._compatibility import BorgCompatibility
|
from vorta.borg._compatibility import BorgCompatibility
|
||||||
from vorta.log import logger
|
from vorta.log import logger
|
||||||
|
@ -166,12 +172,17 @@ def get_dict_from_list(dataDict, mapList):
|
||||||
return reduce(lambda d, k: d.setdefault(k, {}), mapList, dataDict)
|
return reduce(lambda d, k: d.setdefault(k, {}), mapList, dataDict)
|
||||||
|
|
||||||
|
|
||||||
def choose_file_dialog(parent, title, want_folder=True):
|
def choose_file_dialog(parent, title, want_folder=True, file_filter=None, single_selection=False):
|
||||||
dialog = QFileDialog(parent, title, os.path.expanduser('~'))
|
dialog = QFileDialog(parent, title, os.path.expanduser('~'))
|
||||||
dialog.setFileMode(QFileDialog.FileMode.Directory if want_folder else QFileDialog.FileMode.ExistingFiles)
|
dialog.setFileMode(QFileDialog.FileMode.Directory if want_folder else QFileDialog.FileMode.ExistingFiles)
|
||||||
dialog.setParent(parent, QtCore.Qt.WindowType.Sheet)
|
dialog.setParent(parent, QtCore.Qt.WindowType.Sheet)
|
||||||
if want_folder:
|
if want_folder:
|
||||||
dialog.setOption(QFileDialog.Option.ShowDirsOnly)
|
dialog.setOption(QFileDialog.Option.ShowDirsOnly)
|
||||||
|
elif file_filter:
|
||||||
|
dialog.setNameFilter(file_filter)
|
||||||
|
if single_selection:
|
||||||
|
tree_view = dialog.findChild(QTreeView)
|
||||||
|
tree_view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||||
return dialog
|
return dialog
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ from vorta.i18n import get_locale
|
||||||
from vorta.scheduler import ScheduleStatusType
|
from vorta.scheduler import ScheduleStatusType
|
||||||
from vorta.store.models import BackupProfileMixin, EventLogModel, WifiSettingModel
|
from vorta.store.models import BackupProfileMixin, EventLogModel, WifiSettingModel
|
||||||
from vorta.utils import get_asset, get_sorted_wifis
|
from vorta.utils import get_asset, get_sorted_wifis
|
||||||
|
from vorta.views.script_edit_dialog import ScriptEditWindow
|
||||||
from vorta.views.utils import get_colored_icon
|
from vorta.views.utils import get_colored_icon
|
||||||
|
|
||||||
uifile = get_asset('UI/scheduletab.ui')
|
uifile = get_asset('UI/scheduletab.ui')
|
||||||
|
@ -81,6 +82,8 @@ class ScheduleTab(ScheduleBase, ScheduleUI, BackupProfileMixin):
|
||||||
self.scheduleIntervalCount.valueChanged.connect(self.on_scheduler_change)
|
self.scheduleIntervalCount.valueChanged.connect(self.on_scheduler_change)
|
||||||
self.scheduleIntervalUnit.currentIndexChanged.connect(self.on_scheduler_change)
|
self.scheduleIntervalUnit.currentIndexChanged.connect(self.on_scheduler_change)
|
||||||
self.scheduleFixedTime.timeChanged.connect(self.on_scheduler_change)
|
self.scheduleFixedTime.timeChanged.connect(self.on_scheduler_change)
|
||||||
|
self.preBackupScriptEditorButton.clicked.connect(lambda: self.launch_script_editor(context="pre"))
|
||||||
|
self.postBackupScriptEditorButton.clicked.connect(lambda: self.launch_script_editor(context="post"))
|
||||||
|
|
||||||
# Network and shell commands events
|
# Network and shell commands events
|
||||||
self.meteredNetworksCheckBox.stateChanged.connect(
|
self.meteredNetworksCheckBox.stateChanged.connect(
|
||||||
|
@ -135,6 +138,8 @@ class ScheduleTab(ScheduleBase, ScheduleUI, BackupProfileMixin):
|
||||||
self.toolBox.setItemIcon(1, get_colored_icon('wifi'))
|
self.toolBox.setItemIcon(1, get_colored_icon('wifi'))
|
||||||
self.toolBox.setItemIcon(2, get_colored_icon('tasks'))
|
self.toolBox.setItemIcon(2, get_colored_icon('tasks'))
|
||||||
self.toolBox.setItemIcon(3, get_colored_icon('terminal'))
|
self.toolBox.setItemIcon(3, get_colored_icon('terminal'))
|
||||||
|
self.preBackupScriptEditorButton.setIcon(get_colored_icon('edit'))
|
||||||
|
self.postBackupScriptEditorButton.setIcon(get_colored_icon('edit'))
|
||||||
|
|
||||||
def populate_from_profile(self):
|
def populate_from_profile(self):
|
||||||
"""Populate current view with data from selected profile."""
|
"""Populate current view with data from selected profile."""
|
||||||
|
@ -237,3 +242,8 @@ class ScheduleTab(ScheduleBase, ScheduleUI, BackupProfileMixin):
|
||||||
QTableWidgetItem(str(log_line.returncode)),
|
QTableWidgetItem(str(log_line.returncode)),
|
||||||
)
|
)
|
||||||
self.logTableWidget.setSortingEnabled(sorting) # restore sorting now that modifications are done
|
self.logTableWidget.setSortingEnabled(sorting) # restore sorting now that modifications are done
|
||||||
|
|
||||||
|
def launch_script_editor(self, context: str) -> None:
|
||||||
|
edit_window = ScriptEditWindow(context, profile=self.profile())
|
||||||
|
edit_window.exec()
|
||||||
|
self.populate_from_profile() # Refresh the view after the script has been edited.
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
from PyQt6 import QtCore, uic
|
||||||
|
from PyQt6.QtWidgets import QDialogButtonBox
|
||||||
|
|
||||||
|
from vorta.utils import get_asset
|
||||||
|
|
||||||
|
uifile = get_asset("UI/scriptedit.ui")
|
||||||
|
ScriptEditUI, ScriptEditBase = uic.loadUiType(uifile)
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptEditWindow(ScriptEditUI, ScriptEditBase):
|
||||||
|
def __init__(self, context: str, profile, parent=None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||||
|
|
||||||
|
self.context = context
|
||||||
|
if context == "pre":
|
||||||
|
self.setWindowTitle(self.tr("Edit Pre-Backup Script"))
|
||||||
|
self.label.setText(self.tr("Pre-Backup Script:"))
|
||||||
|
elif context == "post":
|
||||||
|
self.setWindowTitle(self.tr("Edit Post-Backup Script"))
|
||||||
|
self.label.setText(self.tr("Post-Backup Script:"))
|
||||||
|
|
||||||
|
self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setText(self.tr("Save"))
|
||||||
|
self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(self.tr("Cancel"))
|
||||||
|
|
||||||
|
self.profile = profile
|
||||||
|
|
||||||
|
self.buttonBox.button(QDialogButtonBox.StandardButton.Save).clicked.connect(self.save_script)
|
||||||
|
self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).clicked.connect(self.close)
|
||||||
|
|
||||||
|
# Populate data from profile
|
||||||
|
self.populate_from_profile()
|
||||||
|
|
||||||
|
def populate_from_profile(self):
|
||||||
|
"""Populate the script editor with the current profile's script."""
|
||||||
|
profile = self.profile
|
||||||
|
if self.context == "pre":
|
||||||
|
self.scriptEdit.setPlainText(profile.pre_backup_cmd)
|
||||||
|
elif self.context == "post":
|
||||||
|
self.scriptEdit.setPlainText(profile.post_backup_cmd)
|
||||||
|
|
||||||
|
def save_profile_attr(self, attr, new_value):
|
||||||
|
profile = self.profile
|
||||||
|
setattr(profile, attr, new_value)
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
def save_script(self):
|
||||||
|
script = self.scriptEdit.toPlainText()
|
||||||
|
profile = self.profile
|
||||||
|
if self.context == "pre":
|
||||||
|
self.save_profile_attr("pre_backup_cmd", script)
|
||||||
|
elif self.context == "post":
|
||||||
|
self.save_profile_attr("post_backup_cmd", script)
|
||||||
|
profile.save()
|
||||||
|
self.close()
|
Loading…
Reference in New Issue