Punishments.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <script setup lang="ts">
  2. import { defineAsyncComponent, ref } from "vue";
  3. import Toast from "toasters";
  4. import { useWebsocketsStore } from "@/stores/websockets";
  5. import { useModalsStore } from "@/stores/modals";
  6. import { TableColumn, TableFilter, TableEvents } from "@/types/advancedTable";
  7. const AdvancedTable = defineAsyncComponent(
  8. () => import("@/components/AdvancedTable.vue")
  9. );
  10. const { socket } = useWebsocketsStore();
  11. const ipBan = ref({
  12. ip: "",
  13. reason: "",
  14. expiresAt: "1h"
  15. });
  16. const columnDefault = ref(<TableColumn>{
  17. sortable: true,
  18. hidable: true,
  19. defaultVisibility: "shown",
  20. draggable: true,
  21. resizable: true,
  22. minWidth: 150,
  23. maxWidth: 600
  24. });
  25. const columns = ref(<TableColumn[]>[
  26. {
  27. name: "options",
  28. displayName: "Options",
  29. properties: ["_id", "active", "status"],
  30. sortable: false,
  31. hidable: false,
  32. resizable: false,
  33. minWidth: 76,
  34. defaultWidth: 76
  35. },
  36. {
  37. name: "status",
  38. displayName: "Status",
  39. properties: ["status"],
  40. sortable: false,
  41. defaultWidth: 150
  42. },
  43. {
  44. name: "type",
  45. displayName: "Type",
  46. properties: ["type"],
  47. sortProperty: "type"
  48. },
  49. {
  50. name: "value",
  51. displayName: "Value",
  52. properties: ["value"],
  53. sortProperty: "value",
  54. defaultWidth: 150
  55. },
  56. {
  57. name: "reason",
  58. displayName: "Reason",
  59. properties: ["reason"],
  60. sortProperty: "reason"
  61. },
  62. {
  63. name: "punishedBy",
  64. displayName: "Punished By",
  65. properties: ["punishedBy"],
  66. sortProperty: "punishedBy",
  67. defaultWidth: 200,
  68. defaultVisibility: "hidden"
  69. },
  70. {
  71. name: "punishedAt",
  72. displayName: "Punished At",
  73. properties: ["punishedAt"],
  74. sortProperty: "punishedAt",
  75. defaultWidth: 200,
  76. defaultVisibility: "hidden"
  77. },
  78. {
  79. name: "expiresAt",
  80. displayName: "Expires At",
  81. properties: ["expiresAt"],
  82. sortProperty: "verifiedAt",
  83. defaultWidth: 200,
  84. defaultVisibility: "hidden"
  85. }
  86. ]);
  87. const filters = ref(<TableFilter[]>[
  88. {
  89. name: "status",
  90. displayName: "Status",
  91. property: "status",
  92. filterTypes: ["exact"],
  93. defaultFilterType: "exact",
  94. dropdown: [
  95. ["Active", "Active"],
  96. ["Inactive", "Inactive"]
  97. ]
  98. },
  99. {
  100. name: "type",
  101. displayName: "Type",
  102. property: "type",
  103. filterTypes: ["exact"],
  104. defaultFilterType: "exact",
  105. dropdown: [
  106. ["banUserId", "User ID"],
  107. ["banUserIp", "IP Address"]
  108. ]
  109. },
  110. {
  111. name: "value",
  112. displayName: "Value",
  113. property: "value",
  114. filterTypes: ["contains", "exact", "regex"],
  115. defaultFilterType: "contains"
  116. },
  117. {
  118. name: "reason",
  119. displayName: "Reason",
  120. property: "reason",
  121. filterTypes: ["contains", "exact", "regex"],
  122. defaultFilterType: "contains"
  123. },
  124. {
  125. name: "punishedBy",
  126. displayName: "Punished By",
  127. property: "punishedBy",
  128. filterTypes: ["contains", "exact", "regex"],
  129. defaultFilterType: "contains"
  130. },
  131. {
  132. name: "punishedAt",
  133. displayName: "Punished At",
  134. property: "punishedAt",
  135. filterTypes: ["datetimeBefore", "datetimeAfter"],
  136. defaultFilterType: "datetimeBefore"
  137. },
  138. {
  139. name: "expiresAt",
  140. displayName: "Expires At",
  141. property: "expiresAt",
  142. filterTypes: ["datetimeBefore", "datetimeAfter"],
  143. defaultFilterType: "datetimeBefore"
  144. }
  145. ]);
  146. const events = ref(<TableEvents>{
  147. adminRoom: "punishments",
  148. updated: {
  149. event: "admin.punishment.updated",
  150. id: "punishment._id",
  151. item: "punishment"
  152. }
  153. });
  154. const { openModal } = useModalsStore();
  155. const banIP = () => {
  156. socket.dispatch(
  157. "punishments.banIP",
  158. ipBan.value.ip,
  159. ipBan.value.reason,
  160. ipBan.value.expiresAt,
  161. res => {
  162. new Toast(res.message);
  163. }
  164. );
  165. };
  166. const getDateFormatted = createdAt => {
  167. const date = new Date(createdAt);
  168. const year = date.getFullYear();
  169. const month = `${date.getMonth() + 1}`.padStart(2, "0");
  170. const day = `${date.getDate()}`.padStart(2, "0");
  171. const hour = `${date.getHours()}`.padStart(2, "0");
  172. const minute = `${date.getMinutes()}`.padStart(2, "0");
  173. return `${year}-${month}-${day} ${hour}:${minute}`;
  174. };
  175. const deactivatePunishment = punishmentId => {
  176. socket.dispatch("punishments.deactivatePunishment", punishmentId, res => {
  177. if (res.status === "success") {
  178. new Toast("Successfully deactivated punishment.");
  179. } else {
  180. new Toast(res.message);
  181. }
  182. });
  183. };
  184. </script>
  185. <template>
  186. <div class="admin-tab container">
  187. <page-metadata title="Admin | Users | Punishments" />
  188. <div class="card tab-info">
  189. <div class="info-row">
  190. <h1>Punishments</h1>
  191. <p>Manage punishments or ban an IP</p>
  192. </div>
  193. </div>
  194. <advanced-table
  195. :column-default="columnDefault"
  196. :columns="columns"
  197. :filters="filters"
  198. :events="events"
  199. data-action="punishments.getData"
  200. name="admin-punishments"
  201. :max-width="1200"
  202. >
  203. <template #column-options="slotProps">
  204. <div class="row-options">
  205. <button
  206. class="button is-primary icon-with-button material-icons"
  207. @click="
  208. openModal({
  209. modal: 'viewPunishment',
  210. data: { punishmentId: slotProps.item._id }
  211. })
  212. "
  213. :disabled="slotProps.item.removed"
  214. content="View Punishment"
  215. v-tippy
  216. >
  217. open_in_full
  218. </button>
  219. <quick-confirm
  220. @confirm="deactivatePunishment(slotProps.item._id)"
  221. :disabled="
  222. !slotProps.item.active || slotProps.item.removed
  223. "
  224. >
  225. <button
  226. class="button is-danger icon-with-button material-icons"
  227. :class="{
  228. disabled:
  229. !slotProps.item.active ||
  230. slotProps.item.removed
  231. }"
  232. :disabled="
  233. !slotProps.item.active || slotProps.item.removed
  234. "
  235. content="Deactivate Punishment"
  236. v-tippy
  237. >
  238. gavel
  239. </button>
  240. </quick-confirm>
  241. </div>
  242. </template>
  243. <template #column-status="slotProps">
  244. <span>{{ slotProps.item.status }}</span>
  245. </template>
  246. <template #column-type="slotProps">
  247. <span
  248. :title="
  249. slotProps.item.type === 'banUserId'
  250. ? 'User ID'
  251. : 'IP Address'
  252. "
  253. >{{
  254. slotProps.item.type === "banUserId"
  255. ? "User ID"
  256. : "IP Address"
  257. }}</span
  258. >
  259. </template>
  260. <template #column-value="slotProps">
  261. <user-link
  262. v-if="slotProps.item.type === 'banUserId'"
  263. :user-id="slotProps.item.value"
  264. :alt="slotProps.item.value"
  265. />
  266. <span v-else :title="slotProps.item.value">{{
  267. slotProps.item.value
  268. }}</span>
  269. </template>
  270. <template #column-reason="slotProps">
  271. <span :title="slotProps.item.reason">{{
  272. slotProps.item.reason
  273. }}</span>
  274. </template>
  275. <template #column-punishedBy="slotProps">
  276. <user-link :user-id="slotProps.item.punishedBy" />
  277. </template>
  278. <template #column-punishedAt="slotProps">
  279. <span :title="new Date(slotProps.item.punishedAt).toString()">{{
  280. getDateFormatted(slotProps.item.punishedAt)
  281. }}</span>
  282. </template>
  283. <template #column-expiresAt="slotProps">
  284. <span :title="new Date(slotProps.item.expiresAt).toString()">{{
  285. getDateFormatted(slotProps.item.expiresAt)
  286. }}</span>
  287. </template>
  288. </advanced-table>
  289. <div class="card">
  290. <h4>Ban an IP</h4>
  291. <hr class="section-horizontal-rule" />
  292. <div class="card-content">
  293. <label class="label">Expires In</label>
  294. <p class="control is-expanded select">
  295. <select v-model="ipBan.expiresAt">
  296. <option value="1h">1 Hour</option>
  297. <option value="12h">12 Hours</option>
  298. <option value="1d">1 Day</option>
  299. <option value="1w">1 Week</option>
  300. <option value="1m">1 Month</option>
  301. <option value="3m">3 Months</option>
  302. <option value="6m">6 Months</option>
  303. <option value="1y">1 Year</option>
  304. </select>
  305. </p>
  306. <label class="label">IP</label>
  307. <p class="control is-expanded">
  308. <input
  309. v-model="ipBan.ip"
  310. class="input"
  311. type="text"
  312. placeholder="IP address (xxx.xxx.xxx.xxx)"
  313. />
  314. </p>
  315. <label class="label">Reason</label>
  316. <p class="control is-expanded">
  317. <input
  318. v-model="ipBan.reason"
  319. class="input"
  320. type="text"
  321. placeholder="Reason"
  322. />
  323. </p>
  324. <button class="button is-primary" @click="banIP()">
  325. Ban IP
  326. </button>
  327. </div>
  328. </div>
  329. </div>
  330. </template>
  331. <style lang="less" scoped>
  332. .card .button.is-primary {
  333. width: 100%;
  334. }
  335. </style>