From b6d22ef41db6e94dfe559927bb8e8522152642d6 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 25 Mar 2025 10:43:26 -0400 Subject: [PATCH] frontend: arcade management rework --- core/const.py | 8 + core/data/schema/arcade.py | 127 ++++++++++++- core/frontend.py | 241 +++++++++++++++++++++--- core/templates/arcade/index.jinja | 94 ++++++++- core/templates/machine/index.jinja | 113 ++++++++++- core/templates/sys/index.jinja | 5 + core/templates/user/index.jinja | 13 +- core/templates/widgets/err_banner.jinja | 4 + 8 files changed, 556 insertions(+), 49 deletions(-) diff --git a/core/const.py b/core/const.py index 535a1bb..d633080 100644 --- a/core/const.py +++ b/core/const.py @@ -45,6 +45,14 @@ class AllnetCountryCode(Enum): SOUTH_KOREA = "KOR" TAIWAN = "TWN" CHINA = "CHN" + AUSTRALIA = "AUS" + INDONESIA = "IDN" + MYANMAR = "MMR" + MALAYSIA = "MYS" + NEW_ZEALAND = "NZL" + PHILIPPINES = "PHL" + THAILAND = "THA" + VIETNAM = "VNM" class AllnetJapanRegionId(Enum): diff --git a/core/data/schema/arcade.py b/core/data/schema/arcade.py index 653fe7c..7951ad1 100644 --- a/core/data/schema/arcade.py +++ b/core/data/schema/arcade.py @@ -115,6 +115,15 @@ class ArcadeData(BaseData): return None return result.lastrowid + async def set_machine_arcade(self, machine_id: int, new_arcade: int) -> bool: + sql = machine.update(machine.c.id == machine_id).values(arcade = new_arcade) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update machine {machine_id} arcade to {new_arcade}") + return False + return True + async def set_machine_serial(self, machine_id: int, serial: str) -> None: result = await self.execute( machine.update(machine.c.id == machine_id).values(keychip=serial) @@ -134,6 +143,60 @@ class ArcadeData(BaseData): f"Failed to update board id for machine {machine_id} -> {boardid}" ) + async def set_machine_game(self, machine_id: int, new_game: Optional[str]) -> bool: + sql = machine.update(machine.c.id == machine_id).values(game = new_game) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update machine {machine_id} game to {new_game}") + return False + return True + + async def set_machine_country(self, machine_id: int, new_country: Optional[str]) -> bool: + sql = machine.update(machine.c.id == machine_id).values(country = new_country) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update machine {machine_id} country to {new_country}") + return False + return True + + async def set_machine_timezone(self, machine_id: int, new_timezone: Optional[str]) -> bool: + sql = machine.update(machine.c.id == machine_id).values(timezone = new_timezone) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update machine {machine_id} timezone to {new_timezone}") + return False + return True + + async def set_machine_real_cabinet(self, machine_id: int, is_real: bool = False) -> bool: + sql = machine.update(machine.c.id == machine_id).values(is_cab = is_real) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update machine {machine_id} is_cab to {is_real}") + return False + return True + + async def set_machine_can_ota(self, machine_id: int, can_ota: bool = False) -> bool: + sql = machine.update(machine.c.id == machine_id).values(ota_enable = can_ota) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update machine {machine_id} ota_enable to {can_ota}") + return False + return True + + async def set_machine_memo(self, machine_id: int, new_memo: Optional[str]) -> bool: + sql = machine.update(machine.c.id == machine_id).values(memo = new_memo) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update machine {machine_id} memo") + return False + return True + async def get_arcade(self, id: int) -> Optional[Row]: sql = arcade.select(arcade.c.id == id) result = await self.execute(sql) @@ -187,8 +250,11 @@ class ArcadeData(BaseData): sql = select(arcade_owner.c.permissions).where(and_(arcade_owner.c.user == user_id, arcade_owner.c.arcade == arcade_id)) result = await self.execute(sql) if result is None: - return False - return result.fetchone() + return None + row = result.fetchone() + if row: + return row['permissions'] + return None async def get_arcade_owners(self, arcade_id: int) -> Optional[Row]: sql = select(arcade_owner).where(arcade_owner.c.arcade == arcade_id) @@ -198,7 +264,7 @@ class ArcadeData(BaseData): return None return result.fetchall() - async def add_arcade_owner(self, arcade_id: int, user_id: int) -> None: + async def add_arcade_owner(self, arcade_id: int, user_id: int, permissions: int = 1) -> Optional[int]: sql = insert(arcade_owner).values(arcade=arcade_id, user=user_id) result = await self.execute(sql) @@ -206,6 +272,17 @@ class ArcadeData(BaseData): return None return result.lastrowid + async def set_arcade_owner_permissions(self, arcade_id: int, user_id: int, new_permissions: int = 1) -> bool: + sql = arcade_owner.update( + and_(arcade_owner.c.arcade == arcade_id, arcade_owner.c.user == user_id) + ).values(permissions = new_permissions) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update arcade owner permissions to {new_permissions} for user {user_id} arcade {arcade_id}") + return False + return True + async def get_arcade_by_name(self, name: str) -> Optional[List[Row]]: sql = arcade.select(or_(arcade.c.name.like(f"%{name}%"), arcade.c.nickname.like(f"%{name}%"))) result = await self.execute(sql) @@ -219,7 +296,49 @@ class ArcadeData(BaseData): if result is None: return None return result.fetchall() - + + async def set_arcade_name_nickname(self, arcade_id: int, new_name: Optional[str], new_nickname: Optional[str]) -> bool: + sql = arcade.update(arcade.c.id == arcade_id).values(name = new_name, nickname = new_nickname) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update arcade {arcade_id} name to {new_name}/{new_nickname}") + return False + return True + + async def set_arcade_region_info(self, arcade_id: int, new_country: Optional[str], new_state: Optional[str], new_city: Optional[str], new_region_id: Optional[int], new_country_id: Optional[int]) -> bool: + sql = arcade.update(arcade.c.id == arcade_id).values( + country = new_country, + state = new_state, + city = new_city, + region_id = new_region_id, + country_id = new_country_id + ) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update arcade {arcade_id} regional info to {new_country}/{new_state}/{new_city}/{new_region_id}/{new_country_id}") + return False + return True + + async def set_arcade_timezone(self, arcade_id: int, new_timezone: Optional[str]) -> bool: + sql = arcade.update(arcade.c.id == arcade_id).values(timezone = new_timezone) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update arcade {arcade_id} timezone to {new_timezone}") + return False + return True + + async def set_arcade_vpn_ip(self, arcade_id: int, new_ip: Optional[str]) -> bool: + sql = arcade.update(arcade.c.id == arcade_id).values(ip = new_ip) + + result = await self.execute(sql) + if result is None: + self.logger.error(f"Failed to update arcade {arcade_id} VPN address to {new_ip}") + return False + return True + async def get_num_generated_keychips(self) -> Optional[int]: result = await self.execute(select(func.count("serial LIKE 'A69A%'")).select_from(machine)) if result: diff --git a/core/frontend.py b/core/frontend.py index 8226759..63abc7f 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -12,7 +12,6 @@ import jwt import yaml import secrets import string -import random from base64 import b64decode from enum import Enum from datetime import datetime, timezone @@ -20,6 +19,7 @@ from os import path, environ, mkdir, W_OK, access from core import CoreConfig, Utils from core.data import Data +from core.const import AllnetCountryCode # A-HJ-NP-Z SERIAL_LETTERS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] @@ -37,8 +37,7 @@ class ShopPermissionOffset(Enum): VIEW = 0 # View info and cabs BOOKKEEP = 1 # View bookeeping info EDITOR = 2 # Can edit name, settings - REGISTRAR = 3 # Can add cabs - # 4 - 6 reserved for future use + # 3 - 6 reserved for future use OWNER = 7 # Can do anything class ShopOwner(): @@ -149,10 +148,13 @@ class FrontendServlet(): Mount("/shop", routes=[ Route("/", self.arcade.render_GET, methods=['GET']), Route("/{shop_id:int}", self.arcade.render_GET, methods=['GET']), + Route("/{shop_id:int}/info.update", self.arcade.update_shop, methods=['POST']), ]), Mount("/cab", routes=[ Route("/", self.machine.render_GET, methods=['GET']), Route("/{machine_id:int}", self.machine.render_GET, methods=['GET']), + Route("/{machine_id:int}/info.update", self.machine.update_cab, methods=['POST']), + Route("/{machine_id:int}/reassign", self.machine.reassign_cab, methods=['POST']), ]), Mount("/game", routes=g_routes), Route("/robots.txt", self.robots) @@ -455,6 +457,16 @@ class FE_User(FE_Base): card_data = [] arcade_data = [] + managed_arcades = await self.data.arcade.get_arcades_managed_by_user(user_id) + if managed_arcades: + for arcade in managed_arcades: + ac = await self.data.arcade.get_arcade(arcade['id']) + if ac: + arcade_data.append({ + "id": ac['id'], + "name": ac['name'], + }) + for c in cards: if c['is_locked']: status = 'Locked' @@ -861,14 +873,16 @@ class FE_System(FE_Base): name = frm.get("shopName", None) country = frm.get("shopCountry", "JPN") ip = frm.get("shopIp", None) + owner = frm.get("shopOwner", None) acid = await self.data.arcade.create_arcade(name if name else None, name if name else None, country) if not acid: return RedirectResponse("/sys/?e=99", 303) - if ip: - # TODO: set IP - pass + await self.data.arcade.set_arcade_vpn_ip(acid, ip if ip else None) + + if owner: + await self.data.arcade.add_arcade_owner(acid, int(owner), 255) return Response(template.render( title=f"{self.core_config.server.name} | System", @@ -947,15 +961,20 @@ class FE_Arcade(FE_Base): shop_id = request.path_params.get('shop_id', None) usr_sesh = self.validate_session(request) - if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD): - self.logger.warning(f"User {usr_sesh.user_id} does not have permission to view shops!") + if not usr_sesh: return RedirectResponse("/gate/", 303) - + if not shop_id: - return Response(template.render( - title=f"{self.core_config.server.name} | Arcade", - sesh=vars(usr_sesh), - ), media_type="text/html; charset=utf-8") + return Response('Not Found', status_code=404) + + is_acmod = self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD) + if not is_acmod: + usr_shop_perm = await self.data.arcade.get_manager_permissions(usr_sesh.user_id, shop_id) + if usr_shop_perm is None or usr_shop_perm == 0: + self.logger.warning(f"User {usr_sesh.user_id} does not have permission to view shop {shop_id}!") + return RedirectResponse("/", 303) + else: + usr_shop_perm = 15 # view, bookeep, edit sinfo = await self.data.arcade.get_arcade(shop_id) if not sinfo: @@ -974,38 +993,204 @@ class FE_Arcade(FE_Base): "game": x['game'], }) + managers = [] + if (usr_shop_perm & 1 << ShopPermissionOffset.OWNER.value) or is_acmod: + mgrs = await self.data.arcade.get_arcade_owners(sinfo['id']) + if mgrs: + for mgr in mgrs: + usr = await self.data.user.get_user(mgr['user']) + managers.append({ + 'user': mgr['user'], + 'name': usr['username'] if usr['username'] else 'No Name Set', + 'is_view': bool(mgr['permissions'] & 1 << ShopPermissionOffset.VIEW.value), + 'is_bookkeep': bool(mgr['permissions'] & 1 << ShopPermissionOffset.BOOKKEEP.value), + 'is_edit': bool(mgr['permissions'] & 1 << ShopPermissionOffset.EDITOR.value), + 'is_owner': bool(mgr['permissions'] & 1 << ShopPermissionOffset.OWNER.value), + }) + + if request.query_params.get("e", None): + err = int(request.query_params.get("e")) + else: + err = 0 + + if request.query_params.get("s", None): + suc = int(request.query_params.get("s")) + else: + suc = 0 + return Response(template.render( title=f"{self.core_config.server.name} | Arcade", sesh=vars(usr_sesh), - arcade={ - "name": sinfo['name'], - "id": sinfo['id'], - "cabs": cablst - } - + cablst=cablst, + arcade=sinfo._asdict(), + can_bookkeep=bool(usr_shop_perm & 1 << ShopPermissionOffset.BOOKKEEP.value) or is_acmod, + can_edit=bool(usr_shop_perm & 1 << ShopPermissionOffset.EDITOR.value) or is_acmod, + is_owner=usr_shop_perm & 1 << ShopPermissionOffset.OWNER.value, + is_acmod=is_acmod, + managers=managers, + error=err, + success=suc ), media_type="text/html; charset=utf-8") + async def update_shop(self, request: Request): + shop_id = request.path_params.get('shop_id', None) + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + sinfo = await self.data.arcade.get_arcade(shop_id) + + if not shop_id or not sinfo: + return RedirectResponse("/", 303) + + if not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD): + usr_shop_perm = await self.data.arcade.get_manager_permissions(usr_sesh.user_id, sinfo['id']) + if usr_shop_perm is None or usr_shop_perm == 0: + self.logger.warning(f"User {usr_sesh.user_id} does not have permission to view shop {sinfo['id']}!") + return RedirectResponse("/", 303) + + frm = await request.form() + new_name = frm.get('name', None) + new_nickname = frm.get('nickname', None) + new_country = frm.get('country', None) + new_region1 = frm.get('region1', None) + new_region2 = frm.get('region2', None) + new_tz = frm.get('tz', None) + new_ip = frm.get('ip', None) + + try: + AllnetCountryCode(new_country) + except ValueError: + new_country = 'JPN' + + did_name = await self.data.arcade.set_arcade_name_nickname(sinfo['id'], new_name if new_name else f'Arcade{sinfo["id"]}', new_nickname if new_nickname else None) + did_region = await self.data.arcade.set_arcade_region_info(sinfo['id'], new_country, new_region1 if new_region1 else None, new_region2 if new_region2 else None, None, None) + did_timezone = await self.data.arcade.set_arcade_timezone(sinfo['id'], new_tz if new_tz else None) + did_vpn = await self.data.arcade.set_arcade_vpn_ip(sinfo['id'], new_ip if new_ip else None) + + if not did_name or not did_region or not did_timezone or not did_vpn: + self.logger.error(f"Failed to update some shop into: Name: {did_name} Region: {did_region} TZ: {did_timezone} VPN: {did_vpn}") + return RedirectResponse(f"/shop/{shop_id}?e=15", 303) + + return RedirectResponse(f"/shop/{shop_id}?s=1", 303) + class FE_Machine(FE_Base): async def render_GET(self, request: Request): template = self.environment.get_template("core/templates/machine/index.jinja") - cab_id = request.path_params.get('cab_id', None) + cab_id = request.path_params.get('machine_id', None) usr_sesh = self.validate_session(request) - if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD): - self.logger.warning(f"User {usr_sesh.user_id} does not have permission to view shops!") + if not usr_sesh: return RedirectResponse("/gate/", 303) + + cab = await self.data.arcade.get_machine(id=cab_id) - if not cab_id: - return Response(template.render( - title=f"{self.core_config.server.name} | Machine", - sesh=vars(usr_sesh), - ), media_type="text/html; charset=utf-8") + if not cab_id or not cab: + return Response('Not Found', status_code=404) + shop = await self.data.arcade.get_arcade(cab['arcade']) + + is_acmod = self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD) + if not is_acmod: + usr_shop_perm = await self.data.arcade.get_manager_permissions(usr_sesh.user_id, shop['id']) + if usr_shop_perm is None or usr_shop_perm == 0: + self.logger.warning(f"User {usr_sesh.user_id} does not have permission to view shop {shop['id']}!") + return RedirectResponse("/", 303) + else: + usr_shop_perm = 15 # view, bookeep, edit + + if request.query_params.get("e", None): + err = int(request.query_params.get("e")) + else: + err = 0 + + if request.query_params.get("s", None): + suc = int(request.query_params.get("s")) + else: + suc = 0 + return Response(template.render( title=f"{self.core_config.server.name} | Machine", sesh=vars(usr_sesh), - arcade={} + arcade=shop._asdict(), + machine=cab._asdict(), + can_bookkeep=bool(usr_shop_perm & 1 << ShopPermissionOffset.BOOKKEEP.value) or is_acmod, + can_edit=bool(usr_shop_perm & 1 << ShopPermissionOffset.EDITOR.value) or is_acmod, + is_owner=usr_shop_perm & 1 << ShopPermissionOffset.OWNER.value, + is_acmod=is_acmod, + error=err, + success=suc ), media_type="text/html; charset=utf-8") + + async def update_cab(self, request: Request): + cab_id = request.path_params.get('machine_id', None) + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + cab = await self.data.arcade.get_machine(id=cab_id) + + if not cab_id or not cab: + return RedirectResponse("/", 303) + + if not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD): + usr_shop_perm = await self.data.arcade.get_manager_permissions(usr_sesh.user_id, cab['arcade']) + if usr_shop_perm is None or usr_shop_perm == 0: + self.logger.warning(f"User {usr_sesh.user_id} does not have permission to view shop {cab['arcade']}!") + return RedirectResponse("/", 303) + + frm = await request.form() + new_game = frm.get('game', None) + new_country = frm.get('country', None) + new_tz = frm.get('tz', None) + new_is_cab = frm.get('is_cab', False) == 'on' + new_is_ota = frm.get('is_ota', False) == 'on' + new_memo = frm.get('memo', None) + + try: + AllnetCountryCode(new_country) + except ValueError: + new_country = None + + did_game = await self.data.arcade.set_machine_game(cab['id'], new_game if new_game else None) + did_country = await self.data.arcade.set_machine_country(cab['id'], new_country if new_country else None) + did_timezone = await self.data.arcade.set_machine_timezone(cab['id'], new_tz if new_tz else None) + did_real_cab = await self.data.arcade.set_machine_real_cabinet(cab['id'], new_is_cab) + did_ota = await self.data.arcade.set_machine_can_ota(cab['id'], new_is_ota) + did_memo = await self.data.arcade.set_machine_memo(cab['id'], new_memo if new_memo else None) + + if not did_game or not did_country or not did_timezone or not did_real_cab or not did_ota or not did_memo: + self.logger.error(f"Failed to update some shop into: Game: {did_game} Country: {did_country} TZ: {did_timezone} Real: {did_real_cab} OTA: {did_ota} Memo: {did_memo}") + return RedirectResponse(f"/cab/{cab['id']}?e=15", 303) + + return RedirectResponse(f"/cab/{cab_id}?s=1", 303) + + async def reassign_cab(self, request: Request): + cab_id = request.path_params.get('machine_id', None) + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + cab = await self.data.arcade.get_machine(id=cab_id) + + if not cab_id or not cab: + return RedirectResponse("/", 303) + + frm = await request.form() + new_shop = frm.get('new_arcade', None) + + if not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD): + self.logger.warning(f"User {usr_sesh.user_id} does not have permission to reassign cab {cab['id']} to arcade !") + return RedirectResponse(f"/cab/{cab_id}?e=11", 303) + + new_sinfo = await self.data.arcade.get_arcade(new_shop) + if not new_sinfo: + return RedirectResponse(f"/cab/{cab_id}?e=14", 303) + + if not await self.data.arcade.set_machine_arcade(cab['id'], new_sinfo['id']): + return RedirectResponse(f"/cab/{cab_id}?e=99", 303) + + return RedirectResponse(f"/cab/{cab_id}?s=2", 303) cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config") cfg: CoreConfig = CoreConfig() diff --git a/core/templates/arcade/index.jinja b/core/templates/arcade/index.jinja index 393443a..1cb4ee9 100644 --- a/core/templates/arcade/index.jinja +++ b/core/templates/arcade/index.jinja @@ -2,18 +2,104 @@ {% block content %} {% if arcade is defined %}

{{ arcade.name }}

-

PCBs assigned to this arcade

+

Assigned Machines

{% if success is defined and success == 3 %}
Cab added successfully
{% endif %} +{% if success is defined and success == 1 %} +
+Info Updated +
+{% endif %} +{% include "core/templates/widgets/err_banner.jinja" %} +Info +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ {% if can_edit %} +
+
+ +
+
+ {% endif %} +
+{% if is_owner or is_acmod %} +
+

Arcade Managers 

+ +{% endif %} {% else %}

Arcade Not Found

{% endif %} -{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/core/templates/machine/index.jinja b/core/templates/machine/index.jinja index 3e122f3..e9b7adb 100644 --- a/core/templates/machine/index.jinja +++ b/core/templates/machine/index.jinja @@ -1,4 +1,113 @@ {% extends "core/templates/index.jinja" %} {% block content %} -

Machine Management

-{% endblock content %} \ No newline at end of file + +

Machine: {{machine.serial}}

+

Arcade: {{ arcade.name }}{% if is_acmod %} {% endif %}

+{% include "core/templates/widgets/err_banner.jinja" %} +{% if success is defined and success == 1 %} +
+Info Updated +
+{% endif %} +{% if success is defined and success == 2 %} +
+Machine successfully reassigned +
+{% endif %} +Info +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+
+ {% if can_edit %} +
+
+ +
+
+ {% endif %} +
+{% if is_acmod %} +
+ +
+{% endif %} + +{% endblock content %} diff --git a/core/templates/sys/index.jinja b/core/templates/sys/index.jinja index 637b8c9..62aac15 100644 --- a/core/templates/sys/index.jinja +++ b/core/templates/sys/index.jinja @@ -137,6 +137,11 @@
+
+ + +
+
diff --git a/core/templates/user/index.jinja b/core/templates/user/index.jinja index 88b91c9..79b73b8 100644 --- a/core/templates/user/index.jinja +++ b/core/templates/user/index.jinja @@ -159,19 +159,10 @@ Update successful {% if arcades is defined and arcades|length > 0 %} -

Arcades

+

Arcades you manage

{% endif %} diff --git a/core/templates/widgets/err_banner.jinja b/core/templates/widgets/err_banner.jinja index f1f4899..d36b856 100644 --- a/core/templates/widgets/err_banner.jinja +++ b/core/templates/widgets/err_banner.jinja @@ -27,6 +27,10 @@ Access Denied Card already registered {% elif error == 13 %} AmusementIC Access Codes beginning with 5 must have IDm +{% elif error == 14 %} +Arcade does not exist +{% elif error == 15 %} +Some info failed to update {% else %} An unknown error occoured {% endif %}