muffin-admin 1.36.4 → 1.36.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -11
- package/src/admin.jsx +78 -0
- package/src/authprovider.js +85 -0
- package/src/buttons/ActionButton.jsx +99 -0
- package/src/buttons/LinkButton.jsx +20 -0
- package/src/dashboard.jsx +74 -0
- package/src/dataprovider.js +94 -0
- package/src/fields/AvatarField.jsx +33 -0
- package/src/fields/FKField.jsx +27 -0
- package/src/fields/JsonField.jsx +26 -0
- package/src/hooks/useAction.js +9 -0
- package/src/index.js +14 -0
- package/src/inputs/FKInput.jsx +41 -0
- package/src/inputs/JsonInput.jsx +62 -0
- package/src/resources.jsx +36 -0
- package/src/ui.jsx +107 -0
- package/src/utils.js +38 -0
- package/src/views/create.jsx +30 -0
- package/src/views/edit.jsx +41 -0
- package/src/views/list.jsx +86 -0
- package/src/views/show.jsx +42 -0
- package/src/web.js +3 -0
- package/.eslintrc.json +0 -44
- package/Makefile +0 -18
- package/jsconfig.json +0 -9
- package/webpack.config.js +0 -45
- /package/dist/{admin.js → cjs/admin.js} +0 -0
- /package/dist/{authprovider.js → cjs/authprovider.js} +0 -0
- /package/dist/{buttons → cjs/buttons}/ActionButton.js +0 -0
- /package/dist/{buttons → cjs/buttons}/LinkButton.js +0 -0
- /package/dist/{dashboard.js → cjs/dashboard.js} +0 -0
- /package/dist/{dataprovider.js → cjs/dataprovider.js} +0 -0
- /package/dist/{fields → cjs/fields}/AvatarField.js +0 -0
- /package/dist/{fields → cjs/fields}/FKField.js +0 -0
- /package/dist/{fields → cjs/fields}/JsonField.js +0 -0
- /package/dist/{hooks → cjs/hooks}/useAction.js +0 -0
- /package/dist/{index.js → cjs/index.js} +0 -0
- /package/dist/{inputs → cjs/inputs}/FKInput.js +0 -0
- /package/dist/{inputs → cjs/inputs}/JsonInput.js +0 -0
- /package/dist/{resources.js → cjs/resources.js} +0 -0
- /package/dist/{ui.js → cjs/ui.js} +0 -0
- /package/dist/{utils.js → cjs/utils.js} +0 -0
- /package/dist/{views → cjs/views}/create.js +0 -0
- /package/dist/{views → cjs/views}/edit.js +0 -0
- /package/dist/{views → cjs/views}/list.js +0 -0
- /package/dist/{views → cjs/views}/show.js +0 -0
- /package/dist/{web.js → cjs/web.js} +0 -0
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muffin-admin",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.5",
|
|
4
4
|
"description": "NPM package for https://github.com/klen/muffin-admin",
|
|
5
|
-
"
|
|
5
|
+
"files": [
|
|
6
|
+
"src",
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"main": "dist/cjs/index.js",
|
|
10
|
+
"module": "dist/esm/index.js",
|
|
6
11
|
"scripts": {
|
|
7
12
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
13
|
},
|
|
@@ -35,14 +40,5 @@
|
|
|
35
40
|
"webpack": "^5.24.4",
|
|
36
41
|
"webpack-cli": "^5.0.0",
|
|
37
42
|
"webpack-dev-server": "^4.3.1"
|
|
38
|
-
},
|
|
39
|
-
"babel": {
|
|
40
|
-
"presets": [
|
|
41
|
-
"@babel/preset-env",
|
|
42
|
-
"@babel/preset-react"
|
|
43
|
-
],
|
|
44
|
-
"plugins": [
|
|
45
|
-
"@babel/plugin-transform-runtime"
|
|
46
|
-
]
|
|
47
43
|
}
|
|
48
44
|
}
|
package/src/admin.jsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Admin,
|
|
5
|
+
AppBar,
|
|
6
|
+
Layout,
|
|
7
|
+
Login,
|
|
8
|
+
ToggleThemeButton,
|
|
9
|
+
defaultTheme,
|
|
10
|
+
} from "react-admin"
|
|
11
|
+
import {
|
|
12
|
+
IconButton,
|
|
13
|
+
SvgIcon,
|
|
14
|
+
Tooltip,
|
|
15
|
+
Typography,
|
|
16
|
+
createTheme,
|
|
17
|
+
} from "@mui/material"
|
|
18
|
+
import * as icons from "@mui/icons-material"
|
|
19
|
+
|
|
20
|
+
import authProvider from "./authprovider"
|
|
21
|
+
import dataProvider from "./dataprovider"
|
|
22
|
+
import { processAdmin, setupAdmin } from "./utils"
|
|
23
|
+
|
|
24
|
+
import "./dashboard"
|
|
25
|
+
import "./resources"
|
|
26
|
+
|
|
27
|
+
const darkTheme = createTheme({
|
|
28
|
+
palette: { mode: "dark" },
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Initialize the admin
|
|
32
|
+
setupAdmin("admin", (props) => {
|
|
33
|
+
const { apiUrl, auth, adminProps, appBarLinks, dashboard, resources } = props
|
|
34
|
+
|
|
35
|
+
let appBar = (props) => (
|
|
36
|
+
<AppBar {...props}>
|
|
37
|
+
<Typography
|
|
38
|
+
variant="h6"
|
|
39
|
+
color="inherit"
|
|
40
|
+
id="react-admin-title"
|
|
41
|
+
style={{
|
|
42
|
+
flex: 1,
|
|
43
|
+
textOverflow: "ellipsis",
|
|
44
|
+
whiteSpace: "nowrap",
|
|
45
|
+
overflow: "hidden",
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
<ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
|
|
49
|
+
{appBarLinks.map((info) => (
|
|
50
|
+
<Tooltip key={info.url} title={info.title}>
|
|
51
|
+
<IconButton color="inherit" href={info.url}>
|
|
52
|
+
<SvgIcon component={icons[info.icon]} />
|
|
53
|
+
</IconButton>
|
|
54
|
+
</Tooltip>
|
|
55
|
+
))}
|
|
56
|
+
</AppBar>
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Admin
|
|
61
|
+
authProvider={processAdmin("auth", auth)}
|
|
62
|
+
dataProvider={processAdmin("data", apiUrl)}
|
|
63
|
+
dashboard={processAdmin("dashboard", dashboard)}
|
|
64
|
+
loginPage={processAdmin("login", auth)}
|
|
65
|
+
layout={(props) => <Layout appBar={appBar} {...props} />}
|
|
66
|
+
{...adminProps}
|
|
67
|
+
>
|
|
68
|
+
{processAdmin("resources", resources)}
|
|
69
|
+
</Admin>
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Initialize authentication and data providers
|
|
74
|
+
setupAdmin("auth", authProvider)
|
|
75
|
+
setupAdmin("data", dataProvider)
|
|
76
|
+
|
|
77
|
+
// Initialize login page
|
|
78
|
+
setupAdmin("login", () => Login)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Cookies from "js-cookie"
|
|
2
|
+
|
|
3
|
+
import { makeRequest, processAdmin, requestHeaders, setupAdmin } from "./utils"
|
|
4
|
+
|
|
5
|
+
setupAdmin(
|
|
6
|
+
"auth-get",
|
|
7
|
+
({ storage }) =>
|
|
8
|
+
(name) =>
|
|
9
|
+
storage == "localstorage" ? localStorage.getItem(name) : Cookies.get(name)
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
setupAdmin("auth-set", ({ storage }) => (name, value) => {
|
|
13
|
+
if (storage == "localstorage") {
|
|
14
|
+
localStorage.setItem(name, value)
|
|
15
|
+
requestHeaders["Authorization"] = value
|
|
16
|
+
} else Cookies.set(name, value)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export default (props) => {
|
|
20
|
+
const { identityURL, authorizeURL, logoutURL, required, storage_name } = props
|
|
21
|
+
|
|
22
|
+
const authGet = processAdmin("auth-get", props)
|
|
23
|
+
const authSet = processAdmin("auth-set", props)
|
|
24
|
+
|
|
25
|
+
// Initialize request headers
|
|
26
|
+
authSet(storage_name, authGet(storage_name))
|
|
27
|
+
|
|
28
|
+
let getIdentity = async () => {
|
|
29
|
+
if (identityURL) {
|
|
30
|
+
let { json } = await makeRequest(identityURL)
|
|
31
|
+
return json
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (required)
|
|
36
|
+
return {
|
|
37
|
+
login: async (data) => {
|
|
38
|
+
if (!authorizeURL) throw { message: "Authorization is not supported" }
|
|
39
|
+
|
|
40
|
+
let { json } = await makeRequest(authorizeURL, { data, method: "POST" })
|
|
41
|
+
authSet(storage_name, json)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
checkError: async (error) => {
|
|
45
|
+
const { status } = error
|
|
46
|
+
|
|
47
|
+
if (status == 401 || status == 403) {
|
|
48
|
+
// Clean storage
|
|
49
|
+
authSet(storage_name, "")
|
|
50
|
+
|
|
51
|
+
throw {
|
|
52
|
+
message: "Invalid authorization",
|
|
53
|
+
redirectTo: logoutURL,
|
|
54
|
+
logoutUser: !logoutURL,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
checkAuth: async () => {
|
|
60
|
+
const auth = authGet(storage_name)
|
|
61
|
+
if (!auth) throw { message: "Authorization required" }
|
|
62
|
+
|
|
63
|
+
if (!identityURL) return auth
|
|
64
|
+
|
|
65
|
+
let user = await getIdentity()
|
|
66
|
+
if (!user) throw { message: "Authorization required" }
|
|
67
|
+
|
|
68
|
+
return user
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
logout: async () => {
|
|
72
|
+
// Clean storage
|
|
73
|
+
authSet(storage_name, "")
|
|
74
|
+
|
|
75
|
+
if (logoutURL) window.location = logoutURL
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
getIdentity,
|
|
79
|
+
|
|
80
|
+
getPermissions: () => {
|
|
81
|
+
const role = authGet(storage_name + "_role")
|
|
82
|
+
return role ? Promise.resolve(role) : Promise.reject()
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
useRefresh,
|
|
6
|
+
useUnselectAll,
|
|
7
|
+
useNotify,
|
|
8
|
+
useRecordContext,
|
|
9
|
+
} from "react-admin"
|
|
10
|
+
import * as icons from "@mui/icons-material"
|
|
11
|
+
|
|
12
|
+
import useAction from "../hooks/useAction"
|
|
13
|
+
|
|
14
|
+
export const BulkActionButton = ({
|
|
15
|
+
label,
|
|
16
|
+
icon,
|
|
17
|
+
title,
|
|
18
|
+
action,
|
|
19
|
+
resource,
|
|
20
|
+
selectedIds,
|
|
21
|
+
}) => {
|
|
22
|
+
const refresh = useRefresh(),
|
|
23
|
+
unselectAll = useUnselectAll(resource),
|
|
24
|
+
notify = useNotify(),
|
|
25
|
+
Icon = icons[icon]
|
|
26
|
+
|
|
27
|
+
const mutation = useAction(resource, action)
|
|
28
|
+
|
|
29
|
+
let onClick = () => {
|
|
30
|
+
mutation.mutate(
|
|
31
|
+
{
|
|
32
|
+
action,
|
|
33
|
+
ids: selectedIds,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
onSuccess: ({ data }) => {
|
|
37
|
+
if (data && data.message) notify(data.message, { type: "success" })
|
|
38
|
+
refresh()
|
|
39
|
+
unselectAll()
|
|
40
|
+
},
|
|
41
|
+
onError: (err) => {
|
|
42
|
+
notify(typeof err === "string" ? err : err.message, { type: "error" })
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Button
|
|
50
|
+
label={label}
|
|
51
|
+
title={title}
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
disabled={mutation.isLoading}
|
|
54
|
+
>
|
|
55
|
+
{Icon && <Icon />}
|
|
56
|
+
</Button>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const ActionButton = (props) => {
|
|
61
|
+
const { icon, label, title, resource, action } = props
|
|
62
|
+
const record = useRecordContext()
|
|
63
|
+
const mutation = useAction(resource, action)
|
|
64
|
+
|
|
65
|
+
const refresh = useRefresh(),
|
|
66
|
+
notify = useNotify(),
|
|
67
|
+
Icon = icons[icon]
|
|
68
|
+
|
|
69
|
+
let onClick = () => {
|
|
70
|
+
mutation.mutate(
|
|
71
|
+
{ record },
|
|
72
|
+
{
|
|
73
|
+
onSuccess: ({ data }) => {
|
|
74
|
+
refresh()
|
|
75
|
+
if (data && data.redirectTo) window.location = data.redirectTo
|
|
76
|
+
if (data && data.message) notify(data.message, { type: "success" })
|
|
77
|
+
},
|
|
78
|
+
onError: (error) => {
|
|
79
|
+
notify(typeof error === "string" ? error : error.message, {
|
|
80
|
+
type: "error",
|
|
81
|
+
})
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Button
|
|
89
|
+
label={label}
|
|
90
|
+
title={title}
|
|
91
|
+
onClick={onClick}
|
|
92
|
+
disabled={mutation.isLoading}
|
|
93
|
+
>
|
|
94
|
+
{Icon && <Icon />}
|
|
95
|
+
</Button>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default ActionButton
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Button } from "react-admin"
|
|
3
|
+
import { Link } from "react-router-dom"
|
|
4
|
+
import * as icons from "@mui/icons-material"
|
|
5
|
+
|
|
6
|
+
const LinkButton = ({ label, icon, title, resource, filters }) => {
|
|
7
|
+
const Icon = icons[icon] || icon
|
|
8
|
+
let linkParams = {
|
|
9
|
+
pathname: `/${resource}`,
|
|
10
|
+
search: filters ? `filter=${JSON.stringify(filters)}` : undefined,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Button label={label} title={title} component={Link} to={linkParams}>
|
|
15
|
+
{Icon && <Icon />}
|
|
16
|
+
</Button>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default LinkButton
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Grid,
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
Typography,
|
|
8
|
+
Table,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableRow,
|
|
11
|
+
TableCell,
|
|
12
|
+
} from "@mui/material"
|
|
13
|
+
|
|
14
|
+
import { setupAdmin } from "./utils"
|
|
15
|
+
|
|
16
|
+
const AdminTableView = ({ src }) => (
|
|
17
|
+
<Table>
|
|
18
|
+
<TableBody>
|
|
19
|
+
{src.map((row, idx) => (
|
|
20
|
+
<TableRow key={idx} hover>
|
|
21
|
+
{row.map((cell, idx) => (
|
|
22
|
+
<TableCell key={idx}>{cell}</TableCell>
|
|
23
|
+
))}
|
|
24
|
+
</TableRow>
|
|
25
|
+
))}
|
|
26
|
+
</TableBody>
|
|
27
|
+
</Table>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const AdminCard = ({ src }) => {
|
|
31
|
+
if (Array.isArray(src)) {
|
|
32
|
+
return (
|
|
33
|
+
<Grid container item spacing={2}>
|
|
34
|
+
{src.map((card, idx) => (
|
|
35
|
+
<AdminCard key={idx} src={card} />
|
|
36
|
+
))}
|
|
37
|
+
</Grid>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let { title, value } = src
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Grid item xs>
|
|
45
|
+
<Card>
|
|
46
|
+
<CardContent>
|
|
47
|
+
<Typography
|
|
48
|
+
variant="h5"
|
|
49
|
+
component="h2"
|
|
50
|
+
m={2}
|
|
51
|
+
style={{ textAlign: "center" }}
|
|
52
|
+
>
|
|
53
|
+
{title}
|
|
54
|
+
</Typography>
|
|
55
|
+
{(Array.isArray(value) && <AdminTableView src={value} />) || (
|
|
56
|
+
<pre>{JSON.stringify(value, null, 2)}</pre>
|
|
57
|
+
)}
|
|
58
|
+
</CardContent>
|
|
59
|
+
</Card>
|
|
60
|
+
</Grid>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Process dashboard
|
|
65
|
+
setupAdmin("dashboard", (props) => {
|
|
66
|
+
if (!props) return
|
|
67
|
+
|
|
68
|
+
const Dashboard = () => (
|
|
69
|
+
<Grid container spacing={1}>
|
|
70
|
+
<AdminCard src={props} />
|
|
71
|
+
</Grid>
|
|
72
|
+
)
|
|
73
|
+
return Dashboard
|
|
74
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { makeRequest } from "./utils"
|
|
2
|
+
|
|
3
|
+
export default (apiUrl) => {
|
|
4
|
+
const methods = {
|
|
5
|
+
getList: async (resource, { filter, pagination, sort }) => {
|
|
6
|
+
const query = {}
|
|
7
|
+
if (filter) query.where = JSON.stringify(filter)
|
|
8
|
+
if (pagination) {
|
|
9
|
+
const { page, perPage: limit } = pagination
|
|
10
|
+
query.limit = limit
|
|
11
|
+
query.offset = limit * (page - 1)
|
|
12
|
+
}
|
|
13
|
+
if (sort) {
|
|
14
|
+
const { field, order } = sort
|
|
15
|
+
query.sort = order == "ASC" ? field : `-${field}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { headers, json } = await makeRequest(`${apiUrl}/${resource}`, {
|
|
19
|
+
query,
|
|
20
|
+
})
|
|
21
|
+
return {
|
|
22
|
+
data: json,
|
|
23
|
+
total: parseInt(headers.get("x-total"), 10),
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
getOne: async (resource, { id }) => {
|
|
28
|
+
const { json } = await makeRequest(`${apiUrl}/${resource}/${id}`)
|
|
29
|
+
return { data: json }
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
create: async (resource, { data }) => {
|
|
33
|
+
const { json } = await makeRequest(`${apiUrl}/${resource}`, {
|
|
34
|
+
data,
|
|
35
|
+
method: "POST",
|
|
36
|
+
})
|
|
37
|
+
return { data: json }
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
update: async (resource, { id, data }) => {
|
|
41
|
+
const { json } = await makeRequest(`${apiUrl}/${resource}/${id}`, {
|
|
42
|
+
data,
|
|
43
|
+
method: "PUT",
|
|
44
|
+
})
|
|
45
|
+
return { data: json }
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
updateMany: async (resource, { ids, data }) => {
|
|
49
|
+
await Promise.all(ids.map((id) => methods.update(resource, { id, data })))
|
|
50
|
+
return { data: ids }
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
delete: async (resource, { id }) => {
|
|
54
|
+
await makeRequest(`${apiUrl}/${resource}/${id}`, {
|
|
55
|
+
method: "DELETE",
|
|
56
|
+
})
|
|
57
|
+
return { data: { id } }
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
deleteMany: async (resource, { ids }) => {
|
|
61
|
+
await makeRequest(`${apiUrl}/${resource}`, {
|
|
62
|
+
data: ids,
|
|
63
|
+
method: "DELETE",
|
|
64
|
+
})
|
|
65
|
+
return { data: ids }
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
getMany: (resource, { ids }) => {
|
|
69
|
+
return methods.getList(resource, { filter: { id: { $in: ids } } })
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
getManyReference: async (resource, { target, id, filter, ...opts }) => {
|
|
73
|
+
filter = filter || {}
|
|
74
|
+
filter[target] = id
|
|
75
|
+
return await methods.getList(resource, { filter, ...opts })
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
runAction: async (resource, action, props) => {
|
|
79
|
+
const { payload, ids, record } = props
|
|
80
|
+
action = action.replace(/^\/+/, "")
|
|
81
|
+
if (record) {
|
|
82
|
+
action = action.replace(/\{([^}]+)\}/, (_, field) => record[field])
|
|
83
|
+
}
|
|
84
|
+
const { json } = await makeRequest(`${apiUrl}/${action}`, {
|
|
85
|
+
query: { ids },
|
|
86
|
+
method: "POST",
|
|
87
|
+
data: payload,
|
|
88
|
+
})
|
|
89
|
+
return { data: json }
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return methods
|
|
94
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import get from "lodash/get"
|
|
3
|
+
import { useRecordContext } from "ra-core"
|
|
4
|
+
import Avatar from "@mui/material/Avatar"
|
|
5
|
+
|
|
6
|
+
const AvatarField = ({ source, alt, style, nameProp, ...props }) => {
|
|
7
|
+
const record = useRecordContext(props)
|
|
8
|
+
|
|
9
|
+
let value = get(record, source),
|
|
10
|
+
name = record[nameProp]
|
|
11
|
+
|
|
12
|
+
const letters = name
|
|
13
|
+
? name
|
|
14
|
+
.trim(" ")
|
|
15
|
+
.split(/\s+/)
|
|
16
|
+
.slice(0, 2)
|
|
17
|
+
.map((n) => n[0].toUpperCase())
|
|
18
|
+
.join("")
|
|
19
|
+
: ""
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Avatar src={value} alt={alt} style={style}>
|
|
23
|
+
{letters}
|
|
24
|
+
</Avatar>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
AvatarField.displayName = "AvatarField"
|
|
29
|
+
AvatarField.defaultProps = {
|
|
30
|
+
addLabel: true,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default AvatarField
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { ReferenceField, TextField } from "react-admin"
|
|
3
|
+
|
|
4
|
+
const FKField = ({ refSource, refID, link, reference, source, ...props }) => {
|
|
5
|
+
return (
|
|
6
|
+
<ReferenceField
|
|
7
|
+
link={link || "show"}
|
|
8
|
+
reference={reference}
|
|
9
|
+
source={source}
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<>
|
|
13
|
+
<TextField source={refSource} />
|
|
14
|
+
{" (#"}
|
|
15
|
+
<TextField source={refID || "id"} />
|
|
16
|
+
{")"}
|
|
17
|
+
</>
|
|
18
|
+
</ReferenceField>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
FKField.displayName = "FKField"
|
|
23
|
+
FKField.defaultProps = {
|
|
24
|
+
addLabel: true,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default FKField
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import get from "lodash/get"
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react"
|
|
4
|
+
import { Button, FunctionField, useRecordContext } from "react-admin"
|
|
5
|
+
|
|
6
|
+
export default function JsonField({ label, source }) {
|
|
7
|
+
const [expand, setExpand] = useState(false)
|
|
8
|
+
const record = useRecordContext()
|
|
9
|
+
const src = JSON.stringify(get(record, source), null, 2)
|
|
10
|
+
const retval = (
|
|
11
|
+
<div>
|
|
12
|
+
<p></p>
|
|
13
|
+
{label}
|
|
14
|
+
<Button
|
|
15
|
+
variant="contained"
|
|
16
|
+
color="primary"
|
|
17
|
+
size="small"
|
|
18
|
+
label={expand ? "colapse" : "expand"}
|
|
19
|
+
onClick={() => setExpand(!expand)}
|
|
20
|
+
/>
|
|
21
|
+
<p>{expand && <pre>{src}</pre>}</p>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return <FunctionField render={() => retval} />
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useDataProvider } from "react-admin"
|
|
2
|
+
import { useMutation } from "react-query"
|
|
3
|
+
|
|
4
|
+
export default function useAction(resource, action) {
|
|
5
|
+
const dataProvider = useDataProvider()
|
|
6
|
+
return useMutation((params) =>
|
|
7
|
+
dataProvider.runAction(resource, action, params)
|
|
8
|
+
)
|
|
9
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createRoot } from "react-dom/client"
|
|
2
|
+
|
|
3
|
+
import "./admin"
|
|
4
|
+
import { processAdmin } from "./utils"
|
|
5
|
+
|
|
6
|
+
export * from "./utils"
|
|
7
|
+
|
|
8
|
+
export async function initAdmin(prefix = "") {
|
|
9
|
+
const response = await fetch(`${prefix}/ra.json`)
|
|
10
|
+
const props = await response.json()
|
|
11
|
+
const container = document.getElementById("root")
|
|
12
|
+
const root = createRoot(container)
|
|
13
|
+
root.render(processAdmin("admin", props))
|
|
14
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { ReferenceInput, AutocompleteInput } from "react-admin"
|
|
3
|
+
|
|
4
|
+
const FKInput = ({
|
|
5
|
+
refProp,
|
|
6
|
+
refSource,
|
|
7
|
+
fullWidth,
|
|
8
|
+
reference,
|
|
9
|
+
source,
|
|
10
|
+
...props
|
|
11
|
+
}) => {
|
|
12
|
+
let filterToQuery = (search) => {
|
|
13
|
+
let filters = {}
|
|
14
|
+
filters[refProp] = search
|
|
15
|
+
return filters
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
refSource = refSource || "id"
|
|
19
|
+
|
|
20
|
+
let renderText = (record) => {
|
|
21
|
+
let pk = record[refSource]
|
|
22
|
+
return (pk && `${record[refProp]} (#${pk})`) || ""
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<ReferenceInput
|
|
27
|
+
reference={reference}
|
|
28
|
+
source={source}
|
|
29
|
+
filterToQuery={filterToQuery}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<AutocompleteInput
|
|
33
|
+
source={refSource}
|
|
34
|
+
optionText={renderText}
|
|
35
|
+
fullWidth={fullWidth}
|
|
36
|
+
/>
|
|
37
|
+
</ReferenceInput>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default FKInput
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { TextInput } from "react-admin"
|
|
3
|
+
import isJSON from "validator/lib/isJSON"
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ERRORTEXT = "Invalid JSON"
|
|
6
|
+
|
|
7
|
+
const parseFunction = (json) => {
|
|
8
|
+
let retval = json
|
|
9
|
+
try {
|
|
10
|
+
retval = JSON.parse(json)
|
|
11
|
+
} finally {
|
|
12
|
+
return retval
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* `JsonInput` validates if the entered value is JSON or not. If entered value is not a invalid JSON, `JsonInput` will throw an error.
|
|
19
|
+
* Default error message is: `Invalid JSON` and can be overridden using `errortext` prop.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* <JsonInput source='config' label='JSON Config' errortext='Enter a valid JSON'/>
|
|
23
|
+
*
|
|
24
|
+
* or use translate function:
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <JsonInput source='config' label={translate('resources.resource_name.fields.config')} errortext={translate('myroot.validate.json')}/>
|
|
28
|
+
*
|
|
29
|
+
* By default, `JsonInput` parses and returns the entered string as object. Instead, to send string directly, please pass `parse` prop as `false`
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* <JsonInput source='config' label='JSON Config' parse={false}/>
|
|
33
|
+
*/
|
|
34
|
+
export default function JsonInput(props) {
|
|
35
|
+
const {
|
|
36
|
+
validate = [],
|
|
37
|
+
errortext = DEFAULT_ERRORTEXT,
|
|
38
|
+
multiline = true,
|
|
39
|
+
parse = true,
|
|
40
|
+
...rest
|
|
41
|
+
} = props
|
|
42
|
+
// const errorobj = { message: errortext }
|
|
43
|
+
|
|
44
|
+
const validateJSON = (value) => {
|
|
45
|
+
if (!value || typeof value === "object") return undefined
|
|
46
|
+
return isJSON(value) ? undefined : errortext
|
|
47
|
+
}
|
|
48
|
+
validate.push(validateJSON)
|
|
49
|
+
rest.validate = validate
|
|
50
|
+
|
|
51
|
+
const formatJSON = (json) => {
|
|
52
|
+
let retval = json
|
|
53
|
+
if (retval && typeof retval === "object")
|
|
54
|
+
retval = JSON.stringify(retval, null, 2)
|
|
55
|
+
return retval
|
|
56
|
+
}
|
|
57
|
+
rest.format = formatJSON
|
|
58
|
+
//
|
|
59
|
+
if (parse) rest.parse = parseFunction
|
|
60
|
+
if (multiline) rest.multiline = true
|
|
61
|
+
return <TextInput {...rest} />
|
|
62
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import { Resource } from "react-admin"
|
|
4
|
+
import * as icons from "@mui/icons-material"
|
|
5
|
+
|
|
6
|
+
import { checkParams, processAdmin, setupAdmin } from "./utils"
|
|
7
|
+
import "./views/list"
|
|
8
|
+
import "./views/show"
|
|
9
|
+
import "./views/edit"
|
|
10
|
+
import "./views/create"
|
|
11
|
+
|
|
12
|
+
// Initialize Resources Components
|
|
13
|
+
setupAdmin("resources", (resources) =>
|
|
14
|
+
resources.map((res) => processAdmin("resource", res, res.name))
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// Initialize a resource's component
|
|
18
|
+
setupAdmin(
|
|
19
|
+
"resource",
|
|
20
|
+
checkParams((props, res) => {
|
|
21
|
+
let { create, edit, icon, list, name, show, ...resProps } = props
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Resource
|
|
25
|
+
key={name}
|
|
26
|
+
name={name}
|
|
27
|
+
icon={icons[icon]}
|
|
28
|
+
create={processAdmin("create", create, res)}
|
|
29
|
+
edit={processAdmin("edit", edit, res)}
|
|
30
|
+
list={processAdmin("list", list, res)}
|
|
31
|
+
show={processAdmin("show", show, res)}
|
|
32
|
+
{...resProps}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
)
|
package/src/ui.jsx
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import { required } from "react-admin"
|
|
4
|
+
import {
|
|
5
|
+
BooleanField,
|
|
6
|
+
ChipField,
|
|
7
|
+
DateField,
|
|
8
|
+
EmailField,
|
|
9
|
+
ImageField,
|
|
10
|
+
FileField,
|
|
11
|
+
NumberField,
|
|
12
|
+
RichTextField,
|
|
13
|
+
TextField,
|
|
14
|
+
UrlField,
|
|
15
|
+
SelectField,
|
|
16
|
+
ArrayField,
|
|
17
|
+
ReferenceField,
|
|
18
|
+
ReferenceManyField,
|
|
19
|
+
ReferenceArrayField,
|
|
20
|
+
} from "react-admin"
|
|
21
|
+
import {
|
|
22
|
+
BooleanInput,
|
|
23
|
+
NullableBooleanInput,
|
|
24
|
+
DateInput,
|
|
25
|
+
DateTimeInput,
|
|
26
|
+
ImageInput,
|
|
27
|
+
FileInput,
|
|
28
|
+
NumberInput,
|
|
29
|
+
PasswordInput,
|
|
30
|
+
TextInput,
|
|
31
|
+
AutocompleteInput,
|
|
32
|
+
RadioButtonGroupInput,
|
|
33
|
+
SelectInput,
|
|
34
|
+
ArrayInput,
|
|
35
|
+
AutocompleteArrayInput,
|
|
36
|
+
CheckboxGroupInput,
|
|
37
|
+
ReferenceArrayInput,
|
|
38
|
+
ReferenceInput,
|
|
39
|
+
} from "react-admin"
|
|
40
|
+
|
|
41
|
+
import AvatarField from "./fields/AvatarField"
|
|
42
|
+
import FKField from "./fields/FKField"
|
|
43
|
+
import FKInput from "./inputs/FKInput"
|
|
44
|
+
import JsonField from "./fields/JsonField"
|
|
45
|
+
import JsonInput from "./inputs/JsonInput"
|
|
46
|
+
|
|
47
|
+
const ui = {
|
|
48
|
+
// Fields
|
|
49
|
+
BooleanField,
|
|
50
|
+
ChipField,
|
|
51
|
+
DateField,
|
|
52
|
+
EmailField,
|
|
53
|
+
ImageField,
|
|
54
|
+
FileField,
|
|
55
|
+
NumberField,
|
|
56
|
+
RichTextField,
|
|
57
|
+
TextField,
|
|
58
|
+
UrlField,
|
|
59
|
+
SelectField,
|
|
60
|
+
ArrayField,
|
|
61
|
+
ReferenceField,
|
|
62
|
+
ReferenceManyField,
|
|
63
|
+
ReferenceArrayField,
|
|
64
|
+
AvatarField,
|
|
65
|
+
JsonField,
|
|
66
|
+
FKField,
|
|
67
|
+
|
|
68
|
+
// Inputs
|
|
69
|
+
BooleanInput,
|
|
70
|
+
NullableBooleanInput,
|
|
71
|
+
DateInput,
|
|
72
|
+
DateTimeInput,
|
|
73
|
+
ImageInput,
|
|
74
|
+
FileInput,
|
|
75
|
+
NumberInput,
|
|
76
|
+
PasswordInput,
|
|
77
|
+
TextInput,
|
|
78
|
+
AutocompleteInput,
|
|
79
|
+
RadioButtonGroupInput,
|
|
80
|
+
SelectInput,
|
|
81
|
+
ArrayInput,
|
|
82
|
+
AutocompleteArrayInput,
|
|
83
|
+
CheckboxGroupInput,
|
|
84
|
+
ReferenceArrayInput,
|
|
85
|
+
ReferenceInput,
|
|
86
|
+
JsonInput,
|
|
87
|
+
FKInput,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const initRAItems = (itemsProps) =>
|
|
91
|
+
itemsProps.map((item) => {
|
|
92
|
+
const Item = ui[item[0]],
|
|
93
|
+
props = { ...item[1] }
|
|
94
|
+
|
|
95
|
+
if (props.required) {
|
|
96
|
+
props.validate = required()
|
|
97
|
+
delete props.required
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
props.fullWidth = props.fullWidth ?? true
|
|
101
|
+
|
|
102
|
+
if (props.children) props.children = initRAItems(props.children)[0]
|
|
103
|
+
|
|
104
|
+
return <Item key={props.source} {...props} />
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
export default initRAItems
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
3
|
+
import { isValidElement } from "react"
|
|
4
|
+
import { fetchUtils } from "ra-core"
|
|
5
|
+
import { stringify } from "query-string"
|
|
6
|
+
|
|
7
|
+
const parts = {}
|
|
8
|
+
|
|
9
|
+
export const setupAdmin = (part, gen) => (parts[part] = gen)
|
|
10
|
+
|
|
11
|
+
export const checkParams = (fn) => (props, res) => {
|
|
12
|
+
if (!props || isValidElement(props)) return props
|
|
13
|
+
return fn(props, res)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const processAdmin = (type, props, res) => {
|
|
17
|
+
if (process.env.NODE_ENV == "development")
|
|
18
|
+
console.log(`${type}${res ? "-" + res : ""}`, props)
|
|
19
|
+
|
|
20
|
+
if (parts[`${type}-${res}`]) return parts[`${type}-${res}`](props, res)
|
|
21
|
+
if (parts[type]) return parts[type](props, res)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const requestHeaders = {}
|
|
25
|
+
|
|
26
|
+
export const makeRequest = (url, params) => {
|
|
27
|
+
let { data, query, ...opts } = params || {}
|
|
28
|
+
|
|
29
|
+
if (data) opts.body = JSON.stringify(data)
|
|
30
|
+
if (query) url = `${url}?${stringify(query)}`
|
|
31
|
+
|
|
32
|
+
opts = {
|
|
33
|
+
...opts,
|
|
34
|
+
headers: new Headers({ ...requestHeaders, ...opts.headers }),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return fetchUtils.fetchJson(url, opts)
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import { Create, SimpleForm, TopToolbar, ListButton } from "react-admin"
|
|
4
|
+
|
|
5
|
+
import initRAItems from "../ui"
|
|
6
|
+
import { checkParams, processAdmin, setupAdmin } from "../utils"
|
|
7
|
+
|
|
8
|
+
// Initialize a create component
|
|
9
|
+
setupAdmin(
|
|
10
|
+
"create",
|
|
11
|
+
checkParams(
|
|
12
|
+
(inputs, res) =>
|
|
13
|
+
function MACreate(props) {
|
|
14
|
+
const Actions = (
|
|
15
|
+
<TopToolbar>
|
|
16
|
+
<ListButton />
|
|
17
|
+
</TopToolbar>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Create actions={Actions} {...props}>
|
|
22
|
+
<SimpleForm>
|
|
23
|
+
{processAdmin("create-inputs", inputs, res)}
|
|
24
|
+
</SimpleForm>
|
|
25
|
+
</Create>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
setupAdmin("create-inputs", initRAItems)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Edit,
|
|
5
|
+
ListButton,
|
|
6
|
+
ShowButton,
|
|
7
|
+
SimpleForm,
|
|
8
|
+
TopToolbar,
|
|
9
|
+
} from "react-admin"
|
|
10
|
+
|
|
11
|
+
import initRAItems from "../ui"
|
|
12
|
+
import { checkParams, processAdmin, setupAdmin } from "../utils"
|
|
13
|
+
import { ActionButton } from "../buttons/ActionButton"
|
|
14
|
+
|
|
15
|
+
// Initialize an edit component
|
|
16
|
+
setupAdmin(
|
|
17
|
+
"edit",
|
|
18
|
+
checkParams(
|
|
19
|
+
({ actions, inputs }, res) =>
|
|
20
|
+
function MAEdit(props) {
|
|
21
|
+
const Actions = ({ basePath, resource, ...baseProps }) => (
|
|
22
|
+
<TopToolbar>
|
|
23
|
+
{actions.map((props, idx) => {
|
|
24
|
+
let aProps = { resource, basePath, ...baseProps, ...props }
|
|
25
|
+
return <ActionButton key={idx} {...aProps} />
|
|
26
|
+
})}
|
|
27
|
+
<ListButton />
|
|
28
|
+
<ShowButton />
|
|
29
|
+
</TopToolbar>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Edit actions={<Actions />} {...props}>
|
|
34
|
+
<SimpleForm>{processAdmin("edit-inputs", inputs, res)}</SimpleForm>
|
|
35
|
+
</Edit>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
setupAdmin("edit-inputs", initRAItems)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BulkDeleteButton,
|
|
5
|
+
Datagrid,
|
|
6
|
+
EditButton,
|
|
7
|
+
Filter,
|
|
8
|
+
List,
|
|
9
|
+
Pagination,
|
|
10
|
+
} from "react-admin"
|
|
11
|
+
import uniq from "lodash/uniq"
|
|
12
|
+
import sortBy from "lodash/sortBy"
|
|
13
|
+
|
|
14
|
+
import initRAItems from "../ui"
|
|
15
|
+
import { checkParams, processAdmin, setupAdmin } from "../utils"
|
|
16
|
+
import { BulkActionButton } from "../buttons/ActionButton"
|
|
17
|
+
|
|
18
|
+
const defaultPagination = <Pagination rowsPerPageOptions={[10, 25, 50, 100]} />
|
|
19
|
+
|
|
20
|
+
// Initiliaze a list component
|
|
21
|
+
setupAdmin(
|
|
22
|
+
"list",
|
|
23
|
+
checkParams((props, res) => {
|
|
24
|
+
let {
|
|
25
|
+
children,
|
|
26
|
+
filters,
|
|
27
|
+
edit,
|
|
28
|
+
pagination,
|
|
29
|
+
show,
|
|
30
|
+
limit,
|
|
31
|
+
limitMax,
|
|
32
|
+
actions,
|
|
33
|
+
...listProps
|
|
34
|
+
} = props
|
|
35
|
+
|
|
36
|
+
children = processAdmin("list-fields", children, res)
|
|
37
|
+
if (edit) children.push(<EditButton key="edit-button" />)
|
|
38
|
+
|
|
39
|
+
pagination = pagination || (
|
|
40
|
+
<Pagination
|
|
41
|
+
rowsPerPageOptions={sortBy(uniq([10, 25, 50, 100, limit, limitMax]))}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return function MAList(props) {
|
|
46
|
+
let Filters = (props) => (
|
|
47
|
+
<Filter {...props}>{processAdmin("list-filters", filters, res)}</Filter>
|
|
48
|
+
)
|
|
49
|
+
let Actions = processAdmin("list-bulkActions", actions)
|
|
50
|
+
|
|
51
|
+
props = { ...props, ...listProps }
|
|
52
|
+
return (
|
|
53
|
+
<List
|
|
54
|
+
filters={<Filters />}
|
|
55
|
+
bulkActionButtons={<Actions />}
|
|
56
|
+
perPage={limit}
|
|
57
|
+
pagination={pagination || defaultPagination}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
<Datagrid rowClick={show ? "show" : null}>{children}</Datagrid>
|
|
61
|
+
</List>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
setupAdmin(
|
|
68
|
+
"list-bulkActions",
|
|
69
|
+
(actions) =>
|
|
70
|
+
function MABulkActions(props) {
|
|
71
|
+
let buttons = actions.map((action, idx) => {
|
|
72
|
+
let aProps = { ...action, ...props }
|
|
73
|
+
return <BulkActionButton key={idx} {...aProps} />
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
{buttons}
|
|
79
|
+
<BulkDeleteButton {...props} />
|
|
80
|
+
</>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
setupAdmin("list-filters", initRAItems)
|
|
86
|
+
setupAdmin("list-fields", initRAItems)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ListButton,
|
|
5
|
+
EditButton,
|
|
6
|
+
Show,
|
|
7
|
+
SimpleShowLayout,
|
|
8
|
+
TopToolbar,
|
|
9
|
+
} from "react-admin"
|
|
10
|
+
|
|
11
|
+
import initRAItems from "../ui"
|
|
12
|
+
import { checkParams, processAdmin, setupAdmin } from "../utils"
|
|
13
|
+
import { ActionButton } from "../buttons/ActionButton"
|
|
14
|
+
|
|
15
|
+
// Initiliaze a show component
|
|
16
|
+
setupAdmin(
|
|
17
|
+
"show",
|
|
18
|
+
checkParams(
|
|
19
|
+
({ actions, fields }, resource) =>
|
|
20
|
+
function MAShow(props) {
|
|
21
|
+
const Actions = () => (
|
|
22
|
+
<TopToolbar>
|
|
23
|
+
{actions.map((props, idx) => {
|
|
24
|
+
return <ActionButton key={idx} resource={resource} {...props} />
|
|
25
|
+
})}
|
|
26
|
+
<ListButton />
|
|
27
|
+
<EditButton />
|
|
28
|
+
</TopToolbar>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Show actions={<Actions />} {...props}>
|
|
33
|
+
<SimpleShowLayout>
|
|
34
|
+
{processAdmin("show-fields", fields, resource)}
|
|
35
|
+
</SimpleShowLayout>
|
|
36
|
+
</Show>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
setupAdmin("show-fields", initRAItems)
|
package/src/web.js
ADDED
package/.eslintrc.json
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"root": true,
|
|
3
|
-
"extends": [
|
|
4
|
-
"eslint:recommended",
|
|
5
|
-
"plugin:react/recommended",
|
|
6
|
-
"prettier"
|
|
7
|
-
],
|
|
8
|
-
"parser": "@babel/eslint-parser",
|
|
9
|
-
"settings": {
|
|
10
|
-
"react": {
|
|
11
|
-
"version": "detect"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"parserOptions": {
|
|
15
|
-
"ecmaFeatures": {
|
|
16
|
-
"jsx": true,
|
|
17
|
-
"experimentalObjectRestSpread": true
|
|
18
|
-
},
|
|
19
|
-
"ecmaVersion": 2020,
|
|
20
|
-
"sourceType": "module",
|
|
21
|
-
"babelOptions": {
|
|
22
|
-
"presets": [
|
|
23
|
-
"@babel/preset-react"
|
|
24
|
-
],
|
|
25
|
-
"rootMode": "upward"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"plugins": [
|
|
29
|
-
"prettier",
|
|
30
|
-
"react"
|
|
31
|
-
],
|
|
32
|
-
"env": {
|
|
33
|
-
"browser": true,
|
|
34
|
-
"es6": true,
|
|
35
|
-
"jest": true,
|
|
36
|
-
"node": true
|
|
37
|
-
},
|
|
38
|
-
"rules": {
|
|
39
|
-
"no-console": "warn",
|
|
40
|
-
"no-debugger": "warn",
|
|
41
|
-
"prettier/prettier": "warn",
|
|
42
|
-
"react/prop-types": "off"
|
|
43
|
-
}
|
|
44
|
-
}
|
package/Makefile
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
all: node_modules
|
|
2
|
-
$(CURDIR)/node_modules/.bin/webpack
|
|
3
|
-
|
|
4
|
-
node_modules: package.json
|
|
5
|
-
npm install
|
|
6
|
-
touch node_modules
|
|
7
|
-
|
|
8
|
-
npm: node_modules
|
|
9
|
-
$(CURDIR)/node_modules/.bin/babel src --out-dir dist
|
|
10
|
-
|
|
11
|
-
pypi:
|
|
12
|
-
$(CURDIR)/node_modules/.bin/webpack
|
|
13
|
-
|
|
14
|
-
watch: node_modules
|
|
15
|
-
$(CURDIR)/node_modules/.bin/webpack --watch
|
|
16
|
-
|
|
17
|
-
dev: node_modules
|
|
18
|
-
NODE_ENV=development $(CURDIR)/node_modules/.bin/webpack serve
|
package/jsconfig.json
DELETED
package/webpack.config.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
const path = require("path"),
|
|
2
|
-
webpack = require("webpack"),
|
|
3
|
-
mode = process.env.NODE_ENV
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
entry: "./src/web",
|
|
7
|
-
|
|
8
|
-
output: {
|
|
9
|
-
filename: "main.js",
|
|
10
|
-
path: path.resolve(__dirname, "../muffin_admin"),
|
|
11
|
-
publicPath: "/admin",
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
module: {
|
|
15
|
-
rules: [
|
|
16
|
-
{
|
|
17
|
-
test: /\.jsx?$/,
|
|
18
|
-
exclude: /node_modules/,
|
|
19
|
-
use: {
|
|
20
|
-
loader: "babel-loader",
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
],
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
plugins: [new webpack.EnvironmentPlugin({ NODE_ENV: "production" })],
|
|
27
|
-
|
|
28
|
-
mode: mode || "production",
|
|
29
|
-
devtool: mode == "development" && "inline-source-map",
|
|
30
|
-
|
|
31
|
-
resolve: {
|
|
32
|
-
extensions: [".jsx", ".js"],
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
devServer: {
|
|
36
|
-
hot: true,
|
|
37
|
-
open: true,
|
|
38
|
-
proxy: [
|
|
39
|
-
{
|
|
40
|
-
context: ["!*.js"],
|
|
41
|
-
target: "http://localhost:5555",
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|