Skip to content

Commit c42bc21

Browse files
authored
Merge pull request #24 from problem-judge/client/admin
Add admin client
2 parents f903157 + cc69671 commit c42bc21

File tree

105 files changed

+5532
-672
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+5532
-672
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22
testing_system
33
cp.sh
44
swag
5+
clients/node_modules
6+
clients/clients
7+
.DS_Store
8+
clients/configs
59
custom

clients/admin/handler.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package admin
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
"net/http"
6+
"testing_system/clients/common"
7+
"testing_system/lib/logger"
8+
)
9+
10+
type Handler struct {
11+
base *common.ClientBase
12+
}
13+
14+
func SetupHandler(base *common.ClientBase) error {
15+
h := &Handler{
16+
base: base,
17+
}
18+
if base.Config.Admin == false {
19+
return logger.Error("No admin config specified")
20+
}
21+
h.setupRoutes()
22+
23+
return nil
24+
}
25+
26+
func (h *Handler) setupRoutes() {
27+
router := h.base.Router.Group("/admin", h.base.RequireAuthMiddleware(true))
28+
router.GET("", h.serveFrontend)
29+
30+
router.GET("/problems", h.serveFrontend)
31+
router.GET("/new/problem", h.serveFrontend)
32+
router.GET("/problem/:id", h.serveFrontend)
33+
34+
router.GET("/submissions", h.serveFrontend)
35+
router.GET("/submission/:id", h.serveFrontend)
36+
router.GET("/new/submission", h.serveFrontend)
37+
38+
router.GET("/status", h.serveFrontend)
39+
}
40+
41+
func (h *Handler) serveFrontend(c *gin.Context) {
42+
c.HTML(
43+
http.StatusOK,
44+
"admin.gohtml",
45+
gin.H{},
46+
)
47+
}

clients/common/client_base.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package common
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
"gopkg.in/yaml.v3"
6+
"gorm.io/gorm"
7+
"os"
8+
"path/filepath"
9+
"testing_system/clients/common/clientconfig"
10+
"testing_system/common/connectors/masterconn"
11+
"testing_system/common/connectors/storageconn"
12+
"testing_system/common/db"
13+
"testing_system/lib/logger"
14+
)
15+
16+
type ClientBase struct {
17+
Config clientconfig.Config
18+
19+
Router *gin.Engine
20+
21+
StorageConnection *storageconn.Connector
22+
MasterConnection *masterconn.Connector
23+
DB *gorm.DB
24+
}
25+
26+
func NewClientBase(configPath string) *ClientBase {
27+
var config clientconfig.Config
28+
configData, err := os.ReadFile(configPath)
29+
if err != nil {
30+
panic(err)
31+
}
32+
err = yaml.Unmarshal(configData, &config)
33+
if err != nil {
34+
panic(err)
35+
}
36+
37+
logger.InitLogger(config.Logger)
38+
39+
base := &ClientBase{
40+
Config: config,
41+
StorageConnection: storageconn.NewConnector(config.StorageConnection),
42+
MasterConnection: masterconn.NewConnector(config.MasterConnection),
43+
}
44+
45+
base.DB, err = db.NewDB(config.DB)
46+
if err != nil {
47+
logger.Panic("Can not set up testing system db, error: %v", err)
48+
}
49+
50+
base.Router = gin.Default()
51+
52+
base.Router.Static("/static", filepath.Join(base.Config.ResourcesPath, "static"))
53+
base.Router.LoadHTMLGlob(filepath.Join(base.Config.ResourcesPath, "templates/*"))
54+
return base
55+
}
56+
57+
func (b *ClientBase) Run() {
58+
err := b.Router.Run(b.Config.Address)
59+
if err != nil {
60+
logger.Panic("Can not start client handler, error: %v", err)
61+
}
62+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package clientconfig
2+
3+
import (
4+
"testing_system/clients/tsapi/tsapiconfig"
5+
tsconfig "testing_system/common/config"
6+
"testing_system/lib/logger"
7+
)
8+
9+
type Config struct {
10+
Address string `yaml:"Address"`
11+
12+
Logger *logger.Config `yaml:"Logger"`
13+
14+
StorageConnection *tsconfig.Connection `yaml:"StorageConnection"`
15+
MasterConnection *tsconfig.Connection `yaml:"MasterConnection"`
16+
DB tsconfig.DBConfig `yaml:"DB"`
17+
18+
ResourcesPath string `yaml:"ResourcesPath"`
19+
20+
TestingSystemAPI *tsapiconfig.Config `yaml:"TestingSystemAPI"`
21+
Admin bool `yaml:"Admin"`
22+
}

clients/common/session.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package common
2+
3+
import "github.com/gin-gonic/gin"
4+
5+
func (b *ClientBase) RequireAuthMiddleware(redirect bool) gin.HandlerFunc {
6+
return func(c *gin.Context) {
7+
// TODO: add check auth
8+
}
9+
}
10+
11+
func (b *ClientBase) CSRFMiddleware(c *gin.Context) {
12+
// TODO: Add check csrf
13+
}

clients/esbuild.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const esbuild = require("esbuild");
2+
3+
async function watch() {
4+
let ctx = await esbuild.context({
5+
entryPoints: ["./frontend/admin/Admin.jsx"],
6+
minify: true,
7+
outfile: "./resources/static/admin/admin.js",
8+
bundle: true,
9+
});
10+
await ctx.watch();
11+
console.log('Watching...');
12+
}
13+
14+
watch()
15+
16+
17+

clients/frontend/admin/Admin.jsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom/client";
3+
import { BrowserRouter, Routes, Route } from "react-router-dom";
4+
import Home from "./pages/Home";
5+
import Problems from "./pages/Problems";
6+
import Problem from "./pages/Problem";
7+
import Submissions from "./pages/Submissions";
8+
import Submission from "./pages/Submission";
9+
import NewProblem from "./pages/NewProblem";
10+
import NewSubmission from "./pages/NewSubmission";
11+
import Status from "./pages/Status";
12+
13+
const root = ReactDOM.createRoot(document.querySelector("#application"));
14+
root.render(
15+
<BrowserRouter>
16+
<Routes>
17+
<Route path="/admin" element={<Home />} />
18+
<Route path="/admin/problems" element={<Problems />} />
19+
<Route path="/admin/problem/:id" element={<Problem />} />
20+
<Route path="/admin/new/problem" element={<NewProblem />} />
21+
<Route path="/admin/submissions" element={<Submissions />} />
22+
<Route path="/admin/submission/:id" element={<Submission />} />
23+
<Route path="/admin/new/submission" element={<NewSubmission />} />
24+
<Route path="/admin/status" element={<Status />} />
25+
</Routes>
26+
</BrowserRouter>
27+
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from "react";
2+
import {Link} from "react-router-dom";
3+
4+
export default function Body(navs, value) {
5+
const lastNav = navs[navs.length - 1];
6+
navs = navs.slice(0, -1);
7+
return (
8+
<div>
9+
<div className="container-fluid nav-links">
10+
<div className="container-lg px-5 py-2">
11+
<div className="d-flex">
12+
{navs.map((nav, i) => (
13+
<div
14+
key={i}
15+
className="text-white d-block"
16+
>
17+
<div className="d-flex">
18+
<Link className="nav-link d-block pe-1" to={nav.path}>{nav.text}</Link>
19+
<div className="d-flex d-block pe-1">/</div>
20+
</div>
21+
</div>
22+
))}
23+
<div className="d-inline-block text-white text-opacity-75">{lastNav.text}</div>
24+
</div>
25+
</div>
26+
</div>
27+
<div className="container-lg px-xl-5 px-0 my-5">
28+
{value}
29+
</div>
30+
</div>
31+
);
32+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from "react";
2+
3+
export default function ChangeAlert(alert) {
4+
if (!alert.hasAlert) {
5+
return null
6+
}
7+
if (alert.ok) {
8+
return <div className="alert alert-success mt-3" role="alert">
9+
{alert.message}
10+
</div>
11+
} else {
12+
return <div className="alert alert-danger mt-3" role="alert">
13+
{alert.message}
14+
</div>
15+
}
16+
}
17+
18+
19+
20+
export function SendAlertRequest(axiosPromise, setAlert, onOK) {
21+
const clearAlert = () => {
22+
setAlert({
23+
hasAlert: false,
24+
ok: false,
25+
message: "",
26+
})
27+
}
28+
29+
axiosPromise.then((resp) => {
30+
if (!resp.data.error) {
31+
if (onOK) {
32+
onOK(resp.data.response)
33+
} else {
34+
setAlert({
35+
hasAlert: true,
36+
ok: true,
37+
message: "Saved changes",
38+
})
39+
}
40+
} else {
41+
setAlert({
42+
hasAlert: false,
43+
ok: false,
44+
message: resp.data.message,
45+
})
46+
}
47+
setTimeout(clearAlert, 3000)
48+
}).catch((err) => {
49+
setAlert({
50+
hasAlert: true,
51+
ok: false,
52+
message: err.response.data.error,
53+
})
54+
setTimeout(clearAlert, 3000)
55+
})
56+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from "react";
2+
import {Link} from "react-router-dom";
3+
4+
export default function Links(links) {
5+
return <div>
6+
{links.map((link, index) =>
7+
<div key={index} className="row border border-primary px-2 py-2 mb-3 rounded-3">
8+
<Link to={link.to}>{link.text}</Link>
9+
</div>
10+
)}
11+
</div>
12+
}

0 commit comments

Comments
 (0)