Punishments.vue 8.0 KB

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