mcsmapi 0.1.8b4__tar.gz → 0.2.0__tar.gz
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.
- mcsmapi-0.2.0/PKG-INFO +116 -0
- mcsmapi-0.2.0/README.md +100 -0
- mcsmapi-0.2.0/mcsmapi/__init__.py +118 -0
- mcsmapi-0.2.0/mcsmapi/apis/daemon.py +70 -0
- mcsmapi-0.2.0/mcsmapi/apis/file.py +370 -0
- mcsmapi-0.2.0/mcsmapi/apis/image.py +121 -0
- mcsmapi-0.2.0/mcsmapi/apis/instance.py +421 -0
- mcsmapi-0.2.0/mcsmapi/apis/java.py +88 -0
- mcsmapi-0.2.0/mcsmapi/apis/mod.py +265 -0
- mcsmapi-0.2.0/mcsmapi/apis/overview.py +108 -0
- mcsmapi-0.2.0/mcsmapi/apis/schedule.py +55 -0
- mcsmapi-0.2.0/mcsmapi/apis/upgrade.py +58 -0
- mcsmapi-0.2.0/mcsmapi/apis/user.py +149 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi/models/common.py +1 -1
- mcsmapi-0.2.0/mcsmapi/models/daemon.py +130 -0
- mcsmapi-0.2.0/mcsmapi/models/file.py +203 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi/models/image.py +36 -5
- mcsmapi-0.2.0/mcsmapi/models/instance.py +348 -0
- mcsmapi-0.2.0/mcsmapi/models/java.py +29 -0
- mcsmapi-0.2.0/mcsmapi/models/mod.py +110 -0
- mcsmapi-0.2.0/mcsmapi/models/overview.py +304 -0
- mcsmapi-0.2.0/mcsmapi/models/schedule.py +82 -0
- mcsmapi-0.2.0/mcsmapi/models/upgrade.py +31 -0
- mcsmapi-0.2.0/mcsmapi/models/user.py +198 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi/pool.py +6 -6
- mcsmapi-0.2.0/mcsmapi/request.py +80 -0
- mcsmapi-0.2.0/mcsmapi.egg-info/PKG-INFO +116 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi.egg-info/SOURCES.txt +8 -1
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi.egg-info/requires.txt +1 -1
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/pyproject.toml +5 -5
- mcsmapi-0.1.8b4/PKG-INFO +0 -94
- mcsmapi-0.1.8b4/README.md +0 -78
- mcsmapi-0.1.8b4/mcsmapi/__init__.py +0 -53
- mcsmapi-0.1.8b4/mcsmapi/apis/daemon.py +0 -109
- mcsmapi-0.1.8b4/mcsmapi/apis/file.py +0 -307
- mcsmapi-0.1.8b4/mcsmapi/apis/image.py +0 -101
- mcsmapi-0.1.8b4/mcsmapi/apis/instance.py +0 -281
- mcsmapi-0.1.8b4/mcsmapi/apis/overview.py +0 -21
- mcsmapi-0.1.8b4/mcsmapi/apis/user.py +0 -82
- mcsmapi-0.1.8b4/mcsmapi/models/__init__.py +0 -0
- mcsmapi-0.1.8b4/mcsmapi/models/daemon.py +0 -179
- mcsmapi-0.1.8b4/mcsmapi/models/file.py +0 -237
- mcsmapi-0.1.8b4/mcsmapi/models/instance.py +0 -310
- mcsmapi-0.1.8b4/mcsmapi/models/overview.py +0 -203
- mcsmapi-0.1.8b4/mcsmapi/models/user.py +0 -102
- mcsmapi-0.1.8b4/mcsmapi/request.py +0 -100
- mcsmapi-0.1.8b4/mcsmapi.egg-info/PKG-INFO +0 -94
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/LICENSE +0 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi/apis/__init__.py +0 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi/exceptions.py +0 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi.egg-info/dependency_links.txt +0 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/mcsmapi.egg-info/top_level.txt +0 -0
- {mcsmapi-0.1.8b4 → mcsmapi-0.2.0}/setup.cfg +0 -0
mcsmapi-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcsmapi
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Shortcut the pypi package of MCSM./快捷操作MCSM的pypi包
|
|
5
|
+
Author-email: sokoko-org <contact@sokoko.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/sokoko-org/mcsmapi
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: httpx>=0.27.0
|
|
14
|
+
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# MCSM API
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+

|
|
21
|
+

|
|
22
|
+
[](https://deepwiki.com/molanp/mcsmapi)
|
|
23
|
+
|
|
24
|
+
English|[Chinese Simplified](README_zh-cn.md)
|
|
25
|
+
|
|
26
|
+
## Documentation
|
|
27
|
+
|
|
28
|
+
📄 Docs: [docs](https://mcsmapi.awkchan.top/en/)
|
|
29
|
+
|
|
30
|
+
💡 Example: [example](example)
|
|
31
|
+
|
|
32
|
+
## Introduction
|
|
33
|
+
|
|
34
|
+
mcsmapi is an async Python SDK for [MCSManager](https://github.com/MCSManager/MCSManager), built on httpx + pydantic.
|
|
35
|
+
|
|
36
|
+
- Fully async (sync/await)
|
|
37
|
+
- Typed models with IDE autocompletion
|
|
38
|
+
- Chainable operations on returned objects
|
|
39
|
+
- Supports sync with context manager
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
`ash
|
|
44
|
+
pip install mcsmapi
|
|
45
|
+
`
|
|
46
|
+
|
|
47
|
+
For the latest build: [Actions](https://github.com/molanp/mcsmapi/actions/workflows/auto-build.yml)
|
|
48
|
+
|
|
49
|
+
## Supported Features
|
|
50
|
+
|
|
51
|
+
- [x] Dashboard data (Overview)
|
|
52
|
+
- [x] User management (User)
|
|
53
|
+
- [x] Instance management (Instance)
|
|
54
|
+
- [x] Daemon management (Daemon)
|
|
55
|
+
- [x] File management (File)
|
|
56
|
+
- [x] Image management (Image)
|
|
57
|
+
- [x] Schedule task management (Schedule)
|
|
58
|
+
- [x] Java runtime management (Java)
|
|
59
|
+
- [x] Mod management (Mod)
|
|
60
|
+
- [x] Panel & Daemon upgrade (Upgrade)
|
|
61
|
+
|
|
62
|
+
## Supported MCSM Versions
|
|
63
|
+
|
|
64
|
+
| MCSM Version | Support Status |
|
|
65
|
+
| :----------: | :------------: |
|
|
66
|
+
| 10.x | ✅ |
|
|
67
|
+
|
|
68
|
+
## Quick Example
|
|
69
|
+
|
|
70
|
+
`python
|
|
71
|
+
import asyncio
|
|
72
|
+
from mcsmapi import MCSMAPI
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def main():
|
|
76
|
+
mcsm = MCSMAPI("http://localhost:23333")
|
|
77
|
+
await mcsm.login("admin", "password")
|
|
78
|
+
|
|
79
|
+
# Dashboard
|
|
80
|
+
data = await mcsm.overview.overview()
|
|
81
|
+
print(f"Panel version: {data.version}")
|
|
82
|
+
|
|
83
|
+
# List daemons
|
|
84
|
+
daemons = await mcsm.daemon.config()
|
|
85
|
+
for d in daemons:
|
|
86
|
+
print(f" {d.remarks} ({d.ip}:{d.port})")
|
|
87
|
+
|
|
88
|
+
# Search instances on a daemon
|
|
89
|
+
instances = await daemons[0].search_instances()
|
|
90
|
+
for inst in instances.data:
|
|
91
|
+
print(f" {inst.config.nickname} [{inst.status.name}]")
|
|
92
|
+
|
|
93
|
+
# Partial config update (only sends provided fields)
|
|
94
|
+
inst = instances.data[0]
|
|
95
|
+
await inst.updateConfig({"nickname": "My Server"})
|
|
96
|
+
|
|
97
|
+
# File operations via chaining
|
|
98
|
+
files = await inst.files(target=".")
|
|
99
|
+
for f in files.items[:5]:
|
|
100
|
+
print(f" {f.name}")
|
|
101
|
+
|
|
102
|
+
await mcsm.close()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
`
|
|
107
|
+
|
|
108
|
+
See [example/](example/) for more usage patterns.
|
|
109
|
+
|
|
110
|
+
## Contributing
|
|
111
|
+
|
|
112
|
+
Feel free to submit an [Issue](https://github.com/molanp/mcsmapi/issues) or [Pull Request](https://github.com/molanp/mcsmapi/pulls).
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
[MIT License](https://opensource.org/licenses/MIT) — see [LICENSE](LICENSE).
|
mcsmapi-0.2.0/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# MCSM API
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](https://deepwiki.com/molanp/mcsmapi)
|
|
7
|
+
|
|
8
|
+
English|[Chinese Simplified](README_zh-cn.md)
|
|
9
|
+
|
|
10
|
+
## Documentation
|
|
11
|
+
|
|
12
|
+
📄 Docs: [docs](https://mcsmapi.awkchan.top/en/)
|
|
13
|
+
|
|
14
|
+
💡 Example: [example](example)
|
|
15
|
+
|
|
16
|
+
## Introduction
|
|
17
|
+
|
|
18
|
+
mcsmapi is an async Python SDK for [MCSManager](https://github.com/MCSManager/MCSManager), built on httpx + pydantic.
|
|
19
|
+
|
|
20
|
+
- Fully async (sync/await)
|
|
21
|
+
- Typed models with IDE autocompletion
|
|
22
|
+
- Chainable operations on returned objects
|
|
23
|
+
- Supports sync with context manager
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
`ash
|
|
28
|
+
pip install mcsmapi
|
|
29
|
+
`
|
|
30
|
+
|
|
31
|
+
For the latest build: [Actions](https://github.com/molanp/mcsmapi/actions/workflows/auto-build.yml)
|
|
32
|
+
|
|
33
|
+
## Supported Features
|
|
34
|
+
|
|
35
|
+
- [x] Dashboard data (Overview)
|
|
36
|
+
- [x] User management (User)
|
|
37
|
+
- [x] Instance management (Instance)
|
|
38
|
+
- [x] Daemon management (Daemon)
|
|
39
|
+
- [x] File management (File)
|
|
40
|
+
- [x] Image management (Image)
|
|
41
|
+
- [x] Schedule task management (Schedule)
|
|
42
|
+
- [x] Java runtime management (Java)
|
|
43
|
+
- [x] Mod management (Mod)
|
|
44
|
+
- [x] Panel & Daemon upgrade (Upgrade)
|
|
45
|
+
|
|
46
|
+
## Supported MCSM Versions
|
|
47
|
+
|
|
48
|
+
| MCSM Version | Support Status |
|
|
49
|
+
| :----------: | :------------: |
|
|
50
|
+
| 10.x | ✅ |
|
|
51
|
+
|
|
52
|
+
## Quick Example
|
|
53
|
+
|
|
54
|
+
`python
|
|
55
|
+
import asyncio
|
|
56
|
+
from mcsmapi import MCSMAPI
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def main():
|
|
60
|
+
mcsm = MCSMAPI("http://localhost:23333")
|
|
61
|
+
await mcsm.login("admin", "password")
|
|
62
|
+
|
|
63
|
+
# Dashboard
|
|
64
|
+
data = await mcsm.overview.overview()
|
|
65
|
+
print(f"Panel version: {data.version}")
|
|
66
|
+
|
|
67
|
+
# List daemons
|
|
68
|
+
daemons = await mcsm.daemon.config()
|
|
69
|
+
for d in daemons:
|
|
70
|
+
print(f" {d.remarks} ({d.ip}:{d.port})")
|
|
71
|
+
|
|
72
|
+
# Search instances on a daemon
|
|
73
|
+
instances = await daemons[0].search_instances()
|
|
74
|
+
for inst in instances.data:
|
|
75
|
+
print(f" {inst.config.nickname} [{inst.status.name}]")
|
|
76
|
+
|
|
77
|
+
# Partial config update (only sends provided fields)
|
|
78
|
+
inst = instances.data[0]
|
|
79
|
+
await inst.updateConfig({"nickname": "My Server"})
|
|
80
|
+
|
|
81
|
+
# File operations via chaining
|
|
82
|
+
files = await inst.files(target=".")
|
|
83
|
+
for f in files.items[:5]:
|
|
84
|
+
print(f" {f.name}")
|
|
85
|
+
|
|
86
|
+
await mcsm.close()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
asyncio.run(main())
|
|
90
|
+
`
|
|
91
|
+
|
|
92
|
+
See [example/](example/) for more usage patterns.
|
|
93
|
+
|
|
94
|
+
## Contributing
|
|
95
|
+
|
|
96
|
+
Feel free to submit an [Issue](https://github.com/molanp/mcsmapi/issues) or [Pull Request](https://github.com/molanp/mcsmapi/pulls).
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
[MIT License](https://opensource.org/licenses/MIT) — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import urllib.parse
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
|
|
4
|
+
from mcsmapi.pool import ApiPool
|
|
5
|
+
from mcsmapi.apis.file import File
|
|
6
|
+
from mcsmapi.apis.java import Java
|
|
7
|
+
from mcsmapi.apis.mod import Mod
|
|
8
|
+
from mcsmapi.apis.user import User
|
|
9
|
+
from mcsmapi.apis.image import Image
|
|
10
|
+
from mcsmapi.apis.daemon import Daemon
|
|
11
|
+
from mcsmapi.apis.instance import Instance
|
|
12
|
+
from mcsmapi.apis.overview import Overview
|
|
13
|
+
from mcsmapi.apis.schedule import Schedule
|
|
14
|
+
from mcsmapi.apis.upgrade import Upgrade
|
|
15
|
+
from mcsmapi.request import Request
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MCSMAPI:
|
|
19
|
+
"""MCSManager API 客户端
|
|
20
|
+
|
|
21
|
+
支持 async context manager::
|
|
22
|
+
|
|
23
|
+
async with MCSMAPI("http://localhost:23333") as mcsm:
|
|
24
|
+
await mcsm.login("admin", "password")
|
|
25
|
+
daemons = await mcsm.daemon.config()
|
|
26
|
+
|
|
27
|
+
也可以手动管理::
|
|
28
|
+
|
|
29
|
+
mcsm = MCSMAPI("http://localhost:23333")
|
|
30
|
+
await mcsm.login("admin", "password")
|
|
31
|
+
...
|
|
32
|
+
await mcsm.close()
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, url: str, timeout: int = 5):
|
|
36
|
+
split_url = urllib.parse.urlsplit(url)
|
|
37
|
+
mcsm_url = f"{split_url.scheme}://{split_url.netloc}/"
|
|
38
|
+
self._req = Request(mcsm_url, timeout)
|
|
39
|
+
|
|
40
|
+
async def __aenter__(self):
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
44
|
+
await self.close()
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
async def login(self, username: str, password: str):
|
|
48
|
+
"""
|
|
49
|
+
使用账号密码登录面板。
|
|
50
|
+
|
|
51
|
+
重复调用时,如果服务端 session 仍有效,MCSManager 会返回 `"Logined"`
|
|
52
|
+
而非新 token。此时会先注销旧 session 再重新登录以获取有效 token。
|
|
53
|
+
"""
|
|
54
|
+
result = await self._req.send(
|
|
55
|
+
"POST",
|
|
56
|
+
f"{ApiPool.AUTH}/login",
|
|
57
|
+
data={"username": username, "password": password},
|
|
58
|
+
)
|
|
59
|
+
if result == "Logined":
|
|
60
|
+
await self._req.send("GET", f"{ApiPool.AUTH}/logout")
|
|
61
|
+
result = await self._req.send(
|
|
62
|
+
"POST",
|
|
63
|
+
f"{ApiPool.AUTH}/login",
|
|
64
|
+
data={"username": username, "password": password},
|
|
65
|
+
)
|
|
66
|
+
self._req.set_token(result)
|
|
67
|
+
self.authentication = "account"
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def login_with_apikey(self, apikey: str):
|
|
71
|
+
"""使用 API Key 认证(无需 await)"""
|
|
72
|
+
self._req.set_apikey(apikey)
|
|
73
|
+
self.authentication = "apikey"
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
async def close(self):
|
|
77
|
+
"""关闭底层 HTTP 连接"""
|
|
78
|
+
await self._req.close()
|
|
79
|
+
|
|
80
|
+
@cached_property
|
|
81
|
+
def overview(self) -> Overview:
|
|
82
|
+
return Overview(self._req)
|
|
83
|
+
|
|
84
|
+
@cached_property
|
|
85
|
+
def instance(self) -> Instance:
|
|
86
|
+
return Instance(self._req)
|
|
87
|
+
|
|
88
|
+
@cached_property
|
|
89
|
+
def user(self) -> User:
|
|
90
|
+
return User(self._req)
|
|
91
|
+
|
|
92
|
+
@cached_property
|
|
93
|
+
def daemon(self) -> Daemon:
|
|
94
|
+
return Daemon(self._req)
|
|
95
|
+
|
|
96
|
+
@cached_property
|
|
97
|
+
def file(self) -> File:
|
|
98
|
+
return File(self._req)
|
|
99
|
+
|
|
100
|
+
@cached_property
|
|
101
|
+
def image(self) -> Image:
|
|
102
|
+
return Image(self._req)
|
|
103
|
+
|
|
104
|
+
@cached_property
|
|
105
|
+
def schedule(self) -> Schedule:
|
|
106
|
+
return Schedule(self._req)
|
|
107
|
+
|
|
108
|
+
@cached_property
|
|
109
|
+
def java(self) -> Java:
|
|
110
|
+
return Java(self._req)
|
|
111
|
+
|
|
112
|
+
@cached_property
|
|
113
|
+
def mod(self) -> Mod:
|
|
114
|
+
return Mod(self._req)
|
|
115
|
+
|
|
116
|
+
@cached_property
|
|
117
|
+
def upgrade(self) -> Upgrade:
|
|
118
|
+
return Upgrade(self._req)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
from mcsmapi.pool import ApiPool
|
|
4
|
+
from mcsmapi.models.daemon import (
|
|
5
|
+
DaemonConfig,
|
|
6
|
+
DaemonStatus,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from mcsmapi.request import Request
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Daemon:
|
|
14
|
+
def __init__(self, req: Request):
|
|
15
|
+
self._req = req
|
|
16
|
+
|
|
17
|
+
async def config(self) -> list[DaemonStatus]:
|
|
18
|
+
"""获取全部节点配置信息"""
|
|
19
|
+
daemons = await self._req.send(
|
|
20
|
+
"GET",
|
|
21
|
+
f"{ApiPool.SERVICE}/remote_services_list",
|
|
22
|
+
)
|
|
23
|
+
return [DaemonStatus(**daemon, _req=self._req) for daemon in daemons]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def add(self, config: DaemonConfig) -> str:
|
|
27
|
+
"""
|
|
28
|
+
新增节点
|
|
29
|
+
|
|
30
|
+
:params config: 节点配置信息
|
|
31
|
+
"""
|
|
32
|
+
return await self._req.send(
|
|
33
|
+
"POST",
|
|
34
|
+
f"{ApiPool.SERVICE}/remote_service",
|
|
35
|
+
data=config.model_dump(),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
async def delete(self, daemonId: str) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
删除节点
|
|
41
|
+
|
|
42
|
+
:params daemonId: 节点的UUID
|
|
43
|
+
"""
|
|
44
|
+
return await self._req.send(
|
|
45
|
+
"DELETE", f"{ApiPool.SERVICE}/remote_service", params={"uuid": daemonId}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
async def link(self, daemonId: str) -> bool:
|
|
49
|
+
"""
|
|
50
|
+
连接节点
|
|
51
|
+
|
|
52
|
+
:params daemonId: 节点的UUID
|
|
53
|
+
"""
|
|
54
|
+
return await self._req.send(
|
|
55
|
+
"GET", f"{ApiPool.SERVICE}/link_remote_service", params={"uuid": daemonId}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
async def update(self, daemonId: str, config: dict[str, Any]) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
更新节点配置(部分更新)
|
|
61
|
+
|
|
62
|
+
:params daemonId: 节点的UUID
|
|
63
|
+
:params config: 要修改的配置字段
|
|
64
|
+
"""
|
|
65
|
+
return await self._req.send(
|
|
66
|
+
"PUT",
|
|
67
|
+
f"{ApiPool.SERVICE}/remote_service",
|
|
68
|
+
params={"uuid": daemonId},
|
|
69
|
+
data=config,
|
|
70
|
+
)
|