from pathlib import Path import datetime from hat import aio import aiohttp.web from hatter import common import hatter.server static_dir: Path = common.package_path / 'ui' async def create(host: str, port: int, server: hatter.server.Server ) -> 'UI': ui = UI() ui._server = server ui._async_group = aio.Group() app = aiohttp.web.Application() get_routes = ( aiohttp.web.get(path, handler) for path, handler in ( ('/', ui._process_get_root), ('/repo/{repo}', ui._process_get_repo), ('/repo/{repo}/commit/{commit}', ui._process_get_commit))) post_routes = ( aiohttp.web.post(path, handler) for path, handler in ( ('/repo/{repo}/run', ui._process_post_run), ('/repo/{repo}/commit/{commit}/remove', ui._process_post_remove))) webhook_route = aiohttp.web.route('*', '/repo/{repo}/webhook', ui._process_webhook) static_route = aiohttp.web.static('/', static_dir) app.add_routes([*get_routes, *post_routes, webhook_route, static_route]) runner = aiohttp.web.AppRunner(app) await runner.setup() ui.async_group.spawn(aio.call_on_cancel, runner.cleanup) try: site = aiohttp.web.TCPSite(runner=runner, host=host, port=port, shutdown_timeout=0.1, reuse_address=True) await site.start() except BaseException: await aio.uncancellable(ui.async_group.async_close()) raise return ui class UI(aio.Resource): @property def async_group(self): return self._async_group async def _process_get_root(self, request): commits = await self._server.get_commits(None) body = (f'{_generate_repos(self._server.repos)}\n' f'{_generate_commits(commits)}') return _create_html_response('hatter', body) async def _process_get_repo(self, request): repo = self._get_repo(request) commits = await self._server.get_commits(repo) title = f'hatter - {repo}' body = (f'{_generate_commits(commits)}\n' f'{_generate_run(repo)}') return _create_html_response(title, body) async def _process_get_commit(self, request): commit = await self._get_commit(request) title = f'hatter - {commit.repo}/{commit.hash}' body = _generate_commit(commit) return _create_html_response(title, body) async def _process_post_run(self, request): repo = self._get_repo(request) body = await request.post() commit_hash = body['hash'] if not commit_hash: raise aiohttp.web.HTTPBadRequest() commit = await self._server.run_commit(repo, commit_hash) url = f'/repo/{commit.repo}/commit/{commit.hash}' raise aiohttp.web.HTTPFound(url) async def _process_post_remove(self, request): commit = await self._get_commit(request) await self._server.remove_commit(commit) raise aiohttp.web.HTTPFound(f'/repo/{commit.repo}') async def _process_webhook(self, request): repo = self._get_repo(request) self._server.sync_repo(repo) return aiohttp.web.Response() def _get_repo(self, request): repo = request.match_info['repo'] if repo not in self._server.repos: raise aiohttp.web.HTTPBadRequest() return repo async def _get_commit(self, request): repo = self._get_repo(request) commit_hash = request.match_info['commit'] commit = await self._server.get_commit(repo, commit_hash) if not commit: raise aiohttp.web.HTTPBadRequest() return commit def _create_html_response(title, body): text = _html_template.format(title=title, body=body) return aiohttp.web.Response(content_type='text/html', text=text) def _generate_repos(repos): items = '\n'.join(f'
  • {repo}
  • ' for repo in repos) return (f'
    \n' f'

    Repositories

    \n' f'\n' f'
    ') def _generate_commits(commits): thead = ('\n' 'Change\n' 'Repo\n' 'Commit\n' 'Status\n' '') tbody = '\n'.join( (f'\n' f'{_format_time(commit.change)}\n' f'{_generate_repo_link(commit.repo)}\n' f'{_generate_commit_link(commit)}\n' f'{commit.status.name}\n' f'') for commit in commits) return (f'
    \n' f'

    Commits

    \n' f'\n' f'\n' f'{thead}\n' f'\n' f'\n' f'{tbody}\n' f'\n' f'
    \n' f'
    ') def _generate_commit(commit): run_action = f'/repo/{commit.repo}/run' run_button = (f'
    \n' f'\n' f'\n' f'
    ') remove_action = f'/repo/{commit.repo}/commit/{commit.hash}/remove' remove_button = (f'
    \n' f'\n' f'
    ') repo_link = _generate_repo_link(commit.repo) return (f'
    \n' f'
    {repo_link}
    \n' f'
    {commit.hash}
    \n' f'
    {_format_time(commit.change)}
    \n' f'
    {commit.status.name}
    \n' f'
    {commit.output}
    \n' f'
    {run_button}{remove_button}
    \n' f'
    ') def _generate_run(repo): return (f'
    \n' f'
    \n' f'\n' f'\n' f'
    \n' f'
    ') def _generate_repo_link(repo): return f'{repo}' def _generate_commit_link(commit): url = f'/repo/{commit.repo}/commit/{commit.hash}' return f'{commit.hash}' def _format_time(t): return datetime.datetime.fromtimestamp(t).strftime("%Y-%m-%d %H:%M:%S") _html_template = r""" {title} {body} """