ragflow-cli 0.21.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.
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragflow-cli
3
+ Version: 0.21.0
4
+ Summary: Admin Service's client of [RAGFlow](https://github.com/infiniflow/ragflow). The Admin Service provides user management and system monitoring.
5
+ Author-email: Lynn <lynn_inf@hotmail.com>
6
+ License: Apache License, Version 2.0
7
+ Requires-Python: <3.13,>=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: requests<3.0.0,>=2.30.0
10
+ Requires-Dist: beartype<0.19.0,>=0.18.5
11
+ Requires-Dist: pycryptodomex>=3.10.0
12
+ Requires-Dist: lark>=1.1.0
13
+
14
+ # RAGFlow Admin Service & CLI
15
+
16
+ ### Introduction
17
+
18
+ Admin Service is a dedicated management component designed to monitor, maintain, and administrate the RAGFlow system. It provides comprehensive tools for ensuring system stability, performing operational tasks, and managing users and permissions efficiently.
19
+
20
+ The service offers real-time monitoring of critical components, including the RAGFlow server, Task Executor processes, and dependent services such as MySQL, Elasticsearch, Redis, and MinIO. It automatically checks their health status, resource usage, and uptime, and performs restarts in case of failures to minimize downtime.
21
+
22
+ For user and system management, it supports listing, creating, modifying, and deleting users and their associated resources like knowledge bases and Agents.
23
+
24
+ Built with scalability and reliability in mind, the Admin Service ensures smooth system operation and simplifies maintenance workflows.
25
+
26
+ It consists of a server-side Service and a command-line client (CLI), both implemented in Python. User commands are parsed using the Lark parsing toolkit.
27
+
28
+ - **Admin Service**: A backend service that interfaces with the RAGFlow system to execute administrative operations and monitor its status.
29
+ - **Admin CLI**: A command-line interface that allows users to connect to the Admin Service and issue commands for system management.
30
+
31
+
32
+
33
+ ### Starting the Admin Service
34
+
35
+ #### Launching from source code
36
+
37
+ 1. Before start Admin Service, please make sure RAGFlow system is already started.
38
+
39
+ 2. Launch from source code:
40
+
41
+ ```bash
42
+ python admin/server/admin_server.py
43
+ ```
44
+ The service will start and listen for incoming connections from the CLI on the configured port.
45
+
46
+ #### Using docker image
47
+
48
+ 1. Before startup, please configure the `docker_compose.yml` file to enable admin server:
49
+
50
+ ```bash
51
+ command:
52
+ - --enable-adminserver
53
+ ```
54
+
55
+ 2. Start the containers, the service will start and listen for incoming connections from the CLI on the configured port.
56
+
57
+
58
+
59
+ ### Using the Admin CLI
60
+
61
+ 1. Ensure the Admin Service is running.
62
+ 2. Install ragflow-cli.
63
+ ```bash
64
+ pip install ragflow-cli
65
+ ```
66
+ 3. Launch the CLI client:
67
+ ```bash
68
+ ragflow-cli -h 0.0.0.0 -p 9381
69
+ ```
70
+ Enter superuser's password to login. Default password is `admin`.
71
+
72
+
73
+
74
+ ## Supported Commands
75
+
76
+ Commands are case-insensitive and must be terminated with a semicolon (`;`).
77
+
78
+ ### Service Management Commands
79
+
80
+ - `LIST SERVICES;`
81
+ - Lists all available services within the RAGFlow system.
82
+ - `SHOW SERVICE <id>;`
83
+ - Shows detailed status information for the service identified by `<id>`.
84
+
85
+
86
+ ### User Management Commands
87
+
88
+ - `LIST USERS;`
89
+ - Lists all users known to the system.
90
+ - `SHOW USER '<username>';`
91
+ - Shows details and permissions for the specified user. The username must be enclosed in single or double quotes.
92
+
93
+ - `CREATE USER <username> <password>;`
94
+ - Create user by username and password. The username and password must be enclosed in single or double quotes.
95
+
96
+ - `DROP USER '<username>';`
97
+ - Removes the specified user from the system. Use with caution.
98
+ - `ALTER USER PASSWORD '<username>' '<new_password>';`
99
+ - Changes the password for the specified user.
100
+ - `ALTER USER ACTIVE <username> <on/off>;`
101
+ - Changes the user to active or inactive.
102
+
103
+
104
+ ### Data and Agent Commands
105
+
106
+ - `LIST DATASETS OF '<username>';`
107
+ - Lists the datasets associated with the specified user.
108
+ - `LIST AGENTS OF '<username>';`
109
+ - Lists the agents associated with the specified user.
110
+
111
+ ### Meta-Commands
112
+
113
+ Meta-commands are prefixed with a backslash (`\`).
114
+
115
+ - `\?` or `\help`
116
+ - Shows help information for the available commands.
117
+ - `\q` or `\quit`
118
+ - Exits the CLI application.
119
+
120
+ ## Examples
121
+
122
+ ```commandline
123
+ admin> list users;
124
+ +-------------------------------+------------------------+-----------+-------------+
125
+ | create_date | email | is_active | nickname |
126
+ +-------------------------------+------------------------+-----------+-------------+
127
+ | Fri, 22 Nov 2024 16:03:41 GMT | jeffery@infiniflow.org | 1 | Jeffery |
128
+ | Fri, 22 Nov 2024 16:10:55 GMT | aya@infiniflow.org | 1 | Waterdancer |
129
+ +-------------------------------+------------------------+-----------+-------------+
130
+
131
+ admin> list services;
132
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
133
+ | extra | host | id | name | port | service_type |
134
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
135
+ | {} | 0.0.0.0 | 0 | ragflow_0 | 9380 | ragflow_server |
136
+ | {'meta_type': 'mysql', 'password': 'infini_rag_flow', 'username': 'root'} | localhost | 1 | mysql | 5455 | meta_data |
137
+ | {'password': 'infini_rag_flow', 'store_type': 'minio', 'user': 'rag_flow'} | localhost | 2 | minio | 9000 | file_store |
138
+ | {'password': 'infini_rag_flow', 'retrieval_type': 'elasticsearch', 'username': 'elastic'} | localhost | 3 | elasticsearch | 1200 | retrieval |
139
+ | {'db_name': 'default_db', 'retrieval_type': 'infinity'} | localhost | 4 | infinity | 23817 | retrieval |
140
+ | {'database': 1, 'mq_type': 'redis', 'password': 'infini_rag_flow'} | localhost | 5 | redis | 6379 | message_queue |
141
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
142
+ ```
@@ -0,0 +1,129 @@
1
+ # RAGFlow Admin Service & CLI
2
+
3
+ ### Introduction
4
+
5
+ Admin Service is a dedicated management component designed to monitor, maintain, and administrate the RAGFlow system. It provides comprehensive tools for ensuring system stability, performing operational tasks, and managing users and permissions efficiently.
6
+
7
+ The service offers real-time monitoring of critical components, including the RAGFlow server, Task Executor processes, and dependent services such as MySQL, Elasticsearch, Redis, and MinIO. It automatically checks their health status, resource usage, and uptime, and performs restarts in case of failures to minimize downtime.
8
+
9
+ For user and system management, it supports listing, creating, modifying, and deleting users and their associated resources like knowledge bases and Agents.
10
+
11
+ Built with scalability and reliability in mind, the Admin Service ensures smooth system operation and simplifies maintenance workflows.
12
+
13
+ It consists of a server-side Service and a command-line client (CLI), both implemented in Python. User commands are parsed using the Lark parsing toolkit.
14
+
15
+ - **Admin Service**: A backend service that interfaces with the RAGFlow system to execute administrative operations and monitor its status.
16
+ - **Admin CLI**: A command-line interface that allows users to connect to the Admin Service and issue commands for system management.
17
+
18
+
19
+
20
+ ### Starting the Admin Service
21
+
22
+ #### Launching from source code
23
+
24
+ 1. Before start Admin Service, please make sure RAGFlow system is already started.
25
+
26
+ 2. Launch from source code:
27
+
28
+ ```bash
29
+ python admin/server/admin_server.py
30
+ ```
31
+ The service will start and listen for incoming connections from the CLI on the configured port.
32
+
33
+ #### Using docker image
34
+
35
+ 1. Before startup, please configure the `docker_compose.yml` file to enable admin server:
36
+
37
+ ```bash
38
+ command:
39
+ - --enable-adminserver
40
+ ```
41
+
42
+ 2. Start the containers, the service will start and listen for incoming connections from the CLI on the configured port.
43
+
44
+
45
+
46
+ ### Using the Admin CLI
47
+
48
+ 1. Ensure the Admin Service is running.
49
+ 2. Install ragflow-cli.
50
+ ```bash
51
+ pip install ragflow-cli
52
+ ```
53
+ 3. Launch the CLI client:
54
+ ```bash
55
+ ragflow-cli -h 0.0.0.0 -p 9381
56
+ ```
57
+ Enter superuser's password to login. Default password is `admin`.
58
+
59
+
60
+
61
+ ## Supported Commands
62
+
63
+ Commands are case-insensitive and must be terminated with a semicolon (`;`).
64
+
65
+ ### Service Management Commands
66
+
67
+ - `LIST SERVICES;`
68
+ - Lists all available services within the RAGFlow system.
69
+ - `SHOW SERVICE <id>;`
70
+ - Shows detailed status information for the service identified by `<id>`.
71
+
72
+
73
+ ### User Management Commands
74
+
75
+ - `LIST USERS;`
76
+ - Lists all users known to the system.
77
+ - `SHOW USER '<username>';`
78
+ - Shows details and permissions for the specified user. The username must be enclosed in single or double quotes.
79
+
80
+ - `CREATE USER <username> <password>;`
81
+ - Create user by username and password. The username and password must be enclosed in single or double quotes.
82
+
83
+ - `DROP USER '<username>';`
84
+ - Removes the specified user from the system. Use with caution.
85
+ - `ALTER USER PASSWORD '<username>' '<new_password>';`
86
+ - Changes the password for the specified user.
87
+ - `ALTER USER ACTIVE <username> <on/off>;`
88
+ - Changes the user to active or inactive.
89
+
90
+
91
+ ### Data and Agent Commands
92
+
93
+ - `LIST DATASETS OF '<username>';`
94
+ - Lists the datasets associated with the specified user.
95
+ - `LIST AGENTS OF '<username>';`
96
+ - Lists the agents associated with the specified user.
97
+
98
+ ### Meta-Commands
99
+
100
+ Meta-commands are prefixed with a backslash (`\`).
101
+
102
+ - `\?` or `\help`
103
+ - Shows help information for the available commands.
104
+ - `\q` or `\quit`
105
+ - Exits the CLI application.
106
+
107
+ ## Examples
108
+
109
+ ```commandline
110
+ admin> list users;
111
+ +-------------------------------+------------------------+-----------+-------------+
112
+ | create_date | email | is_active | nickname |
113
+ +-------------------------------+------------------------+-----------+-------------+
114
+ | Fri, 22 Nov 2024 16:03:41 GMT | jeffery@infiniflow.org | 1 | Jeffery |
115
+ | Fri, 22 Nov 2024 16:10:55 GMT | aya@infiniflow.org | 1 | Waterdancer |
116
+ +-------------------------------+------------------------+-----------+-------------+
117
+
118
+ admin> list services;
119
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
120
+ | extra | host | id | name | port | service_type |
121
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
122
+ | {} | 0.0.0.0 | 0 | ragflow_0 | 9380 | ragflow_server |
123
+ | {'meta_type': 'mysql', 'password': 'infini_rag_flow', 'username': 'root'} | localhost | 1 | mysql | 5455 | meta_data |
124
+ | {'password': 'infini_rag_flow', 'store_type': 'minio', 'user': 'rag_flow'} | localhost | 2 | minio | 9000 | file_store |
125
+ | {'password': 'infini_rag_flow', 'retrieval_type': 'elasticsearch', 'username': 'elastic'} | localhost | 3 | elasticsearch | 1200 | retrieval |
126
+ | {'db_name': 'default_db', 'retrieval_type': 'infinity'} | localhost | 4 | infinity | 23817 | retrieval |
127
+ | {'database': 1, 'mq_type': 'redis', 'password': 'infini_rag_flow'} | localhost | 5 | redis | 6379 | message_queue |
128
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
129
+ ```
@@ -0,0 +1,665 @@
1
+ #
2
+ # Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import argparse
18
+ import base64
19
+ from cmd import Cmd
20
+
21
+ from Cryptodome.PublicKey import RSA
22
+ from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
23
+ from typing import Dict, List, Any
24
+ from lark import Lark, Transformer, Tree, Token
25
+ import requests
26
+ from requests.auth import HTTPBasicAuth
27
+
28
+ GRAMMAR = r"""
29
+ start: command
30
+
31
+ command: sql_command | meta_command
32
+
33
+ sql_command: list_services
34
+ | show_service
35
+ | startup_service
36
+ | shutdown_service
37
+ | restart_service
38
+ | list_users
39
+ | show_user
40
+ | drop_user
41
+ | alter_user
42
+ | create_user
43
+ | activate_user
44
+ | list_datasets
45
+ | list_agents
46
+
47
+ // meta command definition
48
+ meta_command: "\\" meta_command_name [meta_args]
49
+
50
+ meta_command_name: /[a-zA-Z?]+/
51
+ meta_args: (meta_arg)+
52
+
53
+ meta_arg: /[^\\s"']+/ | quoted_string
54
+
55
+ // command definition
56
+
57
+ LIST: "LIST"i
58
+ SERVICES: "SERVICES"i
59
+ SHOW: "SHOW"i
60
+ CREATE: "CREATE"i
61
+ SERVICE: "SERVICE"i
62
+ SHUTDOWN: "SHUTDOWN"i
63
+ STARTUP: "STARTUP"i
64
+ RESTART: "RESTART"i
65
+ USERS: "USERS"i
66
+ DROP: "DROP"i
67
+ USER: "USER"i
68
+ ALTER: "ALTER"i
69
+ ACTIVE: "ACTIVE"i
70
+ PASSWORD: "PASSWORD"i
71
+ DATASETS: "DATASETS"i
72
+ OF: "OF"i
73
+ AGENTS: "AGENTS"i
74
+
75
+ list_services: LIST SERVICES ";"
76
+ show_service: SHOW SERVICE NUMBER ";"
77
+ startup_service: STARTUP SERVICE NUMBER ";"
78
+ shutdown_service: SHUTDOWN SERVICE NUMBER ";"
79
+ restart_service: RESTART SERVICE NUMBER ";"
80
+
81
+ list_users: LIST USERS ";"
82
+ drop_user: DROP USER quoted_string ";"
83
+ alter_user: ALTER USER PASSWORD quoted_string quoted_string ";"
84
+ show_user: SHOW USER quoted_string ";"
85
+ create_user: CREATE USER quoted_string quoted_string ";"
86
+ activate_user: ALTER USER ACTIVE quoted_string status ";"
87
+
88
+ list_datasets: LIST DATASETS OF quoted_string ";"
89
+ list_agents: LIST AGENTS OF quoted_string ";"
90
+
91
+ identifier: WORD
92
+ quoted_string: QUOTED_STRING
93
+ status: WORD
94
+
95
+ QUOTED_STRING: /'[^']+'/ | /"[^"]+"/
96
+ WORD: /[a-zA-Z0-9_\-\.]+/
97
+ NUMBER: /[0-9]+/
98
+
99
+ %import common.WS
100
+ %ignore WS
101
+ """
102
+
103
+
104
+ class AdminTransformer(Transformer):
105
+
106
+ def start(self, items):
107
+ return items[0]
108
+
109
+ def command(self, items):
110
+ return items[0]
111
+
112
+ def list_services(self, items):
113
+ result = {'type': 'list_services'}
114
+ return result
115
+
116
+ def show_service(self, items):
117
+ service_id = int(items[2])
118
+ return {"type": "show_service", "number": service_id}
119
+
120
+ def startup_service(self, items):
121
+ service_id = int(items[2])
122
+ return {"type": "startup_service", "number": service_id}
123
+
124
+ def shutdown_service(self, items):
125
+ service_id = int(items[2])
126
+ return {"type": "shutdown_service", "number": service_id}
127
+
128
+ def restart_service(self, items):
129
+ service_id = int(items[2])
130
+ return {"type": "restart_service", "number": service_id}
131
+
132
+ def list_users(self, items):
133
+ return {"type": "list_users"}
134
+
135
+ def show_user(self, items):
136
+ user_name = items[2]
137
+ return {"type": "show_user", "username": user_name}
138
+
139
+ def drop_user(self, items):
140
+ user_name = items[2]
141
+ return {"type": "drop_user", "username": user_name}
142
+
143
+ def alter_user(self, items):
144
+ user_name = items[3]
145
+ new_password = items[4]
146
+ return {"type": "alter_user", "username": user_name, "password": new_password}
147
+
148
+ def create_user(self, items):
149
+ user_name = items[2]
150
+ password = items[3]
151
+ return {"type": "create_user", "username": user_name, "password": password, "role": "user"}
152
+
153
+ def activate_user(self, items):
154
+ user_name = items[3]
155
+ activate_status = items[4]
156
+ return {"type": "activate_user", "activate_status": activate_status, "username": user_name}
157
+
158
+ def list_datasets(self, items):
159
+ user_name = items[3]
160
+ return {"type": "list_datasets", "username": user_name}
161
+
162
+ def list_agents(self, items):
163
+ user_name = items[3]
164
+ return {"type": "list_agents", "username": user_name}
165
+
166
+ def meta_command(self, items):
167
+ command_name = str(items[0]).lower()
168
+ args = items[1:] if len(items) > 1 else []
169
+
170
+ # handle quoted parameter
171
+ parsed_args = []
172
+ for arg in args:
173
+ if hasattr(arg, 'value'):
174
+ parsed_args.append(arg.value)
175
+ else:
176
+ parsed_args.append(str(arg))
177
+
178
+ return {'type': 'meta', 'command': command_name, 'args': parsed_args}
179
+
180
+ def meta_command_name(self, items):
181
+ return items[0]
182
+
183
+ def meta_args(self, items):
184
+ return items
185
+
186
+
187
+ def encrypt(input_string):
188
+ pub = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB\n-----END PUBLIC KEY-----'
189
+ pub_key = RSA.importKey(pub)
190
+ cipher = Cipher_pkcs1_v1_5.new(pub_key)
191
+ cipher_text = cipher.encrypt(base64.b64encode(input_string.encode('utf-8')))
192
+ return base64.b64encode(cipher_text).decode("utf-8")
193
+
194
+
195
+ def encode_to_base64(input_string):
196
+ base64_encoded = base64.b64encode(input_string.encode('utf-8'))
197
+ return base64_encoded.decode('utf-8')
198
+
199
+
200
+ class AdminCLI(Cmd):
201
+ def __init__(self):
202
+ super().__init__()
203
+ self.parser = Lark(GRAMMAR, start='start', parser='lalr', transformer=AdminTransformer())
204
+ self.command_history = []
205
+ self.is_interactive = False
206
+ self.admin_account = "admin@ragflow.io"
207
+ self.admin_password: str = "admin"
208
+ self.host: str = ""
209
+ self.port: int = 0
210
+
211
+ intro = r"""Type "\h" for help."""
212
+ prompt = "admin> "
213
+
214
+ def onecmd(self, command: str) -> bool:
215
+ try:
216
+ # print(f"command: {command}")
217
+ result = self.parse_command(command)
218
+
219
+ # if 'type' in result and result.get('type') == 'empty':
220
+ # return False
221
+
222
+ if isinstance(result, dict):
223
+ if 'type' in result and result.get('type') == 'empty':
224
+ return False
225
+
226
+ self.execute_command(result)
227
+
228
+ if isinstance(result, Tree):
229
+ return False
230
+
231
+ if result.get('type') == 'meta' and result.get('command') in ['q', 'quit', 'exit']:
232
+ return True
233
+
234
+ except KeyboardInterrupt:
235
+ print("\nUse '\\q' to quit")
236
+ except EOFError:
237
+ print("\nGoodbye!")
238
+ return True
239
+ return False
240
+
241
+ def emptyline(self) -> bool:
242
+ return False
243
+
244
+ def default(self, line: str) -> bool:
245
+ return self.onecmd(line)
246
+
247
+ def parse_command(self, command_str: str) -> dict[str, str] | Tree[Token]:
248
+ if not command_str.strip():
249
+ return {'type': 'empty'}
250
+
251
+ self.command_history.append(command_str)
252
+
253
+ try:
254
+ result = self.parser.parse(command_str)
255
+ return result
256
+ except Exception as e:
257
+ return {'type': 'error', 'message': f'Parse error: {str(e)}'}
258
+
259
+ def verify_admin(self, args):
260
+
261
+ conn_info = self._parse_connection_args(args)
262
+ if 'error' in conn_info:
263
+ print(f"Error: {conn_info['error']}")
264
+ return
265
+
266
+ self.host = conn_info['host']
267
+ self.port = conn_info['port']
268
+ print(f"Attempt to access ip: {self.host}, port: {self.port}")
269
+ url = f'http://{self.host}:{self.port}/api/v1/admin/auth'
270
+
271
+ try_count = 0
272
+ while True:
273
+ try_count += 1
274
+ if try_count > 3:
275
+ return False
276
+
277
+ admin_passwd = input(f"password for {self.admin_account}: ").strip()
278
+ try:
279
+ self.admin_password = encode_to_base64(admin_passwd)
280
+ response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password))
281
+ if response.status_code == 200:
282
+ res_json = response.json()
283
+ error_code = res_json.get('code', -1)
284
+ if error_code == 0:
285
+ print("Authentication successful.")
286
+ return True
287
+ else:
288
+ error_message = res_json.get('message', 'Unknown error')
289
+ print(f"Authentication failed: {error_message}, try again")
290
+ continue
291
+ else:
292
+ print(f"Bad response,status: {response.status_code}, try again")
293
+ except Exception:
294
+ print(f"Can't access {self.host}, port: {self.port}")
295
+
296
+ def _print_table_simple(self, data):
297
+ if not data:
298
+ print("No data to print")
299
+ return
300
+ if isinstance(data, dict):
301
+ # handle single row data
302
+ data = [data]
303
+
304
+ columns = list(data[0].keys())
305
+ col_widths = {}
306
+
307
+ def get_string_width(text):
308
+ half_width_chars = (
309
+ " !\"#$%&'()*+,-./0123456789:;<=>?@"
310
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
311
+ "abcdefghijklmnopqrstuvwxyz{|}~"
312
+ "\t\n\r"
313
+ )
314
+ width = 0
315
+ for char in text:
316
+ if char in half_width_chars:
317
+ width += 1
318
+ else:
319
+ width += 2
320
+ return width
321
+
322
+ for col in columns:
323
+ max_width = get_string_width(str(col))
324
+ for item in data:
325
+ value_len = get_string_width(str(item.get(col, '')))
326
+ if value_len > max_width:
327
+ max_width = value_len
328
+ col_widths[col] = max(2, max_width)
329
+
330
+ # Generate delimiter
331
+ separator = "+" + "+".join(["-" * (col_widths[col] + 2) for col in columns]) + "+"
332
+
333
+ # Print header
334
+ print(separator)
335
+ header = "|" + "|".join([f" {col:<{col_widths[col]}} " for col in columns]) + "|"
336
+ print(header)
337
+ print(separator)
338
+
339
+ # Print data
340
+ for item in data:
341
+ row = "|"
342
+ for col in columns:
343
+ value = str(item.get(col, ''))
344
+ if len(value) > col_widths[col]:
345
+ value = value[:col_widths[col] - 3] + "..."
346
+ row += f" {value:<{col_widths[col]}} |"
347
+ print(row)
348
+
349
+ print(separator)
350
+
351
+ def run_interactive(self):
352
+
353
+ self.is_interactive = True
354
+ print("RAGFlow Admin command line interface - Type '\\?' for help, '\\q' to quit")
355
+
356
+ while True:
357
+ try:
358
+ command = input("admin> ").strip()
359
+ if not command:
360
+ continue
361
+
362
+ print(f"command: {command}")
363
+ result = self.parse_command(command)
364
+ self.execute_command(result)
365
+
366
+ if isinstance(result, Tree):
367
+ continue
368
+
369
+ if result.get('type') == 'meta' and result.get('command') in ['q', 'quit', 'exit']:
370
+ break
371
+
372
+ except KeyboardInterrupt:
373
+ print("\nUse '\\q' to quit")
374
+ except EOFError:
375
+ print("\nGoodbye!")
376
+ break
377
+
378
+ def run_single_command(self, args):
379
+ conn_info = self._parse_connection_args(args)
380
+ if 'error' in conn_info:
381
+ print(f"Error: {conn_info['error']}")
382
+ return
383
+
384
+ def _parse_connection_args(self, args: List[str]) -> Dict[str, Any]:
385
+ parser = argparse.ArgumentParser(description='Admin CLI Client', add_help=False)
386
+ parser.add_argument('-h', '--host', default='localhost', help='Admin service host')
387
+ parser.add_argument('-p', '--port', type=int, default=8080, help='Admin service port')
388
+
389
+ try:
390
+ parsed_args, remaining_args = parser.parse_known_args(args)
391
+ return {
392
+ 'host': parsed_args.host,
393
+ 'port': parsed_args.port,
394
+ }
395
+ except SystemExit:
396
+ return {'error': 'Invalid connection arguments'}
397
+
398
+ def execute_command(self, parsed_command: Dict[str, Any]):
399
+
400
+ command_dict: dict
401
+ if isinstance(parsed_command, Tree):
402
+ command_dict = parsed_command.children[0]
403
+ else:
404
+ if parsed_command['type'] == 'error':
405
+ print(f"Error: {parsed_command['message']}")
406
+ return
407
+ else:
408
+ command_dict = parsed_command
409
+
410
+ # print(f"Parsed command: {command_dict}")
411
+
412
+ command_type = command_dict['type']
413
+
414
+ match command_type:
415
+ case 'list_services':
416
+ self._handle_list_services(command_dict)
417
+ case 'show_service':
418
+ self._handle_show_service(command_dict)
419
+ case 'restart_service':
420
+ self._handle_restart_service(command_dict)
421
+ case 'shutdown_service':
422
+ self._handle_shutdown_service(command_dict)
423
+ case 'startup_service':
424
+ self._handle_startup_service(command_dict)
425
+ case 'list_users':
426
+ self._handle_list_users(command_dict)
427
+ case 'show_user':
428
+ self._handle_show_user(command_dict)
429
+ case 'drop_user':
430
+ self._handle_drop_user(command_dict)
431
+ case 'alter_user':
432
+ self._handle_alter_user(command_dict)
433
+ case 'create_user':
434
+ self._handle_create_user(command_dict)
435
+ case 'activate_user':
436
+ self._handle_activate_user(command_dict)
437
+ case 'list_datasets':
438
+ self._handle_list_datasets(command_dict)
439
+ case 'list_agents':
440
+ self._handle_list_agents(command_dict)
441
+ case 'meta':
442
+ self._handle_meta_command(command_dict)
443
+ case _:
444
+ print(f"Command '{command_type}' would be executed with API")
445
+
446
+ def _handle_list_services(self, command):
447
+ print("Listing all services")
448
+
449
+ url = f'http://{self.host}:{self.port}/api/v1/admin/services'
450
+ response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password))
451
+ res_json = response.json()
452
+ if response.status_code == 200:
453
+ self._print_table_simple(res_json['data'])
454
+ else:
455
+ print(f"Fail to get all users, code: {res_json['code']}, message: {res_json['message']}")
456
+
457
+ def _handle_show_service(self, command):
458
+ service_id: int = command['number']
459
+ print(f"Showing service: {service_id}")
460
+
461
+ url = f'http://{self.host}:{self.port}/api/v1/admin/services/{service_id}'
462
+ response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password))
463
+ res_json = response.json()
464
+ if response.status_code == 200:
465
+ res_data = res_json['data']
466
+ if res_data['alive']:
467
+ print(f"Service {res_data['service_name']} is alive. Detail:")
468
+ if isinstance(res_data['message'], str):
469
+ print(res_data['message'])
470
+ else:
471
+ self._print_table_simple(res_data['message'])
472
+ else:
473
+ print(f"Service {res_data['service_name']} is down. Detail: {res_data['message']}")
474
+ else:
475
+ print(f"Fail to show service, code: {res_json['code']}, message: {res_json['message']}")
476
+
477
+ def _handle_restart_service(self, command):
478
+ service_id: int = command['number']
479
+ print(f"Restart service {service_id}")
480
+
481
+ def _handle_shutdown_service(self, command):
482
+ service_id: int = command['number']
483
+ print(f"Shutdown service {service_id}")
484
+
485
+ def _handle_startup_service(self, command):
486
+ service_id: int = command['number']
487
+ print(f"Startup service {service_id}")
488
+
489
+ def _handle_list_users(self, command):
490
+ print("Listing all users")
491
+
492
+ url = f'http://{self.host}:{self.port}/api/v1/admin/users'
493
+ response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password))
494
+ res_json = response.json()
495
+ if response.status_code == 200:
496
+ self._print_table_simple(res_json['data'])
497
+ else:
498
+ print(f"Fail to get all users, code: {res_json['code']}, message: {res_json['message']}")
499
+
500
+ def _handle_show_user(self, command):
501
+ username_tree: Tree = command['username']
502
+ username: str = username_tree.children[0].strip("'\"")
503
+ print(f"Showing user: {username}")
504
+ url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}'
505
+ response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password))
506
+ res_json = response.json()
507
+ if response.status_code == 200:
508
+ self._print_table_simple(res_json['data'])
509
+ else:
510
+ print(f"Fail to get user {username}, code: {res_json['code']}, message: {res_json['message']}")
511
+
512
+ def _handle_drop_user(self, command):
513
+ username_tree: Tree = command['username']
514
+ username: str = username_tree.children[0].strip("'\"")
515
+ print(f"Drop user: {username}")
516
+ url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}'
517
+ response = requests.delete(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password))
518
+ res_json = response.json()
519
+ if response.status_code == 200:
520
+ print(res_json["message"])
521
+ else:
522
+ print(f"Fail to drop user, code: {res_json['code']}, message: {res_json['message']}")
523
+
524
+ def _handle_alter_user(self, command):
525
+ username_tree: Tree = command['username']
526
+ username: str = username_tree.children[0].strip("'\"")
527
+ password_tree: Tree = command['password']
528
+ password: str = password_tree.children[0].strip("'\"")
529
+ print(f"Alter user: {username}, password: {password}")
530
+ url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}/password'
531
+ response = requests.put(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password),
532
+ json={'new_password': encrypt(password)})
533
+ res_json = response.json()
534
+ if response.status_code == 200:
535
+ print(res_json["message"])
536
+ else:
537
+ print(f"Fail to alter password, code: {res_json['code']}, message: {res_json['message']}")
538
+
539
+ def _handle_create_user(self, command):
540
+ username_tree: Tree = command['username']
541
+ username: str = username_tree.children[0].strip("'\"")
542
+ password_tree: Tree = command['password']
543
+ password: str = password_tree.children[0].strip("'\"")
544
+ role: str = command['role']
545
+ print(f"Create user: {username}, password: {password}, role: {role}")
546
+ url = f'http://{self.host}:{self.port}/api/v1/admin/users'
547
+ response = requests.post(
548
+ url,
549
+ auth=HTTPBasicAuth(self.admin_account, self.admin_password),
550
+ json={'username': username, 'password': encrypt(password), 'role': role}
551
+ )
552
+ res_json = response.json()
553
+ if response.status_code == 200:
554
+ self._print_table_simple(res_json['data'])
555
+ else:
556
+ print(f"Fail to create user {username}, code: {res_json['code']}, message: {res_json['message']}")
557
+
558
+ def _handle_activate_user(self, command):
559
+ username_tree: Tree = command['username']
560
+ username: str = username_tree.children[0].strip("'\"")
561
+ activate_tree: Tree = command['activate_status']
562
+ activate_status: str = activate_tree.children[0].strip("'\"")
563
+ if activate_status.lower() in ['on', 'off']:
564
+ print(f"Alter user {username} activate status, turn {activate_status.lower()}.")
565
+ url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}/activate'
566
+ response = requests.put(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password),
567
+ json={'activate_status': activate_status})
568
+ res_json = response.json()
569
+ if response.status_code == 200:
570
+ print(res_json["message"])
571
+ else:
572
+ print(f"Fail to alter activate status, code: {res_json['code']}, message: {res_json['message']}")
573
+ else:
574
+ print(f"Unknown activate status: {activate_status}.")
575
+
576
+ def _handle_list_datasets(self, command):
577
+ username_tree: Tree = command['username']
578
+ username: str = username_tree.children[0].strip("'\"")
579
+ print(f"Listing all datasets of user: {username}")
580
+ url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}/datasets'
581
+ response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password))
582
+ res_json = response.json()
583
+ if response.status_code == 200:
584
+ self._print_table_simple(res_json['data'])
585
+ else:
586
+ print(f"Fail to get all datasets of {username}, code: {res_json['code']}, message: {res_json['message']}")
587
+
588
+ def _handle_list_agents(self, command):
589
+ username_tree: Tree = command['username']
590
+ username: str = username_tree.children[0].strip("'\"")
591
+ print(f"Listing all agents of user: {username}")
592
+ url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}/agents'
593
+ response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password))
594
+ res_json = response.json()
595
+ if response.status_code == 200:
596
+ self._print_table_simple(res_json['data'])
597
+ else:
598
+ print(f"Fail to get all agents of {username}, code: {res_json['code']}, message: {res_json['message']}")
599
+
600
+ def _handle_meta_command(self, command):
601
+ meta_command = command['command']
602
+ args = command.get('args', [])
603
+
604
+ if meta_command in ['?', 'h', 'help']:
605
+ self.show_help()
606
+ elif meta_command in ['q', 'quit', 'exit']:
607
+ print("Goodbye!")
608
+ else:
609
+ print(f"Meta command '{meta_command}' with args {args}")
610
+
611
+ def show_help(self):
612
+ """Help info"""
613
+ help_text = """
614
+ Commands:
615
+ LIST SERVICES
616
+ SHOW SERVICE <service>
617
+ STARTUP SERVICE <service>
618
+ SHUTDOWN SERVICE <service>
619
+ RESTART SERVICE <service>
620
+ LIST USERS
621
+ SHOW USER <user>
622
+ DROP USER <user>
623
+ CREATE USER <user> <password>
624
+ ALTER USER PASSWORD <user> <new_password>
625
+ ALTER USER ACTIVE <user> <on/off>
626
+ LIST DATASETS OF <user>
627
+ LIST AGENTS OF <user>
628
+
629
+ Meta Commands:
630
+ \\?, \\h, \\help Show this help
631
+ \\q, \\quit, \\exit Quit the CLI
632
+ """
633
+ print(help_text)
634
+
635
+
636
+ def main():
637
+ import sys
638
+
639
+ cli = AdminCLI()
640
+
641
+ if len(sys.argv) == 1 or (len(sys.argv) > 1 and sys.argv[1] == '-'):
642
+ print(r"""
643
+ ____ ___ ______________ ___ __ _
644
+ / __ \/ | / ____/ ____/ /___ _ __ / | ____/ /___ ___ (_)___
645
+ / /_/ / /| |/ / __/ /_ / / __ \ | /| / / / /| |/ __ / __ `__ \/ / __ \
646
+ / _, _/ ___ / /_/ / __/ / / /_/ / |/ |/ / / ___ / /_/ / / / / / / / / / /
647
+ /_/ |_/_/ |_\____/_/ /_/\____/|__/|__/ /_/ |_\__,_/_/ /_/ /_/_/_/ /_/
648
+ """)
649
+ if cli.verify_admin(sys.argv):
650
+ cli.cmdloop()
651
+ else:
652
+ print(r"""
653
+ ____ ___ ______________ ___ __ _
654
+ / __ \/ | / ____/ ____/ /___ _ __ / | ____/ /___ ___ (_)___
655
+ / /_/ / /| |/ / __/ /_ / / __ \ | /| / / / /| |/ __ / __ `__ \/ / __ \
656
+ / _, _/ ___ / /_/ / __/ / / /_/ / |/ |/ / / ___ / /_/ / / / / / / / / / /
657
+ /_/ |_/_/ |_\____/_/ /_/\____/|__/|__/ /_/ |_\__,_/_/ /_/ /_/_/_/ /_/
658
+ """)
659
+ if cli.verify_admin(sys.argv):
660
+ cli.cmdloop()
661
+ # cli.run_single_command(sys.argv[1:])
662
+
663
+
664
+ if __name__ == '__main__':
665
+ main()
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "ragflow-cli"
3
+ version = "0.21.0"
4
+ description = "Admin Service's client of [RAGFlow](https://github.com/infiniflow/ragflow). The Admin Service provides user management and system monitoring. "
5
+ authors = [{ name = "Lynn", email = "lynn_inf@hotmail.com" }]
6
+ license = { text = "Apache License, Version 2.0" }
7
+ readme = "README.md"
8
+ requires-python = ">=3.10,<3.13"
9
+ dependencies = [
10
+ "requests>=2.30.0,<3.0.0",
11
+ "beartype>=0.18.5,<0.19.0",
12
+ "pycryptodomex>=3.10.0",
13
+ "lark>=1.1.0",
14
+ ]
15
+
16
+ [dependency-groups]
17
+ test = [
18
+ "pytest>=8.3.5",
19
+ "requests>=2.32.3",
20
+ "requests-toolbelt>=1.0.0",
21
+ ]
22
+
23
+ [project.scripts]
24
+ ragflow-cli = "admin_client:main"
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragflow-cli
3
+ Version: 0.21.0
4
+ Summary: Admin Service's client of [RAGFlow](https://github.com/infiniflow/ragflow). The Admin Service provides user management and system monitoring.
5
+ Author-email: Lynn <lynn_inf@hotmail.com>
6
+ License: Apache License, Version 2.0
7
+ Requires-Python: <3.13,>=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: requests<3.0.0,>=2.30.0
10
+ Requires-Dist: beartype<0.19.0,>=0.18.5
11
+ Requires-Dist: pycryptodomex>=3.10.0
12
+ Requires-Dist: lark>=1.1.0
13
+
14
+ # RAGFlow Admin Service & CLI
15
+
16
+ ### Introduction
17
+
18
+ Admin Service is a dedicated management component designed to monitor, maintain, and administrate the RAGFlow system. It provides comprehensive tools for ensuring system stability, performing operational tasks, and managing users and permissions efficiently.
19
+
20
+ The service offers real-time monitoring of critical components, including the RAGFlow server, Task Executor processes, and dependent services such as MySQL, Elasticsearch, Redis, and MinIO. It automatically checks their health status, resource usage, and uptime, and performs restarts in case of failures to minimize downtime.
21
+
22
+ For user and system management, it supports listing, creating, modifying, and deleting users and their associated resources like knowledge bases and Agents.
23
+
24
+ Built with scalability and reliability in mind, the Admin Service ensures smooth system operation and simplifies maintenance workflows.
25
+
26
+ It consists of a server-side Service and a command-line client (CLI), both implemented in Python. User commands are parsed using the Lark parsing toolkit.
27
+
28
+ - **Admin Service**: A backend service that interfaces with the RAGFlow system to execute administrative operations and monitor its status.
29
+ - **Admin CLI**: A command-line interface that allows users to connect to the Admin Service and issue commands for system management.
30
+
31
+
32
+
33
+ ### Starting the Admin Service
34
+
35
+ #### Launching from source code
36
+
37
+ 1. Before start Admin Service, please make sure RAGFlow system is already started.
38
+
39
+ 2. Launch from source code:
40
+
41
+ ```bash
42
+ python admin/server/admin_server.py
43
+ ```
44
+ The service will start and listen for incoming connections from the CLI on the configured port.
45
+
46
+ #### Using docker image
47
+
48
+ 1. Before startup, please configure the `docker_compose.yml` file to enable admin server:
49
+
50
+ ```bash
51
+ command:
52
+ - --enable-adminserver
53
+ ```
54
+
55
+ 2. Start the containers, the service will start and listen for incoming connections from the CLI on the configured port.
56
+
57
+
58
+
59
+ ### Using the Admin CLI
60
+
61
+ 1. Ensure the Admin Service is running.
62
+ 2. Install ragflow-cli.
63
+ ```bash
64
+ pip install ragflow-cli
65
+ ```
66
+ 3. Launch the CLI client:
67
+ ```bash
68
+ ragflow-cli -h 0.0.0.0 -p 9381
69
+ ```
70
+ Enter superuser's password to login. Default password is `admin`.
71
+
72
+
73
+
74
+ ## Supported Commands
75
+
76
+ Commands are case-insensitive and must be terminated with a semicolon (`;`).
77
+
78
+ ### Service Management Commands
79
+
80
+ - `LIST SERVICES;`
81
+ - Lists all available services within the RAGFlow system.
82
+ - `SHOW SERVICE <id>;`
83
+ - Shows detailed status information for the service identified by `<id>`.
84
+
85
+
86
+ ### User Management Commands
87
+
88
+ - `LIST USERS;`
89
+ - Lists all users known to the system.
90
+ - `SHOW USER '<username>';`
91
+ - Shows details and permissions for the specified user. The username must be enclosed in single or double quotes.
92
+
93
+ - `CREATE USER <username> <password>;`
94
+ - Create user by username and password. The username and password must be enclosed in single or double quotes.
95
+
96
+ - `DROP USER '<username>';`
97
+ - Removes the specified user from the system. Use with caution.
98
+ - `ALTER USER PASSWORD '<username>' '<new_password>';`
99
+ - Changes the password for the specified user.
100
+ - `ALTER USER ACTIVE <username> <on/off>;`
101
+ - Changes the user to active or inactive.
102
+
103
+
104
+ ### Data and Agent Commands
105
+
106
+ - `LIST DATASETS OF '<username>';`
107
+ - Lists the datasets associated with the specified user.
108
+ - `LIST AGENTS OF '<username>';`
109
+ - Lists the agents associated with the specified user.
110
+
111
+ ### Meta-Commands
112
+
113
+ Meta-commands are prefixed with a backslash (`\`).
114
+
115
+ - `\?` or `\help`
116
+ - Shows help information for the available commands.
117
+ - `\q` or `\quit`
118
+ - Exits the CLI application.
119
+
120
+ ## Examples
121
+
122
+ ```commandline
123
+ admin> list users;
124
+ +-------------------------------+------------------------+-----------+-------------+
125
+ | create_date | email | is_active | nickname |
126
+ +-------------------------------+------------------------+-----------+-------------+
127
+ | Fri, 22 Nov 2024 16:03:41 GMT | jeffery@infiniflow.org | 1 | Jeffery |
128
+ | Fri, 22 Nov 2024 16:10:55 GMT | aya@infiniflow.org | 1 | Waterdancer |
129
+ +-------------------------------+------------------------+-----------+-------------+
130
+
131
+ admin> list services;
132
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
133
+ | extra | host | id | name | port | service_type |
134
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
135
+ | {} | 0.0.0.0 | 0 | ragflow_0 | 9380 | ragflow_server |
136
+ | {'meta_type': 'mysql', 'password': 'infini_rag_flow', 'username': 'root'} | localhost | 1 | mysql | 5455 | meta_data |
137
+ | {'password': 'infini_rag_flow', 'store_type': 'minio', 'user': 'rag_flow'} | localhost | 2 | minio | 9000 | file_store |
138
+ | {'password': 'infini_rag_flow', 'retrieval_type': 'elasticsearch', 'username': 'elastic'} | localhost | 3 | elasticsearch | 1200 | retrieval |
139
+ | {'db_name': 'default_db', 'retrieval_type': 'infinity'} | localhost | 4 | infinity | 23817 | retrieval |
140
+ | {'database': 1, 'mq_type': 'redis', 'password': 'infini_rag_flow'} | localhost | 5 | redis | 6379 | message_queue |
141
+ +-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
142
+ ```
@@ -0,0 +1,9 @@
1
+ README.md
2
+ admin_client.py
3
+ pyproject.toml
4
+ ragflow_cli.egg-info/PKG-INFO
5
+ ragflow_cli.egg-info/SOURCES.txt
6
+ ragflow_cli.egg-info/dependency_links.txt
7
+ ragflow_cli.egg-info/entry_points.txt
8
+ ragflow_cli.egg-info/requires.txt
9
+ ragflow_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ragflow-cli = admin_client:main
@@ -0,0 +1,4 @@
1
+ requests<3.0.0,>=2.30.0
2
+ beartype<0.19.0,>=0.18.5
3
+ pycryptodomex>=3.10.0
4
+ lark>=1.1.0
@@ -0,0 +1 @@
1
+ admin_client
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+