Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
__init__.py619 linesDownload Raw Back to pgagent
1##########################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##########################################################################9 10"""Implements the pgAgent Jobs Node"""11from functools import wraps12import json13from datetime import datetime, time14 15from flask import render_template, request, jsonify16from flask_babel import gettext as _17 18from config import PG_DEFAULT_DRIVER19 20from pgadmin.browser.collection import CollectionNodeModule21from pgadmin.browser.utils import PGChildNodeView22from pgadmin.browser.server_groups import servers23from pgadmin.utils.ajax import make_json_response, internal_server_error, \24    make_response as ajax_response, gone, success_return25from pgadmin.utils.driver import get_driver26from pgadmin.utils.preferences import Preferences27from pgadmin.browser.server_groups.servers.pgagent.utils \28    import format_schedule_data, format_step_data29 30 31class JobModule(CollectionNodeModule):32    _NODE_TYPE = 'pga_job'33    _COLLECTION_LABEL = _("pgAgent Jobs")34 35    def get_nodes(self, gid, sid):36        """37        Generate the collection node38        """39        if self.show_node:40            yield self.generate_browser_collection_node(sid)41 42    @property43    def script_load(self):44        """45        Load the module script for server, when any of the server-group node is46        initialized.47        """48        return servers.ServerModule.node_type49 50    def backend_supported(self, manager, **kwargs):51        if hasattr(self, 'show_node') and not self.show_node:52            return False53 54        conn = manager.connection()55 56        status, res = conn.execute_scalar("""57SELECT58    has_table_privilege(59      'pgagent.pga_job', 'INSERT, SELECT, UPDATE'60    ) has_priviledge61WHERE EXISTS(62    SELECT has_schema_privilege('pgagent', 'USAGE')63    WHERE EXISTS(64        SELECT cl.oid FROM pg_catalog.pg_class cl65        LEFT JOIN pg_catalog.pg_namespace ns ON ns.oid=relnamespace66        WHERE relname='pga_job' AND nspname='pgagent'67    )68)69""")70        if status and res:71            status, res = conn.execute_dict("""72SELECT EXISTS(73        SELECT 1 FROM information_schema.columns74        WHERE75            table_schema='pgagent' AND table_name='pga_jobstep' AND76            column_name='jstconnstr'77    ) has_connstr""")78 79            manager.db_info['pgAgent'] = res['rows'][0]80            return True81        return False82 83    @property84    def csssnippets(self):85        """86        Returns a snippet of css to include in the page87        """88        snippets = [89            render_template(90                self._COLLECTION_CSS,91                node_type=self.node_type,92                _=_93            ),94            render_template(95                "pga_job/css/pga_job.css",96                node_type=self.node_type,97                _=_98            )99        ]100 101        for submodule in self.submodules:102            snippets.extend(submodule.csssnippets)103 104        return snippets105 106    @property107    def module_use_template_javascript(self):108        """109        Returns whether Jinja2 template is used for generating the javascript110        module.111        """112        return False113 114    def register(self, app, options):115        """116        Override the default register function to automagically register117        sub-modules at once.118        """119        from .schedules import blueprint as module120        self.submodules.append(module)121 122        from .steps import blueprint as module123        self.submodules.append(module)124 125        super().register(app, options)126 127 128blueprint = JobModule(__name__)129 130 131class JobView(PGChildNodeView):132    node_type = blueprint.node_type133 134    parent_ids = [135        {'type': 'int', 'id': 'gid'},136        {'type': 'int', 'id': 'sid'}137    ]138    ids = [139        {'type': 'int', 'id': 'jid'}140    ]141 142    operations = dict({143        'obj': [144            {'get': 'properties', 'delete': 'delete', 'put': 'update'},145            {'get': 'properties', 'post': 'create', 'delete': 'delete'}146        ],147        'nodes': [{'get': 'nodes'}, {'get': 'nodes'}],148        'sql': [{'get': 'sql'}],149        'msql': [{'get': 'msql'}, {'get': 'msql'}],150        'run_now': [{'put': 'run_now'}],151        'classes': [{}, {'get': 'job_classes'}],152        'children': [{'get': 'children'}],153        'stats': [{'get': 'statistics'}]154    })155 156    def check_precondition(f):157        """158        This function will behave as a decorator which will checks159        database connection before running view, it will also attaches160        manager,conn & template_path properties to self161        """162 163        @wraps(f)164        def wrap(self, *args, **kwargs):165 166            self.manager = get_driver(167                PG_DEFAULT_DRIVER168            ).connection_manager(169                kwargs['sid']170            )171            self.conn = self.manager.connection()172 173            # Set the template path for the sql scripts.174            self.template_path = 'pga_job/sql/pre3.4'175 176            if 'pgAgent'not in self.manager.db_info:177                _, res = self.conn.execute_dict("""178SELECT EXISTS(179        SELECT 1 FROM information_schema.columns180        WHERE181            table_schema='pgagent' AND table_name='pga_jobstep' AND182            column_name='jstconnstr'183    ) has_connstr""")184 185                self.manager.db_info['pgAgent'] = res['rows'][0]186 187            return f(self, *args, **kwargs)188        return wrap189 190    @check_precondition191    def nodes(self, gid, sid, jid=None):192        SQL = render_template(193            "/".join([self.template_path, self._NODES_SQL]),194            jid=jid, conn=self.conn195        )196        status, rset = self.conn.execute_dict(SQL)197 198        if not status:199            return internal_server_error(errormsg=rset)200 201        if jid is not None:202            if len(rset['rows']) != 1:203                return gone(204                    errormsg=_("Could not find the pgAgent job on the server.")205                )206            return make_json_response(207                data=self.blueprint.generate_browser_node(208                    rset['rows'][0]['jobid'],209                    sid,210                    rset['rows'][0]['jobname'],211                    "icon-pga_job" if rset['rows'][0]['jobenabled'] else212                    "icon-pga_job-disabled",213                    description=rset['rows'][0]['jobdesc']214                ),215                status=200216            )217 218        res = []219        for row in rset['rows']:220            res.append(221                self.blueprint.generate_browser_node(222                    row['jobid'],223                    sid,224                    row['jobname'],225                    "icon-pga_job" if row['jobenabled'] else226                    "icon-pga_job-disabled",227                    description=row['jobdesc']228                )229            )230 231        return make_json_response(232            data=res,233            status=200234        )235 236    @check_precondition237    def properties(self, gid, sid, jid=None):238        SQL = render_template(239            "/".join([self.template_path, self._PROPERTIES_SQL]),240            jid=jid, conn=self.conn241        )242        status, rset = self.conn.execute_dict(SQL)243 244        if not status:245            return internal_server_error(errormsg=rset)246 247        if jid is not None:248            if len(rset['rows']) != 1:249                return gone(250                    errormsg=_(251                        "Could not find the pgAgent job on the server."252                    )253                )254            res = rset['rows'][0]255            status, rset = self.conn.execute_dict(256                render_template(257                    "/".join([self.template_path, 'steps.sql']),258                    jid=jid, conn=self.conn,259                    has_connstr=self.manager.db_info['pgAgent']['has_connstr']260                )261            )262            if not status:263                return internal_server_error(errormsg=rset)264            res['jsteps'] = rset['rows']265            status, rset = self.conn.execute_dict(266                render_template(267                    "/".join([self.template_path, 'schedules.sql']),268                    jid=jid, conn=self.conn269                )270            )271            if not status:272                return internal_server_error(errormsg=rset)273 274            # Create jscexceptions in the correct format that React control275            # required.276            for schedule in rset['rows']:277                if 'jexid' in schedule and schedule['jexid'] is not None \278                        and len(schedule['jexid']) > 0:279                    schedule['jscexceptions'] = []280                    index = 0281                    for exid in schedule['jexid']:282                        schedule['jscexceptions'].append(283                            {'jexid': exid,284                             'jexdate': schedule['jexdate'][index],285                             'jextime': schedule['jextime'][index]286                             }287                        )288 289                        index += 1290 291            res['jschedules'] = rset['rows']292        else:293            res = rset['rows']294 295        return ajax_response(296            response=res,297            status=200298        )299 300    @check_precondition301    def create(self, gid, sid):302        """Create the pgAgent job."""303        required_args = [304            'jobname'305        ]306 307        data = request.form if request.form else json.loads(308            request.data.decode('utf-8')309        )310 311        for arg in required_args:312            if arg not in data:313                return make_json_response(314                    status=410,315                    success=0,316                    errormsg=_(317                        "Could not find the required parameter ({})."318                    ).format(arg)319                )320 321        status, res = self.conn.execute_void('BEGIN')322        if not status:323            return internal_server_error(errormsg=res)324 325        status, res = self.conn.execute_scalar(326            render_template(327                "/".join([self.template_path, self._CREATE_SQL]),328                data=data, conn=self.conn, fetch_id=True,329                has_connstr=self.manager.db_info['pgAgent']['has_connstr']330            )331        )332 333        if not status:334            self.conn.execute_void('END')335            return internal_server_error(errormsg=res)336 337        # We need oid of newly created database338        status, res = self.conn.execute_dict(339            render_template(340                "/".join([self.template_path, self._NODES_SQL]),341                jid=res, conn=self.conn342            )343        )344 345        self.conn.execute_void('END')346        if not status:347            return internal_server_error(errormsg=res)348 349        row = res['rows'][0]350 351        return jsonify(352            node=self.blueprint.generate_browser_node(353                row['jobid'],354                sid,355                row['jobname'],356                icon="icon-pga_job" if row['jobenabled']357                else "icon-pga_job-disabled"358            )359        )360 361    @check_precondition362    def update(self, gid, sid, jid):363        """Update the pgAgent Job."""364 365        data = request.form if request.form else json.loads(366            request.data.decode('utf-8')367        )368 369        # Format the schedule and step data370        self.format_schedule_step_data(data)371 372        status, res = self.conn.execute_void(373            render_template(374                "/".join([self.template_path, self._UPDATE_SQL]),375                data=data, conn=self.conn, jid=jid,376                has_connstr=self.manager.db_info['pgAgent']['has_connstr']377            )378        )379 380        if not status:381            return internal_server_error(errormsg=res)382 383        # We need oid of newly created database384        status, res = self.conn.execute_dict(385            render_template(386                "/".join([self.template_path, self._NODES_SQL]),387                jid=jid, conn=self.conn388            )389        )390 391        if not status:392            return internal_server_error(errormsg=res)393 394        row = res['rows'][0]395 396        return jsonify(397            node=self.blueprint.generate_browser_node(398                jid,399                sid,400                row['jobname'],401                icon="icon-pga_job" if row['jobenabled']402                else "icon-pga_job-disabled",403                description=row['jobdesc']404            )405        )406 407    @check_precondition408    def delete(self, gid, sid, jid=None):409        """Delete the pgAgent Job."""410 411        if jid is None:412            data = request.form if request.form else json.loads(413                request.data414            )415        else:416            data = {'ids': [jid]}417 418        for jid in data['ids']:419            status, res = self.conn.execute_void(420                render_template(421                    "/".join([self.template_path, self._DELETE_SQL]),422                    jid=jid, conn=self.conn423                )424            )425            if not status:426                return internal_server_error(errormsg=res)427 428        return make_json_response(success=1)429 430    @check_precondition431    def msql(self, gid, sid, jid=None):432        """433        This function to return modified SQL.434        """435        data = {}436        for k, v in request.args.items():437            try:438                data[k] = json.loads(439                    v.decode('utf-8') if hasattr(v, 'decode') else v440                )441            except ValueError:442                data[k] = v443 444        # Format the schedule and step data445        self.format_schedule_step_data(data)446 447        return make_json_response(448            data=render_template(449                "/".join([450                    self.template_path,451                    self._CREATE_SQL if jid is None else self._UPDATE_SQL452                ]),453                jid=jid, data=data, conn=self.conn, fetch_id=False,454                has_connstr=self.manager.db_info['pgAgent']['has_connstr']455            ),456            status=200457        )458 459    @check_precondition460    def statistics(self, gid, sid, jid):461        """462        statistics463        Returns the statistics for a particular database if jid is specified,464        otherwise it will return statistics for all the databases in that465        server.466        """467        pref = Preferences.module('browser')468        rows_threshold = pref.preference(469            'pgagent_row_threshold'470        )471 472        status, res = self.conn.execute_dict(473            render_template(474                "/".join([self.template_path, 'stats.sql']),475                jid=jid, conn=self.conn,476                rows_threshold=rows_threshold.get()477            )478        )479 480        if not status:481            return internal_server_error(errormsg=res)482 483        return make_json_response(484            data=res,485            status=200486        )487 488    @check_precondition489    def sql(self, gid, sid, jid):490        """491        This function will generate sql for sql panel492        """493        SQL = render_template(494            "/".join([self.template_path, self._PROPERTIES_SQL]),495            jid=jid, conn=self.conn, last_system_oid=0496        )497        status, res = self.conn.execute_dict(SQL)498        if not status:499            return internal_server_error(errormsg=res)500 501        if len(res['rows']) == 0:502            return gone(503                _("Could not find the object on the server.")504            )505 506        row = res['rows'][0]507 508        status, res = self.conn.execute_dict(509            render_template(510                "/".join([self.template_path, 'steps.sql']),511                jid=jid, conn=self.conn,512                has_connstr=self.manager.db_info['pgAgent']['has_connstr']513            )514        )515        if not status:516            return internal_server_error(errormsg=res)517 518        row['jsteps'] = res['rows']519 520        status, res = self.conn.execute_dict(521            render_template(522                "/".join([self.template_path, 'schedules.sql']),523                jid=jid, conn=self.conn524            )525        )526        if not status:527            return internal_server_error(errormsg=res)528 529        row['jschedules'] = res['rows']530        for schedule in row['jschedules']:531            schedule['jscexceptions'] = []532            if schedule['jexid']:533                idx = 0534                for exc in schedule['jexid']:535                    # Convert datetime.time object to string536                    if isinstance(schedule['jextime'][idx], time):537                        schedule['jextime'][idx] = \538                            schedule['jextime'][idx].strftime("%H:%M:%S")539                    schedule['jscexceptions'].append({540                        'jexid': exc,541                        'jexdate': schedule['jexdate'][idx],542                        'jextime': schedule['jextime'][idx]543                    })544                    idx += 1545            del schedule['jexid']546            del schedule['jexdate']547            del schedule['jextime']548 549        return ajax_response(550            response=render_template(551                "/".join([self.template_path, self._CREATE_SQL]),552                jid=jid, data=row, conn=self.conn, fetch_id=False,553                has_connstr=self.manager.db_info['pgAgent']['has_connstr']554            )555        )556 557    @check_precondition558    def run_now(self, gid, sid, jid):559        """560        This function will set the next run to now, to inform the pgAgent to561        run the job now.562        """563        status, res = self.conn.execute_void(564            render_template(565                "/".join([self.template_path, 'run_now.sql']),566                jid=jid, conn=self.conn567            )568        )569        if not status:570            return internal_server_error(errormsg=res)571 572        return success_return(573            message=_("Updated the next runtime to now.")574        )575 576    @check_precondition577    def job_classes(self, gid, sid):578        """579        This function will return the set of job classes.580        """581        status, res = self.conn.execute_dict(582            render_template("/".join([self.template_path, 'job_classes.sql']))583        )584 585        if not status:586            return internal_server_error(errormsg=res)587 588        return make_json_response(589            data=res['rows'],590            status=200591        )592 593    def format_schedule_step_data(self, data):594        """595        This function is used to format the schedule and step data.596        :param data:597        :return:598        """599        # Format the schedule data. Convert the boolean array600        jschedules = data.get('jschedules', {})601        if isinstance(jschedules, dict):602            for schedule in jschedules.get('added', []):603                format_schedule_data(schedule)604            for schedule in jschedules.get('changed', []):605                format_schedule_data(schedule)606 607        has_connection_str = self.manager.db_info['pgAgent']['has_connstr']608        jssteps = data.get('jsteps', {})609        if isinstance(jssteps, dict):610            for changed_step in jssteps.get('changed', []):611                status, res = format_step_data(612                    data['jobid'], changed_step, has_connection_str,613                    self.conn, self.template_path)614                if not status:615                    internal_server_error(errormsg=res)616 617 618JobView.register_node_view(blueprint)619 
codekingpro/portable-devtools · Team Ai