diff --git a/src/handlers/servers/info.py b/src/handlers/servers/info.py index f679e93..12ba973 100644 --- a/src/handlers/servers/info.py +++ b/src/handlers/servers/info.py @@ -28,8 +28,8 @@ async def servers_info(callback_query: CallbackQuery, callback_data: BotCB, hetz price_hourly = "āž–" price_monthly = "āž–" if prices: - price_hourly = prices[0]["price_hourly"]["gross"] - price_monthly = prices[0]["price_monthly"]["gross"] + price_hourly = f"{float(prices[0]['price_hourly']['gross']):.4f}" + price_monthly = f"{float(prices[0]['price_monthly']['gross']):.2f}" update = await callback_query.message.edit( text=Dialogs.SERVERS_INFO.format( diff --git a/src/handlers/servers/update.py b/src/handlers/servers/update.py index 5ac9dcb..79c681f 100644 --- a/src/handlers/servers/update.py +++ b/src/handlers/servers/update.py @@ -17,6 +17,7 @@ class ServerUpdateForm(StateGroup): image = State() ip = State() input = State() + upgrade = State() @router.callback_query(BotCB.filter(area=AreaType.SERVER, task=TaskType.UPDATE)) @@ -83,6 +84,24 @@ async def servers_update( case StepType.SERVERS_REMARK: text = Dialogs.SERVERS_ENTER_REMARK _state = ServerUpdateForm.input + case StepType.SERVERS_UPGRADE: + server_types = hetzner.server_types.get_all() + current_type = server.server_type + upgrade_plans = [ + st + for st in server_types + if st.architecture == current_type.architecture + and st.id != current_type.id + and (st.memory >= current_type.memory or st.cores >= current_type.cores or st.disk >= current_type.disk) + ] + if not upgrade_plans: + return await callback_query.answer(text=Dialogs.SERVERS_UPGRADE_NOT_FOUND, show_alert=True) + upgrade_plans.sort(key=lambda x: (x.memory, x.cores, x.disk)) + text = Dialogs.SERVERS_UPGRADE_SELECT.format( + current_plan=f"{current_type.name} [{current_type.memory}GB RAM, {current_type.cores} CPU, {current_type.disk}GB Disk]" + ) + _state = ServerUpdateForm.upgrade + kb = BotKB.upgrade_plans_select(plans=upgrade_plans, server_id=server.id) case _: return await callback_query.answer(text="Invalid step!", show_alert=True) await state.upsert_context(db=db, state=_state, step=callback_data.step, target=callback_data.target) @@ -148,6 +167,30 @@ async def select_ip_handler( return await callback_query.message.edit(text=Dialogs.ACTIONS_SUCCESS, reply_markup=BotKB.servers_back(server.id)) +@router.callback_query(StateFilter(ServerUpdateForm.upgrade), BotCB.filter(area=AreaType.SERVER, task=TaskType.UPDATE)) +async def select_upgrade_handler( + callback_query: CallbackQuery, + callback_data: BotCB, + db: AsyncSession, + state: StateManager, + hetzner: GetHetzner, + state_data: dict, +): + server_type = hetzner.server_types.get_by_id(int(callback_data.target)) + if not server_type: + return await callback_query.answer(text=Dialogs.SERVERS_UPGRADE_NOT_FOUND, show_alert=True) + server = hetzner.servers.get_by_id(int(state_data["target"])) + if not server: + return await callback_query.answer(text=Dialogs.SERVERS_NOT_FOUND, show_alert=True) + + if server.status != "off": + return await callback_query.answer(text=Dialogs.SERVERS_SHOULD_BE_OFF, show_alert=True) + await callback_query.message.edit(text=Dialogs.ACTIONS_WAITING) + server.change_type(server_type=server_type, upgrade_disk=True) + await state.clear_state(db=db) + return await callback_query.message.edit(text=Dialogs.SERVERS_UPGRADE_SUCCESS, reply_markup=BotKB.servers_back(server.id)) + + @router.callback_query(StateFilter(ServerUpdateForm.approval), BotCB.filter(area=AreaType.SERVER, task=TaskType.UPDATE)) async def approval_handler( callback_query: CallbackQuery, diff --git a/src/keys/callback.py b/src/keys/callback.py index 557d7d0..05ab217 100644 --- a/src/keys/callback.py +++ b/src/keys/callback.py @@ -36,6 +36,7 @@ class StepType(StrEnum): SERVERS_UNASSIGN_IPV6 = "sua6" SERVERS_ASSIGN_IPV4 = "saa4" SERVERS_ASSIGN_IPV6 = "saa6" + SERVERS_UPGRADE = "sup" SNAPSHOTS_RESTORE = "srs" SNAPSHOTS_DELETE = "sds" SNAPSHOTS_REMARK = "srm" diff --git a/src/keys/manager.py b/src/keys/manager.py index 8e40249..c1605f9 100644 --- a/src/keys/manager.py +++ b/src/keys/manager.py @@ -165,6 +165,7 @@ def servers_update(cls, server: Server) -> InlineKeyboardMarkup: kb = InlineKeyboardBuilder() update = { StepType.SERVERS_REMARK: Buttons.SERVERS_REMARK, + StepType.SERVERS_UPGRADE: Buttons.SERVERS_UPGRADE, StepType.SERVERS_POWER_OFF: Buttons.SERVERS_POWER_OFF, StepType.SERVERS_POWER_ON: Buttons.SERVERS_POWER_ON, StepType.SERVERS_CREATE_SNAPSHOT: Buttons.SERVERS_CREATE_SNAPSHOT, @@ -189,7 +190,7 @@ def servers_update(cls, server: Server) -> InlineKeyboardMarkup: step=step, ).pack(), ) - kb.adjust(1, 2, 1, 2, 1, 2, 1, 2, 2) + kb.adjust(1, 1, 2, 1, 2, 1, 2, 1, 2, 2) cls._back(kb=kb, area=AreaType.SERVER) return kb.as_markup() @@ -247,6 +248,22 @@ def plans_select(cls, plans: List[ServerType]) -> InlineKeyboardMarkup: cls._back(kb=kb, area=AreaType.SERVER) return kb.as_markup() + @classmethod + def upgrade_plans_select(cls, plans: List[ServerType], server_id: int) -> InlineKeyboardMarkup: + kb = InlineKeyboardBuilder() + for plan in plans: + kb.add( + text=f"{plan.name} [{plan.memory} RAM, {plan.cores} CPU, {float(plan.prices[0]['price_monthly']['net'])} EUR]", + callback_data=BotCB( + area=AreaType.SERVER, + task=TaskType.UPDATE, + target=plan.id, + ).pack(), + ) + kb.adjust(1) + cls._back(kb=kb, area=AreaType.SERVER, target=server_id) + return kb.as_markup() + @classmethod def snapshots_menu(cls, snapshots: List[Image]) -> InlineKeyboardMarkup: kb = InlineKeyboardBuilder() diff --git a/src/lang/_button.py b/src/lang/_button.py index fb9b247..d273479 100644 --- a/src/lang/_button.py +++ b/src/lang/_button.py @@ -32,6 +32,7 @@ class Buttons(StrEnum): SERVERS_ASSIGN_IPV6 = "šŸ”— Assign IPv6" SERVERS_UNASSIGN_IPV4 = "āŒ Unassign IPv4" SERVERS_UNASSIGN_IPV6 = "āŒ Unassign IPv6" + SERVERS_UPGRADE = "ā¬†ļø Upgrade" ### Snapshots SNAPSHOTS = "šŸ“ø Snapshots" diff --git a/src/lang/_dialog.py b/src/lang/_dialog.py index eb0fab6..2332d7c 100644 --- a/src/lang/_dialog.py +++ b/src/lang/_dialog.py @@ -72,6 +72,10 @@ class Dialogs(StrEnum): SERVERS_REMARK_VALIDATION = ( "āš ļøāŒ Invalid remark format.\nšŸ” Please enter a valid remark without special characters and space." ) + SERVERS_UPGRADE_SELECT = "ā¬†ļø Select a plan to upgrade the server:\n\nCurrent: {current_plan}" + SERVERS_UPGRADE_NOT_FOUND = "šŸ”āŒ No upgrade plans available for this server." + SERVERS_UPGRADE_SUCCESS = "šŸŽ‰āœ… Server upgraded successfully." + SERVERS_SHOULD_BE_OFF = "āš ļøāŒ Please power off the server and try again." ### Snapshots SNAPSHOTS_MENU = "šŸ“ø Snapshots Menu\nšŸ‘‡ Select an action from the menu below."