feat(dashboard): add search functionality and polish translations
- Add search functionality to all dashboard components (servers, renders, AI tasks)- Implement real-time filtering by name, type, status and other relevant fields- Update search input styles to match form-field design- Add clear search button functionality- Polish UI translations for headers, buttons and placeholders- Ensure consistent styling across all dashboard componentsfeat/x_gpu/chat_gpt_new_version
parent
e8c234ce51
commit
ae781652de
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { store } from './src/redux/store';
|
||||||
|
|
||||||
|
export const wrapRootElement = ({ element }) => (
|
||||||
|
<Provider store={store}>
|
||||||
|
{element}
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { store } from './src/redux/store';
|
||||||
|
|
||||||
|
export const wrapRootElement = ({ element }) => (
|
||||||
|
<Provider store={store}>
|
||||||
|
{element}
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
@ -9,11 +9,13 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"axios": "^1.7.9",
|
"@reduxjs/toolkit": "^2.6.0",
|
||||||
|
"axios": "^1.8.1",
|
||||||
"gatsby": "^5.13.3",
|
"gatsby": "^5.13.3",
|
||||||
"gatsby-plugin-sass": "^6.14.0",
|
"gatsby-plugin-sass": "^6.14.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.1.5",
|
"react-router-dom": "^7.1.5",
|
||||||
"react-tsparticles": "^2.12.2",
|
"react-tsparticles": "^2.12.2",
|
||||||
"sass": "^1.32.7"
|
"sass": "^1.32.7"
|
||||||
|
|
@ -4130,6 +4132,40 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immer": "^10.0.3",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||||
|
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sideway/address": {
|
"node_modules/@sideway/address": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||||
|
|
@ -4469,6 +4505,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz",
|
||||||
"integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0="
|
"integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/yoga-layout": {
|
"node_modules/@types/yoga-layout": {
|
||||||
"version": "1.9.2",
|
"version": "1.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz",
|
||||||
|
|
@ -5415,9 +5457,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.9",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
|
||||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
|
|
@ -9774,6 +9816,15 @@
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gatsby-cli/node_modules/redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gatsby-core-utils": {
|
"node_modules/gatsby-core-utils": {
|
||||||
"version": "4.14.0",
|
"version": "4.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-4.14.0.tgz",
|
||||||
|
|
@ -10293,6 +10344,24 @@
|
||||||
"webpack": "^5.59.0"
|
"webpack": "^5.59.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gatsby/node_modules/redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gatsby/node_modules/redux-thunk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": "^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gensync": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
|
|
@ -14391,6 +14460,29 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.2.25 || ^19",
|
||||||
|
"react": "^18.0 || ^19",
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||||
|
|
@ -14602,19 +14694,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/redux": {
|
"node_modules/redux": {
|
||||||
"version": "4.2.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"dependencies": {
|
"license": "MIT"
|
||||||
"@babel/runtime": "^7.9.2"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/redux-thunk": {
|
"node_modules/redux-thunk": {
|
||||||
"version": "2.4.2",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"redux": "^4"
|
"redux": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
|
|
@ -14850,6 +14941,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz",
|
||||||
"integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q=="
|
"integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
|
|
@ -16985,6 +17082,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|
@ -20330,6 +20436,24 @@
|
||||||
"config-chain": "^1.1.11"
|
"config-chain": "^1.1.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@reduxjs/toolkit": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==",
|
||||||
|
"requires": {
|
||||||
|
"immer": "^10.0.3",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@sideway/address": {
|
"@sideway/address": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||||
|
|
@ -20627,6 +20751,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz",
|
||||||
"integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0="
|
"integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0="
|
||||||
},
|
},
|
||||||
|
"@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
|
||||||
|
},
|
||||||
"@types/yoga-layout": {
|
"@types/yoga-layout": {
|
||||||
"version": "1.9.2",
|
"version": "1.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz",
|
||||||
|
|
@ -21282,9 +21411,9 @@
|
||||||
"integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ=="
|
"integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ=="
|
||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "1.7.9",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
|
||||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
|
|
@ -24551,6 +24680,20 @@
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"neo-async": "^2.6.1"
|
"neo-async": "^2.6.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redux-thunk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
||||||
|
"requires": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -24599,6 +24742,16 @@
|
||||||
"yargs": "^15.4.1",
|
"yargs": "^15.4.1",
|
||||||
"yoga-layout-prebuilt": "^1.10.0",
|
"yoga-layout-prebuilt": "^1.10.0",
|
||||||
"yurnalist": "^2.1.0"
|
"yurnalist": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gatsby-core-utils": {
|
"gatsby-core-utils": {
|
||||||
|
|
@ -27608,6 +27761,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
|
"requires": {
|
||||||
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-refresh": {
|
"react-refresh": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||||
|
|
@ -27738,17 +27900,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redux": {
|
"redux": {
|
||||||
"version": "4.2.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.9.2"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"redux-thunk": {
|
"redux-thunk": {
|
||||||
"version": "2.4.2",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"reflect.getprototypeof": {
|
"reflect.getprototypeof": {
|
||||||
|
|
@ -27923,6 +28082,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz",
|
||||||
"integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q=="
|
"integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q=="
|
||||||
},
|
},
|
||||||
|
"reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
|
||||||
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
|
|
@ -29420,6 +29584,12 @@
|
||||||
"schema-utils": "^3.0.0"
|
"schema-utils": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"use-sync-external-store": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"axios": "^1.7.9",
|
"@reduxjs/toolkit": "^2.6.0",
|
||||||
|
"axios": "^1.8.1",
|
||||||
"gatsby": "^5.13.3",
|
"gatsby": "^5.13.3",
|
||||||
"gatsby-plugin-sass": "^6.14.0",
|
"gatsby-plugin-sass": "^6.14.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.1.5",
|
"react-router-dom": "^7.1.5",
|
||||||
"react-tsparticles": "^2.12.2",
|
"react-tsparticles": "^2.12.2",
|
||||||
"sass": "^1.32.7"
|
"sass": "^1.32.7"
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,25 @@ import React, { useState } from 'react'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
|
||||||
import { userAuthSelector } from '../../../redux/slices/userAuthSlice'
|
import { userAuthSelector } from '../../../redux/slices/userAuthSlice'
|
||||||
import { modelCrudSelector } from '../../../redux/slices/modelCrudSlice'
|
import { threeDModelCrudSelector } from '../../../redux/slices/threeDModelCrudSlice'
|
||||||
import modelCrudAsyncThunk from '../../../redux/asyncThunks/modelCrudAsyncThunk'
|
import { uploadModel } from '../../../redux/asyncThunks/threeDModelCrudAsyncThunk'
|
||||||
|
|
||||||
import FormGenerator from '../formGenerator'
|
import FormGenerator from '../formGenerator'
|
||||||
|
|
||||||
|
|
||||||
const ModelUploadForm = () => {
|
const ModelUploadForm = () => {
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const [blend, setBlend] = useState('')
|
const [blend, setBlend] = useState('')
|
||||||
const [blendInfo, setBlendInfo] = useState('Drop/Click\nfor upload "*.blend" file')
|
const [blendInfo, setBlendInfo] = useState('Drop/Click\nfor upload "*.blend" file')
|
||||||
|
|
||||||
const { upload_blend_file_status } = useSelector( modelCrudSelector )
|
const { upload_blend_file_status } = useSelector( threeDModelCrudSelector )
|
||||||
const { user, token } = useSelector( userAuthSelector )
|
const { user, token } = useSelector( userAuthSelector )
|
||||||
|
|
||||||
let inputList = [
|
const inputList = [
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
action: 'Upload',
|
action: 'Upload',
|
||||||
endpint: 'model/upload',
|
endpoint: 'model/upload',
|
||||||
button_value: 'Upload Model'
|
button_value: 'Upload Model'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -37,20 +35,19 @@ const ModelUploadForm = () => {
|
||||||
]
|
]
|
||||||
|
|
||||||
const handleModelUpload = () => {
|
const handleModelUpload = () => {
|
||||||
let body = {
|
if (!blend) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch( uploadModel({
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
file: blend,
|
file: blend,
|
||||||
token: token
|
token: token
|
||||||
}
|
}));
|
||||||
console.log( body )
|
|
||||||
dispatch( modelCrudAsyncThunk.fetchUploadModel( body ) )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div>
|
||||||
className="float_form_model"
|
|
||||||
style={ { marginTop: '17%'} }
|
|
||||||
>
|
|
||||||
<FormGenerator
|
<FormGenerator
|
||||||
inputList={ inputList }
|
inputList={ inputList }
|
||||||
refList={ [] }
|
refList={ [] }
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
|
||||||
|
import { userAuthSelector } from '../../../redux/slices/userAuthSlice'
|
||||||
|
import { aiModelCrudSelector } from '../../../redux/slices/aiModelCrudSlice'
|
||||||
|
import { uploadModel } from '../../../redux/asyncThunks/aiModelCrudAsyncThunk'
|
||||||
|
|
||||||
|
import FormGenerator from '../formGenerator'
|
||||||
|
|
||||||
|
const AIModelUploadForm = () => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const [model, setModel] = useState('')
|
||||||
|
const [modelInfo, setModelInfo] = useState('Drop/Click\nfor upload AI model file')
|
||||||
|
|
||||||
|
const { upload_model_status } = useSelector(aiModelCrudSelector)
|
||||||
|
const { user, token } = useSelector(userAuthSelector)
|
||||||
|
|
||||||
|
const inputList = [
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
action: 'Upload',
|
||||||
|
endpoint: 'ai-model/upload',
|
||||||
|
button_value: 'Upload AI Model'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'file',
|
||||||
|
name: 'Model',
|
||||||
|
fileType: 'ai',
|
||||||
|
dropInfo: modelInfo,
|
||||||
|
setDropInfo: setModelInfo,
|
||||||
|
file: model,
|
||||||
|
setFile: setModel
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleModelUpload = () => {
|
||||||
|
if (!model) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(uploadModel({
|
||||||
|
user_id: user.id,
|
||||||
|
file: model,
|
||||||
|
token: token
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormGenerator
|
||||||
|
inputList={inputList}
|
||||||
|
refList={[]}
|
||||||
|
action={handleModelUpload}
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
{
|
||||||
|
!upload_model_status
|
||||||
|
? ''
|
||||||
|
: typeof upload_model_status === 'string'
|
||||||
|
? ''
|
||||||
|
: 'info' in upload_model_status
|
||||||
|
? upload_model_status.info
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AIModelUploadForm
|
||||||
|
|
@ -392,7 +392,7 @@ const DownloadFilesListInputGenerator = ({
|
||||||
* Text input generator, example:
|
* Text input generator, example:
|
||||||
* @param {
|
* @param {
|
||||||
* {
|
* {
|
||||||
* type: 'chice-listing',
|
* type: 'choice-listing',
|
||||||
* name: 'name',
|
* name: 'name',
|
||||||
* values: list,
|
* values: list,
|
||||||
* ref: React.createRef()
|
* ref: React.createRef()
|
||||||
|
|
@ -488,21 +488,35 @@ const UploadInputGenerator = ({
|
||||||
|
|
||||||
const setDropInfos = (name, size) => {
|
const setDropInfos = (name, size) => {
|
||||||
input.setDropInfo(
|
input.setDropInfo(
|
||||||
'name: "'
|
{
|
||||||
+ name
|
name: name,
|
||||||
+ '"\nsize: '
|
size: (Math.round(size / 100 + 'e-2') / 100) + ' MB'
|
||||||
+ (Math.round(size / 100 + 'e-2') / 100)
|
}
|
||||||
+ ' MB'
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onDrop={event => onLoadFileDrop(event)} >
|
<div
|
||||||
<pre style={{ marginLeft: '40px' }}>
|
onDrop={event => onLoadFileDrop(event)}
|
||||||
{input.dropInfo}
|
className='upload_input_container'
|
||||||
</pre>
|
>
|
||||||
|
<p>
|
||||||
|
{
|
||||||
|
typeof input.dropInfo === 'string' ?
|
||||||
|
input.dropInfo
|
||||||
|
:
|
||||||
|
input.dropInfo.name
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{
|
||||||
|
typeof input.dropInfo === 'string' ?
|
||||||
|
""
|
||||||
|
:
|
||||||
|
input.dropInfo.size
|
||||||
|
}
|
||||||
|
</p>
|
||||||
<input
|
<input
|
||||||
style={{ marginTop: '-55px' }}
|
|
||||||
id={input.name + info.action + info.endpoint + 'Input'}
|
id={input.name + info.action + info.endpoint + 'Input'}
|
||||||
className='upload_input'
|
className='upload_input'
|
||||||
type='file'
|
type='file'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic List Generator Component
|
* Generic List Generator Component
|
||||||
|
|
@ -25,6 +25,31 @@ export const ListGenerator = ({
|
||||||
const [itemBeingUpdated, setItemBeingUpdated] = useState(null);
|
const [itemBeingUpdated, setItemBeingUpdated] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [selectedItem, setSelectedItem] = useState(null);
|
const [selectedItem, setSelectedItem] = useState(null);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||||
|
|
||||||
|
const pageOptions = [5, 10, 15, 25, 50];
|
||||||
|
const totalPages = Math.ceil(data.length / itemsPerPage);
|
||||||
|
|
||||||
|
// Oblicz aktualnie wyświetlane elementy
|
||||||
|
const currentItems = useMemo(() => {
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
|
return data.slice(startIndex, startIndex + itemsPerPage);
|
||||||
|
}, [data, currentPage, itemsPerPage]);
|
||||||
|
|
||||||
|
// Obsługa zmiany strony
|
||||||
|
const handlePageChange = (newPage) => {
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
setSelectedItem(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Obsługa zmiany liczby elementów na stronie
|
||||||
|
const handleItemsPerPageChange = (event) => {
|
||||||
|
const newItemsPerPage = parseInt(event.target.value);
|
||||||
|
setItemsPerPage(newItemsPerPage);
|
||||||
|
setCurrentPage(1);
|
||||||
|
setSelectedItem(null);
|
||||||
|
};
|
||||||
|
|
||||||
// Toggle the "create" form
|
// Toggle the "create" form
|
||||||
const handleToggleCreate = () => {
|
const handleToggleCreate = () => {
|
||||||
|
|
@ -135,7 +160,7 @@ export const ListGenerator = ({
|
||||||
No items found. {onCreate && `Click '+ ${title || 'Item'}' to add new items.`}
|
No items found. {onCreate && `Click '+ ${title || 'Item'}' to add new items.`}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
data.map((item) => (
|
currentItems.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={`item-row ${selectedItem?.id === item.id ? 'selected' : ''}`}
|
className={`item-row ${selectedItem?.id === item.id ? 'selected' : ''}`}
|
||||||
|
|
@ -203,6 +228,52 @@ export const ListGenerator = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{data.length > 0 && (
|
||||||
|
<div className="pagination-controls">
|
||||||
|
<div className="items-per-page">
|
||||||
|
<span>Items per page:</span>
|
||||||
|
<select value={itemsPerPage} onChange={handleItemsPerPageChange}>
|
||||||
|
{pageOptions.map(option => (
|
||||||
|
<option key={option} value={option}>{option}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="pagination-buttons">
|
||||||
|
<button
|
||||||
|
onClick={() => handlePageChange(1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="pagination-button"
|
||||||
|
>
|
||||||
|
<<
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handlePageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="pagination-button"
|
||||||
|
>
|
||||||
|
<
|
||||||
|
</button>
|
||||||
|
<span className="page-info">
|
||||||
|
Page {currentPage} of {totalPages}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => handlePageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="pagination-button"
|
||||||
|
>
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handlePageChange(totalPages)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="pagination-button"
|
||||||
|
>
|
||||||
|
>>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="element-details">
|
<div className="element-details">
|
||||||
{selectedItem ? (
|
{selectedItem ? (
|
||||||
<div className="details-content">
|
<div className="details-content">
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,10 @@ const DashboardPage = () => {
|
||||||
onClick={() => handleNavigation('servers')}
|
onClick={() => handleNavigation('servers')}
|
||||||
>
|
>
|
||||||
<i className={"fas " + icons_size + " fa-server"}></i>
|
<i className={"fas " + icons_size + " fa-server"}></i>
|
||||||
<p>Dashboard</p>
|
<p>GPU Instances</p>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>Rendering</p>
|
<p>3D Stuff</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li
|
<li
|
||||||
className={isActive('3d-models') ? 'active' : ''}
|
className={isActive('3d-models') ? 'active' : ''}
|
||||||
|
|
@ -80,10 +80,10 @@ const DashboardPage = () => {
|
||||||
onClick={() => handleNavigation('renders')}
|
onClick={() => handleNavigation('renders')}
|
||||||
>
|
>
|
||||||
<i className={"fas " + icons_size + " fa-paint-brush"}></i>
|
<i className={"fas " + icons_size + " fa-paint-brush"}></i>
|
||||||
<p>Rendered Materials</p>
|
<p>3D Rendering</p>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>AI Training</p>
|
<p>AI Stuff</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li
|
<li
|
||||||
className={isActive('ai-models') ? 'active' : ''}
|
className={isActive('ai-models') ? 'active' : ''}
|
||||||
|
|
@ -97,7 +97,7 @@ const DashboardPage = () => {
|
||||||
onClick={() => handleNavigation('ai-tasks')}
|
onClick={() => handleNavigation('ai-tasks')}
|
||||||
>
|
>
|
||||||
<i className={"fas " + icons_size + " fa-microchip"}></i>
|
<i className={"fas " + icons_size + " fa-microchip"}></i>
|
||||||
<p>AI Training Tasks</p>
|
<p>AI Training</p>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>User</p>
|
<p>User</p>
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,244 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { ListGenerator } from '../../components/forms/listGenerator';
|
import { ListGenerator } from '../../components/forms/listGenerator';
|
||||||
import { FormGenerator } from '../../components/forms/formGenerator';
|
import ModelUploadForm from '../../components/forms/3d_model_crud/threeDModelUpload';
|
||||||
|
|
||||||
const ThreeDModelsDashboard = () => {
|
const ThreeDModelsDashboard = () => {
|
||||||
const [selectedModel, setSelectedModel] = useState(null);
|
const [selectedModel, setSelectedModel] = useState(null);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [models, setModels] = useState([
|
const [models, setModels] = useState([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Model A',
|
name: 'Dragon Model',
|
||||||
type: '3D',
|
type: 'Blender',
|
||||||
status: 'Active',
|
status: 'Active',
|
||||||
lastModified: '2024-03-20',
|
lastModified: '2024-03-20',
|
||||||
size: '2.5MB'
|
version: '2.1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Model B',
|
name: 'Medieval Castle',
|
||||||
type: '3D',
|
type: 'Maya',
|
||||||
status: 'Inactive',
|
status: 'Inactive',
|
||||||
lastModified: '2024-03-19',
|
lastModified: '2024-03-19',
|
||||||
size: '1.8MB'
|
version: '1.5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Sci-fi Weapon',
|
||||||
|
type: '3ds Max',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '1.0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Forest Scene',
|
||||||
|
type: 'Blender',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-18',
|
||||||
|
version: '3.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'Robot Character',
|
||||||
|
type: 'Maya',
|
||||||
|
status: 'Inactive',
|
||||||
|
lastModified: '2024-03-17',
|
||||||
|
version: '2.0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'Space Ship',
|
||||||
|
type: '3ds Max',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '1.8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: 'Ancient Temple',
|
||||||
|
type: 'Blender',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-19',
|
||||||
|
version: '2.4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: 'Fantasy Sword',
|
||||||
|
type: 'Maya',
|
||||||
|
status: 'Inactive',
|
||||||
|
lastModified: '2024-03-16',
|
||||||
|
version: '1.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: 'City Block',
|
||||||
|
type: '3ds Max',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '2.7'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: 'Warrior Character',
|
||||||
|
type: 'Blender',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-18',
|
||||||
|
version: '1.9'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: 'Futuristic Car',
|
||||||
|
type: 'Maya',
|
||||||
|
status: 'Inactive',
|
||||||
|
lastModified: '2024-03-15',
|
||||||
|
version: '1.3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: 'Mountain Range',
|
||||||
|
type: '3ds Max',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '2.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: 'Alien Creature',
|
||||||
|
type: 'Blender',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-19',
|
||||||
|
version: '1.6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: 'Magic Staff',
|
||||||
|
type: 'Maya',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '1.4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: 'Underground Cave',
|
||||||
|
type: '3ds Max',
|
||||||
|
status: 'Inactive',
|
||||||
|
lastModified: '2024-03-17',
|
||||||
|
version: '2.3'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const [isFormVisible, setIsFormVisible] = useState(false);
|
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||||
const [formMode, setFormMode] = useState('create'); // 'create' lub 'edit'
|
const [formMode, setFormMode] = useState('create');
|
||||||
const [message, setMessage] = useState({ type: '', text: '' });
|
const [message, setMessage] = useState({ type: '', text: '' });
|
||||||
|
|
||||||
|
const mockModels = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Dragon Model",
|
||||||
|
type: "Character",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Medieval Castle",
|
||||||
|
type: "Environment",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 65
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Sci-fi Weapon",
|
||||||
|
type: "Prop",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Forest Scene",
|
||||||
|
type: "Environment",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Robot Character",
|
||||||
|
type: "Character",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "Space Ship",
|
||||||
|
type: "Vehicle",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "Ancient Temple",
|
||||||
|
type: "Environment",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 78
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "Fantasy Sword",
|
||||||
|
type: "Prop",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: "City Block",
|
||||||
|
type: "Environment",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: "Warrior Character",
|
||||||
|
type: "Character",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 89
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: "Futuristic Car",
|
||||||
|
type: "Vehicle",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: "Mountain Range",
|
||||||
|
type: "Environment",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: "Alien Creature",
|
||||||
|
type: "Character",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: "Magic Staff",
|
||||||
|
type: "Prop",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: "Underground Cave",
|
||||||
|
type: "Environment",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 56
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const handleModelSelect = (model) => {
|
const handleModelSelect = (model) => {
|
||||||
setSelectedModel(model);
|
setSelectedModel(model);
|
||||||
};
|
};
|
||||||
|
|
@ -60,28 +273,7 @@ const ThreeDModelsDashboard = () => {
|
||||||
if (selectedModel?.id === modelId) {
|
if (selectedModel?.id === modelId) {
|
||||||
setSelectedModel(null);
|
setSelectedModel(null);
|
||||||
}
|
}
|
||||||
setMessage({ type: 'success', text: 'Model has been deleted' });
|
setMessage({ type: 'success', text: '3D Model has been deleted' });
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormSubmit = (formData) => {
|
|
||||||
if (formMode === 'create') {
|
|
||||||
const newModel = {
|
|
||||||
id: models.length + 1,
|
|
||||||
...formData,
|
|
||||||
lastModified: new Date().toISOString().split('T')[0],
|
|
||||||
status: 'Active'
|
|
||||||
};
|
|
||||||
setModels([...models, newModel]);
|
|
||||||
setMessage({ type: 'success', text: 'Model has been created' });
|
|
||||||
} else {
|
|
||||||
setModels(models.map(model =>
|
|
||||||
model.id === selectedModel.id
|
|
||||||
? { ...model, ...formData, lastModified: new Date().toISOString().split('T')[0] }
|
|
||||||
: model
|
|
||||||
));
|
|
||||||
setMessage({ type: 'success', text: 'Model has been updated' });
|
|
||||||
}
|
|
||||||
setIsFormVisible(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormCancel = () => {
|
const handleFormCancel = () => {
|
||||||
|
|
@ -104,49 +296,44 @@ const ThreeDModelsDashboard = () => {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const formFields = [
|
const filteredModels = useMemo(() => {
|
||||||
{
|
return models.filter(model =>
|
||||||
name: 'name',
|
model.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
label: 'Model Name',
|
model.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
type: 'text',
|
model.status.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
required: true,
|
);
|
||||||
value: selectedModel?.name || ''
|
}, [models, searchQuery]);
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'type',
|
|
||||||
label: 'Model Type',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ value: '3D', label: '3D' },
|
|
||||||
{ value: '2D', label: '2D' }
|
|
||||||
],
|
|
||||||
value: selectedModel?.type || '3D'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ value: 'Active', label: 'Active' },
|
|
||||||
{ value: 'Inactive', label: 'Inactive' }
|
|
||||||
],
|
|
||||||
value: selectedModel?.status || 'Active'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="list-container">
|
<div className="list-container">
|
||||||
<div className="dashboard-header">
|
<div className="dashboard-header">
|
||||||
<h2>3D Models</h2>
|
<h2>3D Models</h2>
|
||||||
<button
|
<div className="dashboard-controls">
|
||||||
className="create-button"
|
<div className="search-container">
|
||||||
onClick={handleCreateModel}
|
<input
|
||||||
>
|
type="text"
|
||||||
<i className="fas fa-plus"></i>
|
placeholder="Szukaj modeli..."
|
||||||
3D Model
|
value={searchQuery}
|
||||||
</button>
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="search-input"
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
className="clear-search"
|
||||||
|
onClick={() => setSearchQuery('')}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="create-button"
|
||||||
|
onClick={handleCreateModel}
|
||||||
|
>
|
||||||
|
<i className="fas fa-upload"></i>
|
||||||
|
Upload 3D Model
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{message.text && (
|
{message.text && (
|
||||||
|
|
@ -158,18 +345,13 @@ const ThreeDModelsDashboard = () => {
|
||||||
{isFormVisible && (
|
{isFormVisible && (
|
||||||
<div className="form-overlay">
|
<div className="form-overlay">
|
||||||
<div className="form-container">
|
<div className="form-container">
|
||||||
<FormGenerator
|
<ModelUploadForm/>
|
||||||
fields={formFields}
|
|
||||||
onSubmit={handleFormSubmit}
|
|
||||||
onCancel={handleFormCancel}
|
|
||||||
title={formMode === 'create' ? 'Create new model' : 'Edit model'}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListGenerator
|
<ListGenerator
|
||||||
data={models}
|
data={filteredModels}
|
||||||
selectedItem={selectedModel}
|
selectedItem={selectedModel}
|
||||||
onItemSelect={handleModelSelect}
|
onItemSelect={handleModelSelect}
|
||||||
onItemAction={handleModelAction}
|
onItemAction={handleModelAction}
|
||||||
|
|
@ -184,26 +366,30 @@ const ThreeDModelsDashboard = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="item-details">
|
<div className="item-details">
|
||||||
|
<div>Version: {model.version}</div>
|
||||||
<div>Last Modified: {model.lastModified}</div>
|
<div>Last Modified: {model.lastModified}</div>
|
||||||
<div>Size: {model.size}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
renderDetails={(model) => (
|
renderDetails={(model) => (
|
||||||
<div className="details-panel">
|
<div className="details-panel">
|
||||||
<h3>Model Details</h3>
|
<h3>3D Model Details</h3>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
<span className="detail-label">ID:</span>
|
<span className="detail-label">ID:</span>
|
||||||
<span className="detail-value">{model.id}</span>
|
<span className="detail-value">{model.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
<span className="detail-label">Nazwa:</span>
|
<span className="detail-label">Name:</span>
|
||||||
<span className="detail-value">{model.name}</span>
|
<span className="detail-value">{model.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
<span className="detail-label">Typ:</span>
|
<span className="detail-label">Type:</span>
|
||||||
<span className="detail-value">{model.type}</span>
|
<span className="detail-value">{model.type}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="detail-row">
|
||||||
|
<span className="detail-label">Version:</span>
|
||||||
|
<span className="detail-value">{model.version}</span>
|
||||||
|
</div>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
<span className="detail-label">Status:</span>
|
<span className="detail-label">Status:</span>
|
||||||
<span className="detail-value">{model.status}</span>
|
<span className="detail-value">{model.status}</span>
|
||||||
|
|
@ -212,10 +398,6 @@ const ThreeDModelsDashboard = () => {
|
||||||
<span className="detail-label">Last Modified:</span>
|
<span className="detail-label">Last Modified:</span>
|
||||||
<span className="detail-value">{model.lastModified}</span>
|
<span className="detail-value">{model.lastModified}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail-row">
|
|
||||||
<span className="detail-label">Size:</span>
|
|
||||||
<span className="detail-value">{model.size}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useRef, useMemo } from 'react';
|
||||||
import { ListGenerator } from '../../components/forms/listGenerator';
|
import { ListGenerator } from '../../components/forms/listGenerator';
|
||||||
import FormGenerator from '../../components/forms/formGenerator';
|
import FormGenerator from '../../components/forms/formGenerator';
|
||||||
|
import AIModelUploadForm from '../../components/forms/ai_model_crud/AIModelUpload';
|
||||||
|
|
||||||
const AIModelsDashboard = () => {
|
const AIModelsDashboard = () => {
|
||||||
const [selectedModel, setSelectedModel] = useState(null);
|
const [selectedModel, setSelectedModel] = useState(null);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [models, setModels] = useState([
|
const [models, setModels] = useState([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|
@ -20,12 +22,178 @@ const AIModelsDashboard = () => {
|
||||||
status: 'Inactive',
|
status: 'Inactive',
|
||||||
lastModified: '2024-03-19',
|
lastModified: '2024-03-19',
|
||||||
version: '1.0'
|
version: '1.0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'yolo-v8',
|
||||||
|
type: 'object-detection',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '1.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'wav2vec',
|
||||||
|
type: 'speech-recognition',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-18',
|
||||||
|
version: '3.0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'bert-base',
|
||||||
|
type: 'text-classification',
|
||||||
|
status: 'Inactive',
|
||||||
|
lastModified: '2024-03-17',
|
||||||
|
version: '2.0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'resnet-50',
|
||||||
|
type: 'image-classification',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '1.8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: 'detr',
|
||||||
|
type: 'object-detection',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-19',
|
||||||
|
version: '2.4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: 'whisper',
|
||||||
|
type: 'speech-recognition',
|
||||||
|
status: 'Inactive',
|
||||||
|
lastModified: '2024-03-16',
|
||||||
|
version: '1.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: 'dalle-3',
|
||||||
|
type: 'text-to-image',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '2.7'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: 'llama-2',
|
||||||
|
type: 'text-generation',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-18',
|
||||||
|
version: '1.9'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: 'mask-rcnn',
|
||||||
|
type: 'image-segmentation',
|
||||||
|
status: 'Inactive',
|
||||||
|
lastModified: '2024-03-15',
|
||||||
|
version: '1.3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: 'roberta',
|
||||||
|
type: 'text-classification',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '2.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: 'dino',
|
||||||
|
type: 'image-classification',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-19',
|
||||||
|
version: '1.6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: 'sam',
|
||||||
|
type: 'image-segmentation',
|
||||||
|
status: 'Active',
|
||||||
|
lastModified: '2024-03-20',
|
||||||
|
version: '1.4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: 'clip',
|
||||||
|
type: 'image-text',
|
||||||
|
status: 'Inactive',
|
||||||
|
lastModified: '2024-03-17',
|
||||||
|
version: '2.3'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const [isFormVisible, setIsFormVisible] = useState(false);
|
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||||
const [formMode, setFormMode] = useState('create');
|
const [formMode, setFormMode] = useState('create');
|
||||||
const [message, setMessage] = useState({ type: '', text: '' });
|
const [message, setMessage] = useState({ type: '', text: '' });
|
||||||
|
|
||||||
|
const nameInput = React.createRef();
|
||||||
|
const typeInput = React.createRef();
|
||||||
|
const versionInput = React.createRef();
|
||||||
|
const statusInput = React.createRef();
|
||||||
|
|
||||||
|
const formRefs = [
|
||||||
|
nameInput,
|
||||||
|
typeInput,
|
||||||
|
versionInput,
|
||||||
|
statusInput
|
||||||
|
];
|
||||||
|
|
||||||
|
const inputList = [
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
action: formMode === 'create' ? 'Create' : 'Update',
|
||||||
|
endpoint: 'ai/models',
|
||||||
|
button_value: formMode === 'create' ? '+ AI MODEL' : 'UPDATE',
|
||||||
|
allowButtonAction: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'NAME',
|
||||||
|
ref: nameInput,
|
||||||
|
value: selectedModel?.name || '',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'TYPE',
|
||||||
|
ref: typeInput,
|
||||||
|
options: [
|
||||||
|
{ value: 'text-to-image', label: 'Text to Image' },
|
||||||
|
{ value: 'image-to-text', label: 'Image to Text' }
|
||||||
|
],
|
||||||
|
value: selectedModel?.type || 'text-to-image',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'VERSION',
|
||||||
|
ref: versionInput,
|
||||||
|
value: selectedModel?.version || '1.0',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'STATUS',
|
||||||
|
ref: statusInput,
|
||||||
|
options: [
|
||||||
|
{ value: 'Active', label: 'Active' },
|
||||||
|
{ value: 'Inactive', label: 'Inactive' }
|
||||||
|
],
|
||||||
|
value: selectedModel?.status || 'Active',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const handleModelSelect = (model) => {
|
const handleModelSelect = (model) => {
|
||||||
setSelectedModel(model);
|
setSelectedModel(model);
|
||||||
};
|
};
|
||||||
|
|
@ -104,56 +272,44 @@ const AIModelsDashboard = () => {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const formFields = [
|
const filteredModels = useMemo(() => {
|
||||||
{
|
return models.filter(model =>
|
||||||
name: 'name',
|
model.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
label: 'Model Name',
|
model.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
type: 'text',
|
model.status.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
required: true,
|
);
|
||||||
value: selectedModel?.name || ''
|
}, [models, searchQuery]);
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'type',
|
|
||||||
label: 'Model Type',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ value: 'text-to-image', label: 'Text to Image' },
|
|
||||||
{ value: 'image-to-text', label: 'Image to Text' }
|
|
||||||
],
|
|
||||||
value: selectedModel?.type || 'text-to-image'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'version',
|
|
||||||
label: 'Version',
|
|
||||||
type: 'text',
|
|
||||||
required: true,
|
|
||||||
value: selectedModel?.version || '1.0'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ value: 'Active', label: 'Active' },
|
|
||||||
{ value: 'Inactive', label: 'Inactive' }
|
|
||||||
],
|
|
||||||
value: selectedModel?.status || 'Active'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="list-container">
|
<div className="list-container">
|
||||||
<div className="dashboard-header">
|
<div className="dashboard-header">
|
||||||
<h2>AI Models</h2>
|
<h2>AI Models</h2>
|
||||||
<button
|
<div className="dashboard-controls">
|
||||||
className="create-button"
|
<div className="search-container">
|
||||||
onClick={handleCreateModel}
|
<input
|
||||||
>
|
type="text"
|
||||||
<i className="fas fa-plus"></i>
|
placeholder="Szukaj modeli..."
|
||||||
AI Model
|
value={searchQuery}
|
||||||
</button>
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="search-input"
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
className="clear-search"
|
||||||
|
onClick={() => setSearchQuery('')}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="create-button"
|
||||||
|
onClick={() => setIsFormVisible(true)}
|
||||||
|
>
|
||||||
|
<i className="fas fa-upload"></i>
|
||||||
|
Upload AI Model
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{message.text && (
|
{message.text && (
|
||||||
|
|
@ -165,18 +321,13 @@ const AIModelsDashboard = () => {
|
||||||
{isFormVisible && (
|
{isFormVisible && (
|
||||||
<div className="form-overlay">
|
<div className="form-overlay">
|
||||||
<div className="form-container">
|
<div className="form-container">
|
||||||
<FormGenerator
|
<AIModelUploadForm />
|
||||||
fields={formFields}
|
|
||||||
onSubmit={handleFormSubmit}
|
|
||||||
onCancel={handleFormCancel}
|
|
||||||
title={formMode === 'create' ? 'Create new AI Model' : 'Edit AI Model'}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListGenerator
|
<ListGenerator
|
||||||
data={models}
|
data={filteredModels}
|
||||||
selectedItem={selectedModel}
|
selectedItem={selectedModel}
|
||||||
onItemSelect={handleModelSelect}
|
onItemSelect={handleModelSelect}
|
||||||
onItemAction={handleModelAction}
|
onItemAction={handleModelAction}
|
||||||
|
|
@ -204,15 +355,15 @@ const AIModelsDashboard = () => {
|
||||||
<span className="detail-value">{model.id}</span>
|
<span className="detail-value">{model.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
<span className="detail-label">Nazwa:</span>
|
<span className="detail-label">Name:</span>
|
||||||
<span className="detail-value">{model.name}</span>
|
<span className="detail-value">{model.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
<span className="detail-label">Typ:</span>
|
<span className="detail-label">Type:</span>
|
||||||
<span className="detail-value">{model.type}</span>
|
<span className="detail-value">{model.type}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
<span className="detail-label">Wersja:</span>
|
<span className="detail-label">Version:</span>
|
||||||
<span className="detail-value">{model.version}</span>
|
<span className="detail-value">{model.version}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
|
|
@ -220,7 +371,7 @@ const AIModelsDashboard = () => {
|
||||||
<span className="detail-value">{model.status}</span>
|
<span className="detail-value">{model.status}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail-row">
|
<div className="detail-row">
|
||||||
<span className="detail-label">Ostatnia modyfikacja:</span>
|
<span className="detail-label">Last Modified:</span>
|
||||||
<span className="detail-value">{model.lastModified}</span>
|
<span className="detail-value">{model.lastModified}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,232 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useRef, useMemo } from 'react';
|
||||||
import { ListGenerator } from '../../components/forms/listGenerator';
|
import { ListGenerator } from '../../components/forms/listGenerator';
|
||||||
import { FormGenerator } from '../../components/forms/formGenerator';
|
import FormGenerator from '../../components/forms/formGenerator';
|
||||||
|
|
||||||
const AITasksDashboard = () => {
|
const AITasksDashboard = () => {
|
||||||
const [selectedTask, setSelectedTask] = useState(null);
|
const [selectedTask, setSelectedTask] = useState(null);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [tasks, setTasks] = useState([
|
const [tasks, setTasks] = useState([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Task A',
|
name: "Model Training - CNN",
|
||||||
type: 'Training',
|
type: "Training",
|
||||||
status: 'In Progress',
|
status: "Completed",
|
||||||
lastModified: '2024-03-20',
|
progress: 100,
|
||||||
progress: 45
|
model: "Object Detection v2",
|
||||||
|
startTime: "2024-03-20 09:00:00",
|
||||||
|
endTime: "2024-03-20 14:30:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Task B',
|
name: "BERT Fine-tuning",
|
||||||
type: 'Inference',
|
type: "Fine-tuning",
|
||||||
status: 'Completed',
|
status: "In Progress",
|
||||||
lastModified: '2024-03-19',
|
progress: 75,
|
||||||
progress: 100
|
model: "Text Generator",
|
||||||
|
startTime: "2024-03-20 10:15:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Model Evaluation",
|
||||||
|
type: "Evaluation",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
model: "Style Transfer v1",
|
||||||
|
startTime: null,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Performance Testing",
|
||||||
|
type: "Testing",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Face Recognition",
|
||||||
|
startTime: "2024-03-19 15:00:00",
|
||||||
|
endTime: "2024-03-19 17:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Model Training - RNN",
|
||||||
|
type: "Training",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 60,
|
||||||
|
model: "Language Translator",
|
||||||
|
startTime: "2024-03-20 08:45:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "GAN Training",
|
||||||
|
type: "Training",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Image Generation",
|
||||||
|
startTime: "2024-03-19 11:00:00",
|
||||||
|
endTime: "2024-03-19 18:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "Model Optimization",
|
||||||
|
type: "Fine-tuning",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 82,
|
||||||
|
model: "Voice Synthesis",
|
||||||
|
startTime: "2024-03-20 09:30:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "Accuracy Testing",
|
||||||
|
type: "Testing",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
model: "Pose Estimation",
|
||||||
|
startTime: null,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: "Transfer Learning",
|
||||||
|
type: "Training",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Scene Understanding",
|
||||||
|
startTime: "2024-03-19 13:15:00",
|
||||||
|
endTime: "2024-03-19 16:45:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: "Model Validation",
|
||||||
|
type: "Evaluation",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 45,
|
||||||
|
model: "Text Summarizer",
|
||||||
|
startTime: "2024-03-20 11:00:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: "Hyperparameter Tuning",
|
||||||
|
type: "Fine-tuning",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
model: "Speech Recognition",
|
||||||
|
startTime: null,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: "Model Training - YOLO",
|
||||||
|
type: "Training",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Object Tracking",
|
||||||
|
startTime: "2024-03-19 09:00:00",
|
||||||
|
endTime: "2024-03-19 15:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: "Performance Optimization",
|
||||||
|
type: "Fine-tuning",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 68,
|
||||||
|
model: "Image Segmentation",
|
||||||
|
startTime: "2024-03-20 10:00:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: "Model Deployment Test",
|
||||||
|
type: "Testing",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Sentiment Analysis",
|
||||||
|
startTime: "2024-03-19 14:00:00",
|
||||||
|
endTime: "2024-03-19 16:00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: "Model Training - GPT",
|
||||||
|
type: "Training",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 92,
|
||||||
|
model: "Chatbot Model",
|
||||||
|
startTime: "2024-03-20 07:30:00",
|
||||||
|
endTime: null
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const [isFormVisible, setIsFormVisible] = useState(false);
|
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||||
const [formMode, setFormMode] = useState('create');
|
const [formMode, setFormMode] = useState('create');
|
||||||
const [message, setMessage] = useState({ type: '', text: '' });
|
const [message, setMessage] = useState({ type: '', text: '' });
|
||||||
|
|
||||||
|
const nameInput = React.createRef();
|
||||||
|
const typeInput = React.createRef();
|
||||||
|
const statusInput = React.createRef();
|
||||||
|
const progressInput = React.createRef();
|
||||||
|
|
||||||
|
const formRefs = [
|
||||||
|
nameInput,
|
||||||
|
typeInput,
|
||||||
|
statusInput,
|
||||||
|
progressInput
|
||||||
|
];
|
||||||
|
|
||||||
|
const inputList = [
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
action: formMode === 'create' ? 'Create' : 'Update',
|
||||||
|
endpoint: 'ai/tasks',
|
||||||
|
button_value: formMode === 'create' ? '+ AI TASK' : 'UPDATE',
|
||||||
|
allowButtonAction: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'NAME',
|
||||||
|
ref: nameInput,
|
||||||
|
value: selectedTask?.name || '',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'TYPE',
|
||||||
|
ref: typeInput,
|
||||||
|
options: [
|
||||||
|
{ value: 'text-to-image', label: 'Text to Image' },
|
||||||
|
{ value: 'image-to-text', label: 'Image to Text' }
|
||||||
|
],
|
||||||
|
value: selectedTask?.type || 'text-to-image',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'STATUS',
|
||||||
|
ref: statusInput,
|
||||||
|
options: [
|
||||||
|
{ value: 'In Progress', label: 'In Progress' },
|
||||||
|
{ value: 'Completed', label: 'Completed' },
|
||||||
|
{ value: 'Failed', label: 'Failed' },
|
||||||
|
{ value: 'Cancelled', label: 'Cancelled' }
|
||||||
|
],
|
||||||
|
value: selectedTask?.status || 'In Progress',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
name: 'PROGRESS',
|
||||||
|
ref: progressInput,
|
||||||
|
value: selectedTask?.progress || 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const handleTaskSelect = (task) => {
|
const handleTaskSelect = (task) => {
|
||||||
setSelectedTask(task);
|
setSelectedTask(task);
|
||||||
};
|
};
|
||||||
|
|
@ -114,6 +315,14 @@ const AITasksDashboard = () => {
|
||||||
setSelectedTask(null);
|
setSelectedTask(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFormAction = (refs) => {
|
||||||
|
const formData = {};
|
||||||
|
refs.forEach((ref, index) => {
|
||||||
|
formData[inputList[index].name] = ref.current.value;
|
||||||
|
});
|
||||||
|
handleFormSubmit(formData);
|
||||||
|
};
|
||||||
|
|
||||||
const getTaskActions = (task) => {
|
const getTaskActions = (task) => {
|
||||||
const actions = [];
|
const actions = [];
|
||||||
|
|
||||||
|
|
@ -147,52 +356,45 @@ const AITasksDashboard = () => {
|
||||||
return actions;
|
return actions;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formFields = [
|
const filteredTasks = useMemo(() => {
|
||||||
{
|
return tasks.filter(task =>
|
||||||
name: 'name',
|
task.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
label: 'Task Name',
|
task.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
type: 'text',
|
task.status.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
required: true,
|
task.model.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
value: selectedTask?.name || ''
|
);
|
||||||
},
|
}, [tasks, searchQuery]);
|
||||||
{
|
|
||||||
name: 'type',
|
|
||||||
label: 'Task Type',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ value: 'Training', label: 'Training' },
|
|
||||||
{ value: 'Inference', label: 'Inference' },
|
|
||||||
{ value: 'Evaluation', label: 'Evaluation' }
|
|
||||||
],
|
|
||||||
value: selectedTask?.type || 'Training'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ value: 'In Progress', label: 'In Progress' },
|
|
||||||
{ value: 'Completed', label: 'Completed' },
|
|
||||||
{ value: 'Failed', label: 'Failed' },
|
|
||||||
{ value: 'Cancelled', label: 'Cancelled' }
|
|
||||||
],
|
|
||||||
value: selectedTask?.status || 'In Progress'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="list-container">
|
<div className="list-container">
|
||||||
<div className="dashboard-header">
|
<div className="dashboard-header">
|
||||||
<h2>AI Tasks</h2>
|
<h2>AI Training</h2>
|
||||||
<button
|
<div className="dashboard-controls">
|
||||||
className="create-button"
|
<div className="search-container">
|
||||||
onClick={handleCreateTask}
|
<input
|
||||||
>
|
type="text"
|
||||||
<i className="fas fa-plus"></i>
|
placeholder="Szukaj zadań..."
|
||||||
Task
|
value={searchQuery}
|
||||||
</button>
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="search-input"
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
className="clear-search"
|
||||||
|
onClick={() => setSearchQuery('')}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="create-button"
|
||||||
|
onClick={handleCreateTask}
|
||||||
|
>
|
||||||
|
<i className="fas fa-plus"></i>
|
||||||
|
Create Task
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{message.text && (
|
{message.text && (
|
||||||
|
|
@ -205,17 +407,17 @@ const AITasksDashboard = () => {
|
||||||
<div className="form-overlay">
|
<div className="form-overlay">
|
||||||
<div className="form-container">
|
<div className="form-container">
|
||||||
<FormGenerator
|
<FormGenerator
|
||||||
fields={formFields}
|
inputList={inputList}
|
||||||
|
formRefs={formRefs}
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
onCancel={handleFormCancel}
|
onCancel={handleFormCancel}
|
||||||
title={formMode === 'create' ? 'Create new task' : 'Edit task'}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListGenerator
|
<ListGenerator
|
||||||
data={tasks}
|
data={filteredTasks}
|
||||||
selectedItem={selectedTask}
|
selectedItem={selectedTask}
|
||||||
onItemSelect={handleTaskSelect}
|
onItemSelect={handleTaskSelect}
|
||||||
onItemAction={handleTaskAction}
|
onItemAction={handleTaskAction}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useRef, useMemo } from 'react';
|
||||||
import { ListGenerator } from '../../components/forms/listGenerator';
|
import { ListGenerator } from '../../components/forms/listGenerator';
|
||||||
import FormGenerator from '../../components/forms/formGenerator';
|
import FormGenerator from '../../components/forms/formGenerator';
|
||||||
|
|
||||||
|
|
@ -7,26 +7,205 @@ const RendersDashboard = () => {
|
||||||
const [renders, setRenders] = useState([
|
const [renders, setRenders] = useState([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Render A',
|
name: "Character Animation",
|
||||||
type: '3D',
|
type: "Animation",
|
||||||
status: 'Completed',
|
status: "Completed",
|
||||||
lastModified: '2024-03-20',
|
progress: 100,
|
||||||
resolution: '1920x1080',
|
model: "Hero Character",
|
||||||
progress: 100
|
startTime: "2024-03-20 09:00:00",
|
||||||
|
endTime: "2024-03-20 11:30:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Render B',
|
name: "Environment Lighting",
|
||||||
type: '2D',
|
type: "Still",
|
||||||
status: 'In Progress',
|
status: "In Progress",
|
||||||
lastModified: '2024-03-19',
|
progress: 75,
|
||||||
resolution: '3840x2160',
|
model: "Forest Scene",
|
||||||
progress: 45
|
startTime: "2024-03-20 10:15:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Product Showcase",
|
||||||
|
type: "360 View",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
model: "Sports Car",
|
||||||
|
startTime: null,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Battle Scene",
|
||||||
|
type: "Animation",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Warriors",
|
||||||
|
startTime: "2024-03-19 15:00:00",
|
||||||
|
endTime: "2024-03-19 18:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Architectural Visualization",
|
||||||
|
type: "Still",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 60,
|
||||||
|
model: "Modern House",
|
||||||
|
startTime: "2024-03-20 08:45:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "Character Portrait",
|
||||||
|
type: "Still",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Fantasy Character",
|
||||||
|
startTime: "2024-03-19 11:00:00",
|
||||||
|
endTime: "2024-03-19 12:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "Vehicle Animation",
|
||||||
|
type: "Animation",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 82,
|
||||||
|
model: "Racing Car",
|
||||||
|
startTime: "2024-03-20 09:30:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "Product Display",
|
||||||
|
type: "360 View",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
model: "Smartphone",
|
||||||
|
startTime: null,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: "Nature Scene",
|
||||||
|
type: "Still",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Mountain Landscape",
|
||||||
|
startTime: "2024-03-19 13:15:00",
|
||||||
|
endTime: "2024-03-19 15:45:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: "Character Walk Cycle",
|
||||||
|
type: "Animation",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 45,
|
||||||
|
model: "Robot Character",
|
||||||
|
startTime: "2024-03-20 11:00:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: "Jewelry Showcase",
|
||||||
|
type: "360 View",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
model: "Diamond Ring",
|
||||||
|
startTime: null,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: "City Flythrough",
|
||||||
|
type: "Animation",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Future City",
|
||||||
|
startTime: "2024-03-19 09:00:00",
|
||||||
|
endTime: "2024-03-19 14:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: "Interior Design",
|
||||||
|
type: "Still",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 68,
|
||||||
|
model: "Living Room",
|
||||||
|
startTime: "2024-03-20 10:00:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: "Product Animation",
|
||||||
|
type: "Animation",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Gaming Console",
|
||||||
|
startTime: "2024-03-19 14:00:00",
|
||||||
|
endTime: "2024-03-19 16:00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: "Character Showcase",
|
||||||
|
type: "360 View",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 92,
|
||||||
|
model: "Superhero",
|
||||||
|
startTime: "2024-03-20 07:30:00",
|
||||||
|
endTime: null
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const [isFormVisible, setIsFormVisible] = useState(false);
|
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||||
const [formMode, setFormMode] = useState('create');
|
const [formMode, setFormMode] = useState('create');
|
||||||
const [message, setMessage] = useState({ type: '', text: '' });
|
const [message, setMessage] = useState({ type: '', text: '' });
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
|
const nameInput = React.createRef();
|
||||||
|
const typeInput = React.createRef();
|
||||||
|
const resolutionInput = React.createRef();
|
||||||
|
const threeDModelInput = React.createRef();
|
||||||
|
|
||||||
|
const formRefs = [
|
||||||
|
nameInput,
|
||||||
|
typeInput,
|
||||||
|
resolutionInput,
|
||||||
|
threeDModelInput
|
||||||
|
];
|
||||||
|
|
||||||
|
const inputList = [
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
action: formMode === 'create' ? 'Create' : 'Update',
|
||||||
|
endpoint: 'renders',
|
||||||
|
button_value: formMode === 'create' ? '+ RENDER' : 'UPDATE',
|
||||||
|
allowButtonAction: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'Name',
|
||||||
|
ref: nameInput,
|
||||||
|
value: selectedRender?.name || '',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'Resolution',
|
||||||
|
ref: resolutionInput,
|
||||||
|
value: selectedRender?.resolution || '',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'choice-listing',
|
||||||
|
name: '3D Model',
|
||||||
|
ref: threeDModelInput,
|
||||||
|
values: selectedRender?.threeDModel|| '',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const handleRenderSelect = (render) => {
|
const handleRenderSelect = (render) => {
|
||||||
setSelectedRender(render);
|
setSelectedRender(render);
|
||||||
|
|
@ -107,71 +286,198 @@ const RendersDashboard = () => {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const formFields = [
|
const mockRenders = [
|
||||||
{
|
{
|
||||||
name: 'name',
|
id: 1,
|
||||||
label: 'Nazwa renderu',
|
name: "Dragon Scene",
|
||||||
type: 'text',
|
type: "Animation",
|
||||||
required: true,
|
status: "Completed",
|
||||||
value: selectedRender?.name || ''
|
progress: 100,
|
||||||
|
model: "Dragon Model",
|
||||||
|
startTime: "2024-03-01 10:00:00",
|
||||||
|
endTime: "2024-03-01 12:30:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'type',
|
id: 2,
|
||||||
label: 'Typ renderu',
|
name: "Castle Exterior",
|
||||||
type: 'select',
|
type: "Still",
|
||||||
required: true,
|
status: "In Progress",
|
||||||
options: [
|
progress: 65,
|
||||||
{ value: '3D', label: '3D' },
|
model: "Medieval Castle",
|
||||||
{ value: '2D', label: '2D' }
|
startTime: "2024-03-02 09:00:00",
|
||||||
],
|
endTime: null
|
||||||
value: selectedRender?.type || '3D'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'resolution',
|
id: 3,
|
||||||
label: 'Rozdzielczość',
|
name: "Weapon Showcase",
|
||||||
type: 'select',
|
type: "360 View",
|
||||||
required: true,
|
status: "Queued",
|
||||||
options: [
|
progress: 0,
|
||||||
{ value: '1920x1080', label: 'Full HD (1920x1080)' },
|
model: "Sci-fi Weapon",
|
||||||
{ value: '3840x2160', label: '4K (3840x2160)' },
|
startTime: null,
|
||||||
{ value: '7680x4320', label: '8K (7680x4320)' }
|
endTime: null
|
||||||
],
|
|
||||||
value: selectedRender?.resolution || '1920x1080'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'status',
|
id: 4,
|
||||||
label: 'Status',
|
name: "Forest Flythrough",
|
||||||
type: 'select',
|
type: "Animation",
|
||||||
required: true,
|
status: "Completed",
|
||||||
options: [
|
progress: 100,
|
||||||
{ value: 'In Progress', label: 'W trakcie' },
|
model: "Forest Scene",
|
||||||
{ value: 'Completed', label: 'Zakończony' },
|
startTime: "2024-03-01 14:00:00",
|
||||||
{ value: 'Failed', label: 'Nieudany' }
|
endTime: "2024-03-01 16:00:00"
|
||||||
],
|
|
||||||
value: selectedRender?.status || 'In Progress'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'progress',
|
id: 5,
|
||||||
label: 'Postęp',
|
name: "Robot Animation",
|
||||||
type: 'number',
|
type: "Animation",
|
||||||
required: true,
|
status: "In Progress",
|
||||||
min: 0,
|
progress: 45,
|
||||||
max: 100,
|
model: "Robot Character",
|
||||||
value: selectedRender?.progress || 0
|
startTime: "2024-03-02 11:00:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "Spaceship Launch",
|
||||||
|
type: "Animation",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Space Ship",
|
||||||
|
startTime: "2024-03-01 13:00:00",
|
||||||
|
endTime: "2024-03-01 15:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "Temple Interior",
|
||||||
|
type: "Still",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 78,
|
||||||
|
model: "Ancient Temple",
|
||||||
|
startTime: "2024-03-02 10:00:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "Sword Display",
|
||||||
|
type: "360 View",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
model: "Fantasy Sword",
|
||||||
|
startTime: null,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: "City Timelapse",
|
||||||
|
type: "Animation",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "City Block",
|
||||||
|
startTime: "2024-03-01 09:00:00",
|
||||||
|
endTime: "2024-03-01 11:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: "Warrior Battle",
|
||||||
|
type: "Animation",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 89,
|
||||||
|
model: "Warrior Character",
|
||||||
|
startTime: "2024-03-02 13:00:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: "Car Showcase",
|
||||||
|
type: "360 View",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
model: "Futuristic Car",
|
||||||
|
startTime: null,
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: "Mountain Vista",
|
||||||
|
type: "Still",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Mountain Range",
|
||||||
|
startTime: "2024-03-01 15:00:00",
|
||||||
|
endTime: "2024-03-01 16:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: "Alien Movement",
|
||||||
|
type: "Animation",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 34,
|
||||||
|
model: "Alien Creature",
|
||||||
|
startTime: "2024-03-02 14:00:00",
|
||||||
|
endTime: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: "Staff Effects",
|
||||||
|
type: "Animation",
|
||||||
|
status: "Completed",
|
||||||
|
progress: 100,
|
||||||
|
model: "Magic Staff",
|
||||||
|
startTime: "2024-03-01 16:00:00",
|
||||||
|
endTime: "2024-03-01 18:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: "Cave Exploration",
|
||||||
|
type: "Animation",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 56,
|
||||||
|
model: "Underground Cave",
|
||||||
|
startTime: "2024-03-02 12:00:00",
|
||||||
|
endTime: null
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const filteredRenders = useMemo(() => {
|
||||||
|
return renders.filter(render =>
|
||||||
|
render.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
render.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
render.status.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
render.model.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
}, [renders, searchQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="list-container">
|
<div className="list-container">
|
||||||
<div className="dashboard-header">
|
<div className="dashboard-header">
|
||||||
<h2>Rendered Materials</h2>
|
<h2>3D Rendering</h2>
|
||||||
<button
|
<div className="dashboard-controls">
|
||||||
className="create-button"
|
<div className="search-container">
|
||||||
onClick={handleCreateRender}
|
<input
|
||||||
>
|
type="text"
|
||||||
<i className="fas fa-plus"></i>
|
placeholder="Szukaj renderów..."
|
||||||
Render
|
value={searchQuery}
|
||||||
</button>
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="search-input"
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
className="clear-search"
|
||||||
|
onClick={() => setSearchQuery('')}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="create-button"
|
||||||
|
onClick={handleCreateRender}
|
||||||
|
>
|
||||||
|
<i className="fas fa-plus"></i>
|
||||||
|
Create Render
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{message.text && (
|
{message.text && (
|
||||||
|
|
@ -184,17 +490,16 @@ const RendersDashboard = () => {
|
||||||
<div className="form-overlay">
|
<div className="form-overlay">
|
||||||
<div className="form-container">
|
<div className="form-container">
|
||||||
<FormGenerator
|
<FormGenerator
|
||||||
fields={formFields}
|
inputList={inputList}
|
||||||
onSubmit={handleFormSubmit}
|
refList={formRefs}
|
||||||
onCancel={handleFormCancel}
|
action={handleFormSubmit}
|
||||||
title={formMode === 'create' ? 'Utwórz nowy render' : 'Edytuj render'}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListGenerator
|
<ListGenerator
|
||||||
data={renders}
|
data={filteredRenders}
|
||||||
selectedItem={selectedRender}
|
selectedItem={selectedRender}
|
||||||
onItemSelect={handleRenderSelect}
|
onItemSelect={handleRenderSelect}
|
||||||
onItemAction={handleRenderAction}
|
onItemAction={handleRenderAction}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,214 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useRef, useMemo } from 'react';
|
||||||
import { ListGenerator } from '../../components/forms/listGenerator';
|
import { ListGenerator } from '../../components/forms/listGenerator';
|
||||||
import FormGenerator from '../../components/forms/formGenerator';
|
import FormGenerator from '../../components/forms/formGenerator';
|
||||||
|
|
||||||
const ServersDashboard = () => {
|
const ServersDashboard = () => {
|
||||||
const [selectedServer, setSelectedServer] = useState(null);
|
const [selectedServer, setSelectedServer] = useState(null);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [servers, setServers] = useState([
|
const [servers, setServers] = useState([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Server A',
|
name: "Render Node 1",
|
||||||
type: 'Render',
|
type: "Render Node",
|
||||||
status: 'Online',
|
status: "Active",
|
||||||
lastModified: '2024-03-20',
|
progress: 100,
|
||||||
ip: '192.168.1.100'
|
ip: "192.168.1.101",
|
||||||
|
lastActive: "2024-03-20 11:30:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Server B',
|
name: "AI Training Node 1",
|
||||||
type: 'AI',
|
type: "AI Training Node",
|
||||||
status: 'Offline',
|
status: "In Progress",
|
||||||
lastModified: '2024-03-19',
|
progress: 75,
|
||||||
ip: '192.168.1.101'
|
ip: "192.168.1.102",
|
||||||
|
lastActive: "2024-03-20 11:29:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Storage Server 1",
|
||||||
|
type: "Storage Server",
|
||||||
|
status: "Active",
|
||||||
|
progress: 100,
|
||||||
|
ip: "192.168.1.103",
|
||||||
|
lastActive: "2024-03-20 11:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Render Node 2",
|
||||||
|
type: "Render Node",
|
||||||
|
status: "Inactive",
|
||||||
|
progress: 0,
|
||||||
|
ip: "192.168.1.104",
|
||||||
|
lastActive: "2024-03-20 10:15:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "AI Training Node 2",
|
||||||
|
type: "AI Training Node",
|
||||||
|
status: "Active",
|
||||||
|
progress: 100,
|
||||||
|
ip: "192.168.1.105",
|
||||||
|
lastActive: "2024-03-20 11:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "Storage Server 2",
|
||||||
|
type: "Storage Server",
|
||||||
|
status: "Active",
|
||||||
|
progress: 100,
|
||||||
|
ip: "192.168.1.106",
|
||||||
|
lastActive: "2024-03-20 11:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "Render Node 3",
|
||||||
|
type: "Render Node",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 82,
|
||||||
|
ip: "192.168.1.107",
|
||||||
|
lastActive: "2024-03-20 11:29:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "AI Training Node 3",
|
||||||
|
type: "AI Training Node",
|
||||||
|
status: "Queued",
|
||||||
|
progress: 0,
|
||||||
|
ip: "192.168.1.108",
|
||||||
|
lastActive: "2024-03-20 11:00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: "Storage Server 3",
|
||||||
|
type: "Storage Server",
|
||||||
|
status: "Active",
|
||||||
|
progress: 100,
|
||||||
|
ip: "192.168.1.109",
|
||||||
|
lastActive: "2024-03-20 11:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: "Render Node 4",
|
||||||
|
type: "Render Node",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 45,
|
||||||
|
ip: "192.168.1.110",
|
||||||
|
lastActive: "2024-03-20 11:29:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: "AI Training Node 4",
|
||||||
|
type: "AI Training Node",
|
||||||
|
status: "Active",
|
||||||
|
progress: 100,
|
||||||
|
ip: "192.168.1.111",
|
||||||
|
lastActive: "2024-03-20 11:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: "Storage Server 4",
|
||||||
|
type: "Storage Server",
|
||||||
|
status: "Inactive",
|
||||||
|
progress: 0,
|
||||||
|
ip: "192.168.1.112",
|
||||||
|
lastActive: "2024-03-20 10:45:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: "Render Node 5",
|
||||||
|
type: "Render Node",
|
||||||
|
status: "Active",
|
||||||
|
progress: 100,
|
||||||
|
ip: "192.168.1.113",
|
||||||
|
lastActive: "2024-03-20 11:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: "AI Training Node 5",
|
||||||
|
type: "AI Training Node",
|
||||||
|
status: "In Progress",
|
||||||
|
progress: 68,
|
||||||
|
ip: "192.168.1.114",
|
||||||
|
lastActive: "2024-03-20 11:29:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: "Storage Server 5",
|
||||||
|
type: "Storage Server",
|
||||||
|
status: "Active",
|
||||||
|
progress: 100,
|
||||||
|
ip: "192.168.1.115",
|
||||||
|
lastActive: "2024-03-20 11:30:00"
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const [isFormVisible, setIsFormVisible] = useState(false);
|
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||||
const [formMode, setFormMode] = useState('create');
|
const [formMode, setFormMode] = useState('create');
|
||||||
const [message, setMessage] = useState({ type: '', text: '' });
|
const [message, setMessage] = useState({ type: '', text: '' });
|
||||||
|
|
||||||
|
const nameInput = React.createRef();
|
||||||
|
const typeInput = React.createRef();
|
||||||
|
const ipInput = React.createRef();
|
||||||
|
const statusInput = React.createRef();
|
||||||
|
|
||||||
|
const formRefs = [
|
||||||
|
nameInput,
|
||||||
|
typeInput,
|
||||||
|
ipInput,
|
||||||
|
statusInput
|
||||||
|
];
|
||||||
|
|
||||||
|
const inputList = [
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
action: formMode === 'create' ? 'Create' : 'Update',
|
||||||
|
endpoint: 'servers',
|
||||||
|
button_value: formMode === 'create' ? '+ SERVER' : 'UPDATE'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'NAME',
|
||||||
|
ref: nameInput,
|
||||||
|
value: selectedServer?.name || '',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'TYPE',
|
||||||
|
ref: typeInput,
|
||||||
|
options: [
|
||||||
|
{ value: 'Render', label: 'Render' },
|
||||||
|
{ value: 'AI', label: 'AI' },
|
||||||
|
{ value: 'Storage', label: 'Storage' }
|
||||||
|
],
|
||||||
|
value: selectedServer?.type || 'Render',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'IP',
|
||||||
|
ref: ipInput,
|
||||||
|
value: selectedServer?.ip || '',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'STATUS',
|
||||||
|
ref: statusInput,
|
||||||
|
options: [
|
||||||
|
{ value: 'Online', label: 'Online' },
|
||||||
|
{ value: 'Offline', label: 'Offline' },
|
||||||
|
{ value: 'Maintenance', label: 'Maintenance' }
|
||||||
|
],
|
||||||
|
value: selectedServer?.status || 'Offline',
|
||||||
|
onChange: null,
|
||||||
|
validationInfo: null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const handleServerSelect = (server) => {
|
const handleServerSelect = (server) => {
|
||||||
setSelectedServer(server);
|
setSelectedServer(server);
|
||||||
};
|
};
|
||||||
|
|
@ -104,58 +287,53 @@ const ServersDashboard = () => {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const formFields = [
|
const handleFormAction = (refs) => {
|
||||||
{
|
const formData = {};
|
||||||
name: 'name',
|
refs.forEach((ref, index) => {
|
||||||
label: 'Nazwa serwera',
|
formData[inputList[index].name] = ref.current.value;
|
||||||
type: 'text',
|
});
|
||||||
required: true,
|
handleFormSubmit(formData);
|
||||||
value: selectedServer?.name || ''
|
};
|
||||||
},
|
|
||||||
{
|
const filteredServers = useMemo(() => {
|
||||||
name: 'type',
|
return servers.filter(server =>
|
||||||
label: 'Typ serwera',
|
server.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
type: 'select',
|
server.type.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
required: true,
|
server.status.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
options: [
|
server.ip.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
{ value: 'Render', label: 'Render' },
|
);
|
||||||
{ value: 'AI', label: 'AI' },
|
}, [servers, searchQuery]);
|
||||||
{ value: 'Storage', label: 'Storage' }
|
|
||||||
],
|
|
||||||
value: selectedServer?.type || 'Render'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ip',
|
|
||||||
label: 'Adres IP',
|
|
||||||
type: 'text',
|
|
||||||
required: true,
|
|
||||||
value: selectedServer?.ip || ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ value: 'Online', label: 'Online' },
|
|
||||||
{ value: 'Offline', label: 'Offline' },
|
|
||||||
{ value: 'Maintenance', label: 'Maintenance' }
|
|
||||||
],
|
|
||||||
value: selectedServer?.status || 'Offline'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="list-container">
|
<div className="list-container">
|
||||||
<div className="dashboard-header">
|
<div className="dashboard-header">
|
||||||
<h2>Servers</h2>
|
<h2>GPU Instances</h2>
|
||||||
<button
|
<div className="dashboard-controls">
|
||||||
className="create-button"
|
<div className="search-container">
|
||||||
onClick={handleCreateServer}
|
<input
|
||||||
>
|
type="text"
|
||||||
<i className="fas fa-plus"></i>
|
placeholder="Szukaj serwerów..."
|
||||||
Server
|
value={searchQuery}
|
||||||
</button>
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="search-input"
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
className="clear-search"
|
||||||
|
onClick={() => setSearchQuery('')}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="create-button"
|
||||||
|
onClick={handleCreateServer}
|
||||||
|
>
|
||||||
|
<i className="fas fa-plus"></i>
|
||||||
|
Add Server
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{message.text && (
|
{message.text && (
|
||||||
|
|
@ -168,17 +346,16 @@ const ServersDashboard = () => {
|
||||||
<div className="form-overlay">
|
<div className="form-overlay">
|
||||||
<div className="form-container">
|
<div className="form-container">
|
||||||
<FormGenerator
|
<FormGenerator
|
||||||
fields={formFields}
|
inputList={inputList}
|
||||||
onSubmit={handleFormSubmit}
|
refList={formRefs}
|
||||||
onCancel={handleFormCancel}
|
action={handleFormAction}
|
||||||
title={formMode === 'create' ? 'Utwórz nowy serwer' : 'Edytuj serwer'}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListGenerator
|
<ListGenerator
|
||||||
data={servers}
|
data={filteredServers}
|
||||||
selectedItem={selectedServer}
|
selectedItem={selectedServer}
|
||||||
onItemSelect={handleServerSelect}
|
onItemSelect={handleServerSelect}
|
||||||
onItemAction={handleServerAction}
|
onItemAction={handleServerAction}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,16 @@ const UserSettings = () => {
|
||||||
return baseInputs;
|
return baseInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formRefs = [usernameInput, emailInput, currentPasswordInput, newPasswordInput, confirmPasswordInput];
|
||||||
|
|
||||||
|
const handleFormAction = (refs) => {
|
||||||
|
const formData = {};
|
||||||
|
formRefs.forEach((ref, index) => {
|
||||||
|
formData[getInputList()[index].name] = ref.current.value;
|
||||||
|
});
|
||||||
|
handleSubmit(formRefs);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="list-container">
|
<div className="list-container">
|
||||||
<div className="user-settings">
|
<div className="user-settings">
|
||||||
|
|
@ -157,17 +167,24 @@ const UserSettings = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormGenerator
|
{isEditing && (
|
||||||
inputList={getInputList()}
|
<div className="form-overlay">
|
||||||
refList={[
|
<div className="form-container">
|
||||||
usernameInput,
|
<FormGenerator
|
||||||
emailInput,
|
inputList={getInputList().map((field, index) => ({
|
||||||
currentPasswordInput,
|
...field,
|
||||||
newPasswordInput,
|
ref: formRefs[index],
|
||||||
confirmPasswordInput
|
type: field.type,
|
||||||
]}
|
name: field.name,
|
||||||
action={handleSubmit}
|
onChange: field.onChange,
|
||||||
/>
|
validationInfo: field.validationInfo
|
||||||
|
}))}
|
||||||
|
refList={formRefs}
|
||||||
|
action={handleFormAction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const IndexPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Reszta komponentów aplikacji */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IndexPage;
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
||||||
|
|
||||||
|
// Helper function to get auth header
|
||||||
|
const getAuthHeader = (token) => ({
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAiModels = createAsyncThunk(
|
||||||
|
'aiModelCrud/fetchModels',
|
||||||
|
async (_, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.get(`${API_URL}/ai/models`, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchAiModel = createAsyncThunk(
|
||||||
|
'aiModelCrud/fetchModel',
|
||||||
|
async (modelId, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.get(`${API_URL}/ai/models/${modelId}`, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const createAiModel = createAsyncThunk(
|
||||||
|
'aiModelCrud/createModel',
|
||||||
|
async (modelData, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.post(`${API_URL}/ai/models`, modelData, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateAiModel = createAsyncThunk(
|
||||||
|
'aiModelCrud/updateModel',
|
||||||
|
async ({ modelId, updates }, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.put(`${API_URL}/ai/models/${modelId}`, updates, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteAiModel = createAsyncThunk(
|
||||||
|
'aiModelCrud/deleteModel',
|
||||||
|
async (modelId, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
await axios.delete(`${API_URL}/ai/models/${modelId}`, getAuthHeader(token));
|
||||||
|
return modelId;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchAiTasks = createAsyncThunk(
|
||||||
|
'aiModelCrud/fetchTasks',
|
||||||
|
async ({ status, page = 1, limit = 10 }, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const params = { page, limit };
|
||||||
|
if (status) params.status = status;
|
||||||
|
|
||||||
|
const response = await axios.get(`${API_URL}/ai/tasks`, {
|
||||||
|
...getAuthHeader(token),
|
||||||
|
params
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const createAiTask = createAsyncThunk(
|
||||||
|
'aiModelCrud/createTask',
|
||||||
|
async (taskData, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.post(`${API_URL}/ai/tasks`, taskData, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateAiTask = createAsyncThunk(
|
||||||
|
'aiModelCrud/updateTask',
|
||||||
|
async ({ taskId, action }, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.put(
|
||||||
|
`${API_URL}/ai/tasks/${taskId}`,
|
||||||
|
{ action },
|
||||||
|
getAuthHeader(token)
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteAiTask = createAsyncThunk(
|
||||||
|
'aiModelCrud/deleteTask',
|
||||||
|
async (taskId, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
await axios.delete(`${API_URL}/ai/tasks/${taskId}`, getAuthHeader(token));
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
||||||
|
|
||||||
|
// Helper function to get auth header
|
||||||
|
const getAuthHeader = (token) => ({
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchRenders = createAsyncThunk(
|
||||||
|
'renderCrud/fetchRenders',
|
||||||
|
async ({ page = 1, limit = 10 }, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.get(`${API_URL}/renders`, {
|
||||||
|
...getAuthHeader(token),
|
||||||
|
params: { page, limit }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchRender = createAsyncThunk(
|
||||||
|
'renderCrud/fetchRender',
|
||||||
|
async (renderId, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.get(`${API_URL}/renders/${renderId}`, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateRender = createAsyncThunk(
|
||||||
|
'renderCrud/updateRender',
|
||||||
|
async ({ renderId, updates }, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.put(`${API_URL}/renders/${renderId}`, updates, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteRender = createAsyncThunk(
|
||||||
|
'renderCrud/deleteRender',
|
||||||
|
async (renderId, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
await axios.delete(`${API_URL}/renders/${renderId}`, getAuthHeader(token));
|
||||||
|
return renderId;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchRenderTasks = createAsyncThunk(
|
||||||
|
'renderCrud/fetchTasks',
|
||||||
|
async ({ status, page = 1, limit = 10 }, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const params = { page, limit };
|
||||||
|
if (status) params.status = status;
|
||||||
|
|
||||||
|
const response = await axios.get(`${API_URL}/renders/tasks`, {
|
||||||
|
...getAuthHeader(token),
|
||||||
|
params
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const createRenderTask = createAsyncThunk(
|
||||||
|
'renderCrud/createTask',
|
||||||
|
async (taskData, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.post(`${API_URL}/renders/tasks`, taskData, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateRenderTask = createAsyncThunk(
|
||||||
|
'renderCrud/updateTask',
|
||||||
|
async ({ taskId, action }, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.put(
|
||||||
|
`${API_URL}/renders/tasks/${taskId}`,
|
||||||
|
{ action },
|
||||||
|
getAuthHeader(token)
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteRenderTask = createAsyncThunk(
|
||||||
|
'renderCrud/deleteTask',
|
||||||
|
async (taskId, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
await axios.delete(`${API_URL}/renders/tasks/${taskId}`, getAuthHeader(token));
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
||||||
|
|
||||||
|
// Helper function to get auth header
|
||||||
|
const getAuthHeader = (token) => ({
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchModels = createAsyncThunk(
|
||||||
|
'threeDModelCrud/fetchModels',
|
||||||
|
async (_, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.get(`${API_URL}/models`, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchModel = createAsyncThunk(
|
||||||
|
'threeDModelCrud/fetchModel',
|
||||||
|
async (modelId, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.get(`${API_URL}/models/${modelId}`, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const createModel = createAsyncThunk(
|
||||||
|
'threeDModelCrud/createModel',
|
||||||
|
async (modelData, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.post(`${API_URL}/models`, modelData, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateModel = createAsyncThunk(
|
||||||
|
'threeDModelCrud/updateModel',
|
||||||
|
async ({ modelId, updates }, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
const response = await axios.put(`${API_URL}/models/${modelId}`, updates, getAuthHeader(token));
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteModel = createAsyncThunk(
|
||||||
|
'threeDModelCrud/deleteModel',
|
||||||
|
async (modelId, { getState }) => {
|
||||||
|
const { token } = getState().userAuth;
|
||||||
|
await axios.delete(`${API_URL}/models/${modelId}`, getAuthHeader(token));
|
||||||
|
return modelId;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const uploadModel = createAsyncThunk(
|
||||||
|
'threeDModelCrud/uploadModel',
|
||||||
|
async ({ user_id, file, token }) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('user_id', user_id);
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('token', token);
|
||||||
|
|
||||||
|
const response = await axios.post(`${API_URL}/models/upload`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
||||||
|
|
||||||
|
export const loginUser = createAsyncThunk(
|
||||||
|
'userAuth/login',
|
||||||
|
async (credentials) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('username', credentials.username);
|
||||||
|
formData.append('password', credentials.password);
|
||||||
|
|
||||||
|
const response = await axios.post(`${API_URL}/auth`, formData);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const registerUser = createAsyncThunk(
|
||||||
|
'userAuth/register',
|
||||||
|
async (userData) => {
|
||||||
|
const response = await axios.post(`${API_URL}/register`, userData);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const changePassword = createAsyncThunk(
|
||||||
|
'userAuth/changePassword',
|
||||||
|
async (passwordData) => {
|
||||||
|
const response = await axios.post(`${API_URL}/change-password`, passwordData);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
fetchAiModels,
|
||||||
|
fetchAiModel,
|
||||||
|
createAiModel,
|
||||||
|
updateAiModel,
|
||||||
|
deleteAiModel,
|
||||||
|
createAiTask,
|
||||||
|
updateAiTask,
|
||||||
|
deleteAiTask,
|
||||||
|
fetchAiTasks
|
||||||
|
} from '../asyncThunks/aiModelCrudAsyncThunk';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
models: [],
|
||||||
|
selectedModel: null,
|
||||||
|
tasks: [],
|
||||||
|
isLoading: false,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const aiModelCrudSlice = createSlice({
|
||||||
|
name: 'aiModelCrud',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
clearError: (state) => {
|
||||||
|
state.error = null;
|
||||||
|
},
|
||||||
|
setSelectedModel: (state, action) => {
|
||||||
|
state.selectedModel = action.payload;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
// Fetch Models
|
||||||
|
builder.addCase(fetchAiModels.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchAiModels.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.models = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchAiModels.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch Single Model
|
||||||
|
builder.addCase(fetchAiModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchAiModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.selectedModel = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchAiModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Model
|
||||||
|
builder.addCase(createAiModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(createAiModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.models.push(action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(createAiModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update Model
|
||||||
|
builder.addCase(updateAiModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(updateAiModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
const index = state.models.findIndex(model => model.id === action.payload.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.models[index] = action.payload;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.addCase(updateAiModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete Model
|
||||||
|
builder.addCase(deleteAiModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteAiModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.models = state.models.filter(model => model.id !== action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(deleteAiModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch Tasks
|
||||||
|
builder.addCase(fetchAiTasks.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchAiTasks.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.tasks = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchAiTasks.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Task
|
||||||
|
builder.addCase(createAiTask.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(createAiTask.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.tasks.push(action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(createAiTask.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update Task
|
||||||
|
builder.addCase(updateAiTask.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(updateAiTask.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
const index = state.tasks.findIndex(task => task.id === action.payload.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.tasks[index] = action.payload;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.addCase(updateAiTask.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete Task
|
||||||
|
builder.addCase(deleteAiTask.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteAiTask.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.tasks = state.tasks.filter(task => task.id !== action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(deleteAiTask.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { clearError, setSelectedModel } = aiModelCrudSlice.actions;
|
||||||
|
export const aiModelCrudSelector = (state) => state.aiModelCrud;
|
||||||
|
export default aiModelCrudSlice.reducer;
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
fetchRenders,
|
||||||
|
fetchRender,
|
||||||
|
updateRender,
|
||||||
|
deleteRender,
|
||||||
|
fetchRenderTasks,
|
||||||
|
createRenderTask,
|
||||||
|
updateRenderTask,
|
||||||
|
deleteRenderTask
|
||||||
|
} from '../asyncThunks/renderCrudAsyncThunk';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
renders: [],
|
||||||
|
selectedRender: null,
|
||||||
|
tasks: [],
|
||||||
|
isLoading: false,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCrudSlice = createSlice({
|
||||||
|
name: 'renderCrud',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
clearError: (state) => {
|
||||||
|
state.error = null;
|
||||||
|
},
|
||||||
|
setSelectedRender: (state, action) => {
|
||||||
|
state.selectedRender = action.payload;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
// Fetch Renders
|
||||||
|
builder.addCase(fetchRenders.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchRenders.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.renders = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchRenders.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch Single Render
|
||||||
|
builder.addCase(fetchRender.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchRender.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.selectedRender = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchRender.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update Render
|
||||||
|
builder.addCase(updateRender.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(updateRender.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
const index = state.renders.findIndex(render => render.id === action.payload.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.renders[index] = action.payload;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.addCase(updateRender.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete Render
|
||||||
|
builder.addCase(deleteRender.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteRender.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.renders = state.renders.filter(render => render.id !== action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(deleteRender.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch Tasks
|
||||||
|
builder.addCase(fetchRenderTasks.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchRenderTasks.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.tasks = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchRenderTasks.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Task
|
||||||
|
builder.addCase(createRenderTask.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(createRenderTask.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.tasks.push(action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(createRenderTask.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update Task
|
||||||
|
builder.addCase(updateRenderTask.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(updateRenderTask.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
const index = state.tasks.findIndex(task => task.id === action.payload.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.tasks[index] = action.payload;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.addCase(updateRenderTask.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete Task
|
||||||
|
builder.addCase(deleteRenderTask.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteRenderTask.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.tasks = state.tasks.filter(task => task.id !== action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(deleteRenderTask.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { clearError, setSelectedRender } = renderCrudSlice.actions;
|
||||||
|
export const renderCrudSelector = (state) => state.renderCrud;
|
||||||
|
export default renderCrudSlice.reducer;
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
fetchModels,
|
||||||
|
fetchModel,
|
||||||
|
createModel,
|
||||||
|
updateModel,
|
||||||
|
deleteModel,
|
||||||
|
uploadModel
|
||||||
|
} from '../asyncThunks/threeDModelCrudAsyncThunk';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
models: [],
|
||||||
|
selectedModel: null,
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
upload_blend_file_status: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const threeDModelCrudSlice = createSlice({
|
||||||
|
name: 'threeDModelCrud',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
clearError: (state) => {
|
||||||
|
state.error = null;
|
||||||
|
},
|
||||||
|
setSelectedModel: (state, action) => {
|
||||||
|
state.selectedModel = action.payload;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
// Fetch Models
|
||||||
|
builder.addCase(fetchModels.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchModels.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.models = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchModels.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch Single Model
|
||||||
|
builder.addCase(fetchModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.selectedModel = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Model
|
||||||
|
builder.addCase(createModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(createModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.models.push(action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(createModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update Model
|
||||||
|
builder.addCase(updateModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(updateModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
const index = state.models.findIndex(model => model.id === action.payload.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.models[index] = action.payload;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.addCase(updateModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete Model
|
||||||
|
builder.addCase(deleteModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.models = state.models.filter(model => model.id !== action.payload);
|
||||||
|
});
|
||||||
|
builder.addCase(deleteModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Upload Model
|
||||||
|
builder.addCase(uploadModel.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
state.upload_blend_file_status = 'uploading';
|
||||||
|
});
|
||||||
|
builder.addCase(uploadModel.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.upload_blend_file_status = { info: 'Upload successful' };
|
||||||
|
});
|
||||||
|
builder.addCase(uploadModel.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
state.upload_blend_file_status = { info: 'Upload failed' };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { clearError, setSelectedModel } = threeDModelCrudSlice.actions;
|
||||||
|
export const threeDModelCrudSelector = (state) => state.threeDModelCrud;
|
||||||
|
export default threeDModelCrudSlice.reducer;
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import { loginUser, registerUser, changePassword } from '../asyncThunks/userAuthAsyncThunk';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
user: null,
|
||||||
|
token: null,
|
||||||
|
isLoading: false,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const userAuthSlice = createSlice({
|
||||||
|
name: 'userAuth',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
logout: (state) => {
|
||||||
|
state.user = null;
|
||||||
|
state.token = null;
|
||||||
|
state.error = null;
|
||||||
|
},
|
||||||
|
clearError: (state) => {
|
||||||
|
state.error = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
// Login
|
||||||
|
builder.addCase(loginUser.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(loginUser.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.user = action.payload.user;
|
||||||
|
state.token = action.payload.token;
|
||||||
|
});
|
||||||
|
builder.addCase(loginUser.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register
|
||||||
|
builder.addCase(registerUser.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(registerUser.fulfilled, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.user = action.payload.user;
|
||||||
|
state.token = action.payload.token;
|
||||||
|
});
|
||||||
|
builder.addCase(registerUser.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Change Password
|
||||||
|
builder.addCase(changePassword.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(changePassword.fulfilled, (state) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(changePassword.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.error.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { logout, clearError } = userAuthSlice.actions;
|
||||||
|
export const userAuthSelector = (state) => state.userAuth;
|
||||||
|
export default userAuthSlice.reducer;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import userAuthReducer from './slices/userAuthSlice';
|
||||||
|
import threeDModelCrudReducer from './slices/threeDModelCrudSlice';
|
||||||
|
import aiModelCrudReducer from './slices/aiModelCrudSlice';
|
||||||
|
import renderCrudReducer from './slices/renderCrudSlice';
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
userAuth: userAuthReducer,
|
||||||
|
threeDModelCrud: threeDModelCrudReducer,
|
||||||
|
aiModelCrud: aiModelCrudReducer,
|
||||||
|
renderCrud: renderCrudReducer
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -252,15 +252,15 @@ body, html {
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
.list-generator-container {
|
.list-generator-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|
||||||
.table-header {
|
.table-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
@ -313,13 +313,13 @@ body, html {
|
||||||
background-color: rgba($background-color, 0.1);
|
background-color: rgba($background-color, 0.1);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
.item-column-row {
|
.item-column-row {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $title-color;
|
color: $title-color;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.items-list {
|
.items-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
@ -332,7 +332,7 @@ body, html {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-row {
|
.item-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
@ -352,8 +352,8 @@ body, html {
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-info {
|
.item-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
|
|
@ -504,6 +504,75 @@ body, html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-top: 1px solid rgba($subtitle-color, 0.1);
|
||||||
|
|
||||||
|
.items-per-page {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: $subtitle-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: $background-color;
|
||||||
|
color: $title-color;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $first-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
background: $background-color;
|
||||||
|
color: $title-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.pagination-button {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: $background-color;
|
||||||
|
color: $title-color;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: $first-color;
|
||||||
|
border-color: $first-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-info {
|
||||||
|
color: $subtitle-color;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-row {
|
.list-row {
|
||||||
|
|
@ -513,7 +582,7 @@ body, html {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
|
@ -521,7 +590,7 @@ body, html {
|
||||||
background-color: rgba($background-color, 0.05);
|
background-color: rgba($background-color, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-info {
|
.item-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
|
@ -585,7 +654,7 @@ body, html {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background-color: rgba($subtitle-color, 0.1);
|
background-color: rgba($subtitle-color, 0.1);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -1046,25 +1115,29 @@ body, html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.float_form_model {
|
.upload_input_container {
|
||||||
position: fixed;
|
border: dashed 2px rgba(0,128,0,1);
|
||||||
width: 400px;
|
|
||||||
padding: 50px;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
color: green;
|
width: 350px - 4px - 20px - 20px;
|
||||||
background-color: rgba(22,28,29,1);
|
padding-left: 20px;
|
||||||
margin-left: 50%;
|
padding-right: 20px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.upload_input {
|
.upload_input {
|
||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
height: 0px !important;
|
height: 0px !important;
|
||||||
padding-top: 70px;
|
padding-top: 10px;
|
||||||
padding-left: 400px;
|
padding-left: 100%;
|
||||||
margin-left: 0px;
|
padding-bottom: 10px;
|
||||||
color: rgba(0,128,0,1);
|
color: rgba(0,128,0,1);
|
||||||
font-family: Ubuntu;
|
font-family: Ubuntu;
|
||||||
border: dashed 2px rgba(0,128,0,1);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|
@ -1332,34 +1405,79 @@ body, html {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color: $title-color;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-button {
|
.dashboard-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 16px;
|
||||||
padding: 10px 20px;
|
|
||||||
background: $first-color;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
i {
|
.search-container {
|
||||||
font-size: 16px;
|
position: relative;
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 250px;
|
||||||
|
padding: 8px 32px 8px 12px;
|
||||||
|
background: $input-background;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(111,108,106,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $first-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-search {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
.create-button {
|
||||||
background: darken($first-color, 10%);
|
display: flex;
|
||||||
transform: translateY(-2px);
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: $first-color;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: darken($first-color, 10%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1377,12 +1495,65 @@ body, html {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
background: $form-background;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 10px;
|
|
||||||
width: 500px;
|
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Stylizacja natywnych suwaków */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: $form-background;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: $first-color;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: darken($first-color, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: darken($first-color, 20%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button {
|
||||||
|
background: $first-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: darken($first-color, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-button:vertical:start:decrement,
|
||||||
|
::-webkit-scrollbar-button:vertical:end:increment,
|
||||||
|
::-webkit-scrollbar-button:horizontal:start:decrement,
|
||||||
|
::-webkit-scrollbar-button:horizontal:end:increment {
|
||||||
|
background-color: $first-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: $first-color $form-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style dla wyłączonych suwaków */
|
||||||
|
::-webkit-scrollbar-thumb:disabled,
|
||||||
|
::-webkit-scrollbar-button:disabled {
|
||||||
|
background: rgba($first-color, 1);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wymuszenie stylów nawet gdy content nie wymaga przewijania */
|
||||||
|
.dashboard-content {
|
||||||
|
min-height: 100%; /* Wymusza pojawienie się suwaka */
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue