Skip to content

Commit c56e826

Browse files
ui fix
1 parent 3caa741 commit c56e826

7 files changed

Lines changed: 330 additions & 8 deletions

File tree

app.py

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,44 @@ def add_repository():
202202
schedule_type = request.form['schedule_type']
203203
retention_count = int(request.form['retention_count'])
204204

205+
# Handle custom schedule fields
206+
custom_interval = None
207+
custom_unit = None
208+
custom_hour = 2
209+
custom_minute = 0
210+
211+
if schedule_type == 'custom':
212+
custom_interval = int(request.form.get('custom_interval', 1))
213+
custom_unit = request.form.get('custom_unit', 'days')
214+
custom_time = request.form.get('custom_time', '02:00')
215+
216+
# Validate custom schedule parameters
217+
if custom_unit == 'days' and (custom_interval < 1 or custom_interval > 365):
218+
flash('Custom interval for days must be between 1 and 365', 'error')
219+
return render_template('add_repository.html')
220+
elif custom_unit == 'weeks' and (custom_interval < 1 or custom_interval > 52):
221+
flash('Custom interval for weeks must be between 1 and 52', 'error')
222+
return render_template('add_repository.html')
223+
elif custom_unit == 'months' and (custom_interval < 1 or custom_interval > 12):
224+
flash('Custom interval for months must be between 1 and 12', 'error')
225+
return render_template('add_repository.html')
226+
227+
try:
228+
time_parts = custom_time.split(':')
229+
custom_hour = int(time_parts[0])
230+
custom_minute = int(time_parts[1])
231+
232+
if custom_hour < 0 or custom_hour > 23:
233+
flash('Hour must be between 0 and 23', 'error')
234+
return render_template('add_repository.html')
235+
if custom_minute < 0 or custom_minute > 59:
236+
flash('Minute must be between 0 and 59', 'error')
237+
return render_template('add_repository.html')
238+
239+
except (IndexError, ValueError):
240+
flash('Invalid time format. Please use HH:MM format', 'error')
241+
return render_template('add_repository.html')
242+
205243
# Extract repo name from URL
206244
repo_name = repo_url.split('/')[-1].replace('.git', '')
207245

@@ -213,6 +251,10 @@ def add_repository():
213251
backup_format=backup_format,
214252
schedule_type=schedule_type,
215253
retention_count=retention_count,
254+
custom_interval=custom_interval,
255+
custom_unit=custom_unit,
256+
custom_hour=custom_hour,
257+
custom_minute=custom_minute,
216258
is_active=True
217259
)
218260

@@ -239,10 +281,56 @@ def edit_repository(repo_id):
239281
repository.retention_count = int(request.form['retention_count'])
240282
repository.is_active = 'is_active' in request.form
241283

284+
# Handle custom schedule fields
285+
if repository.schedule_type == 'custom':
286+
custom_interval = int(request.form.get('custom_interval', 1))
287+
custom_unit = request.form.get('custom_unit', 'days')
288+
custom_time = request.form.get('custom_time', '02:00')
289+
290+
# Validate custom schedule parameters
291+
if custom_unit == 'days' and (custom_interval < 1 or custom_interval > 365):
292+
flash('Custom interval for days must be between 1 and 365', 'error')
293+
return render_template('edit_repository.html', repository=repository)
294+
elif custom_unit == 'weeks' and (custom_interval < 1 or custom_interval > 52):
295+
flash('Custom interval for weeks must be between 1 and 52', 'error')
296+
return render_template('edit_repository.html', repository=repository)
297+
elif custom_unit == 'months' and (custom_interval < 1 or custom_interval > 12):
298+
flash('Custom interval for months must be between 1 and 12', 'error')
299+
return render_template('edit_repository.html', repository=repository)
300+
301+
repository.custom_interval = custom_interval
302+
repository.custom_unit = custom_unit
303+
304+
try:
305+
time_parts = custom_time.split(':')
306+
repository.custom_hour = int(time_parts[0])
307+
repository.custom_minute = int(time_parts[1])
308+
309+
if repository.custom_hour < 0 or repository.custom_hour > 23:
310+
flash('Hour must be between 0 and 23', 'error')
311+
return render_template('edit_repository.html', repository=repository)
312+
if repository.custom_minute < 0 or repository.custom_minute > 59:
313+
flash('Minute must be between 0 and 59', 'error')
314+
return render_template('edit_repository.html', repository=repository)
315+
316+
except (IndexError, ValueError):
317+
flash('Invalid time format. Please use HH:MM format', 'error')
318+
return render_template('edit_repository.html', repository=repository)
319+
else:
320+
# Reset custom fields when not using custom schedule
321+
repository.custom_interval = None
322+
repository.custom_unit = None
323+
repository.custom_hour = 2
324+
repository.custom_minute = 0
325+
242326
db.session.commit()
243327

244328
# Reschedule the backup job
245-
scheduler.remove_job(f'backup_{repo_id}', jobstore=None)
329+
try:
330+
scheduler.remove_job(f'backup_{repo_id}', jobstore=None)
331+
except:
332+
pass
333+
246334
if repository.is_active:
247335
schedule_backup_job(repository)
248336

@@ -315,6 +403,61 @@ def schedule_backup_job(repository):
315403
trigger = CronTrigger(day_of_week=0, hour=2, minute=0) # Sunday 2 AM
316404
elif repository.schedule_type == 'monthly':
317405
trigger = CronTrigger(day=1, hour=2, minute=0) # 1st of month 2 AM
406+
elif repository.schedule_type == 'custom':
407+
# Handle custom schedule
408+
hour = repository.custom_hour or 2
409+
minute = repository.custom_minute or 0
410+
interval = repository.custom_interval or 1
411+
unit = repository.custom_unit or 'days'
412+
413+
if unit == 'days':
414+
# For daily intervals, use interval_trigger if more than 1 day
415+
if interval == 1:
416+
trigger = CronTrigger(hour=hour, minute=minute) # Daily
417+
else:
418+
# Use interval trigger for multi-day schedules
419+
from apscheduler.triggers.interval import IntervalTrigger
420+
from datetime import datetime, time
421+
# Calculate next run time at the specified hour/minute
422+
now = datetime.now()
423+
start_date = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
424+
if start_date <= now:
425+
start_date = start_date.replace(day=start_date.day + 1)
426+
trigger = IntervalTrigger(days=interval, start_date=start_date)
427+
elif unit == 'weeks':
428+
# For weekly intervals
429+
if interval == 1:
430+
trigger = CronTrigger(day_of_week=0, hour=hour, minute=minute) # Every Sunday
431+
else:
432+
from apscheduler.triggers.interval import IntervalTrigger
433+
from datetime import datetime
434+
now = datetime.now()
435+
start_date = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
436+
# Find next Sunday
437+
days_until_sunday = (6 - now.weekday()) % 7
438+
if days_until_sunday == 0 and start_date <= now:
439+
days_until_sunday = 7
440+
start_date = start_date.replace(day=start_date.day + days_until_sunday)
441+
trigger = IntervalTrigger(weeks=interval, start_date=start_date)
442+
elif unit == 'months':
443+
# For monthly intervals
444+
if interval == 1:
445+
trigger = CronTrigger(day=1, hour=hour, minute=minute) # 1st of every month
446+
else:
447+
from apscheduler.triggers.interval import IntervalTrigger
448+
from datetime import datetime
449+
now = datetime.now()
450+
start_date = now.replace(day=1, hour=hour, minute=minute, second=0, microsecond=0)
451+
if start_date <= now:
452+
# Move to next month
453+
if start_date.month == 12:
454+
start_date = start_date.replace(year=start_date.year + 1, month=1)
455+
else:
456+
start_date = start_date.replace(month=start_date.month + 1)
457+
# Note: Using weeks approximation for months since APScheduler doesn't have months interval
458+
trigger = IntervalTrigger(weeks=interval*4, start_date=start_date)
459+
else:
460+
return # Invalid unit
318461
else:
319462
return # Manual only
320463

init_db.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
from app import app
33
from models import db
4+
from migrate_db import migrate_database
45

56
def ensure_sqlite_path():
67
uri = app.config.get('SQLALCHEMY_DATABASE_URI')
@@ -18,8 +19,13 @@ def ensure_sqlite_path():
1819
db_file = ensure_sqlite_path()
1920
with app.app_context():
2021
try:
22+
# Create tables if they don't exist
2123
db.create_all()
2224
print("Database initialized successfully!")
25+
26+
# Run migration to add new columns if needed
27+
migrate_database()
28+
2329
if db_file and os.path.exists(db_file):
2430
st = os.stat(db_file)
2531
print(f"Database file created: {db_file} (size {st.st_size} bytes, perms {oct(st.st_mode)[-3:]})")

models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@ class Repository(db.Model):
2121
url = db.Column(db.String(200), nullable=False)
2222
github_token = db.Column(db.String(200)) # For private repos
2323
backup_format = db.Column(db.String(20), default='folder') # folder, zip, tar.gz
24-
schedule_type = db.Column(db.String(20), default='daily') # manual, hourly, daily, weekly, monthly
24+
schedule_type = db.Column(db.String(20), default='daily') # manual, hourly, daily, weekly, monthly, custom
2525
retention_count = db.Column(db.Integer, default=5) # Number of backups to keep
26+
# Custom schedule fields
27+
custom_interval = db.Column(db.Integer) # For custom schedule: interval value (e.g., 3 for "every 3 days")
28+
custom_unit = db.Column(db.String(10)) # For custom schedule: unit (days, weeks, months)
29+
custom_hour = db.Column(db.Integer, default=2) # Hour to run backup (0-23)
30+
custom_minute = db.Column(db.Integer, default=0) # Minute to run backup (0-59)
2631
is_active = db.Column(db.Boolean, default=True)
2732
last_backup = db.Column(db.DateTime)
2833
created_at = db.Column(db.DateTime, default=datetime.utcnow)

templates/add_repository.html

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,49 @@ <h4 class="mb-0">
4747

4848
<div class="col-md-6 mb-3">
4949
<label for="schedule_type" class="form-label">Backup Schedule <span class="text-danger">*</span></label>
50-
<select class="form-select" id="schedule_type" name="schedule_type" required>
50+
<select class="form-select" id="schedule_type" name="schedule_type" required onchange="toggleCustomSchedule()">
5151
<option value="manual">Manual Only</option>
5252
<option value="hourly">Every Hour</option>
5353
<option value="daily" selected>Daily (2 AM)</option>
5454
<option value="weekly">Weekly (Sunday 2 AM)</option>
5555
<option value="monthly">Monthly (1st, 2 AM)</option>
56+
<option value="custom">Custom Schedule</option>
5657
</select>
5758
<div class="form-text">
5859
When to automatically backup this repository
5960
</div>
6061
</div>
6162
</div>
6263

63-
<div class="mb-3">
64+
<!-- Custom Schedule Options -->
65+
<div id="custom-schedule-options" class="row" style="display: none;">
66+
<div class="col-md-4 mb-3">
67+
<label for="custom_interval" class="form-label">Every</label>
68+
<input type="number" class="form-control" id="custom_interval" name="custom_interval"
69+
min="1" max="365" value="1">
70+
<div class="form-text">
71+
<span id="interval-help">Enter a number between 1-365</span>
72+
</div>
73+
</div>
74+
<div class="col-md-4 mb-3">
75+
<label for="custom_unit" class="form-label">Unit</label>
76+
<select class="form-select" id="custom_unit" name="custom_unit" onchange="updateIntervalLimits()">
77+
<option value="days">Days</option>
78+
<option value="weeks">Weeks</option>
79+
<option value="months">Months</option>
80+
</select>
81+
</div>
82+
<div class="col-md-4 mb-3">
83+
<label for="custom_time" class="form-label">At Time</label>
84+
<input type="time" class="form-control" id="custom_time" name="custom_time" value="02:00">
85+
<div class="form-text">
86+
Time to run the backup (24-hour format)
87+
</div>
88+
</div>
89+
</div>
90+
91+
<div class="row">
92+
<div class="col-12 mb-3">
6493
<label for="retention_count" class="form-label">Retention Count <span class="text-danger">*</span></label>
6594
<input type="number" class="form-control" id="retention_count" name="retention_count"
6695
value="5" min="1" max="50" required>
@@ -97,6 +126,49 @@ <h4 class="mb-0">
97126

98127
{% block scripts %}
99128
<script>
129+
function toggleCustomSchedule() {
130+
const scheduleType = document.getElementById('schedule_type').value;
131+
const customOptions = document.getElementById('custom-schedule-options');
132+
133+
if (scheduleType === 'custom') {
134+
customOptions.style.display = 'block';
135+
// Make custom fields required when custom is selected
136+
document.getElementById('custom_interval').required = true;
137+
document.getElementById('custom_unit').required = true;
138+
document.getElementById('custom_time').required = true;
139+
updateIntervalLimits();
140+
} else {
141+
customOptions.style.display = 'none';
142+
// Remove required attribute when custom is not selected
143+
document.getElementById('custom_interval').required = false;
144+
document.getElementById('custom_unit').required = false;
145+
document.getElementById('custom_time').required = false;
146+
}
147+
}
148+
149+
function updateIntervalLimits() {
150+
const unit = document.getElementById('custom_unit').value;
151+
const intervalInput = document.getElementById('custom_interval');
152+
const helpText = document.getElementById('interval-help');
153+
154+
if (unit === 'days') {
155+
intervalInput.max = 365;
156+
helpText.textContent = 'Enter a number between 1-365';
157+
} else if (unit === 'weeks') {
158+
intervalInput.max = 52;
159+
helpText.textContent = 'Enter a number between 1-52';
160+
if (parseInt(intervalInput.value) > 52) {
161+
intervalInput.value = 52;
162+
}
163+
} else if (unit === 'months') {
164+
intervalInput.max = 12;
165+
helpText.textContent = 'Enter a number between 1-12';
166+
if (parseInt(intervalInput.value) > 12) {
167+
intervalInput.value = 12;
168+
}
169+
}
170+
}
171+
100172
document.getElementById('repo_url').addEventListener('blur', function() {
101173
const url = this.value;
102174
if (url && url.includes('github.com/')) {
@@ -108,5 +180,10 @@ <h4 class="mb-0">
108180
}
109181
}
110182
});
183+
184+
// Initialize custom schedule toggle on page load
185+
document.addEventListener('DOMContentLoaded', function() {
186+
toggleCustomSchedule();
187+
});
111188
</script>
112189
{% endblock %}

templates/dashboard.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ <h5 class="mb-0">
9090
<strong>{{ repo.name }}</strong>
9191
<br>
9292
<small class="text-muted">
93-
{{ repo.schedule_type.title() }} backup
93+
{% if repo.schedule_type == 'custom' %}
94+
Every {{ repo.custom_interval }} {{ repo.custom_unit }} backup
95+
{% else %}
96+
{{ repo.schedule_type.title() }} backup
97+
{% endif %}
9498
{% if repo.last_backup %}
9599
- Last: {{ repo.last_backup.strftime('%Y-%m-%d %H:%M') }}
96100
{% endif %}

0 commit comments

Comments
 (0)