@necrolab/dashboard 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/.claude/settings.local.json +45 -0
  2. package/.eslintrc.js +24 -0
  3. package/.prettierignore +1 -0
  4. package/.prettierrc +10 -0
  5. package/.vscode/extensions.json +3 -0
  6. package/ICONS.md +21 -0
  7. package/README.md +65 -0
  8. package/backend/api.js +430 -0
  9. package/backend/auth.js +62 -0
  10. package/backend/batching.js +43 -0
  11. package/backend/endpoints.js +343 -0
  12. package/backend/index.js +23 -0
  13. package/backend/mock-data.js +66 -0
  14. package/backend/mock-src/classes/logger.js +112 -0
  15. package/backend/mock-src/classes/utils.js +42 -0
  16. package/backend/mock-src/ticketmaster.js +92 -0
  17. package/backend/validator.js +62 -0
  18. package/config/configs.json +20 -0
  19. package/config/filter.json +3 -0
  20. package/config/presale.csv +3 -0
  21. package/config/proxies.txt +6 -0
  22. package/config/used-codes.json +4 -0
  23. package/index.html +114 -0
  24. package/index.js +2 -0
  25. package/jsconfig.json +16 -0
  26. package/package.json +48 -0
  27. package/postcss.config.js +6 -0
  28. package/postinstall.js +9 -0
  29. package/public/android-chrome-192x192.png +0 -0
  30. package/public/android-chrome-512x512.png +0 -0
  31. package/public/apple-touch-icon.png +0 -0
  32. package/public/favicon-16x16.png +0 -0
  33. package/public/favicon-32x32.png +0 -0
  34. package/public/favicon.ico +0 -0
  35. package/public/flags/ae.svg +1 -0
  36. package/public/flags/at.svg +1 -0
  37. package/public/flags/au.svg +1 -0
  38. package/public/flags/be.svg +1 -0
  39. package/public/flags/ch.svg +1 -0
  40. package/public/flags/cz.svg +1 -0
  41. package/public/flags/de.svg +1 -0
  42. package/public/flags/dk.svg +1 -0
  43. package/public/flags/es.svg +1 -0
  44. package/public/flags/nl.svg +1 -0
  45. package/public/flags/no.svg +1 -0
  46. package/public/flags/nz.svg +1 -0
  47. package/public/flags/pl.svg +1 -0
  48. package/public/flags/se.svg +1 -0
  49. package/public/flags/uk.svg +1 -0
  50. package/public/flags/us.svg +1 -0
  51. package/public/img/award.svg +3 -0
  52. package/public/img/background.svg +14 -0
  53. package/public/img/bag_w.svg +12 -0
  54. package/public/img/banks/amex.svg +4 -0
  55. package/public/img/banks/mastercard.svg +4 -0
  56. package/public/img/banks/visa.svg +4 -0
  57. package/public/img/camera.svg +3 -0
  58. package/public/img/close.svg +3 -0
  59. package/public/img/controls/disable.svg +5 -0
  60. package/public/img/controls/enable.svg +5 -0
  61. package/public/img/groups.svg +3 -0
  62. package/public/img/hand.svg +3 -0
  63. package/public/img/key.svg +3 -0
  64. package/public/img/logo.png +0 -0
  65. package/public/img/logo_icon.png +0 -0
  66. package/public/img/logo_icon_2.png +0 -0
  67. package/public/img/logo_trans.png +0 -0
  68. package/public/img/loyalty.svg +3 -0
  69. package/public/img/mail.svg +3 -0
  70. package/public/img/pencil.svg +3 -0
  71. package/public/img/profile.svg +4 -0
  72. package/public/img/reload.svg +3 -0
  73. package/public/img/sandclock.svg +25 -0
  74. package/public/img/save.svg +5 -0
  75. package/public/img/savings.svg +3 -0
  76. package/public/img/scanner.svg +3 -0
  77. package/public/img/sell.svg +3 -0
  78. package/public/img/shield.svg +3 -0
  79. package/public/img/ski.svg +3 -0
  80. package/public/img/stadium.svg +8 -0
  81. package/public/img/stadium_w.svg +8 -0
  82. package/public/img/timer.svg +3 -0
  83. package/public/manifest.json +27 -0
  84. package/public/robots.txt +2 -0
  85. package/run +10 -0
  86. package/src/App.vue +307 -0
  87. package/src/assets/css/_input.scss +197 -0
  88. package/src/assets/css/main.scss +269 -0
  89. package/src/assets/css/tailwind.css +3 -0
  90. package/src/assets/img/award.svg +3 -0
  91. package/src/assets/img/background.svg +11 -0
  92. package/src/assets/img/camera.svg +3 -0
  93. package/src/assets/img/close.svg +3 -0
  94. package/src/assets/img/eyes/closed.svg +13 -0
  95. package/src/assets/img/eyes/open.svg +12 -0
  96. package/src/assets/img/groups.svg +3 -0
  97. package/src/assets/img/hand.svg +3 -0
  98. package/src/assets/img/key.svg +3 -0
  99. package/src/assets/img/logo.png +0 -0
  100. package/src/assets/img/logo_icon.png +0 -0
  101. package/src/assets/img/logo_icon_2.png +0 -0
  102. package/src/assets/img/logo_trans.png +0 -0
  103. package/src/assets/img/loyalty.svg +3 -0
  104. package/src/assets/img/mail.svg +3 -0
  105. package/src/assets/img/pencil.svg +3 -0
  106. package/src/assets/img/reload.svg +3 -0
  107. package/src/assets/img/savings.svg +3 -0
  108. package/src/assets/img/scanner.svg +3 -0
  109. package/src/assets/img/sell.svg +3 -0
  110. package/src/assets/img/shield.svg +3 -0
  111. package/src/assets/img/ski.svg +3 -0
  112. package/src/assets/img/square_check.svg +5 -0
  113. package/src/assets/img/square_uncheck.svg +5 -0
  114. package/src/assets/img/stadium.svg +8 -0
  115. package/src/assets/img/timer.svg +3 -0
  116. package/src/assets/img/wildcard.svg +7 -0
  117. package/src/components/Auth/LoginForm.vue +48 -0
  118. package/src/components/Editors/Account/Account.vue +119 -0
  119. package/src/components/Editors/Account/AccountCreator.vue +147 -0
  120. package/src/components/Editors/Account/AccountView.vue +87 -0
  121. package/src/components/Editors/Account/CreateAccount.vue +106 -0
  122. package/src/components/Editors/Profile/CreateProfile.vue +321 -0
  123. package/src/components/Editors/Profile/Profile.vue +142 -0
  124. package/src/components/Editors/Profile/ProfileCountryChooser.vue +75 -0
  125. package/src/components/Editors/Profile/ProfileView.vue +96 -0
  126. package/src/components/Editors/TagLabel.vue +16 -0
  127. package/src/components/Editors/TagToggle.vue +41 -0
  128. package/src/components/Filter/Filter.vue +409 -0
  129. package/src/components/Filter/FilterPreview.vue +236 -0
  130. package/src/components/Filter/PriceSortToggle.vue +105 -0
  131. package/src/components/Table/Header.vue +5 -0
  132. package/src/components/Table/Row.vue +5 -0
  133. package/src/components/Table/Table.vue +14 -0
  134. package/src/components/Table/index.js +4 -0
  135. package/src/components/Tasks/CheckStock.vue +62 -0
  136. package/src/components/Tasks/Controls/DesktopControls.vue +73 -0
  137. package/src/components/Tasks/Controls/MobileControls.vue +32 -0
  138. package/src/components/Tasks/Controls/index.js +3 -0
  139. package/src/components/Tasks/CreateTaskAXS.vue +339 -0
  140. package/src/components/Tasks/CreateTaskTM.vue +459 -0
  141. package/src/components/Tasks/MassEdit.vue +50 -0
  142. package/src/components/Tasks/QuickSettings.vue +167 -0
  143. package/src/components/Tasks/ScrapeVenue.vue +42 -0
  144. package/src/components/Tasks/Stats.vue +66 -0
  145. package/src/components/Tasks/Task.vue +296 -0
  146. package/src/components/Tasks/TaskLabel.vue +20 -0
  147. package/src/components/Tasks/TaskView.vue +126 -0
  148. package/src/components/Tasks/Utilities.vue +33 -0
  149. package/src/components/icons/Award.vue +8 -0
  150. package/src/components/icons/Bag.vue +8 -0
  151. package/src/components/icons/BagWhite.vue +8 -0
  152. package/src/components/icons/Box.vue +8 -0
  153. package/src/components/icons/Camera.vue +8 -0
  154. package/src/components/icons/Cart.vue +8 -0
  155. package/src/components/icons/Check.vue +5 -0
  156. package/src/components/icons/Checkmark.vue +11 -0
  157. package/src/components/icons/Click.vue +8 -0
  158. package/src/components/icons/Close.vue +21 -0
  159. package/src/components/icons/CloseX.vue +5 -0
  160. package/src/components/icons/Console.vue +13 -0
  161. package/src/components/icons/Down.vue +8 -0
  162. package/src/components/icons/Edit.vue +13 -0
  163. package/src/components/icons/Event.vue +8 -0
  164. package/src/components/icons/Expand.vue +8 -0
  165. package/src/components/icons/Filter.vue +13 -0
  166. package/src/components/icons/Gear.vue +8 -0
  167. package/src/components/icons/Group.vue +8 -0
  168. package/src/components/icons/Hand.vue +8 -0
  169. package/src/components/icons/Key.vue +21 -0
  170. package/src/components/icons/Logout.vue +13 -0
  171. package/src/components/icons/Loyalty.vue +8 -0
  172. package/src/components/icons/Mail.vue +8 -0
  173. package/src/components/icons/Menu.vue +8 -0
  174. package/src/components/icons/Pause.vue +5 -0
  175. package/src/components/icons/Pencil.vue +21 -0
  176. package/src/components/icons/Play.vue +8 -0
  177. package/src/components/icons/Plus.vue +8 -0
  178. package/src/components/icons/Profile.vue +18 -0
  179. package/src/components/icons/Reload.vue +7 -0
  180. package/src/components/icons/Sandclock.vue +33 -0
  181. package/src/components/icons/Savings.vue +8 -0
  182. package/src/components/icons/Scanner.vue +8 -0
  183. package/src/components/icons/Scrape.vue +8 -0
  184. package/src/components/icons/Sell.vue +21 -0
  185. package/src/components/icons/Shield.vue +8 -0
  186. package/src/components/icons/Shrink.vue +8 -0
  187. package/src/components/icons/Ski.vue +8 -0
  188. package/src/components/icons/Spinner.vue +42 -0
  189. package/src/components/icons/SquareCheck.vue +18 -0
  190. package/src/components/icons/SquareUncheck.vue +18 -0
  191. package/src/components/icons/Stadium.vue +13 -0
  192. package/src/components/icons/StadiumWhite.vue +13 -0
  193. package/src/components/icons/Status.vue +8 -0
  194. package/src/components/icons/Tag.vue +8 -0
  195. package/src/components/icons/Tasks.vue +13 -0
  196. package/src/components/icons/Ticket.vue +8 -0
  197. package/src/components/icons/Timer.vue +8 -0
  198. package/src/components/icons/Trash.vue +8 -0
  199. package/src/components/icons/Up.vue +10 -0
  200. package/src/components/icons/Wildcard.vue +18 -0
  201. package/src/components/icons/index.js +111 -0
  202. package/src/components/ui/Modal.vue +61 -0
  203. package/src/components/ui/Navbar.vue +207 -0
  204. package/src/components/ui/ReconnectIndicator.vue +90 -0
  205. package/src/components/ui/Splash.vue +24 -0
  206. package/src/components/ui/controls/CountryChooser.vue +87 -0
  207. package/src/components/ui/controls/EyeToggle.vue +11 -0
  208. package/src/components/ui/controls/atomic/Checkbox.vue +28 -0
  209. package/src/components/ui/controls/atomic/Dropdown.vue +138 -0
  210. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  211. package/src/components/ui/controls/atomic/MultiDropdown.vue +262 -0
  212. package/src/components/ui/controls/atomic/Switch.vue +84 -0
  213. package/src/libs/Filter.js +593 -0
  214. package/src/libs/ansii.js +565 -0
  215. package/src/libs/panzoom.js +1413 -0
  216. package/src/main.js +23 -0
  217. package/src/registerServiceWorker.js +32 -0
  218. package/src/router/index.js +65 -0
  219. package/src/stores/cities.json +1 -0
  220. package/src/stores/connection.js +399 -0
  221. package/src/stores/countries.js +88 -0
  222. package/src/stores/logger.js +103 -0
  223. package/src/stores/requests.js +88 -0
  224. package/src/stores/sampleData.js +1034 -0
  225. package/src/stores/ui.js +584 -0
  226. package/src/stores/utils.js +554 -0
  227. package/src/types/index.js +42 -0
  228. package/src/utils/debug.js +1 -0
  229. package/src/views/Accounts.vue +191 -0
  230. package/src/views/Console.vue +224 -0
  231. package/src/views/Editor.vue +785 -0
  232. package/src/views/FilterBuilder.vue +785 -0
  233. package/src/views/Login.vue +27 -0
  234. package/src/views/Profiles.vue +209 -0
  235. package/src/views/Tasks.vue +157 -0
  236. package/static/offline.html +184 -0
  237. package/tailwind.config.js +57 -0
  238. package/vite.config.js +63 -0
  239. package/vue.config.js +32 -0
  240. package/workbox-config.js +66 -0
@@ -0,0 +1,785 @@
1
+ <template>
2
+ <div>
3
+ <!-- Heading -->
4
+ <GearIcon class="w-5 cursor-pointer" @click="ui.toggleModal('quick-settings')" />
5
+ <h4 class="text-white text-xl font-bold mb-5 pt-5 flex gap-2 items-center">
6
+ Editor <img src="@/assets/img/pencil.svg" />
7
+ </h4>
8
+
9
+ <div class="card-dark p-2">
10
+ <div v-if="ui.profile.admin">
11
+ <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
12
+ <div class="flex justify-between items-center">
13
+ <div class="w-64 flex">
14
+ <div class="relative flex-grow">
15
+ <Dropdown
16
+ class="bg-dark-500 border-2 border-r-0 border-dark-550 admin-file-dropdown w-full"
17
+ default="Select a file"
18
+ :onClick="(f) => loadFile(f)"
19
+ :options="availableFiles"
20
+ :allowDefault="false"
21
+ rightAmount="right-1"
22
+ />
23
+ </div>
24
+ <button
25
+ class="refresh-button flex items-center justify-center w-10 h-10 text-white bg-dark-400 rounded-r-lg border-2 border-dark-550 border-l-0"
26
+ @click="loadAvailableFiles()"
27
+ title="Refresh file list"
28
+ >
29
+ <ReloadIcon class="refresh-icon" />
30
+ </button>
31
+ </div>
32
+ <!-- Admin editor buttons moved here -->
33
+ <div v-if="textEditorVisible" class="flex gap-x-2" style="margin-right: 1px;">
34
+ <button class="btn-action" @click="format()" v-if="isJsonFile()">
35
+ <svg
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ width="16"
38
+ height="16"
39
+ viewBox="0 0 24 24"
40
+ fill="none"
41
+ stroke="currentColor"
42
+ stroke-width="2"
43
+ stroke-linecap="round"
44
+ stroke-linejoin="round"
45
+ >
46
+ <path d="M21 10H7" />
47
+ <path d="M21 6H3" />
48
+ <path d="M21 14H3" />
49
+ <path d="M21 18H7" />
50
+ </svg>
51
+ <span>Format</span>
52
+ </button>
53
+ <button class="btn-action" @click="saveAll">
54
+ <svg
55
+ xmlns="http://www.w3.org/2000/svg"
56
+ width="16"
57
+ height="16"
58
+ viewBox="0 0 24 24"
59
+ fill="none"
60
+ stroke="currentColor"
61
+ stroke-width="2"
62
+ stroke-linecap="round"
63
+ stroke-linejoin="round"
64
+ >
65
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
66
+ <polyline points="17 21 17 13 7 13 7 21" />
67
+ <polyline points="7 3 7 8 15 8" />
68
+ </svg>
69
+ <span>Save File</span>
70
+ </button>
71
+ </div>
72
+ </div>
73
+ </div>
74
+
75
+ <transition name="fade">
76
+ <div v-if="textEditorVisible" class="my-3 relative">
77
+ <div class="pb-4">
78
+ <!-- Syntax Highlighting Editor -->
79
+ <div class="editor-container">
80
+ <div class="editor-wrapper">
81
+ <pre ref="codeDisplay" class="language-json code-highlight"></pre>
82
+ <textarea
83
+ ref="codeEditor"
84
+ v-model="currentContent"
85
+ class="code-editor"
86
+ spellcheck="false"
87
+ @scroll="syncScroll"
88
+ @input="highlightCode"
89
+ @keydown.tab.prevent="handleTab"
90
+ ></textarea>
91
+ </div>
92
+ </div>
93
+ </div>
94
+
95
+ <p class="text-red-400 text-bold">{{ errorMessage }}</p>
96
+ </div>
97
+ </transition>
98
+ <div class="border border-light-300 my-8" />
99
+
100
+ <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Proxy Editor</h5>
101
+ <div class="flex justify-between items-center mb-4 relative z-60">
102
+ <div class="w-64">
103
+ <div class="relative">
104
+ <Dropdown
105
+ class="bg-dark-500 border-2 border-dark-550 rounded-lg w-full h-10"
106
+ :default="ui.profile.proxyList?.checkout || proxyLists[0]"
107
+ :onClick="loadProxies"
108
+ :options="proxyLists"
109
+ :allowDefault="false"
110
+ rightAmount="right-1"
111
+ />
112
+ </div>
113
+ </div>
114
+ <!-- Proxy save button moved here -->
115
+ <div v-if="currentProxyList" style="margin-right: 1px;">
116
+ <button class="btn-action h-12" @click="saveProxies">
117
+ <svg
118
+ xmlns="http://www.w3.org/2000/svg"
119
+ width="16"
120
+ height="16"
121
+ viewBox="0 0 24 24"
122
+ fill="none"
123
+ stroke="currentColor"
124
+ stroke-width="2"
125
+ stroke-linecap="round"
126
+ stroke-linejoin="round"
127
+ >
128
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
129
+ <polyline points="17 21 17 13 7 13 7 21" />
130
+ <polyline points="7 3 7 8 15 8" />
131
+ </svg>
132
+ <span>Save Proxies</span>
133
+ </button>
134
+ </div>
135
+ </div>
136
+ <!-- Textarea -->
137
+ <transition name="fade">
138
+ <div v-if="currentProxyList" class="relative my-3">
139
+ <div class="pb-4">
140
+ <div class="proxy-editor-container">
141
+ <textarea
142
+ v-model="proxyContent"
143
+ class="proxy-editor"
144
+ spellcheck="false"
145
+ ></textarea>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </transition>
150
+ </div>
151
+ <QuickSettings v-if="activeModal === 'quick-settings'" />
152
+ </div>
153
+ </template>
154
+ <script setup>
155
+ import { ref, computed, watch, onMounted, nextTick } from "vue";
156
+ import { DownIcon, ReloadIcon, EditIcon } from "@/components/icons";
157
+ import { useUIStore } from "@/stores/ui";
158
+ import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
159
+ import { getProxyLists, getProxyFile } from "@/stores/requests";
160
+ import { DEBUG } from "@/utils/debug";
161
+
162
+ const loadFromApi = async (path) => {
163
+ const res = await fetch(path);
164
+ return res.json();
165
+ };
166
+
167
+ const availableFiles = ref([]);
168
+ if (DEBUG) availableFiles.value.push("used-codes.json", "proxies.txt", "toMonitor.txt", "test.txt");
169
+ const currentFile = ref("");
170
+ const textEditorVisible = ref(false);
171
+ const currentContent = ref("");
172
+ const proxyContent = ref("");
173
+ const proxyLists = ref(["loading..."]);
174
+ const currentProxyList = ref("");
175
+ const activeModal = computed(() => ui.activeModal);
176
+ const ui = useUIStore();
177
+
178
+ // References to editor elements
179
+ const codeEditor = ref(null);
180
+ const codeDisplay = ref(null);
181
+
182
+ let previous = {
183
+ proxyContent: { list: ui.profile.proxyList, content: "" },
184
+ currentContent: { file: "", content: "" }
185
+ };
186
+
187
+ const errorMessage = computed(() => {
188
+ if (!currentFile.value.endsWith(".json")) return;
189
+
190
+ let err;
191
+ try {
192
+ JSON.parse(currentContent.value);
193
+ } catch (e) {
194
+ err = e;
195
+ }
196
+ return err?.message;
197
+ });
198
+
199
+ const loadAvailableFiles = async () => (availableFiles.value = await loadFromApi("/api/json-files"));
200
+
201
+ const isJsonFile = () => currentFile.value.endsWith(".json");
202
+
203
+ // Function to highlight code using Prism
204
+ const highlightCode = () => {
205
+ if (!codeDisplay.value || !codeEditor.value) return;
206
+
207
+ // Get the language based on file extension
208
+ let language = "javascript"; // Default language
209
+
210
+ if (currentFile.value) {
211
+ if (currentFile.value.endsWith(".json")) {
212
+ language = "json";
213
+ } else if (currentFile.value.endsWith(".js")) {
214
+ language = "javascript";
215
+ } else if (currentFile.value.endsWith(".txt") || currentFile.value.endsWith(".csv")) {
216
+ language = "text";
217
+ // Plain text doesn't need highlighting
218
+ codeDisplay.value.textContent = currentContent.value || "";
219
+ codeDisplay.value.className = "language-text code-highlight";
220
+ return;
221
+ }
222
+ }
223
+
224
+ // Ensure Prism is available
225
+ if (typeof Prism === "undefined") {
226
+ console.error("Prism is not loaded");
227
+ return;
228
+ }
229
+
230
+ // Use requestAnimationFrame for smoother updates
231
+ requestAnimationFrame(() => {
232
+ try {
233
+ // Update the pre element with highlighted HTML
234
+ const highlighted = Prism.highlight(currentContent.value || "", Prism.languages[language], language);
235
+ codeDisplay.value.innerHTML = highlighted;
236
+ codeDisplay.value.className = `language-${language} code-highlight`;
237
+ } catch (e) {
238
+ console.error("Highlight error:", e);
239
+ // Fallback to plain text if highlighting fails
240
+ codeDisplay.value.textContent = currentContent.value || "";
241
+ }
242
+
243
+ // Ensure scroll positions are synced after highlighting
244
+ syncScroll();
245
+ });
246
+ };
247
+
248
+ // Function to sync scrolling between textarea and highlighted code
249
+ const syncScroll = () => {
250
+ if (!codeDisplay.value || !codeEditor.value) return;
251
+
252
+ // Synchronize scrolling between the textarea and the highlighted code
253
+ requestAnimationFrame(() => {
254
+ codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
255
+ codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
256
+ });
257
+ };
258
+
259
+ // Function to handle tab key press in the editor
260
+ const handleTab = (e) => {
261
+ const textarea = codeEditor.value;
262
+ const start = textarea.selectionStart;
263
+ const end = textarea.selectionEnd;
264
+
265
+ // Insert 4 spaces at cursor position
266
+ const spaces = " ";
267
+ currentContent.value = currentContent.value.substring(0, start) + spaces + currentContent.value.substring(end);
268
+
269
+ // Move cursor position after the inserted tab
270
+ nextTick(() => {
271
+ textarea.selectionStart = textarea.selectionEnd = start + spaces.length;
272
+ highlightCode();
273
+ });
274
+ };
275
+
276
+ const format = () => {
277
+ try {
278
+ if (!isJsonFile()) return;
279
+
280
+ const formatted = JSON.stringify(JSON.parse(currentContent.value), null, 4);
281
+ currentContent.value = formatted;
282
+
283
+ // Apply syntax highlighting after formatting
284
+ nextTick(() => {
285
+ highlightCode();
286
+ });
287
+ } catch (e) {
288
+ ui.logger.Error("Could not format JSON", e);
289
+ }
290
+ };
291
+
292
+ // Watch for content changes to apply syntax highlighting
293
+ watch(currentContent, () => {
294
+ nextTick(() => {
295
+ highlightCode();
296
+ });
297
+ });
298
+
299
+ const loadFile = async (f) => {
300
+ currentFile.value = f;
301
+ if (DEBUG) {
302
+ textEditorVisible.value = true;
303
+ currentProxyList.value = "";
304
+ currentContent.value = new Array(100).fill(0, 0, 100).join("\n");
305
+
306
+ // Update pre element class based on file type
307
+ if (codeDisplay.value) {
308
+ if (f.endsWith(".json")) {
309
+ codeDisplay.value.className = "language-json";
310
+ } else if (f.endsWith(".js")) {
311
+ codeDisplay.value.className = "language-javascript";
312
+ } else {
313
+ codeDisplay.value.className = "language-text";
314
+ }
315
+ }
316
+
317
+ // Apply syntax highlighting after content is set
318
+ nextTick(() => {
319
+ highlightCode();
320
+ });
321
+ return;
322
+ }
323
+
324
+ const res = await loadFromApi("/api/json-file?file=" + currentFile.value);
325
+ const text = atob(res.content);
326
+ textEditorVisible.value = true;
327
+ currentProxyList.value = "";
328
+ currentContent.value = text;
329
+ previous.currentContent = { file: currentFile.value, content: text };
330
+
331
+ // Update pre element class based on file type
332
+ if (codeDisplay.value) {
333
+ if (f.endsWith(".json")) {
334
+ codeDisplay.value.className = "language-json";
335
+ } else if (f.endsWith(".js")) {
336
+ codeDisplay.value.className = "language-javascript";
337
+ } else {
338
+ codeDisplay.value.className = "language-text";
339
+ }
340
+ }
341
+
342
+ // Apply syntax highlighting after content is loaded
343
+ nextTick(() => {
344
+ highlightCode();
345
+ });
346
+ };
347
+
348
+ const saveDataToAPI = async (data, endpoint, msg, json = true) => {
349
+ if (DEBUG) return;
350
+ const options = {
351
+ method: "POST",
352
+ headers: { "Content-Type": "application/json" },
353
+ body: JSON.stringify({ content: data })
354
+ };
355
+
356
+ let j;
357
+ const res = await fetch(endpoint, options);
358
+ if (json) {
359
+ try {
360
+ j = await res.json();
361
+ } catch (e) {
362
+ ui.showError("Error saving proxies");
363
+ return;
364
+ }
365
+ if (j?.error) return ui.showError(j.error);
366
+ } else {
367
+ try {
368
+ j = await res.text();
369
+ } catch (e) {
370
+ ui.showError("Error saving proxies");
371
+ return;
372
+ }
373
+ }
374
+ ui.showSuccess(msg);
375
+ };
376
+
377
+ const saveFile = async () => {
378
+ if (
379
+ btoa(currentContent.value) === previous.currentContent.content &&
380
+ currentFile.value === previous.currentContent.file
381
+ )
382
+ return ui.logger.Info("Content did not change");
383
+ saveDataToAPI(btoa(currentContent.value), "/api/json-file?file=" + currentFile.value, "Successfully saved file");
384
+ previous.currentContent = { file: currentFile.value, content: btoa(currentContent.value) };
385
+ };
386
+
387
+ const saveAll = async () => {
388
+ if (DEBUG) return;
389
+ if (currentFile.value && currentContent.value) await saveFile();
390
+ await saveQuickConfig();
391
+ };
392
+
393
+ const loadProxies = async (list) => {
394
+ try {
395
+ if (DEBUG) {
396
+ currentProxyList.value = list;
397
+ textEditorVisible.value = false;
398
+ proxyContent.value = new Array(100).fill(0, 0, 100).join("\n");
399
+ return;
400
+ }
401
+
402
+ ui.logger.Info("Loading proxies: " + list);
403
+ currentProxyList.value = list;
404
+ const file = await getProxyFile(list);
405
+ proxyContent.value = file;
406
+ previous.proxyContent = { content: proxyContent.value };
407
+ textEditorVisible.value = false;
408
+ } catch (ex) {
409
+ ui.logger.Error("Could not load proxies", ex);
410
+ }
411
+ };
412
+
413
+ const saveProxies = async () => {
414
+ // if (!DEBUG) return;
415
+ if (previous.proxyContent.content === proxyContent.value) ui.logger.Info("proxies did not change");
416
+ else {
417
+ await saveDataToAPI(
418
+ btoa(proxyContent.value),
419
+ "/api/proxies?file=" + currentProxyList.value,
420
+ "Successfully saved proxies",
421
+ false
422
+ );
423
+ previous.proxyContent.content = btoa(proxyContent.value);
424
+ }
425
+ };
426
+
427
+ const loadProxyLists = async () => {
428
+ if (DEBUG) return (proxyLists.value = ["test-proxies", "recaptcha-proxies", "checkout-proxies", "queue-proxies"]);
429
+ const res = await getProxyLists();
430
+ if (Array.isArray(res)) proxyLists.value = res;
431
+ else ui.showError("Could not load proxylists");
432
+ };
433
+
434
+ // Initialize syntax highlighting
435
+ onMounted(() => {
436
+ // Apply highlighting when the component is mounted
437
+ nextTick(() => {
438
+ highlightCode();
439
+
440
+ // Ensure scroll synchronization on initial load
441
+ if (codeEditor.value && codeDisplay.value) {
442
+ syncScroll();
443
+ }
444
+ });
445
+
446
+ // Watch for editor visibility changes
447
+ watch(textEditorVisible, (newValue) => {
448
+ if (newValue) {
449
+ nextTick(() => {
450
+ highlightCode();
451
+
452
+ // Focus the editor when it becomes visible
453
+ if (codeEditor.value) {
454
+ codeEditor.value.focus();
455
+ }
456
+ });
457
+ }
458
+ });
459
+ });
460
+
461
+ if (ui.profile.admin && !DEBUG) loadAvailableFiles();
462
+ loadProxyLists();
463
+ </script>
464
+ <style lang="scss" scoped>
465
+ .console {
466
+ @apply bg-dark-400 p-7 rounded relative border-dark-550 border-2;
467
+
468
+ textarea {
469
+ background: transparent;
470
+ resize: none;
471
+ height: calc(100vh - 450px);
472
+ @apply w-full focus:outline-none text-xs text-white;
473
+ }
474
+
475
+ $border: 2px;
476
+
477
+ // &:before {
478
+ // content: "";
479
+ // @apply absolute top-0 left-0 right-0 bottom-0 opacity-60;
480
+ // z-index: -2;
481
+ // margin: -$border;
482
+ // border-radius: inherit;
483
+ // background: radial-gradient(rgba(96, 66, 255, 0.6), rgba(255, 255, 255, 0));
484
+ // }
485
+ }
486
+
487
+ .z-inf {
488
+ z-index: 1000;
489
+ }
490
+
491
+ .z-inf2 {
492
+ z-index: 2000;
493
+ }
494
+
495
+ .z-inf3 {
496
+ z-index: 3000;
497
+ }
498
+
499
+ .special-dropdown {
500
+ margin-left: 0 !important;
501
+ }
502
+
503
+ .button-default-smaller -smaller {
504
+ @apply h-10;
505
+ border-radius: 0.5rem;
506
+ font-weight: 500;
507
+ --tw-text-opacity: 1;
508
+ color: rgb(255 255 255 / 1);
509
+ transition-duration: 150ms;
510
+ }
511
+
512
+ /* Modern dropdown with refresh button styles */
513
+ /* Removed custom dropdown styling to ensure default behavior */
514
+
515
+ .refresh-button {
516
+ &:hover {
517
+ .refresh-icon {
518
+ transform: rotate(180deg);
519
+ }
520
+ }
521
+ }
522
+
523
+ .refresh-icon {
524
+ transition: transform 0.5s ease;
525
+ }
526
+
527
+ /* Admin file dropdown should match refresh button height and have no right corners */
528
+ .admin-file-dropdown {
529
+ height: 2.5rem !important; /* Always 40px to match refresh button */
530
+ border-top-right-radius: 0 !important;
531
+ border-bottom-right-radius: 0 !important;
532
+ border-top-left-radius: 0.5rem !important; /* lg border radius */
533
+ border-bottom-left-radius: 0.5rem !important; /* lg border radius */
534
+ }
535
+
536
+ /* Prism.js syntax highlighting styles */
537
+ .editor-container {
538
+ position: relative;
539
+ min-height: 300px;
540
+ max-height: 600px;
541
+ border-radius: 8px;
542
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
543
+ overflow: hidden;
544
+ background-color: #1a1a2e;
545
+ }
546
+
547
+ .editor-wrapper {
548
+ position: relative;
549
+ width: 100%;
550
+ height: 100%;
551
+ min-height: 300px;
552
+ max-height: 600px;
553
+ }
554
+
555
+ .code-editor {
556
+ width: 100%;
557
+ height: 100%;
558
+ min-height: 300px;
559
+ max-height: 600px;
560
+ background-color: transparent;
561
+ /* Make text completely transparent */
562
+ color: rgba(0, 0, 0, 0);
563
+ caret-color: #e2e8f0;
564
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
565
+ padding: 12px;
566
+ border: none;
567
+ resize: none;
568
+ font-size: 14px;
569
+ line-height: 1.6;
570
+ tab-size: 4;
571
+ outline: none;
572
+ border: 1px solid #2d2d3b;
573
+ border-radius: 8px;
574
+ z-index: 10;
575
+ position: absolute;
576
+ top: 0;
577
+ left: 0;
578
+ right: 0;
579
+ bottom: 0;
580
+ white-space: pre;
581
+ overflow: auto;
582
+ }
583
+
584
+ /* Enhance the text selection color */
585
+ .code-editor::selection {
586
+ background: rgba(98, 114, 164, 0.4);
587
+ }
588
+
589
+ .code-highlight {
590
+ width: 100%;
591
+ height: 100%;
592
+ min-height: 300px;
593
+ max-height: 600px;
594
+ overflow: auto;
595
+ white-space: pre;
596
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
597
+ font-size: 14px;
598
+ line-height: 1.6;
599
+ background-color: transparent !important;
600
+ pointer-events: none;
601
+ z-index: 5;
602
+ position: absolute;
603
+ top: 0;
604
+ left: 0;
605
+ right: 0;
606
+ bottom: 0;
607
+ padding: 12px;
608
+ margin: 0;
609
+ }
610
+
611
+ .code-editor,
612
+ .prism-editor {
613
+ font-family: "Menlo", "Monaco", "Courier New", monospace !important;
614
+ font-size: 14px !important;
615
+ line-height: 1.5 !important;
616
+ }
617
+
618
+ /* Proxy editor container */
619
+ .proxy-editor-container {
620
+ @apply w-full overflow-hidden rounded-lg border border-dark-550;
621
+ height: 400px;
622
+ max-height: 60vh;
623
+ background-color: #1a1a2e;
624
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
625
+ }
626
+
627
+ @media (min-width: 1024px) {
628
+ .proxy-editor-container {
629
+ max-height: 60vh;
630
+ }
631
+ }
632
+
633
+ @media (min-width: 1280px) {
634
+ .proxy-editor-container {
635
+ max-height: 70vh;
636
+ }
637
+ }
638
+
639
+ /* Proxy editor styles */
640
+ .proxy-editor {
641
+ @apply w-full h-full resize-none focus:outline-none;
642
+ background-color: transparent;
643
+ color: #f8f8f2;
644
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
645
+ padding: 12px;
646
+ border: none;
647
+ font-size: 14px;
648
+ line-height: 1.6;
649
+ tab-size: 4;
650
+ overflow: auto;
651
+ }
652
+
653
+ .proxy-editor:focus {
654
+ outline: none;
655
+ }
656
+
657
+ .proxy-editor-container:focus-within {
658
+ border-color: #6272a4;
659
+ box-shadow: 0 0 0 2px rgba(98, 114, 164, 0.25);
660
+ }
661
+
662
+ /* Prism.js dark theme */
663
+ code[class*="language-"],
664
+ pre[class*="language-"] {
665
+ color: #f8f8f2;
666
+ background: none;
667
+ text-shadow: 0 1px rgba(0, 0, 0, 0.3);
668
+ font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
669
+ text-align: left;
670
+ white-space: pre;
671
+ word-spacing: normal;
672
+ word-break: normal;
673
+ word-wrap: normal;
674
+ line-height: 1.5;
675
+ tab-size: 4;
676
+ hyphens: none;
677
+ }
678
+
679
+ /* Code blocks */
680
+ pre[class*="language-"] {
681
+ padding: 12px;
682
+ margin: 0;
683
+ overflow: auto;
684
+ border-radius: 4px;
685
+ font-family: "Menlo", "Monaco", "Courier New", monospace;
686
+ font-size: 14px;
687
+ line-height: 1.5;
688
+ tab-size: 4;
689
+ }
690
+
691
+ :not(pre) > code[class*="language-"],
692
+ pre[class*="language-"] {
693
+ background: #1e1e2e;
694
+ white-space: pre;
695
+ }
696
+
697
+ /* Inline code */
698
+ :not(pre) > code[class*="language-"] {
699
+ padding: 0.1em;
700
+ border-radius: 0.3em;
701
+ white-space: normal;
702
+ }
703
+
704
+ .token.comment,
705
+ .token.prolog,
706
+ .token.doctype,
707
+ .token.cdata {
708
+ color: #676f7d;
709
+ font-style: italic;
710
+ }
711
+
712
+ .token.punctuation {
713
+ color: #a9b7c6;
714
+ }
715
+
716
+ .namespace {
717
+ opacity: 0.8;
718
+ }
719
+
720
+ .token.property,
721
+ .token.tag,
722
+ .token.constant,
723
+ .token.symbol,
724
+ .token.deleted {
725
+ color: #e06c75;
726
+ }
727
+
728
+ .token.boolean,
729
+ .token.number {
730
+ color: #c792ea;
731
+ }
732
+
733
+ .token.selector,
734
+ .token.attr-name,
735
+ .token.string,
736
+ .token.char,
737
+ .token.builtin,
738
+ .token.inserted {
739
+ color: #98c379;
740
+ }
741
+
742
+ .token.operator,
743
+ .token.entity,
744
+ .token.url,
745
+ .language-css .token.string,
746
+ .style .token.string {
747
+ color: #89ddff;
748
+ }
749
+
750
+ .token.atrule,
751
+ .token.attr-value,
752
+ .token.keyword {
753
+ color: #61afef;
754
+ }
755
+
756
+ .token.function,
757
+ .token.class-name {
758
+ color: #ffcb6b;
759
+ }
760
+
761
+ .token.regex,
762
+ .token.important,
763
+ .token.variable {
764
+ color: #c5e478;
765
+ }
766
+
767
+ /* Add a slight hover effect to the editor for better UX */
768
+ .editor-container:hover {
769
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
770
+ transition: box-shadow 0.3s ease;
771
+ }
772
+
773
+ .token.important,
774
+ .token.bold {
775
+ font-weight: bold;
776
+ }
777
+
778
+ .token.italic {
779
+ font-style: italic;
780
+ }
781
+
782
+ .token.entity {
783
+ cursor: help;
784
+ }
785
+ </style>