This commit is contained in:
parent
51bfddd40f
commit
6803f84677
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
class VisitorRecord {
|
class VisitorRecord {
|
||||||
public:
|
public:
|
||||||
|
int id = -1;
|
||||||
std::string url;
|
std::string url;
|
||||||
std::string userAgent;
|
std::string userAgent;
|
||||||
std::string visitorUuid;
|
std::string visitorUuid;
|
||||||
@ -19,4 +20,6 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using VisitorRecords = Wt::Dbo::collection<Wt::Dbo::ptr<VisitorRecord>>;
|
||||||
|
|
||||||
#endif // __VISITORRECORD_H__
|
#endif // __VISITORRECORD_H__
|
@ -1,54 +1,170 @@
|
|||||||
#include "VisitorRecordTableModel.h"
|
#include "VisitorRecordTableModel.h"
|
||||||
#include "BoostLog.h"
|
#include "BoostLog.h"
|
||||||
|
#include "Database/Session.h"
|
||||||
|
#include "DateTime.h"
|
||||||
|
|
||||||
|
VisitorRecordTableModel::VisitorRecordTableModel(Session &session) : m_session(session) {
|
||||||
|
Wt::Dbo::Transaction transaction(session);
|
||||||
|
Wt::Dbo::collection<Wt::Dbo::ptr<VisitorRecord>> records = session.find<VisitorRecord>();
|
||||||
|
int index = 1;
|
||||||
|
std::unordered_map<std::string, std::string> users;
|
||||||
|
for (auto &record : records) {
|
||||||
|
if (record->url.size() <= 1) continue;
|
||||||
|
if (!users.contains(record->visitorUuid)) {
|
||||||
|
users.insert({record->visitorUuid, std::format("匿名用户{}", index++)});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_records.push_back({*record, false});
|
||||||
|
std::get<0>(m_records.back()).visitorUuid = users.at(record->visitorUuid);
|
||||||
|
std::get<0>(m_records.back()).id = record.id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int VisitorRecordTableModel::columnCount(const Wt::WModelIndex &parent) const {
|
int VisitorRecordTableModel::columnCount(const Wt::WModelIndex &parent) const {
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
int VisitorRecordTableModel::rowCount(const Wt::WModelIndex &parent) const {
|
int VisitorRecordTableModel::rowCount(const Wt::WModelIndex &parent) const {
|
||||||
return 50;
|
return m_records.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Wt::WFlags<Wt::HeaderFlag> VisitorRecordTableModel::headerFlags(int section, Wt::Orientation orientation) const {
|
||||||
|
if (section == 0 && orientation == Wt::Orientation::Horizontal) {
|
||||||
|
return Wt::HeaderFlag::UserCheckable;
|
||||||
|
} else {
|
||||||
|
return Wt::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Wt::cpp17::any VisitorRecordTableModel::headerData(int section, Wt::Orientation orientation, Wt::ItemDataRole role) const {
|
||||||
|
if (orientation == Wt::Orientation::Horizontal) {
|
||||||
|
if (role == Wt::ItemDataRole::Level) return Wt::WAbstractTableModel::headerData(section, orientation, role);
|
||||||
|
if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) &&
|
||||||
|
section == 0) { // Assuming first column for checkbox
|
||||||
|
return m_selectAll;
|
||||||
|
} else if ((section == 1) && (role == Wt::ItemDataRole::Display)) {
|
||||||
|
return "文章";
|
||||||
|
} else if ((section == 2) && (role == Wt::ItemDataRole::Display)) {
|
||||||
|
return "时间";
|
||||||
|
} else if ((section == 3) && (role == Wt::ItemDataRole::Display)) {
|
||||||
|
return "浏览器";
|
||||||
|
} else if ((section == 4) && (role == Wt::ItemDataRole::Display)) {
|
||||||
|
return "用户";
|
||||||
|
} else {
|
||||||
|
return Wt::cpp17::any{};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Wt::WAbstractTableModel::headerData(section, orientation, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VisitorRecordTableModel::setHeaderData(int section, Wt::Orientation orientation, const Wt::cpp17::any &value,
|
||||||
|
Wt::ItemDataRole role) {
|
||||||
|
if ((section == 0) && (role == Wt::ItemDataRole::Checked)) {
|
||||||
|
m_selectAll = Wt::cpp17::any_cast<bool>(value);
|
||||||
|
for (int i = 0; i < m_records.size(); i++) {
|
||||||
|
std::get<1>(m_records[i]) = m_selectAll;
|
||||||
|
}
|
||||||
|
dataChanged().emit(index(0, 0), index(m_records.size(), 0));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return Wt::WAbstractTableModel::setHeaderData(section, orientation, value, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisitorRecordTableModel::sort(int column, Wt::SortOrder order) {
|
||||||
|
if (column == 2) {
|
||||||
|
layoutAboutToBeChanged().emit();
|
||||||
|
std::sort(m_records.begin(), m_records.end(), [order](const VisitorRecord &lfh, const VisitorRecord &rfh) {
|
||||||
|
if (order == Wt::SortOrder::Ascending) {
|
||||||
|
return lfh.time < rfh.time;
|
||||||
|
} else {
|
||||||
|
return lfh.time > rfh.time;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layoutChanged().emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WFlags<Wt::ItemFlag> VisitorRecordTableModel::flags(const Wt::WModelIndex &index) const {
|
Wt::WFlags<Wt::ItemFlag> VisitorRecordTableModel::flags(const Wt::WModelIndex &index) const {
|
||||||
if (index.column() == 0) {
|
if (index.column() == 0) {
|
||||||
return Wt::ItemFlag::UserCheckable | Wt::ItemFlag::Selectable;
|
return Wt::ItemFlag::UserCheckable | Wt::ItemFlag::Selectable;
|
||||||
|
} else if (index.column() == 1) {
|
||||||
|
return Wt::ItemFlag::XHTMLText | Wt::ItemFlag::Selectable;
|
||||||
} else {
|
} else {
|
||||||
return Wt::ItemFlag::Selectable;
|
return Wt::ItemFlag::Selectable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WFlags<Wt::HeaderFlag> VisitorRecordTableModel::headerFlags(int section, Wt::Orientation orientation) const {
|
|
||||||
if (section == 0 && orientation== Wt::Orientation::Horizontal) {
|
|
||||||
return Wt::HeaderFlag::UserCheckable|Wt::HeaderFlag::XHTMLText;
|
|
||||||
} else {
|
|
||||||
return Wt::HeaderFlag::XHTMLText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Wt::cpp17::any VisitorRecordTableModel::headerData(int section, Wt::Orientation orientation, Wt::ItemDataRole role) const {
|
|
||||||
if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) &&
|
|
||||||
section == 0) { // Assuming first column for checkbox
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else if ((role == Wt::ItemDataRole::Display || role == Wt::ItemDataRole::Edit) &&
|
|
||||||
section == 0) { // Assuming first column for checkbox
|
|
||||||
return Wt::cpp17::any{};
|
|
||||||
} else {
|
|
||||||
return Wt::cpp17::any{};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Wt::cpp17::any VisitorRecordTableModel::data(const Wt::WModelIndex &index, Wt::ItemDataRole role) const {
|
Wt::cpp17::any VisitorRecordTableModel::data(const Wt::WModelIndex &index, Wt::ItemDataRole role) const {
|
||||||
LOG(info) << "VisitorRecordTableModel: " << role.value();
|
// 选择框
|
||||||
|
int row = index.row();
|
||||||
|
auto &record = std::get<0>(m_records.at(row));
|
||||||
if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) &&
|
if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) &&
|
||||||
index.column() == 0) { // Assuming first column for checkbox
|
index.column() == 0) { // Assuming first column for checkbox
|
||||||
|
return std::get<1>(m_records.at(row));
|
||||||
return true;
|
} else if ((index.column() == 1) && (role == Wt::ItemDataRole::Display)) {
|
||||||
} else if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) &&
|
auto pos = record.url.rfind("/");
|
||||||
index.column() == 1) { // Assuming first column for checkbox
|
auto name = record.url;
|
||||||
|
if (pos != std::string::npos && pos < (name.size() - 1)) {
|
||||||
return false;
|
name = name.substr(pos + 1);
|
||||||
|
}
|
||||||
|
return std::format("<a href=\"{}\">{}</a>", record.url, name);
|
||||||
|
} else if ((index.column() == 2) && (role == Wt::ItemDataRole::Display)) {
|
||||||
|
return DateTime::toString(record.time);
|
||||||
|
} else if ((index.column() == 3) && (role == Wt::ItemDataRole::Display)) {
|
||||||
|
return record.userAgent;
|
||||||
|
} else if ((index.column() == 4) && (role == Wt::ItemDataRole::Display)) {
|
||||||
|
return record.visitorUuid;
|
||||||
} else {
|
} else {
|
||||||
return Wt::cpp17::any{};
|
return Wt::cpp17::any{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VisitorRecordTableModel::setData(const Wt::WModelIndex &index, const Wt::cpp17::any &value, Wt::ItemDataRole role) {
|
||||||
|
int row = index.row();
|
||||||
|
if ((index.column() == 0) && (role == Wt::ItemDataRole::Checked)) {
|
||||||
|
|
||||||
|
std::get<1>(m_records[row]) = Wt::cpp17::any_cast<bool>(value);
|
||||||
|
auto iterator = std::find_if_not(m_records.begin(), m_records.end(), [](const Item &item) { return std::get<1>(item); });
|
||||||
|
if (iterator == m_records.end()) {
|
||||||
|
if (!m_selectAll) {
|
||||||
|
m_selectAll = true;
|
||||||
|
headerDataChanged().emit(Wt::Orientation::Horizontal, 0, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (m_selectAll) {
|
||||||
|
m_selectAll = false;
|
||||||
|
headerDataChanged().emit(Wt::Orientation::Horizontal, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return Wt::WAbstractTableModel::setData(index, value, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VisitorRecord> VisitorRecordTableModel::selected() const {
|
||||||
|
std::vector<VisitorRecord> ret;
|
||||||
|
for (int i = 0; i < m_records.size(); i++) {
|
||||||
|
if (std::get<1>(m_records.at(i))) {
|
||||||
|
ret.push_back(std::get<0>(m_records.at(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisitorRecordTableModel::removeSelected() {
|
||||||
|
Wt::Dbo::Transaction transaction(m_session);
|
||||||
|
for (auto iterator = m_records.cbegin(); iterator != m_records.cend();) {
|
||||||
|
auto selected = std::get<1>(*iterator);
|
||||||
|
auto &r = std::get<0>(*iterator);
|
||||||
|
if (selected) {
|
||||||
|
Wt::Dbo::ptr<VisitorRecord> item = m_session.find<VisitorRecord>().where("id = ?").bind(r.id);
|
||||||
|
item.remove();
|
||||||
|
iterator = m_records.erase(iterator);
|
||||||
|
} else {
|
||||||
|
++iterator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,33 @@
|
|||||||
#ifndef __VISITORRECORDTABLEMODEL_H__
|
#ifndef __VISITORRECORDTABLEMODEL_H__
|
||||||
#define __VISITORRECORDTABLEMODEL_H__
|
#define __VISITORRECORDTABLEMODEL_H__
|
||||||
|
|
||||||
|
#include "Database/VisitorRecord.h"
|
||||||
#include <Wt/WAbstractTableModel.h>
|
#include <Wt/WAbstractTableModel.h>
|
||||||
|
|
||||||
|
class Session;
|
||||||
|
|
||||||
class VisitorRecordTableModel : public Wt::WAbstractTableModel {
|
class VisitorRecordTableModel : public Wt::WAbstractTableModel {
|
||||||
public:
|
public:
|
||||||
|
VisitorRecordTableModel(Session &session);
|
||||||
Wt::cpp17::any data(const Wt::WModelIndex &index, Wt::ItemDataRole role = Wt::ItemDataRole::Display) const final;
|
Wt::cpp17::any data(const Wt::WModelIndex &index, Wt::ItemDataRole role = Wt::ItemDataRole::Display) const final;
|
||||||
|
bool setData(const Wt::WModelIndex &index, const Wt::cpp17::any &value, Wt::ItemDataRole role = Wt::ItemDataRole::Edit) final;
|
||||||
int columnCount(const Wt::WModelIndex &parent = Wt::WModelIndex()) const final;
|
int columnCount(const Wt::WModelIndex &parent = Wt::WModelIndex()) const final;
|
||||||
int rowCount(const Wt::WModelIndex &parent = Wt::WModelIndex()) const final;
|
int rowCount(const Wt::WModelIndex &parent = Wt::WModelIndex()) const final;
|
||||||
Wt::WFlags<Wt::ItemFlag> flags(const Wt::WModelIndex &index) const final;
|
Wt::WFlags<Wt::ItemFlag> flags(const Wt::WModelIndex &index) const final;
|
||||||
Wt::WFlags<Wt::HeaderFlag> headerFlags(int section, Wt::Orientation orientation = Wt::Orientation::Horizontal) const final;
|
Wt::WFlags<Wt::HeaderFlag> headerFlags(int section, Wt::Orientation orientation = Wt::Orientation::Horizontal) const final;
|
||||||
Wt::cpp17::any headerData(int section, Wt::Orientation orientation = Wt::Orientation::Horizontal,
|
Wt::cpp17::any headerData(int section, Wt::Orientation orientation = Wt::Orientation::Horizontal,
|
||||||
Wt::ItemDataRole role = Wt::ItemDataRole::Display) const final;
|
Wt::ItemDataRole role = Wt::ItemDataRole::Display) const final;
|
||||||
|
bool setHeaderData(int section, Wt::Orientation orientation, const Wt::cpp17::any &value,
|
||||||
|
Wt::ItemDataRole role = Wt::ItemDataRole::Edit) final;
|
||||||
|
void sort(int column, Wt::SortOrder order = Wt::SortOrder::Ascending) final;
|
||||||
|
|
||||||
|
std::vector<VisitorRecord> selected() const;
|
||||||
|
void removeSelected();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Session &m_session;
|
||||||
|
using Item = std::tuple<VisitorRecord, bool>;
|
||||||
|
std::vector<Item> m_records;
|
||||||
|
bool m_selectAll = false;
|
||||||
};
|
};
|
||||||
#endif // __VISITORRECORDTABLEMODEL_H__
|
#endif // __VISITORRECORDTABLEMODEL_H__
|
@ -3,59 +3,60 @@
|
|||||||
#include "Database/Session.h"
|
#include "Database/Session.h"
|
||||||
#include "VisitorRecordTableModel.h"
|
#include "VisitorRecordTableModel.h"
|
||||||
#include <Wt/Dbo/QueryModel.h>
|
#include <Wt/Dbo/QueryModel.h>
|
||||||
|
#include <Wt/WBreak.h>
|
||||||
#include <Wt/WItemDelegate.h>
|
#include <Wt/WItemDelegate.h>
|
||||||
#include <Wt/WLabel.h>
|
#include <Wt/WLabel.h>
|
||||||
|
#include <Wt/WMessageBox.h>
|
||||||
|
#include <Wt/WPushButton.h>
|
||||||
#include <Wt/WTableView.h>
|
#include <Wt/WTableView.h>
|
||||||
|
|
||||||
class CheckBoxDelegate : public Wt::WAbstractItemDelegate {
|
VisitorRecordsPage::VisitorRecordsPage(Session &session) : m_model{std::make_shared<VisitorRecordTableModel>(session)} {
|
||||||
public:
|
|
||||||
CheckBoxDelegate() : Wt::WAbstractItemDelegate() {
|
|
||||||
}
|
|
||||||
std::unique_ptr<Wt::WWidget> update(Wt::WWidget *widget, const Wt::WModelIndex &index,
|
|
||||||
Wt::WFlags<Wt::ViewItemRenderFlag> flags) final {
|
|
||||||
auto checkBox = dynamic_cast<Wt::WCheckBox *>(widget);
|
|
||||||
if (!checkBox) {
|
|
||||||
checkBox = new Wt::WCheckBox();
|
|
||||||
}
|
|
||||||
LOG(info) << Wt::asString(index.data());
|
|
||||||
// checkBox->setChecked(index.data().toBool());
|
|
||||||
checkBox->setEnabled(true);
|
|
||||||
|
|
||||||
return std::unique_ptr<Wt::WWidget>(checkBox);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
VisitorRecordsPage::VisitorRecordsPage(Session &session) {
|
|
||||||
addNew<Wt::WLabel>("访客数据统计");
|
addNew<Wt::WLabel>("访客数据统计");
|
||||||
|
addNew<Wt::WBreak>();
|
||||||
|
auto button = addNew<Wt::WPushButton>("删除");
|
||||||
|
button->clicked().connect(this, &VisitorRecordsPage::onDeleteButtonClicked);
|
||||||
|
|
||||||
auto view = addNew<Wt::WTableView>();
|
auto view = addNew<Wt::WTableView>();
|
||||||
view->setColumnWidth(0, Wt::WLength::Auto);
|
view->setColumnWidth(0, Wt::WLength::Auto);
|
||||||
view->setColumnWidth(1, Wt::WLength::Auto);
|
view->setColumnWidth(1, Wt::WLength::Auto);
|
||||||
view->setHeaderHeight(30); // 设置表头的高度
|
view->setHeaderHeight(30); // 设置表头的高度
|
||||||
view->setWidth(Wt::WLength("99%"));
|
|
||||||
view->setColumnResizeEnabled(true);
|
view->setColumnResizeEnabled(true);
|
||||||
view->setAlternatingRowColors(true);
|
view->setAlternatingRowColors(true); // 限制于 stripe-40px.gif 类似的文件,wt 有提供脚本生成
|
||||||
view->setRowHeight(50);
|
view->setRowHeight(50);
|
||||||
view->setSelectionMode(Wt::SelectionMode::None);
|
view->setSelectionMode(Wt::SelectionMode::None);
|
||||||
view->setColumnAlignment(0, Wt::AlignmentFlag::Center);
|
view->setColumnAlignment(0, Wt::AlignmentFlag::Center);
|
||||||
view->setHeaderAlignment(0,Wt::AlignmentFlag::Center);
|
view->setColumnAlignment(2, Wt::AlignmentFlag::Center);
|
||||||
view->setSortingEnabled(0,false);
|
view->setColumnAlignment(4, Wt::AlignmentFlag::Center);
|
||||||
|
|
||||||
|
view->setHeaderAlignment(0, Wt::AlignmentFlag::Center);
|
||||||
|
view->setHeaderAlignment(1, Wt::AlignmentFlag::Center);
|
||||||
|
view->setHeaderAlignment(2, Wt::AlignmentFlag::Center);
|
||||||
|
view->setHeaderAlignment(3, Wt::AlignmentFlag::Center);
|
||||||
|
view->setHeaderAlignment(4, Wt::AlignmentFlag::Center);
|
||||||
|
|
||||||
|
view->setSortingEnabled(0, false);
|
||||||
|
view->setSortingEnabled(1, false);
|
||||||
|
view->setSortingEnabled(3, false);
|
||||||
|
view->setSortingEnabled(4, false);
|
||||||
// view->setEditTriggers(Wt::EditTrigger::None);
|
// view->setEditTriggers(Wt::EditTrigger::None);
|
||||||
// view->setItemDelegateForColumn(0, std::make_shared<CheckBoxDelegate>());
|
// view->setItemDelegateForColumn(0, std::make_shared<CheckBoxDelegate>());
|
||||||
// view->setHeaderItemDelegate( std::make_shared<CheckBoxDelegate>());
|
// view->setHeaderItemDelegate( std::make_shared<CheckBoxDelegate>());
|
||||||
|
|
||||||
// auto model = std::make_shared<Wt::Dbo::QueryModel<Wt::Dbo::ptr<VisitorRecord>>>();
|
view->setModel(m_model);
|
||||||
// model->setQuery(session.find<VisitorRecord>());
|
view->setColumnWidth(0, 60);
|
||||||
// model->addColumn("id", "ID",Wt::ItemFlag::UserCheckable);
|
view->setColumnWidth(1, 180);
|
||||||
// model->addColumn("url", "URL");
|
view->setColumnWidth(2, 180);
|
||||||
// model->addColumn("time", "Time");
|
view->setColumnWidth(3, 480);
|
||||||
// model->addColumn("user_agent", "User Agent");
|
}
|
||||||
auto model = std::make_shared<VisitorRecordTableModel>();
|
|
||||||
view->setModel(model);
|
void VisitorRecordsPage::onDeleteButtonClicked() {
|
||||||
|
auto selected = m_model->selected();
|
||||||
/*
|
if (selected.empty()) {
|
||||||
* Configure column widths and matching table width
|
Wt::WMessageBox::show("删除记录", "请先选择要删除的记录", Wt::StandardButton::Ok);
|
||||||
*/
|
} else {
|
||||||
const int WIDTH = 240;
|
auto anwser =
|
||||||
for (int i = 0; i < view->model()->columnCount(); ++i) view->setColumnWidth(i, 120);
|
Wt::WMessageBox::show("删除记录", "确定删除选择的记录?", Wt::StandardButton::Ok | Wt::StandardButton::Cancel);
|
||||||
|
if (anwser == Wt::StandardButton::Ok) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,12 +4,19 @@
|
|||||||
#include <Wt/WContainerWidget.h>
|
#include <Wt/WContainerWidget.h>
|
||||||
|
|
||||||
class Session;
|
class Session;
|
||||||
|
class VisitorRecordTableModel;
|
||||||
|
|
||||||
// https://infima.dev/
|
// https://infima.dev/
|
||||||
|
|
||||||
class VisitorRecordsPage : public Wt::WContainerWidget {
|
class VisitorRecordsPage : public Wt::WContainerWidget {
|
||||||
public:
|
public:
|
||||||
VisitorRecordsPage(Session &session);
|
VisitorRecordsPage(Session &session);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDeleteButtonClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<VisitorRecordTableModel> m_model;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // __VISITORRECORDSPAGE_H__
|
#endif // __VISITORRECORDSPAGE_H__
|
Loading…
Reference in New Issue
Block a user