add server group page (basic so far)

This commit is contained in:
Pierre Ducroquet 2023-06-23 09:52:35 +02:00
parent 5945f18c75
commit f40cda49d5
8 changed files with 278 additions and 32 deletions

View File

@ -23,6 +23,9 @@ set(PROJECT_SOURCES
mainwindow.cpp
mainwindow.h
mainwindow.ui
dedicatedservergroupwidget.h
dedicatedservergroupwidget.cpp
dedicatedservergroupwidget.ui
dedicatedserverinfowidget.cpp
dedicatedserverinfowidget.h
dedicatedserverinfowidget.ui

View File

@ -1,6 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="fr_FR">
<context>
<name>DedicatedServerGroupWidget</name>
<message>
<location filename="dedicatedservergroupwidget.ui" line="14"/>
<source>Form</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="dedicatedservergroupwidget.ui" line="22"/>
<source>Total monthly cost</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="dedicatedservergroupwidget.ui" line="35"/>
<location filename="dedicatedservergroupwidget.ui" line="53"/>
<source>...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="dedicatedservergroupwidget.cpp" line="27"/>
<source>Group %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="dedicatedservergroupwidget.cpp" line="55"/>
<source>(incomplete!) %1 %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="dedicatedservergroupwidget.cpp" line="56"/>
<source>For no good reason, OVH sometimes doesn&apos;t provide pricing information, sorry about that</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DedicatedServerInfoWidget</name>
<message>
@ -286,115 +320,120 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="90"/>
<location filename="mainwindow.cpp" line="94"/>
<source>Expired token</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="90"/>
<location filename="mainwindow.cpp" line="94"/>
<source>It seems this token is expired. Please login again to continue.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="148"/>
<location filename="mainwindow.cpp" line="153"/>
<source>Validate credential</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="148"/>
<location filename="mainwindow.cpp" line="153"/>
<source>Have you validated the OVH credential request?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="190"/>
<location filename="mainwindow.cpp" line="209"/>
<source>Group %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="225"/>
<source>Info %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="205"/>
<location filename="mainwindow.cpp" line="240"/>
<source>IPMI not activated</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="205"/>
<location filename="mainwindow.cpp" line="240"/>
<source>IPMI is not activated on this server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="214"/>
<location filename="mainwindow.cpp" line="249"/>
<source>IPMI KVM not available</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="214"/>
<location filename="mainwindow.cpp" line="249"/>
<source>IPMI KVM is not available on this server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="238"/>
<location filename="mainwindow.cpp" line="273"/>
<source>Waiting for task... %1</source>
<oldsource>Waiting for task...</oldsource>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="258"/>
<location filename="mainwindow.cpp" line="293"/>
<source>KVM %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="267"/>
<location filename="mainwindow.cpp" line="269"/>
<location filename="mainwindow.cpp" line="302"/>
<location filename="mainwindow.cpp" line="304"/>
<source>JNLP KVM</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="267"/>
<location filename="mainwindow.cpp" line="302"/>
<source>This server only supports a Java WebStart KVM, it will thus not be embedded in this window.
Java WebStart should be starting right now, wait until it finishes loading to close this dialog.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="269"/>
<location filename="mainwindow.cpp" line="304"/>
<source>Failed to launch javaws. Make sure it is properly installed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="368"/>
<location filename="mainwindow.cpp" line="403"/>
<source>Target archive</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="368"/>
<location filename="mainwindow.cpp" line="403"/>
<source>Zip files (*.zip)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="372"/>
<location filename="mainwindow.cpp" line="407"/>
<source>OVH...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="372"/>
<location filename="mainwindow.cpp" line="407"/>
<source>OVH decided that getting a bill require a &apos;true&apos; login. I&apos;m going to show you a web browser for that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="383"/>
<location filename="mainwindow.cpp" line="418"/>
<source>Failed to open archive</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="383"/>
<location filename="mainwindow.cpp" line="418"/>
<source>Failed to open archive...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwindow.cpp" line="474"/>
<location filename="mainwindow.cpp" line="510"/>
<source>Logins cleaned up</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<location filename="mainwindow.cpp" line="474"/>
<location filename="mainwindow.cpp" line="510"/>
<source>%n login(s) purged</source>
<translation type="unfinished">
<numerusform></numerusform>

View File

@ -0,0 +1,60 @@
#include "dedicatedservergroupwidget.h"
#include "ui_dedicatedservergroupwidget.h"
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include "ovhapi.h"
DedicatedServerGroupWidget::DedicatedServerGroupWidget(OvhApi *api, const QString &groupName, const QStringList &servers, QWidget *parent) :
QWidget(parent),
ui(new Ui::DedicatedServerGroupWidget),
api(api),
_groupName(groupName),
_servers(servers)
{
ui->setupUi(this);
QTimer::singleShot(0, this, &DedicatedServerGroupWidget::fetchOvhData);
}
DedicatedServerGroupWidget::~DedicatedServerGroupWidget()
{
delete ui;
}
QCoro::Task<> DedicatedServerGroupWidget::fetchOvhData()
{
ui->groupLabel->setText(tr("Group %1").arg(_groupName));
double totalMonthlyCost = 0;
bool incompletePricing = false;
QString currency;
for (auto &&server: _servers) {
qDebug() << server;
// Calculate monthly cost, using the /services API
auto serviceInfo = (co_await api->get(QString("/dedicated/server/%1/serviceInfos").arg(server), 1h)).object();
auto serviceId = serviceInfo["serviceId"].toInt();
auto servicesData = (co_await api->get(QString("/services/%1").arg(serviceId), 6h)).object();
auto billingData = servicesData["billing"].toObject();
auto pricingData = billingData["pricing"].toObject();
double totalCost = pricingData["price"].toObject()["value"].toDouble();
totalCost = totalCost / pricingData["interval"].toInt();
if (billingData["pricing"].isNull()) {
incompletePricing = true;
} else {
qDebug() << pricingData["price"] << pricingData["interval"];
if (currency.isEmpty())
currency = pricingData["price"].toObject()["currency"].toString();
// TODO: else ??
totalMonthlyCost += totalCost;
}
}
if (incompletePricing) {
ui->monthlyCost->setText(tr("(incomplete!) %1 %2").arg(totalMonthlyCost).arg(currency));
ui->monthlyCost->setToolTip(tr("For no good reason, OVH sometimes doesn't provide pricing information, sorry about that"));
} else {
ui->monthlyCost->setText(QString("%1 %2").arg(totalMonthlyCost).arg(currency));
}
}

View File

@ -0,0 +1,31 @@
#ifndef DEDICATEDSERVERGROUPWIDGET_H
#define DEDICATEDSERVERGROUPWIDGET_H
#include <QWidget>
#include <QCoroTask>
namespace Ui {
class DedicatedServerGroupWidget;
}
class OvhApi;
class DedicatedServerGroupWidget : public QWidget
{
Q_OBJECT
public:
explicit DedicatedServerGroupWidget(OvhApi *api, const QString &groupName, const QStringList &servers, QWidget *parent = nullptr);
~DedicatedServerGroupWidget();
QString groupName() const { return _groupName; }
private slots:
QCoro::Task<> fetchOvhData();
private:
Ui::DedicatedServerGroupWidget *ui;
OvhApi *api;
QString _groupName;
QStringList _servers;
};
#endif // DEDICATEDSERVERGROUPWIDGET_H

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DedicatedServerGroupWidget</class>
<widget class="QWidget" name="DedicatedServerGroupWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Total monthly cost</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="monthlyCost">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="groupLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -24,11 +24,15 @@
#include <KArchive>
#include <KZip>
#include "servicesview.h"
#include "dedicatedservergroupwidget.h"
#include "dedicatedserverinfowidget.h"
#include "ovhwebauthentication.h"
using namespace std::chrono_literals;
const auto ServerGroup = Qt::UserRole + 1;
const auto ServerName = Qt::UserRole + 2;
const auto ten_years = 87600h;
MainWindow::MainWindow(QWidget *parent)
@ -107,7 +111,7 @@ QCoro::Task<> MainWindow::login(const QString &ck) {
}
}
QCoro::Task<> MainWindow::loadServerInfo(QString server) {
QCoro::Task<> MainWindow::loadServerInfo(const QString &server) {
qDebug() << "Loading for " << server;
auto serverInfo = (co_await api->get(QString("/dedicated/server/") + server, 1h)).object();
auto displayName = serverInfo["reverse"].toString();
@ -120,6 +124,7 @@ QCoro::Task<> MainWindow::loadServerInfo(QString server) {
if (!topLevelNodes.contains(serverDomain)) {
topDomain = new QTreeWidgetItem(ui->serverList);
topDomain->setText(0, serverDomain);
topDomain->setData(0, ServerGroup, serverDomain);
ui->serverList->addTopLevelItem(topDomain);
topLevelNodes.insert(serverDomain, topDomain);
} else {
@ -127,7 +132,7 @@ QCoro::Task<> MainWindow::loadServerInfo(QString server) {
}
auto serverItem = new QTreeWidgetItem(topDomain);
serverItem->setText(0, serverInfo["reverse"].toString());
serverItem->setData(0, Qt::UserRole, server);
serverItem->setData(0, ServerName, server);
topDomain->addChild(serverItem);
ui->serverList->sortItems(0, Qt::AscendingOrder);
}
@ -171,11 +176,41 @@ QCoro::Task<> MainWindow::on_actionLogin_triggered()
}
QCoro::Task<> MainWindow::on_serverList_itemClicked(QTreeWidgetItem *item, int column)
QCoro::Task<> MainWindow::on_serverList_itemClicked(QTreeWidgetItem *item, [[maybe_unused]] int column)
{
auto serverName = item->data(0, Qt::UserRole).toString();
if (serverName.isEmpty())
auto serverName = item->data(0, ServerName).toString();
if (serverName.isEmpty()) {
// Maybe it's a server group
auto groupName = item->data(0, ServerGroup).toString();
if (groupName.isEmpty())
co_return;
// Find existing tab
for (int i = 0 ; i < ui->tabWidget->count() ; i++) {
auto existingTab = ui->tabWidget->widget(i);
auto existingGroupInfo = qobject_cast<DedicatedServerGroupWidget*>(existingTab);
if (existingGroupInfo && existingGroupInfo->groupName() == groupName) {
ui->tabWidget->setCurrentIndex(i);
co_return;
}
}
// Build and fill a server group widget
QStringList serverNames;
for (int i = 0 ; i < item->childCount() ; i++) {
auto node = item->child(i);
serverName = node->data(0, ServerName).toString();
if (!serverName.isEmpty())
serverNames.append(serverName);
}
qDebug() << serverNames;
auto groupView = new DedicatedServerGroupWidget(api, groupName, serverNames, this);
int tabId = ui->tabWidget->addTab(groupView, tr("Group %1").arg(groupName));
ui->tabWidget->setCurrentIndex(tabId);
co_return;
}
auto serverReverse = item->text(0);
for (int i = 0 ; i < ui->tabWidget->count() ; i++) {
@ -471,6 +506,7 @@ QCoro::Task<> MainWindow::on_actionPurgeLogins_triggered()
validLoginActions.append(loginAction);
}
}
// TODO : purge cache db
QMessageBox::information(this, tr("Logins cleaned up"), tr("%n login(s) purged", nullptr, loginActions.size() - validLoginActions.size()));
loginActions = validLoginActions;
}

View File

@ -31,7 +31,7 @@ private slots:
void on_tabWidget_tabCloseRequested(int index);
QCoro::Task<> loadServerInfo(QString serverName);
QCoro::Task<> loadServerInfo(const QString &serverName);
void loadKvm(const QString &name, const QString &kvmType, const QString &data);

View File

@ -82,10 +82,10 @@ QCoro::Task<QByteArray> OvhApi::download(const QUrl &url)
QCoro::Task<QJsonDocument> OvhApi::get(const QString &path, std::chrono::seconds cacheDuration, bool forceRefresh)
{
if (cacheDuration.count() > 0 && !forceRefresh) {
qDebug() << "Gonna look at the cache";
// qDebug() << "Gonna look at the cache";
QSqlDatabase db = QSqlDatabase::database();
if (db.isValid()) {
qDebug() << "Cache check";
// qDebug() << "Cache check";
// Check from cache first
auto cacheQuery = QSqlQuery{"SELECT data FROM cache WHERE ck=? AND path=? AND expiration>?", db};
cacheQuery.bindValue(0, consumerKey);
@ -110,7 +110,7 @@ QCoro::Task<QJsonDocument> OvhApi::get(const QString &path, std::chrono::seconds
auto answer = QJsonDocument::fromJson(rawAnswer);
if (cacheDuration.count() > 0 || forceRefresh) {
qDebug() << "Gonna store in the cache";
// qDebug() << "Gonna store in the cache";
QSqlDatabase db = QSqlDatabase::database();
if (db.isValid()) {
// Check from cache first