瀏覽代碼

Merge remote-tracking branch 'origin/polishing' into owen

Owen Diffey 3 年之前
父節點
當前提交
cfad761483

+ 4 - 3
.wiki/Configuration.md

@@ -31,10 +31,11 @@ Location: `frontend/dist/config/default.json`
 
 | Property | Description |
 | --- | --- |
-| `apiDomain` | Should be the url where the backend will be accessible from, usually `http://localhost/backend` for docker or `http://localhost:8080` for non-Docker. |
-| `websocketsDomain` | Should be the same as the `apiDomain`, except using the `ws://` protocol instead of `http://` and with `/ws` at the end. |
+| `backend.apiDomain` | Should be the url where the backend will be accessible from, usually `http://localhost/backend` for docker or `http://localhost:8080` for non-Docker. |
+| `backend.websocketsDomain` | Should be the same as the `apiDomain`, except using the `ws://` protocol instead of `http://` and with `/ws` at the end. |
+| `devServer.webSocketURL` | Should be the webpack-dev-server websocket URL, usually `ws://localhost/ws`. |
+| `devServer.port` | Should be the port where webpack-dev-server will be accessible from, should always be port `81` for Docker since nginx listens on port 80, and is recommended to be port `80` for non-Docker. |
 | `frontendDomain` | Should be the url where the frontend will be accessible from, usually `http://localhost` for docker or `http://localhost:80` for non-Docker. |
-| `frontendPort` | Should be the port where the frontend will be accessible from, should always be port `81` for Docker, and is recommended to be port `80` for non-Docker. |
 | `recaptcha.key` | Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin). |
 | `recaptcha.enabled` | Keep at false to keep disabled. |
 | `cookie.domain` | Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`. |

+ 1 - 0
frontend/.eslintrc

@@ -36,6 +36,7 @@
 		"no-shadow": 0,
 		"no-new": 0,
 		"import/no-unresolved": 0,
+		"import/extensions": 0,
 		"prettier/prettier": [
 			"error"
 		],

+ 1 - 1
frontend/Dockerfile

@@ -8,7 +8,7 @@ WORKDIR /opt
 ADD package.json /opt/package.json
 ADD package-lock.json /opt/package-lock.json
 
-RUN npm install -g webpack@5.38.0 webpack-cli@4.8.0
+RUN npm install -g webpack@5.58.1 webpack-cli@4.9.0
 
 RUN npm install
 

+ 9 - 4
frontend/dist/config/template.json

@@ -3,10 +3,15 @@
 		"key": "",
 		"enabled": false
 	},
-	"apiDomain": "http://localhost/backend",
-	"websocketsDomain": "ws://localhost/backend/ws",
+	"backend": {
+		"apiDomain": "http://localhost/backend",
+		"websocketsDomain": "ws://localhost/backend/ws"
+	},
+	"devServer": {
+		"port": "81",
+		"webSocketURL": "ws://localhost/ws"
+	},
 	"frontendDomain": "http://localhost",
-	"frontendPort": "81",
 	"mode": "development",
 	"cookie": {
 		"domain": "localhost",
@@ -32,5 +37,5 @@
 	// 	}
 	// },
 	"skipConfigVersionCheck": false,
-	"configVersion": 7
+	"configVersion": 8
 }

File diff suppressed because it is too large
+ 260 - 394
frontend/package-lock.json


+ 18 - 18
frontend/package.json

@@ -17,47 +17,47 @@
     "prod": "npx webpack --config webpack.prod.js"
   },
   "devDependencies": {
-    "@babel/core": "^7.15.5",
-    "@babel/eslint-parser": "^7.15.7",
+    "@babel/core": "^7.15.8",
+    "@babel/eslint-parser": "^7.15.8",
     "@babel/plugin-proposal-object-rest-spread": "^7.15.6",
     "@babel/plugin-syntax-dynamic-import": "^7.8.3",
-    "@babel/plugin-transform-runtime": "^7.15.0",
-    "@babel/preset-env": "^7.15.6",
-    "@vue/compiler-sfc": "^3.2.19",
+    "@babel/plugin-transform-runtime": "^7.15.8",
+    "@babel/preset-env": "^7.15.8",
+    "@vue/compiler-sfc": "^3.2.20",
     "babel-loader": "^8.2.2",
-    "css-loader": "^5.2.7",
+    "css-loader": "^6.4.0",
     "eslint": "^7.32.0",
     "eslint-config-prettier": "^8.3.0",
     "eslint-plugin-import": "^2.24.2",
-    "eslint-plugin-prettier": "^3.4.1",
-    "eslint-plugin-vue": "^7.18.0",
-    "eslint-webpack-plugin": "^2.5.4",
+    "eslint-plugin-prettier": "^4.0.0",
+    "eslint-plugin-vue": "^7.19.1",
+    "eslint-webpack-plugin": "^3.0.1",
     "fetch": "^1.1.0",
     "node-sass": "^6.0.1",
-    "prettier": "2.3.2",
+    "prettier": "^2.4.1",
     "sass-loader": "^12.1.0",
     "vue-style-loader": "^4.1.3",
-    "webpack-cli": "^4.8.0",
-    "webpack-dev-server": "^3.11.2"
+    "webpack-cli": "^4.9.0",
+    "webpack-dev-server": "^4.3.1"
   },
   "dependencies": {
     "@babel/runtime": "^7.15.4",
     "config": "^3.3.6",
-    "date-fns": "^2.24.0",
+    "date-fns": "^2.25.0",
     "dompurify": "^2.3.3",
     "eslint-config-airbnb-base": "^14.2.1",
     "html-webpack-plugin": "^5.3.2",
-    "marked": "^2.1.3",
+    "marked": "^3.0.7",
     "toasters": "^2.3.1",
-    "vue": "^3.2.19",
+    "vue": "^3.2.20",
     "vue-content-loader": "^2.0.0",
     "vue-loader": "^16.8.1",
     "vue-router": "^4.0.11",
-    "vue-tippy": "^6.0.0-alpha.33",
+    "vue-tippy": "^6.0.0-alpha.34",
     "vuedraggable": "^4.1.0",
     "vuex": "^4.0.2",
-    "webpack": "5.38.0",
+    "webpack": "5.58.1",
     "webpack-bundle-analyzer": "^4.4.2",
     "webpack-merge": "^5.8.0"
   }
-}
+}

+ 1 - 1
frontend/src/App.vue

@@ -181,7 +181,7 @@ export default {
 			this.socketConnected = false;
 		});
 
-		this.apiDomain = await lofig.get("apiDomain");
+		this.apiDomain = await lofig.get("backend.apiDomain");
 
 		this.$router.isReady().then(() => {
 			if (this.$route.query.err) {

+ 1 - 1
frontend/src/components/modals/EditPlaylist/index.vue

@@ -510,7 +510,7 @@ export default {
 		},
 		async downloadPlaylist() {
 			if (this.apiDomain === "")
-				this.apiDomain = await lofig.get("apiDomain");
+				this.apiDomain = await lofig.get("backend.apiDomain");
 
 			fetch(
 				`${this.apiDomain}/export/privatePlaylist/${this.playlist._id}`,

+ 3 - 0
frontend/src/components/modals/EditSong/Tabs/Reports.vue

@@ -203,9 +203,12 @@
 
 <script>
 import { mapState, mapGetters, mapActions } from "vuex";
+
 import Toast from "toasters";
 import ReportInfoItem from "@/components/ReportInfoItem.vue";
 
+import ReportInfoItem from "@/components/ReportInfoItem.vue";
+
 export default {
 	components: { ReportInfoItem },
 	data() {

+ 1 - 1
frontend/src/components/modals/EditSong/Tabs/Youtube.vue

@@ -97,7 +97,7 @@ export default {
 
 		.search-query-item {
 			/deep/ .thumbnail-and-info {
-				width: calc(100% - 29px);
+				width: calc(100% - 59px);
 			}
 
 			.icon-selected {

+ 1 - 1
frontend/src/components/modals/ImportAlbum.vue

@@ -387,7 +387,7 @@ export default {
 	},
 	watch: {
 		/* eslint-disable */
-		"modals.editSong": function(value) {
+		"modals.editSong": function (value) {
 			if (!value) this.editNextSong();
 		}
 		/* eslint-enable */

+ 1 - 1
frontend/src/components/modals/Login.vue

@@ -136,7 +136,7 @@ export default {
 		};
 	},
 	async mounted() {
-		this.apiDomain = await lofig.get("apiDomain");
+		this.apiDomain = await lofig.get("backend.apiDomain");
 
 		if (this.$route.path === "/login") this.isPage = true;
 	},

+ 1 - 1
frontend/src/components/modals/Register.vue

@@ -242,7 +242,7 @@ export default {
 	async mounted() {
 		if (this.$route.path === "/register") this.isPage = true;
 
-		this.apiDomain = await lofig.get("apiDomain");
+		this.apiDomain = await lofig.get("backend.apiDomain");
 
 		lofig.get("recaptcha").then(obj => {
 			this.recaptcha.enabled = obj.enabled;

+ 1 - 1
frontend/src/components/modals/RemoveAccount.vue

@@ -197,7 +197,7 @@ export default {
 		socket: "websockets/getSocket"
 	}),
 	async mounted() {
-		this.apiDomain = await lofig.get("apiDomain");
+		this.apiDomain = await lofig.get("backend.apiDomain");
 		this.accountRemovalMessage = await lofig.get("messages.accountRemoval");
 	},
 	methods: {

+ 1 - 0
frontend/src/components/modals/ViewPunishment.vue

@@ -11,6 +11,7 @@
 <script>
 import { mapState, mapGetters, mapActions } from "vuex";
 import { format, formatDistance, parseISO } from "date-fns";
+
 import Toast from "toasters";
 import ws from "@/ws";
 

+ 44 - 45
frontend/src/main.js

@@ -9,7 +9,7 @@ import store from "./store";
 
 import AppComponent from "./App.vue";
 
-const REQUIRED_CONFIG_VERSION = 7;
+const REQUIRED_CONFIG_VERSION = 8;
 
 lofig.folder = "../config/default.json";
 
@@ -126,7 +126,10 @@ const router = createRouter({
 		},
 		{
 			path: "/reset_password",
-			component: () => import("@/pages/ResetPassword.vue")
+			component: () => import("@/pages/ResetPassword.vue"),
+			meta: {
+				guestsOnly: true
+			}
 		},
 		{
 			path: "/set_password",
@@ -172,6 +175,44 @@ const router = createRouter({
 	]
 });
 
+router.beforeEach((to, from, next) => {
+	if (window.stationInterval) {
+		clearInterval(window.stationInterval);
+		window.stationInterval = 0;
+	}
+
+	if (ws.socket && to.fullPath !== from.fullPath) {
+		ws.clearCallbacks();
+		ws.destroyListeners();
+	}
+
+	if (to.meta.loginRequired || to.meta.adminRequired || to.meta.guestsOnly) {
+		const gotData = () => {
+			if (to.meta.loginRequired && !store.state.user.auth.loggedIn)
+				next({ path: "/login" });
+			else if (
+				to.meta.adminRequired &&
+				store.state.user.auth.role !== "admin"
+			)
+				next({ path: "/" });
+			else if (to.meta.guestsOnly && store.state.user.auth.loggedIn)
+				next({ path: "/" });
+			else next();
+		};
+
+		if (store.state.user.auth.gotData) gotData();
+		else {
+			const watcher = store.watch(
+				state => state.user.auth.gotData,
+				() => {
+					watcher();
+					gotData();
+				}
+			);
+		}
+	} else next();
+});
+
 app.use(router);
 
 (async () => {
@@ -189,7 +230,7 @@ app.use(router);
 		}
 	});
 
-	const websocketsDomain = await lofig.get("websocketsDomain");
+	const websocketsDomain = await lofig.get("backend.websocketsDomain");
 	ws.init(websocketsDomain);
 
 	ws.socket.on("ready", res => {
@@ -248,47 +289,5 @@ app.use(router);
 			);
 	});
 
-	router.beforeEach((to, from, next) => {
-		if (window.stationInterval) {
-			clearInterval(window.stationInterval);
-			window.stationInterval = 0;
-		}
-
-		if (ws.socket && to.fullPath !== from.fullPath) {
-			ws.clearCallbacks();
-			ws.destroyListeners();
-		}
-
-		if (
-			to.meta.loginRequired ||
-			to.meta.adminRequired ||
-			to.meta.guestsOnly
-		) {
-			const gotData = () => {
-				if (to.meta.loginRequired && !store.state.user.auth.loggedIn)
-					next({ path: "/login" });
-				else if (
-					to.meta.adminRequired &&
-					store.state.user.auth.role !== "admin"
-				)
-					next({ path: "/" });
-				else if (to.meta.guestsOnly && store.state.user.auth.loggedIn)
-					next({ path: "/" });
-				else next();
-			};
-
-			if (store.state.user.auth.gotData) gotData();
-			else {
-				const watcher = store.watch(
-					state => state.user.auth.gotData,
-					() => {
-						watcher();
-						gotData();
-					}
-				);
-			}
-		} else next();
-	});
-
 	app.mount("#root");
 })();

+ 4 - 1
frontend/src/pages/Admin/tabs/HiddenSongs.vue

@@ -113,7 +113,7 @@
 			</table>
 		</div>
 		<import-album v-if="modals.importAlbum" />
-		<edit-song v-if="modals.editSong" />
+		<edit-song v-if="modals.editSong" song-type="songs" :key="song._id" />
 		<request-song v-if="modals.requestSong" />
 		<floating-box
 			id="keyboardShortcutsHelper"
@@ -225,6 +225,9 @@ export default {
 		...mapState("admin/hiddenSongs", {
 			songs: state => state.songs
 		}),
+		...mapState("modals/editSong", {
+			song: state => state.song
+		}),
 		...mapGetters({
 			socket: "websockets/getSocket"
 		})

+ 2 - 0
frontend/src/pages/Admin/tabs/Reports.vue

@@ -92,6 +92,8 @@ import Toast from "toasters";
 import ReportInfoItem from "@/components/ReportInfoItem.vue";
 import ws from "@/ws";
 
+import ReportInfoItem from "@/components/ReportInfoItem.vue";
+
 export default {
 	components: {
 		ViewReport: defineAsyncComponent(() =>

+ 4 - 1
frontend/src/pages/Admin/tabs/UnverifiedSongs.vue

@@ -127,7 +127,7 @@
 			</table>
 		</div>
 		<import-album v-if="modals.importAlbum" />
-		<edit-song v-if="modals.editSong" />
+		<edit-song v-if="modals.editSong" song-type="songs" :key="song._id" />
 		<request-song v-if="modals.requestSong" />
 		<floating-box
 			id="keyboardShortcutsHelper"
@@ -243,6 +243,9 @@ export default {
 		...mapState("admin/unverifiedSongs", {
 			songs: state => state.songs
 		}),
+		...mapState("modals/editSong", {
+			song: state => state.song
+		}),
 		...mapGetters({
 			socket: "websockets/getSocket"
 		})

+ 4 - 1
frontend/src/pages/Admin/tabs/VerifiedSongs.vue

@@ -158,7 +158,7 @@
 			</table>
 		</div>
 		<import-album v-if="modals.importAlbum" />
-		<edit-song v-if="modals.editSong" song-type="songs" />
+		<edit-song v-if="modals.editSong" song-type="songs" :key="song._id" />
 		<request-song v-if="modals.requestSong" />
 		<floating-box
 			id="keyboardShortcutsHelper"
@@ -375,6 +375,9 @@ export default {
 		...mapState("admin/verifiedSongs", {
 			songs: state => state.songs
 		}),
+		...mapState("modals/editSong", {
+			song: state => state.song
+		}),
 		...mapGetters({
 			socket: "websockets/getSocket"
 		})

+ 1 - 1
frontend/src/pages/Settings/Tabs/Security.vue

@@ -220,7 +220,7 @@ export default {
 		}
 	},
 	async mounted() {
-		this.apiDomain = await lofig.get("apiDomain");
+		this.apiDomain = await lofig.get("backend.apiDomain");
 	},
 	methods: {
 		togglePasswordVisibility(ref) {

+ 19 - 11
frontend/src/ws.js

@@ -3,6 +3,7 @@ import store from "./store";
 import ListenerHandler from "./classes/ListenerHandler.class";
 
 const onConnect = [];
+let ready = false;
 
 let pendingDispatches = [];
 
@@ -20,7 +21,7 @@ export default {
 	dispatcher: null,
 
 	onConnect(cb) {
-		if (this.socket.readyState === 1) cb();
+		if (this.socket.readyState === 1 && ready) cb();
 
 		return onConnect.push(cb);
 	},
@@ -105,15 +106,7 @@ export default {
 		store.dispatch("websockets/createSocket", this.socket);
 
 		this.socket.onopen = () => {
-			console.log("WS: SOCKET CONNECTED");
-
-			setTimeout(() => {
-				onConnect.forEach(cb => cb());
-
-				// dispatches that were attempted while the server was offline
-				pendingDispatches.forEach(cb => cb());
-				pendingDispatches = [];
-			}, 150); // small delay between readyState being 1 and the server actually receiving dispatches
+			console.log("WS: SOCKET OPENED");
 		};
 
 		this.socket.onmessage = message => {
@@ -136,7 +129,9 @@ export default {
 		};
 
 		this.socket.onclose = () => {
-			console.log("WS: SOCKET DISCONNECTED");
+			console.log("WS: SOCKET CLOSED");
+
+			ready = false;
 
 			onDisconnect.temp.forEach(cb => cb());
 			onDisconnect.persist.forEach(cb => cb());
@@ -150,5 +145,18 @@ export default {
 
 			// new Toast("Cannot perform this action at this time.");
 		};
+
+		this.socket.on("ready", () => {
+			console.log("WS: SOCKET READY");
+			ready = true;
+
+			setTimeout(() => {
+				onConnect.forEach(cb => cb());
+
+				// dispatches that were attempted while the server was offline
+				pendingDispatches.forEach(cb => cb());
+				pendingDispatches = [];
+			}, 150); // small delay between readyState being 1 and the server actually receiving dispatches
+		});
 	}
 };

+ 10 - 10
frontend/webpack.dev.js

@@ -3,7 +3,6 @@ const config = require("config");
 
 const { merge } = require("webpack-merge");
 const common = require("./webpack.common.js");
-
 module.exports = merge(common, {
 	mode: "development",
 	devtool: "inline-source-map",
@@ -17,16 +16,17 @@ module.exports = merge(common, {
 		}
 	},
 	devServer: {
-		contentBase: "./dist/",
-		historyApiFallback: true,
+		static: {
+			directory: "./dist/",
+			watch: true
+		},
+		client: {
+			webSocketURL: config.get("devServer.webSocketURL")
+		},
 		hot: true,
-		port: config.get("frontendPort"),
-		public: config.get("frontendDomain"),
+		historyApiFallback: true,
+		port: config.get("devServer.port"),
 		host: "0.0.0.0",
-		watchOptions: {
-			aggregateTimeout: 300,
-			poll: 1000
-		},
-		disableHostCheck: true
+		allowedHosts: "all"
 	}
 });

Some files were not shown because too many files changed in this diff