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."