+
+
+ Proxy
+ Proxy username
+ Is working
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+AccountModal.propTypes = {
+ editMode: PropTypes.bool.isRequired,
+};
+
+export default AccountModal;
diff --git a/TbsReact/ClientApp/src/components/sidebar/SideBar.js b/TbsReact/ClientApp/src/components/sidebar/SideBar.js
new file mode 100644
index 000000000..11c4f45b8
--- /dev/null
+++ b/TbsReact/ClientApp/src/components/sidebar/SideBar.js
@@ -0,0 +1,88 @@
+import React, { useEffect, useState } from "react";
+import { useSelector, useDispatch } from "react-redux";
+
+import { resetAccount } from "../../slices/account";
+
+import { Drawer, IconButton, Grid, Button } from "@mui/material";
+import { Menu, ChevronLeft } from "@mui/icons-material";
+
+import AccountTable from "./AccountTable";
+import AccountModal from "./Modal/AccountModal";
+
+import { deleteAccount } from "../../api/Accounts/Account";
+import { login, logout, getStatus } from "../../api/Accounts/Driver";
+
+const SideBar = () => {
+ const [open, setOpen] = useState(false);
+ const [status, setStatus] = useState(false);
+
+ const dispatch = useDispatch();
+ const account = useSelector((state) => state.account.info);
+
+ useEffect(() => {
+ if (account.id !== -1) {
+ getStatus(account.id).then((status) => setStatus(status));
+ }
+ }, [account.id]);
+
+ const handleDrawerOpen = () => {
+ setOpen(true);
+ };
+
+ const handleDrawerClose = () => {
+ setOpen(false);
+ };
+
+ const onDelete = async () => {
+ dispatch(resetAccount);
+ await deleteAccount(account.id);
+ };
+
+ const onLog = async () => {
+ if (status === true) {
+ await logout(account.id);
+ setStatus(await getStatus(account.id));
+ } else {
+ await login(account.id);
+ setStatus(await getStatus(account.id));
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default SideBar;
diff --git a/TbsReact/ClientApp/src/hooks/usePrevious.js b/TbsReact/ClientApp/src/hooks/usePrevious.js
new file mode 100644
index 000000000..4072dcdd1
--- /dev/null
+++ b/TbsReact/ClientApp/src/hooks/usePrevious.js
@@ -0,0 +1,9 @@
+import { useRef, useEffect } from "react";
+
+export function usePrevious(value) {
+ const ref = useRef();
+ useEffect(() => {
+ ref.current = value;
+ }, [value]);
+ return ref.current;
+}
diff --git a/TbsReact/ClientApp/src/hooks/useSignalR.js b/TbsReact/ClientApp/src/hooks/useSignalR.js
new file mode 100644
index 000000000..0b236ca1a
--- /dev/null
+++ b/TbsReact/ClientApp/src/hooks/useSignalR.js
@@ -0,0 +1,15 @@
+import { createContext, useContext } from "react";
+
+const SignalRContext = createContext();
+
+const useSignalR = () => {
+ const context = useContext(SignalRContext);
+
+ if (!context) {
+ throw new Error("useSignalR must be used within a ");
+ }
+
+ return context;
+};
+
+export { SignalRContext, useSignalR };
diff --git a/TbsReact/ClientApp/src/hooks/useVillage.js b/TbsReact/ClientApp/src/hooks/useVillage.js
new file mode 100644
index 000000000..a152f19ef
--- /dev/null
+++ b/TbsReact/ClientApp/src/hooks/useVillage.js
@@ -0,0 +1,15 @@
+import { createContext, useContext } from "react";
+
+const VillageContext = createContext();
+
+const useVillage = () => {
+ const context = useContext(VillageContext);
+
+ if (!context) {
+ throw new Error("useVillage must be used within a ");
+ }
+
+ return context;
+};
+
+export { VillageContext, useVillage };
diff --git a/TbsReact/ClientApp/src/index.js b/TbsReact/ClientApp/src/index.js
new file mode 100644
index 000000000..127e56454
--- /dev/null
+++ b/TbsReact/ClientApp/src/index.js
@@ -0,0 +1,39 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import { BrowserRouter } from "react-router-dom";
+import App from "./App";
+import * as serviceWorkerRegistration from "./serviceWorkerRegistration";
+
+import { Provider } from "react-redux";
+import store from "./store";
+import axios from "axios";
+
+axios.defaults.baseURL = "api/";
+
+const baseUrl = document.getElementsByTagName("base")[0].getAttribute("href");
+const rootElement = document.getElementById("root");
+
+ReactDOM.render(
+
+
+
+
+ ,
+ rootElement
+);
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: https://cra.link/PWA
+serviceWorkerRegistration.unregister();
+
+if (module.hot) {
+ module.hot.accept(); // already had this init code
+
+ module.hot.addStatusHandler((status) => {
+ if (status === "prepare") {
+ console.log("=============================");
+ console.log("Reloaded");
+ }
+ });
+}
diff --git a/TbsReact/ClientApp/src/realtime/account.js b/TbsReact/ClientApp/src/realtime/account.js
new file mode 100644
index 000000000..744d5a9f2
--- /dev/null
+++ b/TbsReact/ClientApp/src/realtime/account.js
@@ -0,0 +1,10 @@
+const changeAccount = (signalRConnection, index, oldIndex) => {
+ if (index !== -1) {
+ signalRConnection.invoke("AddGroup", index);
+ }
+ if (oldIndex !== -1) {
+ signalRConnection.invoke("RemoveGroup", oldIndex);
+ }
+};
+
+export { changeAccount };
diff --git a/TbsReact/ClientApp/src/registerServiceWorker.js b/TbsReact/ClientApp/src/registerServiceWorker.js
new file mode 100644
index 000000000..750d75bed
--- /dev/null
+++ b/TbsReact/ClientApp/src/registerServiceWorker.js
@@ -0,0 +1,111 @@
+// In production, we register a service worker to serve assets from local cache.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on the "N+1" visit to a page, since previously
+// cached resources are updated in the background.
+
+// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
+// This link also includes instructions on opting out of this behavior.
+
+const isLocalhost = Boolean(
+ window.location.hostname === "localhost" ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === "[::1]" ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export default function register() {
+ if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener("load", () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Lets check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl);
+ } else {
+ // Is not local host. Just register service worker
+ registerValidSW(swUrl);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then((registration) => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === "installed") {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the old content will have been purged and
+ // the fresh content will have been added to the cache.
+ // It's the perfect time to display a "New content is
+ // available; please refresh." message in your web app.
+ console.log(
+ "New content is available; please refresh."
+ );
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log("Content is cached for offline use.");
+ }
+ }
+ };
+ };
+ })
+ .catch((error) => {
+ console.error("Error during service worker registration:", error);
+ });
+}
+
+function checkValidServiceWorker(swUrl) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then((response) => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ if (
+ response.status === 404 ||
+ response.headers.get("content-type").indexOf("javascript") ===
+ -1
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then((registration) => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl);
+ }
+ })
+ .catch(() => {
+ console.log(
+ "No internet connection found. App is running in offline mode."
+ );
+ });
+}
+
+export function unregister() {
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.ready.then((registration) => {
+ registration.unregister();
+ });
+ }
+}
diff --git a/TbsReact/ClientApp/src/service-worker.js b/TbsReact/ClientApp/src/service-worker.js
new file mode 100644
index 000000000..590730f33
--- /dev/null
+++ b/TbsReact/ClientApp/src/service-worker.js
@@ -0,0 +1,73 @@
+/* eslint-disable no-restricted-globals */
+
+// This service worker can be customized!
+// See https://developers.google.com/web/tools/workbox/modules
+// for the list of available Workbox modules, or add any other
+// code you'd like.
+// You can also remove this file if you'd prefer not to use a
+// service worker, and the Workbox build step will be skipped.
+
+import { clientsClaim } from "workbox-core";
+import { ExpirationPlugin } from "workbox-expiration";
+import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
+import { registerRoute } from "workbox-routing";
+import { StaleWhileRevalidate } from "workbox-strategies";
+
+clientsClaim();
+
+// Precache all of the assets generated by your build process.
+// Their URLs are injected into the manifest variable below.
+// This variable must be present somewhere in your service worker file,
+// even if you decide not to use precaching. See https://cra.link/PWA
+precacheAndRoute(self.__WB_MANIFEST);
+
+// Set up App Shell-style routing, so that all navigation requests
+// are fulfilled with your index.html shell. Learn more at
+// https://developers.google.com/web/fundamentals/architecture/app-shell
+const fileExtensionRegexp = /[^/?]+\\.[^/]+$'/;
+registerRoute(
+ // Return false to exempt requests from being fulfilled by index.html.
+ ({ request, url }) => {
+ // If this isn't a navigation, skip.
+ if (request.mode !== "navigate") {
+ return false;
+ } // If this is a URL that starts with /_, skip.
+
+ if (url.pathname.startsWith("/_")) {
+ return false;
+ } // If this looks like a URL for a resource, because it contains // a file extension, skip.
+
+ if (url.pathname.match(fileExtensionRegexp)) {
+ return false;
+ } // Return true to signal that we want to use the handler.
+
+ return true;
+ },
+ createHandlerBoundToURL(process.env.PUBLIC_URL + "/index.html")
+);
+
+// An example runtime caching route for requests that aren't handled by the
+// precache, in this case same-origin .png requests like those from in public/
+registerRoute(
+ // Add in any other file extensions or routing criteria as needed.
+ ({ url }) =>
+ url.origin === self.location.origin && url.pathname.endsWith(".png"), // Customize this strategy as needed, e.g., by changing to CacheFirst.
+ new StaleWhileRevalidate({
+ cacheName: "images",
+ plugins: [
+ // Ensure that once this runtime cache reaches a maximum size the
+ // least-recently used images are removed.
+ new ExpirationPlugin({ maxEntries: 50 }),
+ ],
+ })
+);
+
+// This allows the web app to trigger skipWaiting via
+// registration.waiting.postMessage({type: 'SKIP_WAITING'})
+self.addEventListener("message", (event) => {
+ if (event.data && event.data.type === "SKIP_WAITING") {
+ self.skipWaiting();
+ }
+});
+
+// Any other custom service worker logic can go here.
diff --git a/TbsReact/ClientApp/src/serviceWorkerRegistration.js b/TbsReact/ClientApp/src/serviceWorkerRegistration.js
new file mode 100644
index 000000000..3740a9108
--- /dev/null
+++ b/TbsReact/ClientApp/src/serviceWorkerRegistration.js
@@ -0,0 +1,142 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://cra.link/PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === "localhost" ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === "[::1]" ||
+ // 127.0.0.0/8 are considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export function register(config) {
+ if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener("load", () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ "This web app is being served cache-first by a service " +
+ "worker. To learn more, visit https://cra.link/PWA"
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then((registration) => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === "installed") {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log(
+ "New content is available and will be used when all " +
+ "tabs for this page are closed. See https://cra.link/PWA."
+ );
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log("Content is cached for offline use.");
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch((error) => {
+ console.error("Error during service worker registration:", error);
+ });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl, {
+ headers: { "Service-Worker": "script" },
+ })
+ .then((response) => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get("content-type");
+ if (
+ response.status === 404 ||
+ (contentType != null &&
+ contentType.indexOf("javascript") === -1)
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then((registration) => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log(
+ "No internet connection found. App is running in offline mode."
+ );
+ });
+}
+
+export function unregister() {
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.ready
+ .then((registration) => {
+ registration.unregister();
+ })
+ .catch((error) => {
+ console.error(error.message);
+ });
+ }
+}
diff --git a/TbsReact/ClientApp/src/slices/account.js b/TbsReact/ClientApp/src/slices/account.js
new file mode 100644
index 000000000..cb33468b9
--- /dev/null
+++ b/TbsReact/ClientApp/src/slices/account.js
@@ -0,0 +1,57 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+
+import { getAccount } from "../api/Accounts/Account";
+import { getStatus } from "../api/Accounts/Driver";
+export const fetchAccountByID = createAsyncThunk(
+ "account/fetcInfohById",
+ async (id, thunkAPI) => {
+ const data = await getAccount(id);
+ return data;
+ }
+);
+
+export const fetchStatusByID = createAsyncThunk(
+ "account/fetchStatusById",
+ async (id, thunkAPI) => {
+ const data = await getStatus(id);
+ return data;
+ }
+);
+
+const initialState = {
+ info: {
+ id: -1,
+ name: "Not selected",
+ serverUrl: "",
+ },
+ status: false,
+};
+
+export const accountSlice = createSlice({
+ name: "account",
+ initialState,
+ reducers: {
+ setAccount: (state, action) => {
+ state.info = action.payload;
+ },
+
+ resetAccount: (state, action) => {
+ return initialState;
+ },
+
+ setStatus: (state, action) => {
+ state.status = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder.addCase(fetchAccountByID.fulfilled, (state, action) => {
+ state.info = action.payload;
+ });
+ builder.addCase(fetchStatusByID.fulfilled, (state, action) => {
+ state.status = action.payload;
+ });
+ },
+});
+
+export const { setAccount, resetAccount, setStatus } = accountSlice.actions;
+export default accountSlice.reducer;
diff --git a/TbsReact/ClientApp/src/slices/village.js b/TbsReact/ClientApp/src/slices/village.js
new file mode 100644
index 000000000..71c6703c0
--- /dev/null
+++ b/TbsReact/ClientApp/src/slices/village.js
@@ -0,0 +1,29 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ info: {
+ id: -1,
+ name: 1,
+ coords: {
+ x: 1,
+ y: 1,
+ },
+ },
+};
+
+export const villageSlice = createSlice({
+ name: "village",
+ initialState,
+ reducers: {
+ setVillage: (state, action) => {
+ state.info = action.payload;
+ },
+
+ resetVillage: (state, action) => {
+ return initialState;
+ },
+ },
+});
+
+export const { setVillage, resetVillage } = villageSlice.actions;
+export default villageSlice.reducer;
diff --git a/TbsReact/ClientApp/src/store.js b/TbsReact/ClientApp/src/store.js
new file mode 100644
index 000000000..44b844eb1
--- /dev/null
+++ b/TbsReact/ClientApp/src/store.js
@@ -0,0 +1,11 @@
+import { configureStore } from "@reduxjs/toolkit";
+
+import accountReducer from "./slices/account";
+import villageReducer from "./slices/village";
+
+export default configureStore({
+ reducer: {
+ account: accountReducer,
+ village: villageReducer,
+ },
+});
diff --git a/TbsReact/ClientApp/src/styles/box.js b/TbsReact/ClientApp/src/styles/box.js
new file mode 100644
index 000000000..e6bb79554
--- /dev/null
+++ b/TbsReact/ClientApp/src/styles/box.js
@@ -0,0 +1,7 @@
+export default {
+ bgcolor: "background.paper",
+ border: "2px solid #000",
+ boxShadow: 24,
+ p: 2,
+ justifyContent: "center",
+};
diff --git a/TbsReact/ClientApp/src/styles/modal.js b/TbsReact/ClientApp/src/styles/modal.js
new file mode 100644
index 000000000..c7e9fef62
--- /dev/null
+++ b/TbsReact/ClientApp/src/styles/modal.js
@@ -0,0 +1,11 @@
+export default {
+ bgcolor: "background.paper",
+ border: "2px solid #000",
+ boxShadow: 24,
+ p: 4,
+ justifyContent: "center",
+ position: "absolute",
+ top: "50%",
+ left: "50%",
+ transform: "translate(-50%, -50%)",
+};
diff --git a/TbsReact/ClientApp/src/yup/Settings/ActivitySchema.js b/TbsReact/ClientApp/src/yup/Settings/ActivitySchema.js
new file mode 100644
index 000000000..47c135618
--- /dev/null
+++ b/TbsReact/ClientApp/src/yup/Settings/ActivitySchema.js
@@ -0,0 +1,43 @@
+import * as yup from "yup";
+
+const ActivitySchema = yup
+ .object()
+ .shape({
+ work: yup.object().shape({
+ min: yup
+ .number()
+ .min(1)
+ .integer()
+ .required("Minimum is required")
+ .typeError("Minimum must be a number"),
+ max: yup
+ .number()
+ .moreThan(
+ yup.ref("min"),
+ `Maximum must be greater than minimum`
+ )
+ .integer()
+ .required("Maximum is required")
+ .typeError("Maximum must be a number"),
+ }),
+ sleep: yup.object().shape({
+ min: yup
+ .number()
+ .min(1)
+ .integer()
+ .required("Minimum is required")
+ .typeError("Minimum must be a number"),
+ max: yup
+ .number()
+ .moreThan(
+ yup.ref("min"),
+ `Maximum must be greater than minimum`
+ )
+ .integer()
+ .required("Maximum is required")
+ .typeError("Maximum must be a number"),
+ }),
+ })
+ .required();
+
+export default ActivitySchema;
diff --git a/TbsReact/ClientApp/src/yup/Settings/ChomreSchema.js b/TbsReact/ClientApp/src/yup/Settings/ChomreSchema.js
new file mode 100644
index 000000000..cbd90affc
--- /dev/null
+++ b/TbsReact/ClientApp/src/yup/Settings/ChomreSchema.js
@@ -0,0 +1,26 @@
+import * as yup from "yup";
+
+const ChromeSchema = yup
+ .object()
+ .shape({
+ click: yup.object().shape({
+ min: yup
+ .number()
+ .min(1)
+ .integer()
+ .required("Minimum is required")
+ .typeError("Minimum must be a number"),
+ max: yup
+ .number()
+ .moreThan(
+ yup.ref("min"),
+ `Maximum must be greater than minimum`
+ )
+ .integer()
+ .required("Maximum is required")
+ .typeError("Maximum must be a number"),
+ }),
+ })
+ .required();
+
+export default ChromeSchema;
diff --git a/TbsReact/Controllers/AccessesController.cs b/TbsReact/Controllers/AccessesController.cs
new file mode 100644
index 000000000..093b4e4ee
--- /dev/null
+++ b/TbsReact/Controllers/AccessesController.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models;
+using TbsReact.Singleton;
+using TbsReact.Extension;
+using System.Collections.Generic;
+
+namespace TbsReact.Controllers
+{
+ [ApiController]
+ [Route("api/accesses/{indexAcc:int}")]
+ public class AccessesControler : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult> GetAccesses(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var accesses = new List();
+ for (int i = 0; i < acc.Access.AllAccess.Count; i++)
+ {
+ accesses.Add(acc.Access.AllAccess[i].GetAccount(i));
+ }
+
+ return accesses;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/AccountController.cs b/TbsReact/Controllers/AccountController.cs
new file mode 100644
index 000000000..6edbccb8c
--- /dev/null
+++ b/TbsReact/Controllers/AccountController.cs
@@ -0,0 +1,113 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models;
+using TbsReact.Singleton;
+using TbsReact.Extension;
+using System.Collections.Generic;
+using TbsCore.Database;
+using System;
+
+namespace TbsReact.Controllers
+{
+ [ApiController]
+ [Route("api/accounts")]
+ public class AccountController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult GetAccounts()
+ {
+ var AccountInfoList = new List();
+ for (int i = 0; i < AccountData.Accounts.Count; i++)
+ {
+ AccountInfoList.Add(AccountData.Accounts[i]);
+ }
+
+ return Ok(AccountInfoList);
+ }
+
+ [HttpGet("{index:int}")]
+ public ActionResult GetAccount(int index)
+ {
+ var acc = AccountData.GetAccount(index);
+ if (acc == null)
+ {
+ return NotFound();
+ }
+ return Ok(acc);
+ }
+
+ [HttpPost]
+ public ActionResult AddAccount([FromBody] NewAccount data)
+ {
+ var account = data.Account;
+ var accesses = data.Accesses;
+ if (string.IsNullOrEmpty(account.Name)
+ || string.IsNullOrEmpty(account.ServerUrl))
+ {
+ return BadRequest();
+ }
+ var acc = account.GetAccount(accesses);
+ DbRepository.SaveAccount(acc);
+ AccountManager.Accounts.Add(acc);
+ var result = AccountData.AddAccount(account);
+
+ return CreatedAtAction(nameof(GetAccount), new { id = result.Id }, result);
+ }
+
+ [HttpPatch("{index:int}")]
+ public ActionResult EditAccount(int index, [FromBody] NewAccount data)
+ {
+ var account = data.Account;
+ var accesses = data.Accesses;
+
+ if (string.IsNullOrEmpty(account.Name) ||
+ string.IsNullOrEmpty(account.ServerUrl))
+ {
+ return BadRequest();
+ }
+ var accountOld = AccountData.GetAccount(index);
+ if (accountOld == null)
+ {
+ return NotFound();
+ }
+
+ var acc = AccountManager.GetAccount(accountOld);
+
+ AccountData.EditAccount(index, account);
+
+ acc.AccInfo.Nickname = account.Name;
+ acc.AccInfo.ServerUrl = account.ServerUrl;
+
+ acc.Access.AllAccess.Clear();
+ foreach (var access in accesses)
+ {
+ acc.Access.AllAccess.Add(new TbsCore.Models.Access.Access
+ {
+ Password = access.Password,
+ Proxy = access.Proxy.Ip,
+ ProxyPort = access.Proxy.Port,
+ ProxyUsername = access.Proxy.Username,
+ ProxyPassword = access.Proxy.Password,
+ });
+ }
+ DbRepository.SaveAccount(acc);
+ return Ok();
+ }
+
+ [HttpDelete("{index:int}")]
+ public ActionResult DeleteAccount(int index)
+ {
+ var account = AccountData.GetAccount(index);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ DbRepository.RemoveAccount(acc);
+ AccountManager.Accounts.Remove(acc);
+ AccountData.DeleteAccount(index);
+
+ return Ok();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/ActivityController.cs b/TbsReact/Controllers/ActivityController.cs
new file mode 100644
index 000000000..d2577eef9
--- /dev/null
+++ b/TbsReact/Controllers/ActivityController.cs
@@ -0,0 +1,38 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Singleton;
+
+namespace TbsReact.Controllers
+{
+ [Route("api")]
+ public class ActivityController : ControllerBase
+ {
+ [HttpGet]
+ [Route("task/{indexAcc:int}")]
+ public ActionResult GetTask(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var list = TaskManager.GetTaskList(account.Name);
+ if (list == null)
+ {
+ return Ok("null");
+ }
+ return Ok(list);
+ }
+
+ [HttpGet]
+ [Route("logger/{indexAcc:int}")]
+ public ActionResult GetLog(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ return Ok(LogManager.GetLogData(account.Name));
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/BuildController.cs b/TbsReact/Controllers/BuildController.cs
new file mode 100644
index 000000000..d95dd0c0e
--- /dev/null
+++ b/TbsReact/Controllers/BuildController.cs
@@ -0,0 +1,371 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models.Villages.Building;
+using TbsReact.Singleton;
+using TbsReact.Extension;
+using System.Collections.Generic;
+using System.Linq;
+using TbsCore.Models.BuildingModels;
+using static TbsCore.Helpers.Classificator;
+using System;
+using TbsCore.Helpers;
+using TbsCore.Tasks;
+using TbsReact.Models;
+
+namespace TbsReact.Controllers
+{
+ [ApiController]
+ [Route("api/villages/{indexAcc:int}/build/{indexVill:int}")]
+ public class BuildController : ControllerBase
+ {
+ [HttpGet("buildings")]
+ public ActionResult> GetBuildings(int indexAcc, int indexVill)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+ var buildings = village.Build.Buildings;
+ var result = new List();
+ for (int i = 0; i < buildings.Length; i++)
+ {
+ result.Add(buildings[i].GetInfo(i));
+ }
+
+ return result;
+ }
+
+ [HttpGet("current")]
+ public ActionResult> GetCurrent(int indexAcc, int indexVill)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+ var buildings = village.Build.CurrentlyBuilding;
+ var result = new List();
+ for (int i = 0; i < buildings.Count; i++)
+ {
+ result.Add(buildings[i].GetInfo(i));
+ }
+
+ return result;
+ }
+
+ [HttpGet("queue")]
+ public ActionResult> GetQueue(int indexAcc, int indexVill)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+ var buildings = village.Build.Tasks;
+ var result = new List();
+ for (int i = 0; i < buildings.Count; i++)
+ {
+ result.Add(buildings[i].GetInfo(i));
+ }
+
+ return result;
+ }
+
+ [HttpPost("queue/normal")]
+ public ActionResult> AddNormal(int indexAcc, int indexVill, [FromBody] RequestNormal request)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+ var buildings = village.Build.Tasks;
+
+ var task = new BuildingTask
+ {
+ TaskType = BuildingType.General,
+ Level = request.Level,
+ BuildingId = (byte)request.Location,
+ };
+
+ // Building already planned on this ID
+ var plannedBuilding = buildings.FirstOrDefault(x => x.BuildingId == task.BuildingId);
+ var selectedBuilding = village.Build.Buildings.FirstOrDefault(x => x.Id == task.BuildingId);
+
+ //Create building task, construct new building
+ if (selectedBuilding.Type == BuildingEnum.Site)
+ {
+ if (plannedBuilding == null) // No building has been planned on this ID
+ {
+ _ = Enum.TryParse(request.Building.ToString(), out BuildingEnum building);
+ task.Building = building;
+ task.ConstructNew = true;
+ }
+ else // Building was already planned
+ {
+ task.Building = plannedBuilding.Building;
+ }
+ }
+ else //upgrade existing building
+ {
+ task.Building = selectedBuilding.Type;
+ }
+
+ BuildingHelper.AddBuildingTask(acc, village, task);
+
+ var result = new List();
+ for (int i = 0; i < buildings.Count; i++)
+ {
+ result.Add(buildings[i].GetInfo(i));
+ }
+ return Ok(result);
+ }
+
+ [HttpPost("queue/resource")]
+ public ActionResult> AddResource(int indexAcc, int indexVill, [FromBody] RequestResource request)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+ var buildings = village.Build.Tasks;
+
+ var task = new BuildingTask
+ {
+ TaskType = BuildingType.AutoUpgradeResFields,
+ Level = request.Level,
+ ResourceType = (ResTypeEnum)request.Type,
+ BuildingStrategy = (BuildingStrategyEnum)request.Strategy
+ };
+ BuildingHelper.AddBuildingTask(acc, village, task);
+
+ var result = new List();
+ for (int i = 0; i < buildings.Count; i++)
+ {
+ result.Add(buildings[i].GetInfo(i));
+ }
+ return Ok(result);
+ }
+
+ [HttpPost("queue/prerequisites")]
+ public ActionResult> AddPrerequisites(int indexAcc, int indexVill, [FromBody] RequestPrerequisites request)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+ var buildings = village.Build.Tasks;
+ _ = Enum.TryParse(request.Building, out BuildingEnum building);
+ BuildingHelper.AddBuildingPrerequisites(acc, village, building);
+
+ BuildingHelper.AddBuildingTask(acc, village, new BuildingTask()
+ {
+ Building = building,
+ Level = 1,
+ TaskType = BuildingType.General
+ });
+
+ var result = new List();
+ for (int i = 0; i < buildings.Count; i++)
+ {
+ result.Add(buildings[i].GetInfo(i));
+ }
+ return Ok(result);
+ }
+
+ [HttpPatch("queue")]
+ public ActionResult> UpdatePosition(int indexAcc, int indexVill, [FromBody] RequestChange request)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+ var buildings = village.Build.Tasks;
+
+ Swap(buildings, request.IndexNew, request.IndexOld);
+
+ var result = new List();
+ for (int i = 0; i < buildings.Count; i++)
+ {
+ result.Add(buildings[i].GetInfo(i));
+ }
+ return Ok(result);
+ }
+
+ [HttpDelete("queue/{position:int}")]
+ public ActionResult> DeletePosition(int indexAcc, int indexVill, int position)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+ var buildings = village.Build.Tasks;
+ buildings.RemoveAt(position);
+
+ var result = new List();
+ for (int i = 0; i < buildings.Count; i++)
+ {
+ result.Add(buildings[i].GetInfo(i));
+ }
+ return Ok(result);
+ }
+
+ ///
+ /// Get buildings can build in normal
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpGet("buildings/normal/{position:int}")]
+ public ActionResult GetNormalBuildings(int indexAcc, int indexVill, int position)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+
+ if (position < 0 || position > village.Build.Buildings.Length) return null;
+
+ var result = new NormalBuild();
+ // Check if there is already a building planner for that id
+ var selectedBuilding = village.Build.Buildings[position];
+ var planedBuilding = village.Build.Tasks.LastOrDefault(x => x.BuildingId == position);
+
+ // level
+ if (selectedBuilding.Type != BuildingEnum.Site) result.Level = selectedBuilding.Level + 1;
+ else if (planedBuilding != null) result.Level = planedBuilding.Level + 1;
+ else result.Level = 1;
+
+ result.BuildList = new List();
+ // build list
+ if (selectedBuilding.Type == BuildingEnum.Site)
+ {
+ if (planedBuilding != null)
+ {
+ result.BuildList.Add(new Entity
+ {
+ Name = planedBuilding.Building.ToString(),
+ Id = result.BuildList.Count
+ });
+ return result;
+ }
+
+ for (int i = 5; i <= 45; i++)
+ {
+ if (BuildingHelper.BuildingRequirementsAreMet((BuildingEnum)i, village, acc.AccInfo.Tribe ?? TribeEnum.Natars))
+ {
+ result.BuildList.Add(new Entity
+ {
+ Name = ((BuildingEnum)i).ToString(),
+ Id = result.BuildList.Count
+ });
+ }
+ }
+ return result;
+ }
+ else // Building already there
+ {
+ result.BuildList.Add(new Entity
+ {
+ Name = selectedBuilding.Type.ToString(),
+ Id = result.BuildList.Count
+ });
+ return result;
+ }
+ }
+
+ [HttpGet("buildings/prerequisites")]
+ public ActionResult> GetPrerequisitesBuildings(int indexAcc, int indexVill)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var village = acc.Villages.FirstOrDefault(x => x.Id == indexVill);
+ if (village == null)
+ {
+ return NotFound();
+ }
+
+ var result = new List();
+ var prereqComboList = BuildingHelper.SetPrereqCombo(acc, village);
+
+ prereqComboList.ForEach(x => result.Add(new Entity
+ {
+ Name = x,
+ Id = result.Count
+ }));
+
+ return result;
+ }
+
+ private static void Swap(List list, int indexA, int indexB)
+ {
+ BuildingTask tmp = list[indexA];
+ list[indexA] = list[indexB];
+ list[indexB] = tmp;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/DriverController.cs b/TbsReact/Controllers/DriverController.cs
new file mode 100644
index 000000000..15dc359c9
--- /dev/null
+++ b/TbsReact/Controllers/DriverController.cs
@@ -0,0 +1,76 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using TbsCore.Helpers;
+using TbsReact.Singleton;
+
+namespace TbsReact.Controllers
+{
+ [ApiController]
+ [Route("api")]
+ public class DriverController : ControllerBase
+ {
+ [HttpPost("login/{index:int}")]
+ public async Task Login(int index)
+ {
+ var account = AccountData.GetAccount(index);
+ if (account == null)
+ {
+ return NotFound();
+ }
+
+ var acc = AccountManager.GetAccount(account);
+
+ if (acc.Access.AllAccess.Count > 0)
+ {
+ AccountManager.SendMessage(account.Name, "message", $"Account {account.Name} is logging");
+ await IoHelperCore.LoginAccount(acc);
+ TaskManager.AddAccount(acc);
+ AccountManager.SendMessage(account.Name, "message", $"Account {account.Name} logged in");
+
+ return Ok();
+ }
+
+ return new BadRequestObjectResult("Account you are trying to login has no access defined. Please edit the account.");
+ }
+
+ [HttpPost("logout/{index:int}")]
+ public ActionResult Logout(int index)
+ {
+ var account = AccountData.GetAccount(index);
+ if (account == null)
+ {
+ return NotFound();
+ }
+
+ var acc = AccountManager.GetAccount(account);
+
+ if (acc.TaskTimer != null && acc.TaskTimer.IsBotRunning() == true)
+ {
+ IoHelperCore.Logout(acc);
+ return Ok();
+ }
+
+ return BadRequest();
+ }
+
+ [Route("status/{index:int}")]
+ public ActionResult GetStatus(int index)
+ {
+ var account = AccountData.GetAccount(index);
+ if (account == null)
+ {
+ return NotFound();
+ }
+
+ var acc = AccountManager.GetAccount(account);
+ if (acc.TaskTimer != null && acc.TaskTimer.IsBotRunning() == true)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/Setting/ActivityController.cs b/TbsReact/Controllers/Setting/ActivityController.cs
new file mode 100644
index 000000000..e9be1daf5
--- /dev/null
+++ b/TbsReact/Controllers/Setting/ActivityController.cs
@@ -0,0 +1,59 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models;
+using TbsReact.Models.Setting;
+using TbsReact.Singleton;
+
+namespace TbsReact.Controllers.Setting
+{
+ [ApiController]
+ [Route("api/settings/activity/{indexAcc:int}")]
+ public class ActivityController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult GetSetting(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ var setting = new Activity
+ {
+ SleepTime = new Range
+ {
+ Min = acc.Settings.Time.MinSleep,
+ Max = acc.Settings.Time.MaxSleep,
+ },
+ WorkTime = new Range
+ {
+ Min = acc.Settings.Time.MinWork,
+ Max = acc.Settings.Time.MaxWork,
+ }
+ };
+
+ return Ok(setting);
+ }
+
+ [HttpPut]
+ public ActionResult PutSetting(int indexAcc, [FromBody] Activity setting)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ acc.Settings.Time.MinSleep = setting.SleepTime.Min;
+ acc.Settings.Time.MaxSleep = setting.SleepTime.Max;
+ acc.Settings.Time.MinWork = setting.WorkTime.Min;
+ acc.Settings.Time.MaxWork = setting.WorkTime.Max;
+
+ return Ok();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/Setting/ChromeController.cs b/TbsReact/Controllers/Setting/ChromeController.cs
new file mode 100644
index 000000000..e74a9a19b
--- /dev/null
+++ b/TbsReact/Controllers/Setting/ChromeController.cs
@@ -0,0 +1,56 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models;
+using TbsReact.Models.Setting;
+using TbsReact.Singleton;
+
+namespace TbsReact.Controllers.Setting
+{
+ [ApiController]
+ [Route("api/settings/chrome/{indexAcc:int}")]
+ public class ChromeController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult GetSetting(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ var setting = new Chrome
+ {
+ Click = new Range
+ {
+ Min = acc.Settings.DelayClickingMin,
+ Max = acc.Settings.DelayClickingMax,
+ },
+ DisableImages = acc.Settings.DisableImages,
+ AutoClose = acc.Settings.AutoCloseDriver,
+ };
+
+ return Ok(setting);
+ }
+
+ [HttpPut]
+ public ActionResult PutSetting(int indexAcc, [FromBody] Chrome setting)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ acc.Settings.DelayClickingMin = setting.Click.Min;
+ acc.Settings.DelayClickingMax = setting.Click.Max;
+ acc.Settings.DisableImages = setting.DisableImages;
+ acc.Settings.AutoCloseDriver = setting.AutoClose;
+
+ return Ok();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/Setting/DiscordController.cs b/TbsReact/Controllers/Setting/DiscordController.cs
new file mode 100644
index 000000000..c032cb90a
--- /dev/null
+++ b/TbsReact/Controllers/Setting/DiscordController.cs
@@ -0,0 +1,50 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models.Setting;
+using TbsReact.Singleton;
+
+namespace TbsReact.Controllers.Setting
+{
+ [ApiController]
+ [Route("api/ssettings/discordwebhook/{indexAcc:int}")]
+ public class DiscordController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult GetSetting(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ var setting = new DiscordWebhook
+ {
+ IsActive = acc.Settings.DiscordWebhook,
+ IsOnlineMsg = acc.Settings.DiscordOnlineAnnouncement,
+ UrlWebhook = acc.AccInfo.WebhookUrl,
+ };
+
+ return Ok(setting);
+ }
+
+ [HttpPut]
+ public ActionResult PutSetting(int indexAcc, [FromBody] DiscordWebhook setting)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ acc.Settings.DiscordWebhook = setting.IsActive;
+ acc.Settings.DiscordOnlineAnnouncement = setting.IsOnlineMsg;
+ acc.AccInfo.WebhookUrl = setting.UrlWebhook;
+
+ return Ok();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/Setting/HeroController.cs b/TbsReact/Controllers/Setting/HeroController.cs
new file mode 100644
index 000000000..ba7d913a3
--- /dev/null
+++ b/TbsReact/Controllers/Setting/HeroController.cs
@@ -0,0 +1,83 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models;
+using TbsReact.Models.Setting;
+using TbsReact.Singleton;
+
+namespace TbsReact.Controllers.Setting
+{
+ [ApiController]
+ [Route("api/settings/hero/{indexAcc:int}")]
+ public class HeroController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult GetSetting(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ var setting = new Hero
+ {
+ AutoAdventure = new AutoAdventure
+ {
+ IsActive = acc.Hero.Settings.AutoSendToAdventure,
+ MaxDistance = acc.Hero.Settings.MaxDistance,
+ MinHealth = acc.Hero.Settings.MinHealth,
+ },
+ AutoPoint = new AutoPoint
+ {
+ IsActive = acc.Hero.Settings.AutoSetPoints,
+ Points = acc.Hero.Settings.Upgrades,
+ },
+ AutoRefresh = new AutoRefresh
+ {
+ IsActive = acc.Hero.Settings.AutoRefreshInfo,
+ Frequency = new Range
+ {
+ Min = acc.Hero.Settings.MinUpdate,
+ Max = acc.Hero.Settings.MaxUpdate,
+ }
+ },
+ AutoRevive = new AutoRevive
+ {
+ IsActive = acc.Hero.Settings.AutoReviveHero,
+ VillageId = acc.Hero.HomeVillageId,
+ },
+ };
+
+ return Ok(setting);
+ }
+
+ [HttpPut]
+ public ActionResult PutSetting(int indexAcc, [FromBody] Hero setting)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ acc.Hero.Settings.AutoSendToAdventure = setting.AutoAdventure.IsActive;
+ acc.Hero.Settings.MaxDistance = setting.AutoAdventure.MaxDistance;
+ acc.Hero.Settings.MinHealth = setting.AutoAdventure.MinHealth;
+
+ acc.Hero.Settings.AutoSetPoints = setting.AutoPoint.IsActive;
+ acc.Hero.Settings.Upgrades = setting.AutoPoint.Points;
+
+ acc.Hero.Settings.AutoRefreshInfo = setting.AutoRefresh.IsActive;
+ acc.Hero.Settings.MinUpdate = setting.AutoRefresh.Frequency.Min;
+ acc.Hero.Settings.MaxUpdate = setting.AutoRefresh.Frequency.Max;
+
+ acc.Hero.Settings.AutoReviveHero = setting.AutoRevive.IsActive;
+ acc.Hero.HomeVillageId = setting.AutoRevive.VillageId;
+
+ return Ok();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/Setting/QuestController.cs b/TbsReact/Controllers/Setting/QuestController.cs
new file mode 100644
index 000000000..9b95dbe3c
--- /dev/null
+++ b/TbsReact/Controllers/Setting/QuestController.cs
@@ -0,0 +1,50 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models.Setting;
+using TbsReact.Singleton;
+
+namespace TbsReact.Controllers.Setting
+{
+ [ApiController]
+ [Route("api/settings/quest/{indexAcc:int}")]
+ public class QuestController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult GetSetting(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ var setting = new Quest
+ {
+ Beginner = acc.Quests.ClaimBeginnerQuests,
+ Daily = acc.Quests.ClaimDailyQuests,
+ VillageId = acc.Quests.VillToClaim,
+ };
+
+ return Ok(setting);
+ }
+
+ [HttpPut]
+ public ActionResult PutSetting(int indexAcc, [FromBody] Quest setting)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+
+ acc.Quests.ClaimBeginnerQuests = setting.Beginner;
+ acc.Quests.ClaimDailyQuests = setting.Daily;
+ acc.Quests.VillToClaim = setting.VillageId;
+
+ return Ok();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Controllers/VillagesController.cs b/TbsReact/Controllers/VillagesController.cs
new file mode 100644
index 000000000..0bb5180be
--- /dev/null
+++ b/TbsReact/Controllers/VillagesController.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Mvc;
+using TbsReact.Models.Villages;
+using TbsReact.Singleton;
+using TbsReact.Extension;
+using System.Collections.Generic;
+
+namespace TbsReact.Controllers
+{
+ [ApiController]
+ [Route("api/villages/{indexAcc:int}")]
+ public class VillagesController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult> GetVillages(int indexAcc)
+ {
+ var account = AccountData.GetAccount(indexAcc);
+ if (account == null)
+ {
+ return NotFound();
+ }
+ var acc = AccountManager.GetAccount(account);
+ var villages = new List();
+ for (int i = 0; i < acc.Villages.Count; i++)
+ {
+ villages.Add(acc.Villages[i].GetInfo());
+ }
+
+ return villages;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Dockerfile b/TbsReact/Dockerfile
new file mode 100644
index 000000000..e4e05a6af
--- /dev/null
+++ b/TbsReact/Dockerfile
@@ -0,0 +1,34 @@
+FROM ubuntu:18.04
+
+RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
+ xvfb \
+ xauth \
+ x11vnc \
+ x11-utils \
+ x11-xserver-utils \
+ wget \
+ gnupg \
+ gdebi-core \
+ ca-certificates \
+ coreutils \
+ && rm -rf /var/lib/apt/lists/*
+
+ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE 1
+RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add
+
+RUN echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list
+RUN apt-get update && apt-get install google-chrome-stable -y --no-install-recommends
+
+ENV DISPLAY :0.0
+EXPOSE 5900
+COPY docker-entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+
+COPY bin/Debug/net5.0/linux-x64/publish/ App/
+#COPY bin/Publish/Linux App/
+WORKDIR /App
+
+EXPOSE 5000
+
+# switch to user and start
+ENTRYPOINT ["/entrypoint.sh"]
\ No newline at end of file
diff --git a/TbsReact/Extension/Account/AccessExtension.cs b/TbsReact/Extension/Account/AccessExtension.cs
new file mode 100644
index 000000000..dad1c27e9
--- /dev/null
+++ b/TbsReact/Extension/Account/AccessExtension.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using TbsReact.Models;
+
+namespace TbsReact.Extension
+{
+ public static class AccessExtension
+ {
+ public static Access GetAccount(this TbsCore.Models.Access.Access access, int index)
+ {
+ return new Access
+ {
+ Id = index,
+ Password = access.Password,
+ Proxy = new Proxy
+ {
+ Ip = access.Proxy,
+ Port = access.ProxyPort,
+ Username = access.ProxyUsername,
+ Password = access.ProxyPassword,
+ OK = access.Ok
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Extension/Account/AccountExtension.cs b/TbsReact/Extension/Account/AccountExtension.cs
new file mode 100644
index 000000000..29088b112
--- /dev/null
+++ b/TbsReact/Extension/Account/AccountExtension.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using TbsReact.Models;
+
+namespace TbsReact.Extension
+{
+ public static class AccountExtension
+ {
+ public static TbsCore.Models.AccModels.Account GetAccount(this Account accout, List accesses)
+ {
+ var acc = new TbsCore.Models.AccModels.Account();
+ acc.Init();
+
+ acc.AccInfo.Nickname = accout.Name;
+ acc.AccInfo.ServerUrl = accout.ServerUrl;
+
+ foreach (var access in accesses)
+ {
+ acc.Access.AllAccess.Add(new TbsCore.Models.Access.Access
+ {
+ Password = access.Password,
+ Proxy = access.Proxy.Ip,
+ ProxyPort = access.Proxy.Port,
+ ProxyUsername = access.Proxy.Username,
+ ProxyPassword = access.Proxy.Password,
+ });
+ }
+
+ return acc;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Extension/VillageExtension.cs b/TbsReact/Extension/VillageExtension.cs
new file mode 100644
index 000000000..b2e445ac6
--- /dev/null
+++ b/TbsReact/Extension/VillageExtension.cs
@@ -0,0 +1,102 @@
+using TbsCore.Tasks;
+using TbsReact.Models;
+using TbsReact.Models.Villages;
+using TbsReact.Models.Villages.Building;
+
+namespace TbsReact.Extension
+{
+ public static class VillageExtension
+ {
+ public static Village GetInfo(this TbsCore.Models.VillageModels.Village village)
+ {
+ return new Village
+ {
+ Id = village.Id,
+ Name = village.Name,
+ Coordinate = new Coordinate
+ {
+ X = village.Coordinates.x,
+ Y = village.Coordinates.y,
+ },
+ };
+ }
+
+ public static Building GetInfo(this TbsCore.Models.VillageModels.Building building, int index)
+ {
+ return new Building
+ {
+ Id = index,
+ Name = building.Type.ToString(),
+ Location = building.Id,
+ Level = building.Level,
+ UnderConstruction = building.UnderConstruction,
+ };
+ }
+
+ public static TaskBuilding GetInfo(this TbsCore.Models.BuildingModels.BuildingTask task, int index)
+ {
+ string Name;
+ if (task.TaskType == TbsCore.Helpers.Classificator.BuildingType.AutoUpgradeResFields)
+ {
+ Name = AutoBuildResFieldsStr(task);
+ }
+ else
+ {
+ Name = TbsCore.Helpers.VillageHelper.BuildingTypeToString(task.Building);
+ }
+ return new TaskBuilding
+ {
+ Id = index,
+ Name = Name,
+ Level = task.Level,
+ Location = task.BuildingId ?? -1,
+ };
+ }
+
+ public static CurrentBuilding GetInfo(this TbsCore.Models.VillageModels.BuildingCurrently building, int index)
+ {
+ return new CurrentBuilding
+ {
+ Id = index,
+ Name = building.Building.ToString(),
+ Level = building.Level,
+ CompleteTime = building.Duration,
+ };
+ }
+
+ private static string AutoBuildResFieldsStr(TbsCore.Models.BuildingModels.BuildingTask task)
+ {
+ var str = "";
+ switch (task.ResourceType)
+ {
+ case ResTypeEnum.AllResources:
+ str += "All fields";
+ break;
+
+ case ResTypeEnum.ExcludeCrop:
+ str += "Exclude crop";
+ break;
+
+ case ResTypeEnum.OnlyCrop:
+ str += "Only crop";
+ break;
+ }
+ str += "-";
+ switch (task.BuildingStrategy)
+ {
+ case BuildingStrategyEnum.BasedOnLevel:
+ str += "Based on level";
+ break;
+
+ case BuildingStrategyEnum.BasedOnProduction:
+ str += "Based on production";
+ break;
+
+ case BuildingStrategyEnum.BasedOnRes:
+ str += "Based on storage";
+ break;
+ }
+ return str;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Hub/GroupHub.cs b/TbsReact/Hub/GroupHub.cs
new file mode 100644
index 000000000..a5466cf16
--- /dev/null
+++ b/TbsReact/Hub/GroupHub.cs
@@ -0,0 +1,26 @@
+using Microsoft.AspNetCore.SignalR;
+using System.Threading.Tasks;
+using TbsReact.Singleton;
+
+namespace TbsReact.Hubs
+{
+ public class GroupHub : Hub
+ {
+ public async Task AddGroup(int index)
+ {
+ var acc = AccountData.GetAccount(index);
+ await Groups.AddToGroupAsync(Context.ConnectionId, acc.Name);
+ await Clients.Caller.SendAsync("message", $"{Context.ConnectionId} is now on account {acc.Name}");
+
+ AccountManager.ClientConnect(acc.Name);
+ }
+
+ public async Task RemoveGroup(int index)
+ {
+ var acc = AccountData.GetAccount(index);
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, acc.Name);
+ await Clients.Caller.SendAsync("message", $"{Context.ConnectionId} didn't watch account {acc.Name} anymore");
+ AccountManager.ClientDisconnect(acc.Name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Account/Access.cs b/TbsReact/Models/Account/Access.cs
new file mode 100644
index 000000000..5170d6e50
--- /dev/null
+++ b/TbsReact/Models/Account/Access.cs
@@ -0,0 +1,9 @@
+namespace TbsReact.Models
+{
+ public class Access
+ {
+ public int Id { get; set; }
+ public string Password { get; set; }
+ public Proxy Proxy { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Account/Account.cs b/TbsReact/Models/Account/Account.cs
new file mode 100644
index 000000000..1b2d8f790
--- /dev/null
+++ b/TbsReact/Models/Account/Account.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace TbsReact.Models
+{
+ public class Account
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string ServerUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Account/NewAccount.cs b/TbsReact/Models/Account/NewAccount.cs
new file mode 100644
index 000000000..e0ecf3841
--- /dev/null
+++ b/TbsReact/Models/Account/NewAccount.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace TbsReact.Models
+{
+ public class NewAccount
+ {
+ public Account Account { get; set; }
+ public List Accesses { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Account/Proxy.cs b/TbsReact/Models/Account/Proxy.cs
new file mode 100644
index 000000000..4a1a49da4
--- /dev/null
+++ b/TbsReact/Models/Account/Proxy.cs
@@ -0,0 +1,11 @@
+namespace TbsReact.Models
+{
+ public class Proxy
+ {
+ public string Ip { get; set; }
+ public int Port { get; set; }
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public bool OK { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Entity.cs b/TbsReact/Models/Entity.cs
new file mode 100644
index 000000000..4169d0589
--- /dev/null
+++ b/TbsReact/Models/Entity.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Models
+{
+ public class Entity
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Setting/Activity.cs b/TbsReact/Models/Setting/Activity.cs
new file mode 100644
index 000000000..e3537e26f
--- /dev/null
+++ b/TbsReact/Models/Setting/Activity.cs
@@ -0,0 +1,10 @@
+using TbsReact.Models;
+
+namespace TbsReact.Models.Setting
+{
+ public class Activity
+ {
+ public Range WorkTime { get; set; }
+ public Range SleepTime { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Setting/Chrome.cs b/TbsReact/Models/Setting/Chrome.cs
new file mode 100644
index 000000000..5b89af3af
--- /dev/null
+++ b/TbsReact/Models/Setting/Chrome.cs
@@ -0,0 +1,11 @@
+using TbsReact.Models;
+
+namespace TbsReact.Models.Setting
+{
+ public class Chrome
+ {
+ public Range Click { get; set; }
+ public bool DisableImages { get; set; }
+ public bool AutoClose { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Setting/DiscordWebhook.cs b/TbsReact/Models/Setting/DiscordWebhook.cs
new file mode 100644
index 000000000..a00201ff3
--- /dev/null
+++ b/TbsReact/Models/Setting/DiscordWebhook.cs
@@ -0,0 +1,9 @@
+namespace TbsReact.Models.Setting
+{
+ public class DiscordWebhook
+ {
+ public bool IsActive { get; set; }
+ public bool IsOnlineMsg { get; set; }
+ public string UrlWebhook { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Setting/Hero.cs b/TbsReact/Models/Setting/Hero.cs
new file mode 100644
index 000000000..1f2689252
--- /dev/null
+++ b/TbsReact/Models/Setting/Hero.cs
@@ -0,0 +1,38 @@
+using TbsReact.Models;
+
+namespace TbsReact.Models.Setting
+{
+ public class Hero
+ {
+ public AutoAdventure AutoAdventure { get; set; }
+ public AutoRefresh AutoRefresh { get; set; }
+ public AutoPoint AutoPoint { get; set; }
+ public AutoRevive AutoRevive { get; set; }
+ }
+
+ public class AutoAdventure
+ {
+ public bool IsActive { get; set; }
+
+ public int MinHealth { get; set; }
+ public int MaxDistance { get; set; }
+ }
+
+ public class AutoRefresh
+ {
+ public bool IsActive { get; set; }
+ public Range Frequency { get; set; }
+ }
+
+ public class AutoRevive
+ {
+ public bool IsActive { get; set; }
+ public int VillageId { get; set; }
+ }
+
+ public class AutoPoint
+ {
+ public bool IsActive { get; set; }
+ public byte[] Points { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Setting/Quest.cs b/TbsReact/Models/Setting/Quest.cs
new file mode 100644
index 000000000..684713f69
--- /dev/null
+++ b/TbsReact/Models/Setting/Quest.cs
@@ -0,0 +1,9 @@
+namespace TbsReact.Models.Setting
+{
+ public class Quest
+ {
+ public bool Daily { get; set; }
+ public bool Beginner { get; set; }
+ public int VillageId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Task.cs b/TbsReact/Models/Task.cs
new file mode 100644
index 000000000..bcdba4721
--- /dev/null
+++ b/TbsReact/Models/Task.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace TbsReact.Models
+{
+ public class Task
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string VillName { get; set; }
+ public string Priority { get; set; }
+ public string Stage { get; set; }
+ public DateTime ExecuteAt { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Utils.cs b/TbsReact/Models/Utils.cs
new file mode 100644
index 000000000..1b7c349a5
--- /dev/null
+++ b/TbsReact/Models/Utils.cs
@@ -0,0 +1,14 @@
+namespace TbsReact.Models
+{
+ public class Range
+ {
+ public int Min { get; set; }
+ public int Max { get; set; }
+ }
+
+ public class Coordinate
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Building/Building.cs b/TbsReact/Models/Villages/Building/Building.cs
new file mode 100644
index 000000000..7dac52de7
--- /dev/null
+++ b/TbsReact/Models/Villages/Building/Building.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Models.Villages.Building
+{
+ public class Building
+ {
+ public int Id { get; set; }
+ public int Location { get; set; }
+ public string Name { get; set; }
+ public int Level { get; set; }
+ public bool UnderConstruction { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Building/CurrentBuilding.cs b/TbsReact/Models/Villages/Building/CurrentBuilding.cs
new file mode 100644
index 000000000..484e7816c
--- /dev/null
+++ b/TbsReact/Models/Villages/Building/CurrentBuilding.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Models.Villages.Building
+{
+ public class CurrentBuilding
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+ public int Level { get; set; }
+ public DateTime CompleteTime { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Building/NormalBuild.cs b/TbsReact/Models/Villages/Building/NormalBuild.cs
new file mode 100644
index 000000000..300e9fd25
--- /dev/null
+++ b/TbsReact/Models/Villages/Building/NormalBuild.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace TbsReact.Models.Villages.Building
+{
+ public class NormalBuild
+ {
+ public List BuildList { get; set; }
+ public int Level { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Building/RequestChange.cs b/TbsReact/Models/Villages/Building/RequestChange.cs
new file mode 100644
index 000000000..86cc39e9b
--- /dev/null
+++ b/TbsReact/Models/Villages/Building/RequestChange.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Models.Villages.Building
+{
+ public class RequestChange
+ {
+ public int IndexOld { get; set; }
+ public int IndexNew { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Building/RequestNormal.cs b/TbsReact/Models/Villages/Building/RequestNormal.cs
new file mode 100644
index 000000000..74c15bf4e
--- /dev/null
+++ b/TbsReact/Models/Villages/Building/RequestNormal.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Models.Villages.Building
+{
+ public class RequestNormal
+ {
+ public string Building { get; set; }
+ public int Level { get; set; }
+ public int Location { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Building/RequestPrerequisites.cs b/TbsReact/Models/Villages/Building/RequestPrerequisites.cs
new file mode 100644
index 000000000..cb2be610a
--- /dev/null
+++ b/TbsReact/Models/Villages/Building/RequestPrerequisites.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Models.Villages.Building
+{
+ public class RequestPrerequisites
+ {
+ public string Building { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Building/RequestResource.cs b/TbsReact/Models/Villages/Building/RequestResource.cs
new file mode 100644
index 000000000..5d1c0e4f5
--- /dev/null
+++ b/TbsReact/Models/Villages/Building/RequestResource.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Models.Villages.Building
+{
+ public class RequestResource
+ {
+ public int Level { get; set; }
+ public int Type { get; set; }
+ public int Strategy { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Building/TaskBuilding.cs b/TbsReact/Models/Villages/Building/TaskBuilding.cs
new file mode 100644
index 000000000..40ebf1ca5
--- /dev/null
+++ b/TbsReact/Models/Villages/Building/TaskBuilding.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Models.Villages.Building
+{
+ public class TaskBuilding
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public int Level { get; set; }
+ public int Location { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Models/Villages/Village.cs b/TbsReact/Models/Villages/Village.cs
new file mode 100644
index 000000000..31518ced1
--- /dev/null
+++ b/TbsReact/Models/Villages/Village.cs
@@ -0,0 +1,12 @@
+using TbsReact.Models;
+
+namespace TbsReact.Models.Villages
+{
+ public class Village
+
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public Coordinate Coordinate { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Pages/Error.cshtml b/TbsReact/Pages/Error.cshtml
new file mode 100644
index 000000000..6f92b9565
--- /dev/null
+++ b/TbsReact/Pages/Error.cshtml
@@ -0,0 +1,26 @@
+@page
+@model ErrorModel
+@{
+ ViewData["Title"] = "Error";
+}
+
+
Error.
+
An error occurred while processing your request.
+
+@if (Model.ShowRequestId)
+{
+
+ Request ID:@Model.RequestId
+
+}
+
+
Development Mode
+
+ Swapping to the Development environment displays detailed information about the error that occurred.
+
+
+ The Development environment shouldn't be enabled for deployed applications.
+ It can result in displaying sensitive information from exceptions to end users.
+ For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
+ and restarting the app.
+
diff --git a/TbsReact/Pages/Error.cshtml.cs b/TbsReact/Pages/Error.cshtml.cs
new file mode 100644
index 000000000..a4e23bad8
--- /dev/null
+++ b/TbsReact/Pages/Error.cshtml.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TbsReact.Pages
+{
+ [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
+ public class ErrorModel : PageModel
+ {
+ private readonly ILogger _logger;
+
+ public ErrorModel(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public string RequestId { get; set; }
+
+ public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+
+ public void OnGet()
+ {
+ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
+ }
+ }
+}
diff --git a/TbsReact/Pages/_ViewImports.cshtml b/TbsReact/Pages/_ViewImports.cshtml
new file mode 100644
index 000000000..7f96d899f
--- /dev/null
+++ b/TbsReact/Pages/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@using TbsReact
+@namespace TbsReact.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/TbsReact/Program.cs b/TbsReact/Program.cs
new file mode 100644
index 000000000..2b509f3f6
--- /dev/null
+++ b/TbsReact/Program.cs
@@ -0,0 +1,26 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Hosting;
+using TbsReact.Hubs;
+using TbsReact.Singleton;
+
+namespace TbsReact
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var host = CreateHostBuilder(args).Build();
+ var hubContext = host.Services.GetService(typeof(IHubContext)) as IHubContext;
+ AccountManager.SetHub(hubContext);
+ host.Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Properties/PublishProfiles/Linux.pubxml b/TbsReact/Properties/PublishProfiles/Linux.pubxml
new file mode 100644
index 000000000..0594888c5
--- /dev/null
+++ b/TbsReact/Properties/PublishProfiles/Linux.pubxml
@@ -0,0 +1,23 @@
+
+
+
+
+ True
+ False
+ True
+ Release
+ Any CPU
+ FileSystem
+ bin\Publish\Linux
+ FileSystem
+
+ net5.0
+ linux-x64
+ 8a9d5fc4-e3fb-47fa-8253-6a68b604111d
+ true
+ True
+ True
+
+
\ No newline at end of file
diff --git a/TbsReact/Properties/PublishProfiles/Mac.pubxml b/TbsReact/Properties/PublishProfiles/Mac.pubxml
new file mode 100644
index 000000000..6d15cc161
--- /dev/null
+++ b/TbsReact/Properties/PublishProfiles/Mac.pubxml
@@ -0,0 +1,23 @@
+
+
+
+
+ True
+ False
+ True
+ Release
+ Any CPU
+ FileSystem
+ bin\Publish\Mac
+ FileSystem
+
+ net5.0
+ osx-x64
+ 8a9d5fc4-e3fb-47fa-8253-6a68b604111d
+ true
+ True
+ True
+
+
\ No newline at end of file
diff --git a/TbsReact/Properties/PublishProfiles/Windows.pubxml b/TbsReact/Properties/PublishProfiles/Windows.pubxml
new file mode 100644
index 000000000..0cdb63052
--- /dev/null
+++ b/TbsReact/Properties/PublishProfiles/Windows.pubxml
@@ -0,0 +1,24 @@
+
+
+
+
+ True
+ False
+ True
+ Release
+ x64
+ FileSystem
+ bin\Publish\Windows
+ FileSystem
+
+ net5.0
+ win-x64
+ False
+ 8a9d5fc4-e3fb-47fa-8253-6a68b604111d
+ true
+ True
+ True
+
+
\ No newline at end of file
diff --git a/TbsReact/Properties/launchSettings.json b/TbsReact/Properties/launchSettings.json
new file mode 100644
index 000000000..86f879ad2
--- /dev/null
+++ b/TbsReact/Properties/launchSettings.json
@@ -0,0 +1,37 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:3220",
+ "sslPort": 44303
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "TbsReact": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:5000"
+ },
+ "WSL": {
+ "commandName": "WSL2",
+ "launchBrowser": true,
+ "launchUrl": "https://localhost:5001",
+ "environmentVariables": {
+ "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000",
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "distributionName": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Singleton/AccountManager.cs b/TbsReact/Singleton/AccountManager.cs
new file mode 100644
index 000000000..2fe9f46fb
--- /dev/null
+++ b/TbsReact/Singleton/AccountManager.cs
@@ -0,0 +1,89 @@
+using Microsoft.AspNetCore.SignalR;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using TbsCore.Database;
+using TbsCore.Helpers;
+using TbsReact.Hubs;
+using TbsReact.Models;
+
+namespace TbsReact.Singleton
+{
+ public sealed class AccountManager
+ {
+ private static readonly AccountManager instance = new();
+
+ private ConcurrentDictionary group = new();
+
+ private IHubContext _groupContext;
+ private List accounts = new();
+
+ public static List Accounts
+ {
+ get { return Instance.accounts; }
+ }
+
+ private AccountManager()
+ {
+ LoadAccounts();
+ }
+
+ public static AccountManager Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ public static TbsCore.Models.AccModels.Account GetAccount(Account account)
+ {
+ return Accounts.FirstOrDefault(x => x.AccInfo.Nickname.Equals(account.Name) && x.AccInfo.ServerUrl.Equals(account.ServerUrl));
+ }
+
+ private void LoadAccounts()
+ {
+ accounts = DbRepository.GetAccounts();
+ accounts.ForEach(x => ObjectHelper.FixAccObj(x, x));
+ }
+
+ public void SaveAccounts()
+ {
+ IoHelperCore.SaveAccounts(accounts, true);
+ }
+
+ #region Hub SignalR
+
+ public static void SetHub(IHubContext groupContext)
+ {
+ instance._groupContext = groupContext;
+ }
+
+ public static void ClientConnect(string key)
+ {
+ instance.group.AddOrUpdate(key, 1, (key, value) => value++);
+ }
+
+ public static void ClientDisconnect(string key)
+ {
+ instance.group.AddOrUpdate(key, 0, (key, value) => value--);
+ }
+
+ public static bool CheckGroup(string key)
+ {
+ if (instance.group.TryGetValue(key, out int value))
+ {
+ return (value > 0);
+ }
+ return false;
+ }
+
+ public static async void SendMessage(string groupkey, string type, string message)
+ {
+ await Instance._groupContext.Clients.Group(groupkey).SendAsync(type, message);
+ }
+
+ #endregion Hub SignalR
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Singleton/Datas/AccountData.cs b/TbsReact/Singleton/Datas/AccountData.cs
new file mode 100644
index 000000000..f4169d753
--- /dev/null
+++ b/TbsReact/Singleton/Datas/AccountData.cs
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using System.Linq;
+using TbsReact.Models;
+
+namespace TbsReact.Singleton
+{
+ public sealed class AccountData
+ {
+ private static readonly AccountData _instance = new();
+ private List _accounts = new();
+
+ private AccountData()
+ {
+ for (int i = 0; i < AccountManager.Accounts.Count; i++)
+ {
+ _accounts.Add(new Account
+ {
+ Id = i,
+ Name = AccountManager.Accounts[i].AccInfo.Nickname,
+ ServerUrl = AccountManager.Accounts[i].AccInfo.ServerUrl,
+ });
+ }
+ }
+
+ public static List Accounts => Instance._accounts;
+
+ public static Account GetAccount(int index)
+ {
+ return Accounts.FirstOrDefault(x => x.Id == index);
+ }
+
+ public static Account AddAccount(Account account)
+ {
+ if (Accounts.Count > 0)
+ {
+ account.Id = Accounts.Last().Id + 1;
+ }
+ else
+ {
+ account.Id = 0;
+ }
+ Accounts.Add(account);
+ return Accounts.Last();
+ }
+
+ public static bool EditAccount(int index, Account account)
+ {
+ var current = Accounts.FirstOrDefault(x => x.Id == index);
+ if (current == null) return false;
+
+ current.Name = account.Name;
+ current.ServerUrl = account.ServerUrl;
+ return true;
+ }
+
+ public static bool DeleteAccount(int index)
+ {
+ return Accounts.Remove(Accounts.FirstOrDefault(x => x.Id == index));
+ }
+
+ public static AccountData Instance
+ {
+ get { return _instance; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Singleton/LogManager.cs b/TbsReact/Singleton/LogManager.cs
new file mode 100644
index 000000000..c3233f176
--- /dev/null
+++ b/TbsReact/Singleton/LogManager.cs
@@ -0,0 +1,54 @@
+using TbsCore.Models.Logging;
+
+namespace TbsReact.Singleton
+{
+ public class LogManager
+ {
+ private static readonly LogManager instance = new();
+ private LogOutput Log;
+
+ private LogManager()
+ {
+ }
+
+ public static LogManager Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private void _setLogOutput(LogOutput logOutput)
+ {
+ instance.Log = logOutput;
+ instance.Log.LogUpdated += LogUpdate;
+ }
+
+ public static void SetLogOutput(LogOutput logOutput)
+ {
+ instance._setLogOutput(logOutput);
+ }
+
+ private string _getLogData(string username)
+ {
+ return Log.GetLog(username);
+ }
+
+ public static string GetLogData(string username)
+ {
+ return instance._getLogData(username);
+ }
+
+ private void LogUpdate(object sender, UpdateLogEventArgs e)
+ {
+ UpdateLogData(e.Username);
+ }
+
+ public void UpdateLogData(string username)
+ {
+ if (!AccountManager.CheckGroup(username)) return;
+ AccountManager.SendMessage(username, "logger", Log.GetLastLog(username));
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Singleton/TaskManager.cs b/TbsReact/Singleton/TaskManager.cs
new file mode 100644
index 000000000..be265e59f
--- /dev/null
+++ b/TbsReact/Singleton/TaskManager.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using TbsReact.Models;
+
+namespace TbsReact.Singleton
+{
+ public class TaskManager
+ {
+ private static readonly TaskManager instance = new();
+
+ private TaskManager()
+ {
+ }
+
+ public static TaskManager Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ private static void UpdateTaskTable(string username)
+ {
+ if (!AccountManager.CheckGroup(username)) return;
+ AccountManager.SendMessage(username, "task", "reset");
+ }
+
+ private List _getTaskList(string username)
+ {
+ var acc = AccountManager.Accounts.FirstOrDefault(x => x.AccInfo.Nickname.Equals(username));
+ if (acc == null) return null;
+ if (acc.Tasks == null) return null;
+ List list = new();
+ foreach (var task in acc.Tasks.ToList())
+ {
+ var tasKOjb = new Task
+ {
+ Id = list.Count,
+ Name = task.ToString().Split('.').Last(),
+ VillName = task.Vill?.Name ?? "/",
+ Priority = task.Priority.ToString(),
+ Stage = task.Stage.ToString(),
+ ExecuteAt = task.ExecuteAt
+ };
+ list.Add(tasKOjb);
+ }
+
+ return list;
+ }
+
+ public static void AddAccount(TbsCore.Models.AccModels.Account account)
+ {
+ account.Tasks.OnUpdateTask = UpdateTaskTable;
+ if (!AccountManager.CheckGroup(account.AccInfo.Nickname)) return;
+ AccountManager.SendMessage(account.AccInfo.Nickname, "task", "waiting");
+ }
+
+ public static List GetTaskList(string username)
+ {
+ return instance._getTaskList(username);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/Startup.cs b/TbsReact/Startup.cs
new file mode 100644
index 000000000..12df7ebea
--- /dev/null
+++ b/TbsReact/Startup.cs
@@ -0,0 +1,140 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using TbsReact.Singleton;
+using TbsReact.Hubs;
+using System.Globalization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Primitives;
+
+namespace TbsReact
+{
+ public class Startup
+ {
+ public Startup()
+ {
+ _token = TokenGenerator.Generate(12, 3);
+ }
+
+ public Startup(IConfiguration configuration) : this()
+
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+ private string _token;
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton(AccountManager.Instance);
+ services.AddSingleton(AccountData.Instance);
+ services.AddSingleton(LogManager.Instance);
+ services.AddSingleton(TaskManager.Instance);
+ services.AddSignalR();
+
+ TbsCore.Models.Logging.SerilogSingleton.Init();
+ LogManager.SetLogOutput(TbsCore.Models.Logging.SerilogSingleton.LogOutput);
+
+ services.AddControllersWithViews();
+
+ // In production, the React files will be served from this directory
+ services.AddSpaStaticFiles(configuration =>
+ {
+ configuration.RootPath = "ClientApp/build";
+ });
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ app.UseExceptionHandler("/Error");
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ app.UseHsts();
+ }
+ app.UseStaticFiles();
+ app.UseSpaStaticFiles();
+
+ app.Use(async (context, next) =>
+ {
+ if (context.Request.Path.Value.Contains("/api"))
+ {
+ if (context.Request.Headers.TryGetValue("token", out StringValues token))
+ {
+ if (token[0].Contains(_token))
+ {
+ if (context.Request.Path.Value.Contains("/api/checkToken"))
+ {
+ context.Response.StatusCode = 200;
+ await context.Response.WriteAsync("OK");
+
+ return;
+ }
+ await next();
+ return;
+ }
+ }
+ context.Response.StatusCode = 401;
+ await context.Response.WriteAsync("401 - Lacks valid authentication credentials");
+ return;
+ }
+ await next();
+ });
+
+ app.UseRouting();
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllerRoute(
+ name: "default",
+ pattern: "{controller=Home}/{action=Index}/{id?}");
+ });
+
+ app.UseSpa(spa =>
+ {
+ spa.Options.SourcePath = "ClientApp";
+
+ if (env.IsDevelopment())
+ {
+ spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
+ }
+ });
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapHub("/live");
+ });
+ lifetime.ApplicationStarted.Register(() =>
+ {
+ Console.WriteLine("=====================");
+ Console.WriteLine("=====================");
+ Console.WriteLine("YOUR TOKEN");
+ Console.WriteLine("=====================\n\n");
+ Console.WriteLine(_token);
+ Console.WriteLine("\n\n=====================");
+ Console.WriteLine("YOUR TOKEN");
+ Console.WriteLine("=====================");
+ Console.WriteLine("=====================");
+ });
+ lifetime.ApplicationStopping.Register(OnShutdown);
+ lifetime.ApplicationStopped.Register(() =>
+ {
+ Console.WriteLine("*** Application is shut down ***");
+ });
+ }
+
+ private void OnShutdown()
+ {
+ AccountManager.Instance.SaveAccounts();
+ Console.WriteLine("*** Account saved ***");
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/TbsReact.csproj b/TbsReact/TbsReact.csproj
new file mode 100644
index 000000000..e5f22aced
--- /dev/null
+++ b/TbsReact/TbsReact.csproj
@@ -0,0 +1,58 @@
+
+
+
+ net5.0
+ true
+ Latest
+ false
+ ClientApp\
+ $(DefaultItemExcludes);$(SpaRoot)node_modules\**
+ Exe
+ true
+ 2f4cf8f4-975c-481e-9f90-193010f82aca
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(DistFiles.Identity)
+ PreserveNewest
+ true
+
+
+
+
+
diff --git a/TbsReact/TokenGenerator.cs b/TbsReact/TokenGenerator.cs
new file mode 100644
index 000000000..7a5acf603
--- /dev/null
+++ b/TbsReact/TokenGenerator.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+
+namespace TbsReact
+{
+ public static class TokenGenerator
+ {
+ private static readonly char[] Punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray();
+
+ public static string Generate(int length, int numberOfNonAlphanumericCharacters)
+ {
+ if (length < 1 || length > 128)
+ {
+ throw new ArgumentException(nameof(length));
+ }
+
+ if (numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0)
+ {
+ throw new ArgumentException(nameof(numberOfNonAlphanumericCharacters));
+ }
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ var byteBuffer = new byte[length];
+
+ rng.GetBytes(byteBuffer);
+
+ var count = 0;
+ var characterBuffer = new char[length];
+
+ for (var iter = 0; iter < length; iter++)
+ {
+ var i = byteBuffer[iter] % 87;
+
+ if (i < 10)
+ {
+ characterBuffer[iter] = (char)('0' + i);
+ }
+ else if (i < 36)
+ {
+ characterBuffer[iter] = (char)('A' + i - 10);
+ }
+ else if (i < 62)
+ {
+ characterBuffer[iter] = (char)('a' + i - 36);
+ }
+ else
+ {
+ characterBuffer[iter] = Punctuations[i - 62];
+ count++;
+ }
+ }
+
+ if (count >= numberOfNonAlphanumericCharacters)
+ {
+ return new string(characterBuffer);
+ }
+
+ int j;
+ var rand = new Random();
+
+ for (j = 0; j < numberOfNonAlphanumericCharacters - count; j++)
+ {
+ int k;
+ do
+ {
+ k = rand.Next(0, length);
+ }
+ while (!char.IsLetterOrDigit(characterBuffer[k]));
+
+ characterBuffer[k] = Punctuations[rand.Next(0, Punctuations.Length)];
+ }
+
+ return new string(characterBuffer);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TbsReact/appsettings.Development.json b/TbsReact/appsettings.Development.json
new file mode 100644
index 000000000..8983e0fc1
--- /dev/null
+++ b/TbsReact/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/TbsReact/appsettings.json b/TbsReact/appsettings.json
new file mode 100644
index 000000000..ad75fee41
--- /dev/null
+++ b/TbsReact/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+"AllowedHosts": "*"
+}
diff --git a/TbsReact/docker-compose.yml b/TbsReact/docker-compose.yml
new file mode 100644
index 000000000..3045e656c
--- /dev/null
+++ b/TbsReact/docker-compose.yml
@@ -0,0 +1,23 @@
+version: '3.4'
+
+services:
+ tbs:
+ image: travbotsharp/tbsreact:${TAG}
+ ports:
+ - ${WEB_PORT}:5000
+ - ${VNC_PORT}:5009
+ build:
+ context: .
+ dockerfile: ./Dockerfile
+ environment:
+ - ASPNETCORE_ENVIRONMENT=Production
+ - ASPNETCORE_URLS=http://+:5000
+
+ - VNC_PASSWORD=${VNC_PASS}
+ - XFB_SCREEN=1280x800x24
+ volumes:
+ - data:${HOME}/.config/TravBotSharp
+ - logs:/App/logs
+volumes:
+ data:
+ logs:
\ No newline at end of file
diff --git a/TbsReact/docker-entrypoint.sh b/TbsReact/docker-entrypoint.sh
new file mode 100644
index 000000000..3444aa290
--- /dev/null
+++ b/TbsReact/docker-entrypoint.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+if [ -z "$VNC_PASSWORD" ]; then
+ echo >&2 'error: No password for VNC connection set.'
+ echo >&2 ' Did you forget to add -e VNC_PASSWORD=... ?'
+ exit 1
+fi
+
+if [ -z "$XFB_SCREEN" ]; then
+ XFB_SCREEN=1024x768x24
+fi
+
+# now boot X-Server, tell it to our cookie and give it sometime to start up
+Xvfb :0 -screen 0 $XFB_SCREEN >>~/xvfb.log 2>&1 &
+sleep 10
+
+# finally we can run the VNC-Server based on our just started X-Server
+x11vnc -forever -passwd $VNC_PASSWORD -display :0 &
+
+./TbsReact &
diff --git a/TbsReact/install_docker.sh b/TbsReact/install_docker.sh
new file mode 100644
index 000000000..5f24519ad
--- /dev/null
+++ b/TbsReact/install_docker.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+sudo apt-get update
+sudo apt-get remove docker docker-engine docker.io containerd runc
+sudo apt-get install \
+ ca-certificates \
+ curl \
+ gnupg \
+ lsb-release -y
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
+echo \
+ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
+ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
+sudo apt-get update
+sudo apt-get install docker-ce docker-ce-cli containerd.io
\ No newline at end of file
diff --git a/TbsReact/libman.json b/TbsReact/libman.json
new file mode 100644
index 000000000..ceee2710f
--- /dev/null
+++ b/TbsReact/libman.json
@@ -0,0 +1,5 @@
+{
+ "version": "1.0",
+ "defaultProvider": "cdnjs",
+ "libraries": []
+}
\ No newline at end of file
diff --git a/TbsReact/start_bot.sh b/TbsReact/start_bot.sh
new file mode 100644
index 000000000..b2873ea8d
--- /dev/null
+++ b/TbsReact/start_bot.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+docker-compose pull
+docker-compose up
\ No newline at end of file
diff --git a/TravBotSharp.sln b/TravBotSharp.sln
index f09bb3321..2c1cb82e6 100644
--- a/TravBotSharp.sln
+++ b/TravBotSharp.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28917.181
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TbsWinForms", "TravBotSharp\TbsWinForms.csproj", "{D0B75C28-A48F-4C0E-AD41-99A8EA28496D}"
EndProject
@@ -9,6 +9,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TbsCore", "TbsCore\TbsCore.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TbsCoreTest", "TbsCoreTest\TbsCoreTest.csproj", "{BD027CDE-12E3-4548-8B32-3EEF83B73646}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A387655F-C99A-4EA2-ADD7-C6D130F0D669}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TbsReact", "TbsReact\TbsReact.csproj", "{8A9D5FC4-E3FB-47FA-8253-6A68B604111D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -55,6 +62,18 @@ Global
{BD027CDE-12E3-4548-8B32-3EEF83B73646}.Release|x64.Build.0 = Release|Any CPU
{BD027CDE-12E3-4548-8B32-3EEF83B73646}.Release|x86.ActiveCfg = Release|Any CPU
{BD027CDE-12E3-4548-8B32-3EEF83B73646}.Release|x86.Build.0 = Release|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Debug|x64.Build.0 = Debug|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Debug|x86.Build.0 = Debug|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Release|x64.ActiveCfg = Release|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Release|x64.Build.0 = Release|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Release|x86.ActiveCfg = Release|Any CPU
+ {8A9D5FC4-E3FB-47FA-8253-6A68B604111D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/TravBotSharp/ControlPanel.cs b/TravBotSharp/ControlPanel.cs
index 16a59cc68..e8389827d 100644
--- a/TravBotSharp/ControlPanel.cs
+++ b/TravBotSharp/ControlPanel.cs
@@ -143,7 +143,7 @@ private void button2_Click(object sender, EventArgs e) //login button
{
await IoHelperCore.LoginAccount(acc);
acc.Tasks.OnUpdateTask = debugUc1.UpdateTaskTable;
- debugUc1.UpdateTaskTable();
+ debugUc1.UpdateTaskTable(acc.AccInfo.Nickname);
}).Start();
generalUc1.UpdateBotRunning("true");
return;
@@ -197,7 +197,7 @@ private void accListView_SelectedIndexChanged(object sender, EventArgs e) // Dif
if (acc.Tasks != null)
{
acc.Tasks.OnUpdateTask = debugUc1.UpdateTaskTable;
- debugUc1.UpdateTaskTable();
+ debugUc1.UpdateTaskTable(acc.AccInfo.Nickname);
}
}
diff --git a/TravBotSharp/TbsWinForms.csproj b/TravBotSharp/TbsWinForms.csproj
index 4a885412a..2a1ed0062 100644
--- a/TravBotSharp/TbsWinForms.csproj
+++ b/TravBotSharp/TbsWinForms.csproj
@@ -17,6 +17,7 @@
false
+ 1975.30.4.1\TravBotSharp\Publish\trueDisk
@@ -35,7 +36,6 @@
truetruetrue
- 1975.30.4.1x86
@@ -674,6 +674,9 @@
VillagesUc.cs
+
+ .editorconfig
+
diff --git a/TravBotSharp/Views/DebugUc.cs b/TravBotSharp/Views/DebugUc.cs
index 74f7f890b..889b1108e 100644
--- a/TravBotSharp/Views/DebugUc.cs
+++ b/TravBotSharp/Views/DebugUc.cs
@@ -28,7 +28,7 @@ public void InitLog(LogOutput log)
public void UpdateUc()
{
active = true;
- UpdateTaskTable();
+ UpdateTaskTable(GetSelectedAcc()?.AccInfo.Nickname ?? "");
GetLogData();
this.Focus();
}
@@ -80,14 +80,14 @@ public void UpdateLogData()
logTextBox.Text = $"{Log.GetLastLog(acc.AccInfo.Nickname)}{logTextBox.Text}";
}
- public void UpdateTaskTable()
+ public void UpdateTaskTable(string username)
{
if (!active) return;
if (taskListView.InvokeRequired)
{
taskListView.BeginInvoke(new Action(delegate
{
- UpdateTaskTable();
+ UpdateTaskTable(username);
}));
return;
}
diff --git a/version.txt b/version.txt
deleted file mode 100644
index 8c9166749..000000000
--- a/version.txt
+++ /dev/null
@@ -1 +0,0 @@
-298
\ No newline at end of file