YouTube.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <template>
  2. <div class="admin-tab container">
  3. <page-metadata title="Admin | YouTube" />
  4. <div class="card">
  5. <h1>YouTube API</h1>
  6. <p>
  7. Analyze YouTube quota usage and API requests made on this
  8. instance
  9. </p>
  10. </div>
  11. <div class="card">
  12. <h4>Quota Stats</h4>
  13. <hr class="section-horizontal-rule" />
  14. <div class="card-content">
  15. <p v-if="fromDate">As of {{ fromDate }}</p>
  16. <div
  17. v-for="[quotaName, quotaObject] in Object.entries(
  18. quotaStatus
  19. )"
  20. :key="quotaName"
  21. >
  22. <p>{{ quotaName }}</p>
  23. <hr />
  24. <p>Quota used: {{ quotaObject.quotaUsed }}</p>
  25. <p>Limit: {{ quotaObject.limit }}</p>
  26. <p>Quota exceeded: {{ quotaObject.quotaExceeded }}</p>
  27. </div>
  28. </div>
  29. </div>
  30. <div class="card">
  31. <h4>API Requests</h4>
  32. <hr class="section-horizontal-rule" />
  33. <advanced-table
  34. :column-default="columnDefault"
  35. :columns="columns"
  36. :filters="filters"
  37. data-action="youtube.getApiRequests"
  38. name="admin-youtube-api-requests"
  39. :max-width="1140"
  40. >
  41. <template #column-options="slotProps">
  42. <div class="row-options">
  43. <button
  44. class="button is-primary icon-with-button material-icons"
  45. @click="
  46. openModal({
  47. modal: 'viewApiRequest',
  48. data: {
  49. requestId: slotProps.item._id
  50. }
  51. })
  52. "
  53. :disabled="slotProps.item.removed"
  54. content="View API Request"
  55. v-tippy
  56. >
  57. open_in_full
  58. </button>
  59. </div>
  60. </template>
  61. <template #column-_id="slotProps">
  62. <span :title="slotProps.item._id">{{
  63. slotProps.item._id
  64. }}</span>
  65. </template>
  66. <template #column-quotaCost="slotProps">
  67. <span :title="slotProps.item.quotaCost">{{
  68. slotProps.item.quotaCost
  69. }}</span>
  70. </template>
  71. <template #column-timestamp="slotProps">
  72. <span :title="new Date(slotProps.item.date)">{{
  73. getDateFormatted(slotProps.item.date)
  74. }}</span>
  75. </template>
  76. <template #column-url="slotProps">
  77. <span :title="slotProps.item.url">{{
  78. slotProps.item.url
  79. }}</span>
  80. </template>
  81. </advanced-table>
  82. </div>
  83. </div>
  84. </template>
  85. <script>
  86. import { mapActions, mapGetters } from "vuex";
  87. import AdvancedTable from "@/components/AdvancedTable.vue";
  88. import ws from "@/ws";
  89. export default {
  90. components: {
  91. AdvancedTable
  92. },
  93. data() {
  94. return {
  95. quotaStatus: {},
  96. fromDate: null,
  97. columnDefault: {
  98. sortable: true,
  99. hidable: true,
  100. defaultVisibility: "shown",
  101. draggable: true,
  102. resizable: true,
  103. minWidth: 150,
  104. maxWidth: 600
  105. },
  106. columns: [
  107. {
  108. name: "options",
  109. displayName: "Options",
  110. properties: ["_id"],
  111. hidable: false,
  112. resizable: false,
  113. minWidth: 76,
  114. defaultWidth: 76
  115. },
  116. {
  117. name: "quotaCost",
  118. displayName: "Quota Cost",
  119. properties: ["quotaCost"],
  120. sortProperty: ["quotaCost"],
  121. minWidth: 150,
  122. defaultWidth: 150
  123. },
  124. {
  125. name: "timestamp",
  126. displayName: "Timestamp",
  127. properties: ["date"],
  128. sortProperty: ["date"],
  129. minWidth: 150,
  130. defaultWidth: 150
  131. },
  132. {
  133. name: "url",
  134. displayName: "URL",
  135. properties: ["url"],
  136. sortProperty: ["url"]
  137. },
  138. {
  139. name: "_id",
  140. displayName: "Request ID",
  141. properties: ["_id"],
  142. sortProperty: ["_id"],
  143. minWidth: 230,
  144. defaultWidth: 230
  145. }
  146. ],
  147. filters: [
  148. {
  149. name: "_id",
  150. displayName: "Request ID",
  151. property: "_id",
  152. filterTypes: ["exact"],
  153. defaultFilterType: "exact"
  154. },
  155. {
  156. name: "quotaCost",
  157. displayName: "Quota Cost",
  158. property: "quotaCost",
  159. filterTypes: [
  160. "numberLesserEqual",
  161. "numberLesser",
  162. "numberGreater",
  163. "numberGreaterEqual",
  164. "numberEquals"
  165. ],
  166. defaultFilterType: "numberLesser"
  167. },
  168. {
  169. name: "timestamp",
  170. displayName: "Timestamp",
  171. property: "date",
  172. filterTypes: ["datetimeBefore", "datetimeAfter"],
  173. defaultFilterType: "datetimeBefore"
  174. },
  175. {
  176. name: "url",
  177. displayName: "URL",
  178. property: "url",
  179. filterTypes: ["contains", "exact", "regex"],
  180. defaultFilterType: "contains"
  181. }
  182. ]
  183. };
  184. },
  185. computed: mapGetters({
  186. socket: "websockets/getSocket"
  187. }),
  188. mounted() {
  189. ws.onConnect(this.init);
  190. },
  191. methods: {
  192. init() {
  193. if (this.$route.query.fromDate)
  194. this.fromDate = this.$route.query.fromDate;
  195. this.socket.dispatch(
  196. "youtube.getQuotaStatus",
  197. this.fromDate,
  198. res => {
  199. if (res.status === "success")
  200. this.quotaStatus = res.data.status;
  201. }
  202. );
  203. },
  204. getDateFormatted(createdAt) {
  205. const date = new Date(createdAt);
  206. const year = date.getFullYear();
  207. const month = `${date.getMonth() + 1}`.padStart(2, 0);
  208. const day = `${date.getDate()}`.padStart(2, 0);
  209. const hour = `${date.getHours()}`.padStart(2, 0);
  210. const minute = `${date.getMinutes()}`.padStart(2, 0);
  211. return `${year}-${month}-${day} ${hour}:${minute}`;
  212. },
  213. ...mapActions("modalVisibility", ["openModal"])
  214. }
  215. };
  216. </script>
  217. <style lang="less" scoped>
  218. .night-mode {
  219. .table {
  220. color: var(--light-grey-2);
  221. background-color: var(--dark-grey-3);
  222. thead tr {
  223. background: var(--dark-grey-3);
  224. td {
  225. color: var(--white);
  226. }
  227. }
  228. tbody tr:hover {
  229. background-color: var(--dark-grey-4) !important;
  230. }
  231. tbody tr:nth-child(even) {
  232. background-color: var(--dark-grey-2);
  233. }
  234. strong {
  235. color: var(--light-grey-2);
  236. }
  237. }
  238. }
  239. td {
  240. vertical-align: middle;
  241. }
  242. .is-primary:focus {
  243. background-color: var(--primary-color) !important;
  244. }
  245. </style>