diff --git a/CHANGELOG.md b/CHANGELOG.md index d71c4e5f8..ea9530424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ New features: * Added column 'Username' in 'Job Info' -> 'Hashes'. * Added column 'Username' in 'Hashes'. * Added dropdown menu to specify input format. +* Discord notifications. General improvements: * Improved/reimplemeted generator and scheduling logic. Better precision, more predictable workunit sizes. Default desired seconds per workunit changed to 600 seconds. diff --git a/backend/src/app.py b/backend/src/app.py index 0749f96ce..beed35bf2 100755 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -38,6 +38,8 @@ from src.api.fitcrack.endpoints.pcfg.pcfg import ns as pcfg_ns from src.api.fitcrack.endpoints.settings.settings import ns as settings_ns +from src.api.fitcrack.endpoints.notifications.notifier import notify + from src.database import db from src.database.models import FcUser @@ -122,17 +124,14 @@ def main(): db.init_app(app) -def my_job(): +def notifier(): with app.app_context(): - # Read from User table - users = FcUser.query.all() - print("List of users:") - for user in users: - print(user.username) + for user in FcUser.query.all(): + notify(user.id) scheduler = APScheduler() scheduler.init_app(app) -# scheduler.add_job(id='my_job_id', func=my_job, trigger='interval', seconds=5) +scheduler.add_job(id='notifier', func=notifier, trigger='interval', seconds=10) scheduler.start() if __name__ == "__main__": diff --git a/backend/src/requirements.txt b/backend/src/requirements.txt index 9fbafbce7..b5bea090d 100644 --- a/backend/src/requirements.txt +++ b/backend/src/requirements.txt @@ -13,3 +13,4 @@ msgpack==1.0.4 PyJWT==2.7.0 werkzeug==2.3.7 SQLAlchemy==2.0.20 +apprise==1.5.0 diff --git a/backend/src/src/api/fitcrack/endpoints/notifications/notifications.py b/backend/src/src/api/fitcrack/endpoints/notifications/notifications.py index 91609b465..467203ad1 100644 --- a/backend/src/src/api/fitcrack/endpoints/notifications/notifications.py +++ b/backend/src/src/api/fitcrack/endpoints/notifications/notifications.py @@ -32,10 +32,9 @@ def get(self): args = notifications_parser.parse_args(request) page = args.get('page', 1) per_page = args.get('per_page', 10) - markAsSeen = args.get('seen', True) + mark_as_seen = args.get('seen', True) - notifications = getNotifications(current_user.id, page, per_page, markAsSeen) - # notifications = getNotifications(7, page, per_page, markAsSeen) + notifications = getNotifications(current_user.id, page, per_page, mark_as_seen) return notifications @ns.route('/count') @@ -50,6 +49,4 @@ def get(self): count = FcNotification.query.filter(FcNotification.user_id == current_user.id).filter( FcNotification.seen == False).count() - # count = FcNotification.query.filter(FcNotification.user_id == 7).filter( - # FcNotification.seen == False).count() return {'count': count} diff --git a/backend/src/src/api/fitcrack/endpoints/notifications/notifier.py b/backend/src/src/api/fitcrack/endpoints/notifications/notifier.py new file mode 100644 index 000000000..654a0c826 --- /dev/null +++ b/backend/src/src/api/fitcrack/endpoints/notifications/notifier.py @@ -0,0 +1,31 @@ +import apprise + +from src.database.models import FcNotification, FcSettings, FcUser +from src.database import db + +from src.api.fitcrack.lang import job_status_text_info_to_code_dict + +class Notification: + def __init__(self, title, body): + self.title = title + self.body = body + +def notify(userId): + settings = FcSettings.query.filter_by(user_id=userId).one() + apobj = apprise.Apprise() + + if settings.discord_notifications: + apobj.add(settings.discord_webhook_url) + + for user in FcUser.query.all(): + new_notifications = FcNotification.query.filter(FcNotification.user_id == userId).filter(FcNotification.discord_sent == False) + + for notif in new_notifications: + title = notif.source.name if notif.source else '' + body = title + ": " + job_status_text_info_to_code_dict[notif.new_value] + print(title, body) + + apobj.notify(body=body) + notif.discord_sent = True + + db.session.commit() \ No newline at end of file diff --git a/backend/src/src/api/fitcrack/endpoints/settings/argumentsParser.py b/backend/src/src/api/fitcrack/endpoints/settings/argumentsParser.py index 9d6f15c79..706708f1e 100644 --- a/backend/src/src/api/fitcrack/endpoints/settings/argumentsParser.py +++ b/backend/src/src/api/fitcrack/endpoints/settings/argumentsParser.py @@ -13,4 +13,7 @@ settings_arguments.add_argument('verify_hash_format', type=bool, help='', required=False, location='json') settings_arguments.add_argument('auto_add_hosts_to_running_jobs', type=bool, help='', required=False, location='json') settings_arguments.add_argument('bench_runtime_limit', type=int, help='', required=False, location='json') -settings_arguments.add_argument('workunit_status_update', type=int, help='', required=False, location='json') \ No newline at end of file +settings_arguments.add_argument('workunit_status_update', type=int, help='', required=False, location='json') + +settings_arguments.add_argument('discord_notifications', type=bool, help='', required=False, location='json') +settings_arguments.add_argument('discord_webhook_url', type=str, help='', required=False, location='json') \ No newline at end of file diff --git a/backend/src/src/api/fitcrack/endpoints/settings/responseModels.py b/backend/src/src/api/fitcrack/endpoints/settings/responseModels.py index eb9dd90a0..451725397 100644 --- a/backend/src/src/api/fitcrack/endpoints/settings/responseModels.py +++ b/backend/src/src/api/fitcrack/endpoints/settings/responseModels.py @@ -16,4 +16,6 @@ 'auto_add_hosts_to_running_jobs': fields.Boolean(), 'bench_runtime_limit': fields.Integer(), 'workunit_status_update': fields.Integer(), + 'discord_notifications': fields.Boolean(), + 'discord_webhook_url': fields.String(), }) \ No newline at end of file diff --git a/backend/src/src/api/fitcrack/endpoints/settings/settings.py b/backend/src/src/api/fitcrack/endpoints/settings/settings.py index 64c01491d..5d042d8a3 100644 --- a/backend/src/src/api/fitcrack/endpoints/settings/settings.py +++ b/backend/src/src/api/fitcrack/endpoints/settings/settings.py @@ -15,7 +15,7 @@ from src.api.fitcrack.endpoints.settings.responseModels import settings_model from src.api.fitcrack.responseModels import simpleResponse from src.database import db -from src.database.models import FcSettings +from src.database.models import FcSettings, FcNotification log = logging.getLogger(__name__) ns = api.namespace('settings', description='Endpoints for manipulating system settings.') @@ -47,6 +47,9 @@ def post(self): bench_runtime_limit = args['bench_runtime_limit'] workunit_status_update = args['workunit_status_update'] + discord_notifications = args['discord_notifications'] + discord_webhook_url = args['discord_webhook_url'] + settings = FcSettings.query.filter_by(user_id=current_user.id).one_or_none() if not settings: abort(404, 'User settings not found.') @@ -58,6 +61,17 @@ def post(self): if (auto_add_hosts_to_running_jobs is not None): settings.auto_add_hosts_to_running_jobs = auto_add_hosts_to_running_jobs if (bench_runtime_limit is not None): settings.bench_runtime_limit = bench_runtime_limit if (workunit_status_update is not None): settings.workunit_status_update = workunit_status_update + + if discord_notifications: + if not discord_webhook_url: + abort(400, 'Discord webhook URL is required when enabling Discord notifications.') + settings.discord_notifications = discord_notifications + + FcNotification.query.filter(FcNotification.user_id == current_user.id).update({FcNotification.discord_sent: True}) + + if discord_webhook_url: + settings.discord_webhook_url = discord_webhook_url + db.session.commit() return { diff --git a/backend/src/src/database/models.py b/backend/src/src/database/models.py index 2647fe515..7aca6d5f0 100644 --- a/backend/src/src/database/models.py +++ b/backend/src/src/database/models.py @@ -542,6 +542,9 @@ class FcSettings(Base): bench_runtime_limit = Column(Integer, nullable=False, server_default=text("'30'")) workunit_status_update = Column(Integer, nullable=False, server_default=text("'5'")) + discord_notifications = Column(Integer, nullable=False, server_default=text("'0'")) + discord_webhook_url = Column(String(200), nullable=True) + user = relationship('FcUser') class FcJobGraph(Base): @@ -763,6 +766,7 @@ class FcNotification(Base): old_value = Column(SmallInteger) new_value = Column(SmallInteger) seen = Column(Integer, server_default=text("'0'")) + discord_sent = Column(Integer, server_default=text("'0'")) time = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP")) source = relationship('FcJob') diff --git a/frontend/src/components/settings/settingsView.vue b/frontend/src/components/settings/settingsView.vue index 8beec4fda..9d36a3c38 100644 --- a/frontend/src/components/settings/settingsView.vue +++ b/frontend/src/components/settings/settingsView.vue @@ -58,12 +58,12 @@ - - mdi-cursor-pointer - - Behavior - - + + mdi-cursor-pointer + + Behavior + + + + + + mdi-alert-circle-outline + + Notifications + + + + + diff --git a/server/sql/10_create_tables.sql b/server/sql/10_create_tables.sql index 4d8314cc8..97a35bcbb 100644 --- a/server/sql/10_create_tables.sql +++ b/server/sql/10_create_tables.sql @@ -228,6 +228,7 @@ CREATE TABLE IF NOT EXISTS `fc_notification` ( `old_value` smallint(6) DEFAULT NULL, `new_value` smallint(6) DEFAULT NULL, `seen` tinyint(1) DEFAULT '0', + `discord_sent` tinyint(1) DEFAULT '0', `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `user_id` (`user_id`), @@ -493,6 +494,9 @@ CREATE TABLE IF NOT EXISTS `fc_settings` ( `auto_add_hosts_to_running_jobs` tinyint(1) unsigned NOT NULL DEFAULT '0', `bench_runtime_limit` int(10) unsigned NOT NULL DEFAULT '30', `workunit_status_update` int(10) unsigned NOT NULL DEFAULT '5', + + `discord_notifications` tinyint(1) unsigned NOT NULL DEFAULT '0', + `discord_webhook_url` varchar(200) NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/server/sql/20_create_triggers.sql b/server/sql/20_create_triggers.sql index 4cc4c97d0..17b6d49b4 100644 --- a/server/sql/20_create_triggers.sql +++ b/server/sql/20_create_triggers.sql @@ -39,7 +39,7 @@ CREATE TRIGGER `job_notification` AFTER UPDATE ON `fc_job` IF done THEN LEAVE user_loop; END IF; - INSERT INTO fc_notification VALUES (DEFAULT, userID, DEFAULT,NEW.id,OLD.status,NEW.status,DEFAULT, DEFAULT); + INSERT INTO fc_notification VALUES (DEFAULT, userID, DEFAULT,NEW.id,OLD.status,NEW.status, DEFAULT, DEFAULT, DEFAULT); END LOOP; CLOSE usersCursor; END IF;