Index: /branches/rel_apv_10_7/tools/update/ustacksystem.ks
===================================================================
--- /branches/rel_apv_10_7/tools/update/ustacksystem.ks	(revision 38821)
+++ /branches/rel_apv_10_7/tools/update/ustacksystem.ks	(working copy)
@@ -93,6 +93,8 @@
 # for azure-cli
 python3-3.9.20-1.el7.centos.x86_64
 azure-cli-2.38.2-1.el7.x86_64
+# for postgresql
+postgresql-server-9.2.24-9.el7_9.x86_64
 
 #below packages are dependence generated by yum
 polkit-pkla-compat-0.1-4.el7.x86_64
@@ -445,6 +447,20 @@
 rpm -ivh http://192.168.100.11/arrayepel.72/gnutls-3.7.11-0.x86_64.rpm --force --nodeps
 rpm -ivh http://192.168.100.11/arrayepel.72/chrony-4.6-0.x86_64.rpm --force --nodeps
 
+# for postgresql and prometheus
+LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/ca/lib/ pip3 install -i http://192.168.100.11/pypirepo/py3/simple psycopg psycopg_binary pandas Flask cryptography prometheus_client gevent concurrent-log-handler tzlocal --trusted-host 192.168.100.11
+mkdir -p /ca/pgsql
+chown postgres:postgres /ca/pgsql/
+su -c "/bin/initdb -D /ca/pgsql/ --auth-local peer --auth-host reject" postgres
+sed -i "s|^#log_directory = .*|log_directory = '/var/crash/ca_log/pg_log/'|" /ca/pgsql/postgresql.conf
+sed -i "s|^log_timezone.*|log_timezone = 'UTC'|" /ca/pgsql/postgresql.conf
+sed -i "s|^timezone.*|timezone = 'UTC'|" /ca/pgsql/postgresql.conf
+mkdir -p /etc/systemd/system/postgresql.service.d/
+echo "[Service]" > /etc/systemd/system/postgresql.service.d/override.conf
+echo "Environment=\"LD_LIBRARY_PATH=/usr/lib64/:$LD_LIBRARY_PATH\"" >> /etc/systemd/system/postgresql.service.d/override.conf
+echo "Environment=\"PGDATA=/ca/pgsql/\"" >> /etc/systemd/system/postgresql.service.d/override.conf
+
+
 # install azure-mgmt-compute azure-mgmt-network azure-identity azure-mgmt-resource psutil 
 LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/ca/lib/ pip3 install -i http://192.168.100.11/pypirepo/py3/simple azure-mgmt-compute azure-mgmt-network azure-identity azure-mgmt-resource psutil --trusted-host 192.168.100.11
 
Index: /branches/rel_apv_10_7/tools/ustack.spec
===================================================================
--- /branches/rel_apv_10_7/tools/ustack.spec	(revision 38821)
+++ /branches/rel_apv_10_7/tools/ustack.spec	(working copy)
@@ -115,6 +115,7 @@
 /usr/bin/cp -af %{ustack_rootdir}/anroot/ca %{buildroot}/
 /usr/bin/cp -af %{ustack_rootdir}/usr/click/tools/nagelfar132 %{buildroot}/ca/bin/
 
+/usr/bin/cp -af %{ustack_rootdir}/usr/click/tools/prometheus_client %{buildroot}/ca/bin/
 /usr/bin/cp -af %{ustack_rootdir}/usr/click/tools/azure %{buildroot}/ca/bin/
 
 install -Dm 0755 -t %{buildroot}/ca/bin \
Index: /branches/rel_apv_10_7/usr/click/bin/backend/Makefile
===================================================================
--- /branches/rel_apv_10_7/usr/click/bin/backend/Makefile	(revision 38821)
+++ /branches/rel_apv_10_7/usr/click/bin/backend/Makefile	(working copy)
@@ -148,7 +148,8 @@
 	-L${.OBJDIR}/../../lib/libuinet-atcp/lib/libuinet_memstat -luinet_memstat \
 	-L${.OBJDIR}/../../lib/libfetch -lfetch \
 	-L${.CURDIR}/../../lib/libsmanager -lsmanager \
-	-L${.CURDIR}/../../lib/libsessmgr_api -lsessmgr_api
+	-L${.CURDIR}/../../lib/libsessmgr_api -lsessmgr_api \
+	-L${.OBJDIR}/../../lib/libprometheus_cli -lprometheus_cli
 .if defined(ARM64)
 LDADD+= -lbsd \
 	-lkqueue
Index: /branches/rel_apv_10_7/usr/click/bin/backend/sys_tool.c
===================================================================
--- /branches/rel_apv_10_7/usr/click/bin/backend/sys_tool.c	(revision 38821)
+++ /branches/rel_apv_10_7/usr/click/bin/backend/sys_tool.c	(working copy)
@@ -1204,6 +1204,11 @@
 		CMD_NORMAL|CMD_ARRAYOS|CMD_SPROXY|CMD_GLOBAL, 
 		"#orchestrator configuration" 
 	},
+	{
+		write_prometheus_config,
+		CMD_NORMAL|CMD_ARRAYOS|CMD_SPROXY|CMD_GLOBAL, 
+		"#prometheus configuration" 
+	},
 
 	/*last entry is empty*/
     {
@@ -2073,6 +2078,10 @@
 		clear_ipgroup,
 		CMD_NORMAL|CMD_ARRAYOS|CMD_GLOBAL,
 	},
+	{
+		clear_prometheus_config,
+		CMD_NORMAL|CMD_ARRAYOS|CMD_SPROXY|CMD_GLOBAL 
+	},
 	/*last entry is empty*/ 
 	{ 
 		NULL,
Index: /branches/rel_apv_10_7/usr/click/bin/backend/users.c
===================================================================
--- /branches/rel_apv_10_7/usr/click/bin/backend/users.c	(revision 38821)
+++ /branches/rel_apv_10_7/usr/click/bin/backend/users.c	(working copy)
@@ -1802,7 +1802,13 @@
 int
 ui_adduser(char *name, char *pass, char *group)
 {
-	return ui_adduser_internal(name, pass, group, 1);
+	int ret = ui_adduser_internal(name, pass, group, 1);
+	if (ret == 0) {
+		char cmd[256];
+		snprintf(cmd, 256, "python3 /ca/bin/prometheus_client/update_accounts_table.py add_user --user %s", name);
+		system(cmd);
+	}
+	return ret;
 }
 
 int
@@ -2335,6 +2341,10 @@
 		update_webuiuser();
 	}
 
+	char cmd[256];
+	snprintf(cmd, 256, "python3 /ca/bin/prometheus_client/update_accounts_table.py del_user --user %s", name);
+	system(cmd);
+
 	return 0;
 }
 
Index: /branches/rel_apv_10_7/usr/click/etc/rc.local
===================================================================
--- /branches/rel_apv_10_7/usr/click/etc/rc.local	(revision 38821)
+++ /branches/rel_apv_10_7/usr/click/etc/rc.local	(working copy)
@@ -29,5 +29,8 @@
 #bind9 will be managed by the CLICK, but it may have been started by the systemctl. So, stop bind9 anyway
 systemctl stop bind9
 
+# Tokens database initialization
+/ca/bin/prometheus_client/tokens_db_init.sh
+
 #array os startup
 /ca/bin/array_startup.sh
Index: /branches/rel_apv_10_7/usr/click/lib/Makefile
===================================================================
--- /branches/rel_apv_10_7/usr/click/lib/Makefile	(revision 38821)
+++ /branches/rel_apv_10_7/usr/click/lib/Makefile	(working copy)
@@ -158,5 +158,6 @@
 SUBDIR+= libqat
 .endif
 SUBDIR+= libbreakpad
+SUBDIR+= libprometheus_cli
 
 .include <bsd.subdir.mk>
Index: /branches/rel_apv_10_7/usr/click/lib/libcaui/ca_ui.h
===================================================================
--- /branches/rel_apv_10_7/usr/click/lib/libcaui/ca_ui.h	(revision 38821)
+++ /branches/rel_apv_10_7/usr/click/lib/libcaui/ca_ui.h	(working copy)
@@ -644,4 +644,8 @@
 extern char *ip2ifinfo_v6(struct in6_addr *ip6, struct in6_addr * if_ip6, int *prefixlen);
 #endif
 
+/* prometheus configuration */
+extern char *write_prometheus_config(char *segment);
+extern int clear_prometheus_config();
+
 #endif /* _CA_UI_H_ */
Index: /branches/rel_apv_10_7/usr/click/lib/libparser/commands.pm
===================================================================
--- /branches/rel_apv_10_7/usr/click/lib/libparser/commands.pm	(revision 38821)
+++ /branches/rel_apv_10_7/usr/click/lib/libparser/commands.pm	(working copy)
@@ -22,6 +22,348 @@
 #At the top it is an array of hashesdeemed
 my @AoH = (
 
+# PROMETHEUS CONNAND START
+
+	# prometheus
+	{
+		#Menu objects are not that special, just a hash
+		obj_type => "MENU",
+		parent_menu => ".",
+		name => "prometheus" ,
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		help_string => "Prometheus client is used for event monitoring configuration.",
+		# each menu must have a unique name; this is the internal name
+		# used to build a command tree.
+		uniq_name => "root_prometheus",
+	},
+	# prometheus on
+	{
+		obj_type => "ITEM",
+		name => "on",
+		menu => "root_prometheus",
+		help_string => "Enable Prometheus client",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_on",
+		function_args => [],
+	},
+	# prometheus off
+	{
+		obj_type => "ITEM",
+		name => "off",
+		menu => "root_prometheus",
+		help_string => "Disable Prometheus client",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_off",
+		function_args => [],
+	},
+	# root_show_prometheus
+	{
+		obj_type => "MENU",
+		name => "prometheus",
+		parent_menu => "root_show",
+		uniq_name => "root_show_prometheus",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client data and configuration",
+	},
+	# show prometheus status
+	{
+		obj_type => "ITEM",
+		name => "status",
+		menu => "root_show_prometheus",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client status",
+		function_name => "show_prometheus",
+		function_args => [],
+	},
+	# root_prometheus_token
+	{
+		#Menu objects are not that special, just a hash
+		obj_type => "MENU",
+		parent_menu => "root_prometheus",
+		name => "token" ,
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		help_string => "Prometheus client token management.",
+		# each menu must have a unique name; this is the internal name
+		# used to build a command tree.
+		uniq_name => "root_prometheus_token",
+	},
+	# prometheus token new
+	{
+		obj_type => "ITEM",
+		name => "new",
+		menu => "root_prometheus_token",
+		help_string => "Generate a new token for Prometheus",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_new_token",
+		function_args => [],
+	},
+	# root_no_prometheus
+	{
+		obj_type => "MENU",
+		name => "prometheus",
+		parent_menu => "root_no",
+		uniq_name => "root_no_prometheus",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		help_string => "Delete Prometheus client settings",
+	},
+	# no prometheus token <token>
+	{
+		obj_type => "ITEM",
+		menu => "root_no_prometheus",
+		name => "token",
+		help_string => "Revoke Prometheus client token",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_del_token",
+		function_args => [
+			{
+				type => "STRING",
+				help_string => "Prometheus client token",
+				optional => "NO",
+			},
+		],
+	},
+	# root_show_prometheus_token
+	{
+		obj_type => "MENU",
+		name => "token",
+		parent_menu => "root_show_prometheus",
+		uniq_name => "root_show_prometheus_token",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client token data and configuration",
+	},
+	# show prometheus token all
+	{
+		obj_type => "ITEM",
+		name => "all",
+		menu => "root_show_prometheus_token",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client all token",
+		function_name => "prometheus_show_token_all",
+		function_args => [],
+	},
+	# show prometheus token valid
+	{
+		obj_type => "ITEM",
+		name => "valid",
+		menu => "root_show_prometheus_token",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client valid token",
+		function_name => "prometheus_show_token_valid",
+		function_args => [],
+	},
+	# show prometheus token expired
+	{
+		obj_type => "ITEM",
+		name => "expired",
+		menu => "root_show_prometheus_token",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client expired token",
+		function_name => "prometheus_show_token_expired",
+		function_args => [],
+	},
+	# prometheus port <port_number>
+	{
+		obj_type => "ITEM",
+		name => "port",
+		menu => "root_prometheus",
+		help_string => "Setting Prometheus client use port",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_set_port",
+		function_args => [
+			{
+				type => "U16",
+				help_string => "Prometheus client port (1025-65000, default = 9100)",
+				optional => "NO",
+				min => "1025",
+				max => "65000",
+			},
+		],
+	},
+	# show prometheus port
+	{
+		obj_type => "ITEM",
+		name => "port",
+		menu => "root_show_prometheus",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client port",
+		function_name => "prometheus_show_port",
+		function_args => [],
+	},
+	# root_prometheus_https
+	{
+		#Menu objects are not that special, just a hash
+		obj_type => "MENU",
+		parent_menu => "root_prometheus",
+		name => "https" ,
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		help_string => "Prometheus client https management.",
+		# each menu must have a unique name; this is the internal name
+		# used to build a command tree.
+		uniq_name => "root_prometheus_https",
+	},
+	# prometheus https on
+	{
+		obj_type => "ITEM",
+		name => "on",
+		menu => "root_prometheus_https",
+		help_string => "Enable Prometheus client https, it must be configured when 'prometheus off'.",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_https_on",
+		function_args => [],
+	},
+	# prometheus https off
+	{
+		obj_type => "ITEM",
+		name => "off",
+		menu => "root_prometheus_https",
+		help_string => "Disable Prometheus client https, it must be configured when 'prometheus off'.",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_https_off",
+		function_args => [],
+	},
+	# root_show_prometheus_https
+	{
+		obj_type => "MENU",
+		name => "https",
+		parent_menu => "root_show_prometheus",
+		uniq_name => "root_show_prometheus_https",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client https configuration",
+	},
+	# show prometheus https status
+	{
+		obj_type => "ITEM",
+		name => "status",
+		menu => "root_show_prometheus_https",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client https status",
+		function_name => "prometheus_show_https",
+		function_args => [],
+	},
+	# prometheus https ssl <key_url> <crt_url>
+	{
+		obj_type => "ITEM",
+		name => "ssl",
+		menu => "root_prometheus_https",
+		help_string => "Setting Prometheus client https ssl, it must be configured when 'prometheus off'.",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_https_ssl",
+		function_args => [
+			{
+				type => "STRING",
+				help_string => "private private key file for SSL. PEM format. URL or File Path.",
+				optional => "NO",
+			},
+			{
+				type => "STRING",
+				help_string => "SSL certificate file for https. PEM format. URL or File Path.",
+				optional => "NO",
+			},
+		],
+	},
+	# root_no_prometheus_https
+	{
+		obj_type => "MENU",
+		name => "https",
+		parent_menu => "root_no_prometheus",
+		uniq_name => "root_no_prometheus_https",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		help_string => "Delete Prometheus client https settings",
+	},
+	# no prometheus https ssl
+	{
+		obj_type => "ITEM",
+		menu => "root_no_prometheus_https",
+		name => "ssl",
+		help_string => "Delete Prometheus client https ssl settings, it must be configured when 'prometheus off'",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_del_https_ssl",
+		function_args => [],
+	},
+	# show prometheus https ssl
+	{
+		obj_type => "ITEM",
+		name => "ssl",
+		menu => "root_show_prometheus_https",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client ssl key and certificate",
+		function_name => "prometheus_show_https_ssl",
+		function_args => [],
+	},
+	# prometheus token ttl <minutes>
+	{
+		obj_type => "ITEM",
+		name => "ttl",
+		menu => "root_prometheus_token",
+		help_string => "Setting token expiration duration based on inactivity",
+		cmd_attribute => "CMD_ARRAYOS|CMD_SPROXY|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_CONFIG",
+		function_name => "prometheus_set_token_ttl",
+		function_args => [
+			{
+				type => "U16",
+				help_string => "Prometheus client token ttl (minute)(3-60, default = 5)",
+				optional => "NO",
+				min => "3",
+				max => "60",
+			},
+		],
+	},
+	# show prometheus token ttl
+	{
+		obj_type => "ITEM",
+		name => "ttl",
+		menu => "root_show_prometheus_token",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Display Prometheus client token",
+		function_name => "prometheus_show_token_ttl",
+		function_args => [],
+	},
+	# show prometheus log
+	{
+		obj_type => "ITEM",
+		name => "log",
+		menu => "root_show_prometheus",
+		cmd_attribute => "CMD_ARRAYOS|CMD_NORMAL|CMD_GLOBAL",
+		user_level => "CLI_LEVEL_ENABLE",
+		help_string => "Print Prometheus client log",
+		function_name => "prometheus_show_log",
+		function_args => [
+			{
+				type => "U32",
+				help_string => "Number of lines printed. If 0, print all log. (default = 0)",
+				optional => "YES",
+				default_value => "0",
+			},
+		],
+	},
+# PROMETHEUS CONNAND END
+
 # AZURE COMMAND START
 
 	# cloud
Index: /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/Makefile
===================================================================
--- /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/Makefile	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/Makefile	(working copy)
@@ -0,0 +1,10 @@
+LIB=	prometheus_cli
+SRCS=   prometheus_cli.c prometheus_cli.h
+INCS=	prometheus_cli.h
+
+CFLAGS+=-I${.CURDIR} \
+	-I${.CURDIR}/../libcaui \
+	-I${.CURDIR}/../libcautil
+
+CFLAGS+=-fPIC
+.include <bsd.lib.mk>
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/prometheus_cli.h
===================================================================
--- /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/prometheus_cli.h	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/prometheus_cli.h	(working copy)
@@ -0,0 +1,190 @@
+/**
+ * @file prometheus_cli.h
+ * @author Tseng Wei Kai
+ * @brief Prometheus CLI C library header file
+ * @date 2025-01-04
+ * 
+ * @copyright Copyright (c) 2025 Array Networks Inc. All rights reserved.
+ * 
+ */
+
+#ifndef _PROMETHEUS_CLI_H_
+#define _PROMETHEUS_CLI_H_
+
+/**
+ * @brief Start the Prometheus client service
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"prometheus on" to start the Prometheus client service.
+ */
+void prometheus_on();
+
+/**
+ * @brief Stop the Prometheus client service
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"prometheus off" to stop the Prometheus client service.
+ */
+void prometheus_off();
+
+/**
+ * @brief Print the status of the Prometheus client service
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"show prometheus status" to print the status of the Prometheus client service.
+ */
+void show_prometheus();
+
+/**
+ * @brief Create a unique token and bind it to the user
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"prometheus token new" to create a unique token and bind it to the user.
+ */
+void prometheus_new_token();
+
+/**
+ * @brief Revoke a token and bind it to the user
+ * 
+ * @param token Token
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"no prometheus token <token>" to revoke a token and bind it to the user.
+ */
+void prometheus_del_token(char *token);
+
+/**
+ * @brief Print all token info
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"show prometheus token all" to print all token info.
+ */
+void prometheus_show_token_all();
+
+/**
+ * @brief Print valid token info
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"show prometheus token valid" to print valid token info.
+ */
+void prometheus_show_token_valid();
+
+/**
+ * @brief Print expired token info
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"show prometheus token expired" to print expired token info.
+ */
+void prometheus_show_token_expired();
+
+/**
+ * @brief Set the port number of the Prometheus client service in the configuration file
+ * 
+ * @param port Port number
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"prometheus port <port number>" to Set the port number of the Prometheus client service in the configuration file.
+ */
+void prometheus_set_port(int port);
+
+/**
+ * @brief Print the port number of the Prometheus client service
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"show prometheus port" to Print the port number of the Prometheus client service.
+ */
+void prometheus_show_port();
+
+/**
+ * @brief Set the HTTPS status of the Prometheus client service to "on" in the configuration file
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"prometheus https on" to set the HTTPS status of the Prometheus client service to "on" in the configuration file.
+ */
+void prometheus_https_on();
+
+/**
+ * @brief Set the HTTPS status of the Prometheus client service to "off" in the configuration file
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"prometheus https off" to set the HTTPS status of the Prometheus client service to "off" in the configuration file.
+ */
+void prometheus_https_off();
+
+/**
+ * @brief Print the HTTPS status of the Prometheus client service
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"show prometheus https status" to print the HTTPS status of the Prometheus client service.
+ */
+void prometheus_show_https();
+
+/**
+ * @brief Set the HTTPS ssl of the Prometheus client service in the configuration file
+ * 
+ * @param key_url SSL private key url
+ * @param crt_url SSL certificate url
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"prometheus https ssl <key_url> <crt_url>" to set the HTTPS ssl of the Prometheus client service in the configuration file.
+ */
+void prometheus_https_ssl(char *key_url, char *crt_url);
+
+/**
+ * @brief Delete the private key and certificate
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"no prometheus https ssl" to delete the private key and certificate.
+ */
+void prometheus_del_https_ssl();
+
+/**
+ * @brief Print the contents of the private key and certificate
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"show prometheus https ssl" to print the contents of the private key and certificate.
+ */
+void prometheus_show_https_ssl();
+
+/**
+ * @brief Set the TTL (Time to Live) of the token in the configuration file
+ * 
+ * @param token_ttl TTL (Time to Live) of the token
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"prometheus token ttl <minutes>" to set the TTL (Time to Live) of the token in the configuration file.
+ */
+void prometheus_set_token_ttl(int minute);
+
+/**
+ * @brief Print the TTL (Time to Live) of the token
+ * 
+ * @details This function is typically triggered by the APV CLI command
+ * 			"show prometheus token ttl" to print the TTL (Time to Live) of the token.
+ */
+void prometheus_show_token_ttl();
+
+/**
+ * @brief Print log
+ * 
+ * @param lines Number of lines printed
+ */
+void prometheus_show_log(int lines);
+
+/**
+ * @brief Write the Prometheus client service configuration to the APV settings
+ * 
+ * @param segment Unknown, refer to previous code framework
+ * 
+ * @return APV configuration command
+ */
+char *write_prometheus_config(char *segment);
+
+/**
+ * @brief Initialize the Prometheus client service configuration in the APV settings
+ * 
+ * @return error code
+ */
+int clear_prometheus_config();
+
+#endif /*_PROMETHEUS_CLI_H_*/
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/prometheus_cli.c
===================================================================
--- /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/prometheus_cli.c	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/lib/libprometheus_cli/prometheus_cli.c	(working copy)
@@ -0,0 +1,246 @@
+/**
+ * @file prometheus_cli.c
+ * @author Tseng Wei Kai
+ * @brief Prometheus CLI C library
+ * @date 2025-01-04
+ * 
+ * @copyright Copyright (c) 2025 Array Networks Inc. All rights reserved.
+ * 
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ca_ui.h"
+
+/**
+ * @brief Run a command and print the result to stdio
+ * 
+ * @param cmd command
+ */
+static void
+rum_prometheus_cmd(char *cmd)
+{
+    FILE *fp;
+	fp = popen(cmd, "r");
+	if (fp == NULL) {
+		printf("Fail\n");
+		exit(1);
+	} else {
+		char buffer[1035];
+		 while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) {
+			printf("%s", buffer);
+		}
+		pclose(fp);
+	}
+}
+
+void 
+prometheus_on()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py set status --switch on");
+}
+
+void 
+prometheus_off()
+{
+	rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py set status --switch off");
+}
+
+void 
+show_prometheus()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py show status");
+}
+
+void 
+prometheus_new_token()
+{
+    char cmd[256];
+    snprintf(cmd, 256, "python3 /ca/bin/prometheus_client/update_tokens_table.py new --user %s", sess_state.account);
+    rum_prometheus_cmd(cmd);
+}
+
+void 
+prometheus_del_token(char *token)
+{
+	char cmd[256];
+	snprintf(cmd, 256, "python3 /ca/bin/prometheus_client/update_tokens_table.py del --user %s --token %s", sess_state.account, token);
+    rum_prometheus_cmd(cmd);
+}
+
+/**
+ * @brief Print the token info
+ * 
+ * @param filter all/valid/expired
+ */
+void 
+prometheus_show_token(char *filter)
+{
+	char cmd[256];
+	snprintf(cmd, 256, "python3 /ca/bin/prometheus_client/update_tokens_table.py show --user %s --filter %s", sess_state.account, filter);
+    rum_prometheus_cmd(cmd);
+}
+
+void 
+prometheus_show_token_all()
+{
+	prometheus_show_token("all");
+}
+
+void 
+prometheus_show_token_valid()
+{
+	prometheus_show_token("valid");
+}
+
+void 
+prometheus_show_token_expired()
+{
+	prometheus_show_token("expired");
+}
+
+void 
+prometheus_set_port(int port)
+{
+	char cmd[256];
+	snprintf(cmd, 256, "python3 /ca/bin/prometheus_client/update_client_config.py set port --port_number %d", port);
+    rum_prometheus_cmd(cmd);
+}
+
+void 
+prometheus_show_port()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py show port");
+}
+
+void 
+prometheus_https_on()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py set https_status --switch on");
+}
+
+void 
+prometheus_https_off()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py set https_status --switch off");
+}
+
+void 
+prometheus_show_https()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py show https_status");
+}
+
+void 
+prometheus_https_ssl(char *key_url, char *crt_url)
+{
+	char cmd[1025];
+	snprintf(cmd, 1025, "python3 /ca/bin/prometheus_client/update_client_config.py set ssl --key %s --crt %s", key_url, crt_url);
+    rum_prometheus_cmd(cmd);
+}
+
+void 
+prometheus_del_https_ssl()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py del ssl");
+}
+
+void 
+prometheus_show_https_ssl()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py show ssl");
+}
+
+void 
+prometheus_set_token_ttl(int minute)
+{
+	char cmd[256];
+	snprintf(cmd, 256, "python3 /ca/bin/prometheus_client/update_client_config.py set token_ttl --ttl %d", minute);
+    rum_prometheus_cmd(cmd);
+}
+
+void 
+prometheus_show_token_ttl()
+{
+    rum_prometheus_cmd("python3 /ca/bin/prometheus_client/update_client_config.py show token_ttl");
+}
+
+void 
+prometheus_show_log(int lines)
+{
+    char cmd[256];
+	snprintf(cmd, 256, "python3 /ca/bin/prometheus_client/prometheus_logger.py --lines %d", lines);
+    rum_prometheus_cmd(cmd);
+}
+
+char *
+write_prometheus_config(char *segment)
+{
+	char config[1024];
+	char *result_config;
+
+	int offset = 0;
+	char buffer[256];
+
+	FILE *fp;
+
+	// Prometheus client port number: 9100
+	fp = popen("python3 /ca/bin/prometheus_client/update_client_config.py show port", "r");
+	if (fp != NULL) {
+		if (fgets(buffer, sizeof(buffer), fp) != NULL) {
+			char* port_str = strstr(buffer, "number: ");
+			port_str += strlen("number: ");
+			int port_number = atoi(port_str);
+			offset += snprintf(config + offset, 1024 - offset, "prometheus port %d\n", port_number);
+		}
+		fclose(fp);
+	}
+
+	// Prometheus client https status: on
+	// Prometheus client https status: off
+	fp = popen("python3 /ca/bin/prometheus_client/update_client_config.py show https_status", "r");
+	if (fp != NULL) {
+		if (fgets(buffer, sizeof(buffer), fp) != NULL) {
+			char* status = strstr(buffer, "status: ");
+			status += strlen("status: ");
+			if (strncmp (status, "on", 2) == 0) {
+				offset += snprintf(config + offset, 1024 - offset, "prometheus https %s\n", "on");
+			} else {
+				offset += snprintf(config + offset, 1024 - offset, "prometheus https %s\n", "off");
+			}
+		}
+		fclose(fp);
+	}
+
+	// Prometheus client status: on
+	// Prometheus client status: off
+	fp = popen("python3 /ca/bin/prometheus_client/update_client_config.py show status", "r");
+	if (fp != NULL) {
+		if (fgets(buffer, sizeof(buffer), fp) != NULL) {
+			char* status = strstr(buffer, "status: ");
+			status += strlen("status: ");
+			if (strncmp (status, "on", 2) == 0) {
+				offset += snprintf(config + offset, 1024 - offset, "prometheus %s\n", "on");
+			} else {
+				offset += snprintf(config + offset, 1024 - offset, "prometheus %s\n", "off");
+			}
+		}
+		fclose(fp);
+	}
+
+
+
+	int len = strlen(config) + 1;
+	result_config = malloc(len);
+	snprintf(result_config, len, "%s", config);
+
+	return result_config;
+}
+
+int
+clear_prometheus_config()
+{
+	prometheus_off();
+	return 0;
+}
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_client_config.json
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_client_config.json	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_client_config.json	(working copy)
@@ -0,0 +1,8 @@
+{
+    "status": "off",
+    "port": 9100,
+    "token_ttl": 5,
+    "https_status": "off",
+    "ssl_key_path": "",
+    "ssl_crt_path": ""
+}
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_client_service.py
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_client_service.py	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_client_service.py	(working copy)
@@ -0,0 +1,236 @@
+#!/bin/python3
+
+"""
+@file prometheus_client_service.py
+@brief Provide Prometheus client service in APV
+@author Tseng Wei Kai
+@date 2025-01-04
+@copyright Copyright (c) 2025 Array Networks Inc. All rights reserved.
+"""
+from gevent import monkey; monkey.patch_all()
+
+import psutil
+import ssl
+import threading
+import time
+import update_client_config
+import update_tokens_table
+import prometheus_logger
+
+from flask import Flask, request, Response
+from gevent.pywsgi import WSGIServer
+from prometheus_client import CollectorRegistry, Gauge, generate_latest
+
+app = Flask(__name__)
+
+registry = CollectorRegistry()
+cpu_usage = Gauge(
+        'cpu_usage', 
+        'Current CPU load(%)', 
+        ['core'], 
+        registry=registry
+    )
+memory_status = Gauge(
+        'memory_status', 
+        'Represents the current memory usage status of the system, including total, used, available, and usage percentage. Unit in MB', 
+        ['status'], 
+        registry=registry
+    )
+disk_status = Gauge(
+        'disk_status', 
+        'Monitor the disk usage, including total, used, free, and usage percent. Unit in GB', 
+        ['status'], 
+        registry=registry
+    )
+network_throughput = Gauge(
+        'network_throughput', 
+        'Current network throughput (Mb/s) for all network interfaces.', 
+        ['interface'], 
+        registry=registry
+    )
+start_time_seconds = Gauge(
+        'start_time_seconds', 
+        'Start time of the APV since unix epoch in seconds.', 
+        registry=registry
+    )
+
+bytes_per_mb = 1024 ** 2
+bytes_per_gb = 1024 ** 3
+
+cpu_usage_result = []
+
+net_io_new, net_io_old = None, None
+throughput_time = 3.0
+
+# for development
+# BEARER_TOKEN = "mysecrettoken"
+
+def get_token():
+    """
+    @brief Retrieve Authorization from header
+    @return Authorization content
+    """
+    return request.headers.get('Authorization')
+
+def check_bearer_token(token):
+    """
+    @brief Check if the Authorization content is a Bearer token and if it is valid
+    @param token Authorization content
+    @return valid Bearer token
+    """
+    if not token:
+        return False
+    if token.startswith('Bearer '):
+        # for development
+        # return token.split(' ')[1] == BEARER_TOKEN
+        return update_tokens_table.token_is_valid(token.split(' ')[1])
+    return False
+
+def update_metrics():
+    """
+    @brief Update the Prometheus metrics content
+    """
+    update_cpu_usage()
+    update_memory_status()
+    update_disk_status()
+    update_network_throughput()
+    update_start_time_seconds()
+
+def update_memory_status():
+    """
+    @brief Update the Prometheus memory metrics
+    """
+    virtual_memory = psutil.virtual_memory()
+    memory_status.labels(status='total').set(round(virtual_memory.total / bytes_per_mb, 2))
+    memory_status.labels(status='used').set(round(virtual_memory.used / bytes_per_mb, 2))
+    memory_status.labels(status='available').set(round(virtual_memory.available / bytes_per_mb, 2))
+    memory_status.labels(status='usage').set(round(virtual_memory.percent, 2))
+
+def update_cpu_usage():
+    """
+    @brief Update the Prometheus CPU metrics
+    """
+    global cpu_usage_result
+    if len(cpu_usage_result) > 0:
+        for i in range(len(cpu_usage_result)):
+            cpu_usage.labels(core=f'core_{i}').set(round(cpu_usage_result[i], 2))
+        cpu_usage.labels(core='core_avg').set(round(sum(cpu_usage_result) / len(cpu_usage_result), 2))
+
+def get_cpu_usage():
+    """
+    @brief Regularly retrieve cpu_usage data
+    """
+    global cpu_usage_result
+    while True:
+        cpu_usage_result = psutil.cpu_percent(interval=1, percpu=True)
+        time.sleep(3)
+
+def update_disk_status():
+    """
+    @brief Update the Prometheus disk metrics
+    """
+    disk = psutil.disk_usage('/')
+    disk_status.labels(status='total').set(round(disk.total / bytes_per_gb, 2))
+    disk_status.labels(status='used').set(round(disk.used / bytes_per_gb, 2))
+    disk_status.labels(status='free').set(round(disk.free / bytes_per_gb, 2))
+    disk_status.labels(status='usage').set(round(disk.percent, 2))
+
+def update_network_throughput():
+    """
+    @brief Update the Prometheus network_throughput metrics
+    """
+    global net_io_new, net_io_old
+    up = 0
+    down = 0
+
+    if net_io_new is not None:
+        for io_new in net_io_new:
+            if ('atcp_veth' in io_new or 'atcp_vbond' in io_new) and io_new in net_io_old:
+                io_data_new = net_io_new[io_new]
+                io_data_old = net_io_old[io_new]
+                name = None
+                if 'atcp_veth' in io_new:
+                    name = io_new.replace('atcp_veth', 'port')
+                elif 'atcp_vbond' in io_new:
+                    name = io_new.replace('atcp_vbond', 'bond')
+
+                if name is not None:
+                    tmp_up = ((io_data_new.bytes_sent - io_data_old.bytes_sent) / bytes_per_mb) / throughput_time
+                    tmp_down = ((io_data_new.bytes_recv - io_data_old.bytes_recv) / bytes_per_mb) / throughput_time
+
+                    network_throughput.labels(interface=f'{name}_up').set(round(tmp_up, 2))
+                    network_throughput.labels(interface=f'{name}_down').set(round(tmp_down, 2))
+
+                    up += tmp_up
+                    down += tmp_down
+
+    network_throughput.labels(interface='total_up').set(round(up, 2))
+    network_throughput.labels(interface='total_down').set(round(down, 2))
+
+def get_network_throughput():
+    """
+    @brief Regularly retrieve network_throughput data
+    """
+    global net_io_new, net_io_old
+    net_io_new = psutil.net_io_counters(pernic=True)
+    net_io_old = net_io_new
+    while True:
+        time.sleep(throughput_time)
+        net_io_new, net_io_old = psutil.net_io_counters(pernic=True), net_io_new
+
+def update_start_time_seconds():
+    """
+    @brief Update the Prometheus start_time_seconds metrics
+    """
+    start_time_seconds.set(time.time() - psutil.boot_time())
+
+@app.route('/metrics')
+def metrics():
+    """
+    @brief Handle the /metrics route.
+    """
+
+    token = get_token()
+    if not check_bearer_token(token):
+        prometheus_logger.warning(f'{request.remote_addr} accessed /metrics using an invalid token ({token}).')
+        return Response('Unauthorized', status=401)
+
+    update_metrics()
+
+    prometheus_logger.info(f'{request.remote_addr} accessed /metrics using the token ({token}).')
+
+    return generate_latest(registry)
+
+if __name__ == '__main__':
+    update_metrics()
+
+    http_server = None
+    https_ssl = None
+    port_number = update_client_config.get_client_port()
+
+    if update_client_config.get_https_status():
+        # for development
+        # https_ssl = (update_client_config.get_ssl_crt_path(), update_client_config.get_ssl_key_path())
+        https_ssl = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+        https_ssl.load_cert_chain(
+            certfile=update_client_config.get_ssl_crt_path(),
+            keyfile=update_client_config.get_ssl_key_path()
+        )
+
+    # for development
+    if https_ssl is None:
+        # app.run(host='0.0.0.0', port=port_number)
+        http_server = WSGIServer(('0.0.0.0', port_number), app)
+    else:
+        # app.run(host='0.0.0.0', port=port_number, ssl_context=https_ssl)
+        http_server = WSGIServer(('0.0.0.0', port_number), app, ssl_context=https_ssl)
+
+    network_throughput_thread = threading.Thread(target=get_network_throughput)
+    network_throughput_thread.daemon = True
+    network_throughput_thread.start()
+
+    cpu_thread = threading.Thread(target=get_cpu_usage)
+    cpu_thread.daemon = True
+    cpu_thread.start()
+
+    http_server.serve_forever()
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_logger.py
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_logger.py	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_logger.py	(working copy)
@@ -0,0 +1,123 @@
+#!/bin/python3
+
+"""
+@file prometheus_logger.py
+@brief Log tool
+@author Tseng Wei Kai
+@date 2025-01-04
+@copyright Copyright (c) 2025 Array Networks Inc. All rights reserved.
+"""
+
+import argparse
+import logging
+from concurrent_log_handler import ConcurrentRotatingFileHandler
+
+LOG_FILE = '/var/crash/ca_log/prometheus_log/prometheus_log.log'
+BACKUP_COUNT = 10
+
+handler = ConcurrentRotatingFileHandler(LOG_FILE, 'a', maxBytes=10*1024*1024, backupCount=BACKUP_COUNT)
+formatter = logging.Formatter('%(asctime)s:%(levelname)s: %(message)s')
+handler.setFormatter(formatter)
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.INFO)
+logger.addHandler(handler)
+
+def log_message(level, message):
+    """
+    @brief Log all level logs
+    @param level Log level
+    @param message Log message
+    """
+    try:
+        if level == logging.DEBUG:
+            logger.debug(message)
+        elif level == logging.INFO:
+            logger.info(message)
+        elif level == logging.WARNING:
+            logger.warning(message)
+        elif level == logging.ERROR:
+            logger.error(message)
+        elif level == logging.CRITICAL:
+            logger.critical(message)
+    except Exception as e:
+        pass
+
+def debug(message):
+    """
+    @brief Log debug level logs
+    @param message Log message
+    """
+    log_message(logging.DEBUG, message)
+
+def info(message):
+    """
+    @brief Log info level logs
+    @param message Log message
+    """
+    log_message(logging.INFO, message)
+
+def warning(message):
+    """
+    @brief Log warning level logs
+    @param message Log message
+    """
+    log_message(logging.WARNING, message)
+
+def error(message):
+    """
+    @brief Log error level logs
+    @param message Log message
+    """
+    log_message(logging.ERROR, message)
+
+def critical(message):
+    """
+    @brief Log critical level logs
+    @param message Log message
+    """
+    log_message(logging.CRITICAL, message)
+
+def show_log(lines):
+    """
+    @brief Print log
+    @param lines Number of lines printed
+    """
+    print_count = lines
+    file_list = [LOG_FILE] + [f'{LOG_FILE}.{i+1}' for i in range(BACKUP_COUNT)]
+    print_list = []
+
+    for file_path in file_list:
+        try:
+            with open(file_path, 'r') as log_file:
+                log_lines = []
+                for line in log_file:
+                    log_lines.append(line.strip())
+
+                if lines == 0:
+                    print_list += log_lines[::-1]
+                else:
+                    log_lines_len = len(log_lines)
+                    if print_count >= log_lines_len:
+                        pass
+                    else:
+                        log_lines_len = print_count
+
+                    print_list += log_lines[-1:-log_lines_len-1:-1]
+                    print_count -= log_lines_len
+
+                    if print_count <= 0:
+                        break
+        except Exception as e:
+            pass
+
+    print('\n'.join(print_list))
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--lines', type=int, help='Number of lines printed. If 0, print all log.', default=0)
+
+    args = parser.parse_args()
+    show_log(args.lines)
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_service_monitor.py
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_service_monitor.py	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/tools/prometheus_client/prometheus_service_monitor.py	(working copy)
@@ -0,0 +1,26 @@
+#!/bin/python3
+
+"""
+@file prometheus_service_monitor.py
+@brief Monitor the Prometheus client service
+@author Tseng Wei Kai
+@date 2025-01-04
+@copyright Copyright (c) 2025 Array Networks Inc. All rights reserved.
+"""
+
+import time
+import psutil
+import update_client_config
+import prometheus_logger
+
+def main():
+    while update_client_config.get_status():
+        if not update_client_config.find_service(update_client_config.CLIENT_SERVICE):
+            prometheus_logger.error('Prometheus service stopped due to an unknown reason.')
+            prometheus_logger.info('Prometheus service is attempting to restart.')
+            update_client_config.start_client_service()
+
+        time.sleep(5)
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/prometheus_client/tokens_db_init.sh
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/prometheus_client/tokens_db_init.sh	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/tools/prometheus_client/tokens_db_init.sh	(working copy)
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+# Create Prometheus log directory
+if [ ! -d "/var/crash/ca_log/prometheus_log" ]; then
+	mkdir -p /var/crash/ca_log/prometheus_log
+fi
+
+# Create PostgreSQL log directory
+if [ ! -d "/var/crash/ca_log/pg_log" ]; then
+	mkdir -p /var/crash/ca_log/pg_log
+fi
+
+# Change the owner and group of the PostgreSQL log directory
+if [ "$(stat -c %U /var/crash/ca_log/pg_log)" != "postgres" ] || [ "$(stat -c %G /var/crash/ca_log/pg_log)" != "postgres" ]; then
+	chown postgres:postgres /var/crash/ca_log/pg_log
+fi
+
+# Restart the PostgreSQL service
+systemctl enable postgresql
+systemctl restart postgresql
+
+if [ -f /ca/bin/prometheus_client/pgsql_backup.sql ]; then
+	su -c "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ psql -f /ca/bin/prometheus_client/pgsql_backup.sql" postgres
+	mv /ca/bin/prometheus_client/pgsql_backup.sql /ca/bin/prometheus_client/pgsql_backup.sql_old
+else
+	# Create root superuser login with Peer Authentication
+	result=$(su -c "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ psql -tAc \"SELECT 1 FROM pg_roles WHERE rolname = 'root'\"" postgres 2>/dev/null)
+	if [ "$result" != "1" ]; then
+		su -c "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ psql -c \"CREATE USER root WITH SUPERUSER\"" postgres
+	fi
+
+	# Create the database prometheus_token under the root user
+	if ! LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ psql -lqt | cut -d \| -f 1 | grep -qw prometheus_token; then
+		LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ createdb -O root prometheus_token
+	fi
+
+	# Create the accounts table in the prometheus_token database
+	result=$(LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ psql -U root -d prometheus_token -tAc "SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'accounts');")
+	if [ "$result" == "f" ]; then
+		LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ psql -U root -d prometheus_token -c "CREATE TABLE accounts (id SERIAL PRIMARY KEY, username VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP DEFAULT NULL);"
+	fi
+
+	# Create the tokens table in the prometheus_token database
+	result=$(LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ psql -U root -d prometheus_token -tAc "SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'tokens');")
+	if [ "$result" == "f" ]; then
+		LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ psql -U root -d prometheus_token -c "CREATE TABLE tokens (id SERIAL PRIMARY KEY, token UUID UNIQUE NOT NULL, account_id INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP DEFAULT NULL, last_used_at TIMESTAMP DEFAULT NULL, is_expired BOOLEAN DEFAULT FALSE);"
+	fi
+fi
+
+python3 /ca/bin/prometheus_client/update_accounts_table.py boot_load
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_accounts_table.py
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_accounts_table.py	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_accounts_table.py	(working copy)
@@ -0,0 +1,108 @@
+#!/bin/python3
+
+"""
+@file update_accounts_table.py
+@brief Database table "accounts" interface
+@author Tseng Wei Kai
+@date 2025-01-04
+@copyright Copyright (c) 2025 Array Networks Inc. All rights reserved.
+"""
+
+import re
+import psycopg
+import argparse
+import update_tokens_table
+import prometheus_logger
+
+POSTGRESQL_CONNECT = 'dbname=prometheus_token user=root'
+CLIENT_CONFIG = '/ca/bin/prometheus_client/prometheus_client_config.json'
+
+POSTGRESQL_CONNECT = 'dbname=prometheus_token user=root'
+CONF_PATH = '/ca/conf/ca.conf'
+pattern = r'user\s+"([^"]+)"\s+"([^"]+)"\s+"([^"]+)"'
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('mode', type=str, help='Select mode (boot_load|add_user|del_user)')
+    parser.add_argument('--user', type=str, help='User name', default='')
+
+    args = parser.parse_args()
+    if args.mode == 'boot_load':
+        boot_load()
+    elif args.mode == 'add_user':
+        if len(args.user.strip()) > 0:
+            add_user(args.user.strip())
+    elif args.mode == 'del_user':
+        if len(args.user.strip()) > 0:
+            del_user(args.user.strip())
+
+def boot_load():
+    """
+    @brief The system boot updates user data based on the APV configuration file
+    """
+    # Read the APV configuration file
+    with open(CONF_PATH, 'r') as file:
+        lines = file.readlines()
+
+    # Find the APV users who have config privileges
+    for line in lines:
+        match = re.search(pattern, line)
+        if match:
+            username = match.group(1)
+            if len(username.strip()) > 0:
+                add_user(username.strip())
+
+def query_one_account_id(username):
+    """
+    @brief Query the user ID using the username
+    @param username User name
+    @return Id
+    """
+    result = None
+
+    # Connect to the prometheus_token database as the root user
+    with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+        with conn.cursor() as cur:
+            cur.execute("SELECT accounts.id FROM accounts WHERE username = %s AND deleted_at IS NULL", (username,))
+            result = cur.fetchone()
+            if result is not None:
+                result = result[0]
+
+    return result
+
+def add_user(username):
+    """
+    @brief Add using the username.
+    @param username User name
+    """
+    account_id = query_one_account_id(username)
+
+    if account_id is None:
+        # Connect to the prometheus_token database as the root user
+        with psycopg.connect('dbname=prometheus_token user=root') as conn:
+            with conn.cursor() as cur:
+                # Create a new account
+                cur.execute("INSERT INTO accounts (username) VALUES (%s)", (username,))
+                conn.commit()
+                prometheus_logger.info(f'Add a user, {username}.')
+
+def del_user(username):
+    """
+    @brief Delete using the username.
+    @param username User name
+    """
+    account_id = query_one_account_id(username)
+
+    if account_id is not None:
+        # Connect to the prometheus_token database as the root user
+        with psycopg.connect('dbname=prometheus_token user=root') as conn:
+            with conn.cursor() as cur:
+                # Update the deleted_at timestamp
+                cur.execute("UPDATE accounts SET deleted_at = CURRENT_TIMESTAMP WHERE username = %s AND deleted_at IS NULL", (username,))
+                conn.commit()
+                prometheus_logger.info(f'Revoke a user, {username}.')
+
+        update_tokens_table.revoke_user_token(account_id)
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_client_config.py
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_client_config.py	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_client_config.py	(working copy)
@@ -0,0 +1,578 @@
+#!/bin/python3
+
+"""
+@file update_client_config.py
+@brief Prometheus client service configuration file interface
+@author Tseng Wei Kai
+@date 2025-01-04
+@copyright Copyright (c) 2025 Array Networks Inc. All rights reserved.
+"""
+
+import os
+import re
+import shutil
+import argparse
+import json
+import requests
+import subprocess
+import psutil
+import time
+import prometheus_logger
+
+from ftplib import FTP
+from urllib.parse import urlparse
+
+from cryptography.x509 import load_pem_x509_certificate
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives import serialization
+
+CLIENT_CONFIG = '/ca/bin/prometheus_client/prometheus_client_config.json'
+
+KEY_TMP_PATCH = '/tmp/tmp_key.key'
+CRT_TMP_PATCH = '/tmp/tmp_crt.crt'
+
+KEY_SAVE_PATCH = '/ca/bin/prometheus_client/server.key'
+CRT_SAVE_PATCH = '/ca/bin/prometheus_client/server.crt'
+
+CLIENT_SERVICE = 'python3 /ca/bin/prometheus_client/prometheus_client_service.py &'
+MONITOR_SERVICE = 'python3 /ca/bin/prometheus_client/prometheus_service_monitor.py &'
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('mode', type=str, help='Select mode (show|set|del)')
+    parser.add_argument('function', type=str, help='Select function (status|port|https_status|ssl|token_ttl)')
+    parser.add_argument('--switch', type=str, help='For status and https_status (on|off)', default='')
+    parser.add_argument('--port_number', type=int, help='For port', default=9100)
+    parser.add_argument('--key', type=str, help='For ssl', default='')
+    parser.add_argument('--crt', type=str, help='For ssl', default='')
+    parser.add_argument('--ttl', type=int, help='For token_ttl', default=3)
+
+    args = parser.parse_args()
+    try:
+        if args.function == 'status':
+            if args.mode == 'show':
+                show_status()
+            elif args.mode == 'set':
+                set_status(args.switch)
+        elif args.function == 'port':
+            if args.mode == 'show':
+                show_client_port()
+            elif args.mode == 'set':
+                set_client_port(args.port_number)
+        elif args.function == 'https_status':
+            if args.mode == 'show':
+                show_https_status()
+            elif args.mode == 'set':
+                set_https_status(args.switch.strip())
+        elif args.function == 'ssl':
+            if args.mode == 'show':
+                show_ssl()
+            elif args.mode == 'set':
+                set_ssl(args.key, args.crt)
+            elif args.mode == 'del':
+                del_ssl()
+        elif args.function == 'token_ttl':
+            if args.mode == 'show':
+                show_token_ttl()
+            elif args.mode == 'set':
+                set_token_ttl(args.ttl)
+    except Exception as ex:
+        print(ex)
+
+def get_config(key):
+    """
+    @brief Get the value corresponding to the key from the configuration file
+    @param key Data index
+    @return Data value
+    """
+    with open(CLIENT_CONFIG, 'r', encoding='utf-8') as file:
+        data = json.load(file)
+        if key in data:
+            return data[key]
+    return None
+
+def set_config(key, val):
+    """
+    @brief Set the value using the key and save it to the configuration file
+    @param key Data index
+    @param val Data value
+    """
+    data = None
+    check_key = False
+
+    with open(CLIENT_CONFIG, 'r', encoding='utf-8') as file:
+        data = json.load(file)
+        if key in data:
+            check_key = True
+
+    if check_key:
+        data[key] = val
+        with open(CLIENT_CONFIG, 'w', encoding='utf-8') as file:
+            json.dump(data, file, ensure_ascii=False, indent=4)
+        prometheus_logger.info(f'Set {key} = {val}')
+
+# ==================== token_ttl ==================== #
+def get_token_ttl():
+    """
+    @brief Get the TTL (Time to Live) of the token from the configuration file
+    @return TTL (Time to Live) of the token
+    """
+    token_ttl = get_config('token_ttl')
+    if token_ttl is None:
+        # default
+        token_ttl = 5
+    return token_ttl
+
+def show_token_ttl():
+    """
+    @brief Print the TTL (Time to Live) of the token
+    """
+    print(f'{get_token_ttl()} minute(s)')
+
+def set_token_ttl(token_ttl):
+    """
+    @brief Set the TTL (Time to Live) of the token in the configuration file
+    @param token_ttl TTL (Time to Live) of the token
+    """
+    if 3 <= token_ttl <= 60:
+        set_config('token_ttl', token_ttl)
+    else:
+        print('The range of token_ttl is 3 to 60 minutes.')
+
+# ==================== status ==================== #
+def get_status():
+    """
+    @brief Get the status of the Prometheus client service from the configuration file
+    @return status of the Prometheus client service
+    """
+    status = get_config('status')
+    if status == 'on':
+        return True
+    return False
+
+def show_status():
+    """
+    @brief Print the status of the Prometheus client service
+    """
+    print(f'Prometheus client status: {"on" if get_status() else "off"}')
+
+def find_and_kill_service(running_cmd):
+    """
+    @brief Find and kill the running process
+    @param running_cmd Command for the process
+    """
+    for proc in psutil.process_iter(['pid', 'cmdline']):
+        try:
+            cmd = ' '.join(proc.info['cmdline'])
+            if cmd == running_cmd:
+                proc.terminate()
+                proc.wait()
+        except Exception as e:
+            pass
+
+def find_service(running_cmd):
+    """
+    @brief Find the running process
+    @param running_cmd Command for the process
+    @return Found it
+    """
+    for proc in psutil.process_iter(['pid', 'cmdline']):
+        try:
+            cmd = ' '.join(proc.info['cmdline'])
+            if cmd == running_cmd:
+                return True
+        except Exception as e:
+            pass
+    return False
+
+def open_firewalld_port(port):
+    """
+    @brief Open the firewall TCP port
+    @param port Port number
+    """
+    subprocess.Popen(f'firewall-cmd --zone=public --add-port={port}/tcp --permanent'.split(), shell=False, stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
+    time.sleep(0.5)
+    subprocess.Popen('firewall-cmd --reload'.split(), shell=False, stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
+
+def close_firewalld_port(port):
+    """
+    @brief Close the firewall TCP port
+    @param port Port number
+    """
+    subprocess.Popen(f'firewall-cmd --zone=public --remove-port={port}/tcp --permanent'.split(), shell=False, stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
+    time.sleep(0.5)
+    subprocess.Popen('firewall-cmd --reload'.split(), shell=False, stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
+
+def port_in_use(port):
+    """
+    @brief Check if the port is in use
+    @param port Port number
+    @return port is in use
+    """
+    for conn in psutil.net_connections(kind='inet'):
+        if conn.laddr.port == port:
+            return True
+    return False
+
+def start_client_service():
+    """
+    @brief Start the Prometheus client service
+    """
+    if not find_service(CLIENT_SERVICE):
+        if port_in_use(get_client_port()):
+            print(f'The {get_client_port()} port is already in use.')
+            return
+
+        ssl_match_flag = get_https_status()
+        if ssl_match_flag:
+            ssl_match_flag, _ = check_ssl_match()
+        else:
+            ssl_match_flag = True
+
+        if ssl_match_flag:
+            subprocess.Popen(CLIENT_SERVICE.split(), shell=False, stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
+            time.sleep(3)
+            if find_service(CLIENT_SERVICE):
+                open_firewalld_port(get_client_port())
+                if not find_service(MONITOR_SERVICE):
+                    subprocess.Popen(MONITOR_SERVICE.split(), shell=False, stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
+            else:
+                print('The Prometheus client service failed to start.')
+        else:
+            print('Please set the private key and certificate first.')
+    else:
+        print('The Prometheus client service is already running.')
+    set_config('status', 'on')
+
+def stop_client_service():
+    """
+    @brief Stop the Prometheus client service
+    """
+    if find_service(CLIENT_SERVICE):
+        find_and_kill_service(MONITOR_SERVICE)
+        find_and_kill_service(CLIENT_SERVICE)
+        close_firewalld_port(get_client_port())
+    else:
+        print('The Prometheus client service has been stopped.')
+    set_config('status', 'off')
+
+def set_status(switch):
+    """
+    @brief Start or stop the Prometheus client service
+    @param switch on/off
+    """
+    if switch in ['on', 'off']:
+        status = get_status()
+        real_status = find_service(CLIENT_SERVICE)
+        if switch == 'on':
+            start_client_service()
+        else:
+            stop_client_service()
+    else:
+        print("Setting on/off.")
+
+# ==================== port ==================== #
+def get_client_port():
+    """
+    @brief Get the port number of the Prometheus client service from the configuration file
+    @return port number of the Prometheus client service
+    """
+    client_port = get_config('port')
+    if client_port is None:
+        # default
+        client_port = 9100
+    return client_port
+
+def show_client_port():
+    """
+    @brief Print the port number of the Prometheus client service
+    """
+    print(f'Prometheus client port number: {get_client_port()}')
+
+def set_client_port(port_number):
+    """
+    @brief Set the port number of the Prometheus client service in the configuration file
+    @param port_number Port number
+    """
+    if 1025 <= port_number <= 65000:
+        if not get_status():
+            set_config('port', port_number)
+        else:
+            print('The Prometheus client service cannot be changed while it is running.')
+    else:
+        print('The range of port number is 1025 to 65000.')
+
+# ==================== https_status ==================== #
+def get_https_status():
+    """
+    @brief Get the HTTPS status of the Prometheus client service from the configuration file
+    @return HTTPS status of the Prometheus client service
+    """
+    https_status = get_config('https_status')
+    if https_status == 'on':
+        return True
+    return False
+
+def show_https_status():
+    """
+    @brief Print the HTTPS status of the Prometheus client service
+    """
+    print(f'Prometheus client https status: {"on" if get_https_status() else "off"}')
+
+def set_https_status(switch):
+    """
+    @brief Set the HTTPS status of the Prometheus client service in the configuration file
+    @param switch on/off
+    """
+    if switch in ['on', 'off']:
+        if get_status():
+            print('The Prometheus client service cannot be changed while it is running.')
+        else:
+            if switch == 'on':
+                flag, _ = check_ssl_match()
+                if flag:
+                    set_config('https_status', switch)
+                else:
+                    print('Please set the private key and certificate first.')
+            else:
+                set_config('https_status', switch)
+    else:
+        print("Setting on/off.")
+
+# ==================== ssl ==================== #
+def get_ssl_key_path():
+    """
+    @brief Get the SSL private key path from the configuration file
+    @return SSL private key path
+    """
+    return get_config('ssl_key_path')
+
+def get_ssl_crt_path():
+    """
+    @brief Get the SSL certificate path from the configuration file
+    @return SSL certificate path
+    """
+    return get_config('ssl_crt_path')
+
+def read_PEM(path, binary):
+    """
+    @brief Read the file in binary mode
+    @return Binary data
+    """
+    try:
+        with open(path, 'rb' if binary else 'r') as file:
+            content = file.read()
+            return content
+    except Exception as e:
+        pass
+
+    return ''
+
+def show_ssl():
+    """
+    @brief Print the contents of the private key and certificate
+    """
+    ssl_key_path = get_ssl_key_path()
+    key_content = read_PEM(ssl_key_path, False)
+
+    ssl_crt_path = get_ssl_crt_path()
+    crt_content = read_PEM(ssl_crt_path, False)
+
+    if key_content == '' or crt_content == '':
+        print('Please set the private key and certificate first.')
+    else:
+        print(f'The content of the private key.\n{key_content}')
+        print()
+        print(f'The certificate of the private key.\n{crt_content}')
+
+def del_ssl():
+    """
+    @brief Delete the private key and certificate
+    """
+    set_config('ssl_key_path', '')
+    set_config('ssl_crt_path', '')
+
+def check_ssl_match(ssl_key_path=None, ssl_crt_path=None):
+    """
+    @brief Check if the private key and certificate match and conform to the PEM format
+    @param ssl_key_path SSL private key path
+    @param ssl_crt_path SSL certificate path
+    @return Private key and certificate match
+    """
+    if ssl_key_path is None:
+        ssl_key_path = get_ssl_key_path()
+    key_content = read_PEM(ssl_key_path, True)
+
+    if ssl_crt_path is None:
+        ssl_crt_path = get_ssl_crt_path()
+    crt_content = read_PEM(ssl_crt_path, True)
+
+    try:
+        private_key = serialization.load_pem_private_key(key_content, password=None, backend=default_backend())
+    except Exception as e:
+        return False, "The content of the private key is incorrect. It does not conform to the PEM format."
+
+    try:
+        cert = load_pem_x509_certificate(crt_content, default_backend())
+        cert_public_key = cert.public_key()
+    except Exception as e:
+        return False, "The content of the certificate is incorrect. It does not conform to the PEM format."
+
+    message = b"Test message"
+    match_flag = False
+
+    if not match_flag:
+        try:
+            signature = private_key.sign(message, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256())
+            cert_public_key.verify(signature, message, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256())
+            match_flag = True
+        except Exception as e:
+            pass
+
+    if not match_flag:
+        try:
+            signature = private_key.sign(message, padding.PKCS1v15(), hashes.SHA256())
+            cert_public_key.verify(signature, message, padding.PKCS1v15(), hashes.SHA256())
+            match_flag = True
+        except Exception as e:
+            pass
+
+    if match_flag:
+        return True, "The private key and certificate match."
+    else:
+        return False, "The private key and certificate do not match."
+
+def is_url(path):
+    """
+    @brief Check if the path is in URL format
+    @param path Path
+    @return is URL
+    """
+    return re.match(r'^(http://|https://|ftp://|ftps://)', path) is not None
+
+def is_local_path(path):
+    """
+    @brief Check if the path is local path
+    @param path Path
+    @return is local path
+    """
+    return os.path.isabs(path) and os.path.isfile(path)
+
+def download_file_from_url(url, dest_path):
+    """
+    @brief Download a file from a URL
+    @param url Patch
+    @param dest_path Saved path
+    @return Download successful
+    """
+    try:
+        if url.startswith(('http://', 'https://')):
+            response = requests.get(url, stream=True)
+            response.raise_for_status()
+
+            total_size = int(response.headers.get('content-length', 0))
+            downloaded_size = 0
+
+            with open(dest_path, 'wb') as f:
+                for chunk in response.iter_content(chunk_size=1024):
+                    if chunk:
+                        f.write(chunk)
+                        downloaded_size += len(chunk)
+                        progress = (downloaded_size / total_size) * 100
+                        print(f"Downloading: {progress:.2f}% ({downloaded_size}/{total_size} bytes)", end='\r')
+
+        elif url.startswith(('ftp://', 'ftps://')):
+            parsed_url = urlparse(url)
+            host = parsed_url.hostname
+            path = parsed_url.path.lstrip("/")
+
+            user = parsed_url.username or 'anonymous'
+            passwd = parsed_url.password or ''
+
+            ftp = FTP(host)
+            ftp.login(user, passwd)
+
+            total_size = ftp.size(path)
+            downloaded_size = 0
+
+            def progress_callback(data):
+                nonlocal downloaded_size
+                downloaded_size += len(data)
+                progress = (downloaded_size / total_size) * 100
+                print(f"Downloading: {progress:.2f}% ({downloaded_size}/{total_size} bytes)", end='\r')
+
+            with open(dest_path, 'wb') as f:
+                ftp.retrbinary(f"RETR {path}", f.write, 1024, callback=progress_callback)
+            ftp.quit()
+
+        print(f"\nDownload completed successfully. {url}")
+    except Exception as e:
+        print(f"Error downloading file: {e}")
+        return False
+
+    return True
+
+def set_ssl(key_url, crt_url):
+    """
+    @brief Set the HTTPS ssl of the Prometheus client service in the configuration file
+    @param key_url SSL private key url
+    @param crt_url SSL certificate url
+    """
+    if get_status():
+        print('The Prometheus client service cannot be changed while it is running.')
+        return
+
+    key_url = key_url.strip()
+    crt_url = crt_url.strip()
+
+    key_flag = None
+    crt_flag = None
+
+    key_path = None
+    crt_path = None
+
+    if is_url(key_url):
+        key_flag = 'url'
+    elif is_local_path(key_url):
+        key_flag = 'local'
+        key_path = key_url
+    else:
+        print('The private key path is neither a URL nor a local path.')
+        return
+
+    if is_url(crt_url):
+        crt_flag = 'url'
+    elif is_local_path(crt_url):
+        crt_flag = 'local'
+        crt_path = crt_url
+    else:
+        print('The certificate path is neither a URL nor a local path.')
+        return
+
+    if key_flag == 'url':
+        key_path = KEY_TMP_PATCH
+        if not download_file_from_url(key_url, key_path):
+            print('The private key download failed.')
+            return
+
+    if crt_flag == 'url':
+        crt_path = CRT_TMP_PATCH
+        if not download_file_from_url(crt_url, crt_path):
+            print('The certificate download failed.')
+            return
+
+    match_flag, message = check_ssl_match(ssl_key_path=key_path, ssl_crt_path=crt_path)
+
+    if match_flag:
+        shutil.copy(key_path , KEY_SAVE_PATCH)
+        shutil.copy(crt_path , CRT_SAVE_PATCH)
+        set_config('ssl_key_path', KEY_SAVE_PATCH)
+        set_config('ssl_crt_path', CRT_SAVE_PATCH)
+        print(message)
+    else:
+        print(message)
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_tokens_table.py
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_tokens_table.py	(revision 0)
+++ /branches/rel_apv_10_7/usr/click/tools/prometheus_client/update_tokens_table.py	(working copy)
@@ -0,0 +1,317 @@
+#!/bin/python3
+
+"""
+@file update_tokens_table.py
+@brief Database table "tokens" interface
+@author Tseng Wei Kai
+@date 2025-01-04
+@copyright Copyright (c) 2025 Array Networks Inc. All rights reserved.
+"""
+
+import argparse
+import psycopg
+import pandas as pd
+import uuid
+import tzlocal
+import update_accounts_table
+import update_client_config
+import prometheus_logger
+
+POSTGRESQL_CONNECT = 'dbname=prometheus_token user=root'
+CLIENT_CONFIG = '/ca/bin/prometheus_client/prometheus_client_config.json'
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('mode', type=str, help='Select mode (show|new|del)')
+    parser.add_argument('--filter', type=str, help='Filters tokens by their status (all|valid|expired)', default='')
+    parser.add_argument('--user', type=str, help='User name', default='')
+    parser.add_argument('--token', type=str, help='Token', default='')
+
+    args = parser.parse_args()
+    try:
+        if args.mode == 'show' or args.filter in ['all', 'valid', 'expired']:
+            show_tokens(args.filter)
+        elif args.mode == 'new':
+            if len(args.user.strip()) > 0:
+                new_token(args.user.strip())
+        elif args.mode == 'del':
+            if len(args.user.strip()) > 0:
+                del_token(args.user.strip(), args.token.strip())
+    except Exception as ex:
+        print(ex)
+
+def get_new_uuid():
+    """
+    @brief Create a token that is unique and does not duplicate in the database
+    @return Token
+    """
+    # Connect to the prometheus_token database as the root user
+    with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+        with conn.cursor() as cur:
+            new_token = uuid.uuid4()
+            check_flag = False
+            while not check_flag:
+                cur.execute("SELECT token FROM tokens WHERE token = %s", (new_token,))
+                result = cur.fetchone()
+                if result is not None:
+                    new_token = uuid.uuid4()
+                else:
+                    check_flag = True
+
+    return new_token
+
+def print_token_result(query_result):
+    """
+    @brief Print the token query results
+    @param query_result Query results
+    """
+    columns = ['Id', 'User', 'Token', 'Created At', 'Expires At', 'Last Used At', 'Is Expired']
+    result = []
+    if query_result is None or len(query_result) == 0:
+        result = [(None, None, None, None, None, None, None, )]
+    else:
+        result = [(row[0], row[1].decode('ascii'), row[2], row[3], row[4], row[5], row[6],) for row in query_result]
+    df = pd.DataFrame(result, columns=columns)
+
+    # Get OS timezone
+    timezone_str = tzlocal.get_localzone().key
+
+    # The column width is automatically adjusted to prevent truncation.
+    pd.set_option('display.width', None)
+    # Time display formatting.
+    df['Created At'] = pd.to_datetime(df['Created At'], utc=True).dt.tz_convert(timezone_str).dt.strftime('%Y-%m-%d %H:%M:%S')
+    df['Expires At'] = pd.to_datetime(df['Expires At'], utc=True).dt.tz_convert(timezone_str).dt.strftime('%Y-%m-%d %H:%M:%S')
+    df['Last Used At'] = pd.to_datetime(df['Last Used At'], utc=True).dt.tz_convert(timezone_str).dt.strftime('%Y-%m-%d %H:%M:%S')
+    df = df.sort_values(by='Id')
+    print(df.to_string(index=False))
+
+def query_one_token_info(token):
+    """
+    @brief Query the token info using the token
+    @param token Token
+    @return Token info
+    """
+    result = None
+
+    # Connect to the prometheus_token database as the root user
+    with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+        with conn.cursor() as cur:
+            query = """
+            SELECT 
+                t.id, 
+                a.username, 
+                t.token, 
+                t.created_at, 
+                t.expires_at, 
+                t.last_used_at, 
+                t.is_expired 
+            FROM 
+                accounts a 
+            JOIN 
+                tokens t ON a.id = t.account_id 
+            WHERE 
+                t.token = %s;
+            """
+            cur.execute(query, (token, ))
+            result = cur.fetchall()
+
+    return result
+
+def insert_new_token(account_id, token, token_ttl):
+    """
+    @brief Insert the new token into the database
+    @param account_id User id
+    @param token Token
+    @param token_ttl TTL (Time to Live) of the token
+    @return Insertion successful
+    """
+    # Connect to the prometheus_token database as the root user
+    with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+        with conn.cursor() as cur:
+            interval_str = f"CURRENT_TIMESTAMP + INTERVAL '{token_ttl} minute'"
+
+            insert = f"""
+                INSERT INTO tokens (token, account_id, expires_at)
+                VALUES (%s, %s, {interval_str})
+            """
+            cur.execute(insert, (token, account_id, ))
+            if cur.rowcount > 0:
+                conn.commit()
+                return True
+    return False
+
+def revoke_token(token):
+    """
+    @brief Revoke the token in the database
+    @param token Token
+    @return Revoke successful
+    """
+    # Connect to the prometheus_token database as the root user
+    with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+        with conn.cursor() as cur:
+            update_query = """
+            UPDATE tokens 
+            SET 
+                expires_at = CURRENT_TIMESTAMP, 
+                is_expired = TRUE 
+            WHERE
+                token = %s;
+            """
+            cur.execute(update_query, (token, ))
+            if cur.rowcount > 0:
+                conn.commit()
+                return True
+    return False
+
+def revoke_user_token(account_id):
+    """
+    @brief Revoke user's token in the database
+    @param account_id User id
+    @return Revoke successful
+    """
+    if account_id is not None:
+        # Connect to the prometheus_token database as the root user
+        with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+            with conn.cursor() as cur:
+                update_query = """
+                UPDATE tokens 
+                SET 
+                    expires_at = CURRENT_TIMESTAMP, 
+                    is_expired = TRUE 
+                WHERE 
+                    account_id = %s;
+                """
+                cur.execute(update_query, (account_id, ))
+                if cur.rowcount > 0:
+                    conn.commit()
+                    return True
+    return False
+
+def show_tokens(filter):
+    """
+    @brief Print the token info
+    @param filter all/valid/expired
+    """
+    update_token_status()
+
+    # Connect to the prometheus_token database as the root user
+    with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+        with conn.cursor() as cur:
+            query = """
+            SELECT 
+                t.id, 
+                a.username, 
+                t.token, 
+                t.created_at, 
+                t.expires_at, 
+                t.last_used_at, 
+                t.is_expired 
+            FROM 
+                accounts a 
+            JOIN 
+                tokens t ON a.id = t.account_id
+            """
+            if filter == 'all':
+                query += ';'
+            elif filter == 'valid':
+                query += '\n WHERE t.is_expired = FALSE;'
+            elif filter == 'expired':
+                query += '\n WHERE t.is_expired = TRUE;'
+
+            # Filter tokens.
+            cur.execute(query)
+            result = cur.fetchall()
+
+            print_token_result(result)
+
+def new_token(username):
+    """
+    @brief Create a unique token and bind it to the user
+    @param username User name
+    """
+    update_token_status()
+
+    new_token = get_new_uuid()
+    # Verify if there are any existing valid accounts in the accounts table
+    account_id = update_accounts_table.query_one_account_id(username)
+    new_flag = False
+
+    if account_id is not None:
+        token_ttl = update_client_config.get_token_ttl()
+        new_flag = insert_new_token(account_id, new_token, token_ttl)
+    else:
+        print('User does not exist.')
+        prometheus_logger.warning(f'User({username}) does not exist.')
+        return
+
+    if new_flag:
+        result = query_one_token_info(new_token)
+        print_token_result(result)
+        prometheus_logger.info(f'{username} created a new token, which is {new_token}')
+    else:
+        print('Failed to generate a new token.')
+        prometheus_logger.warning(f'{username} failed to generate a new token.')
+
+def del_token(username, token):
+    """
+    @brief Revoke a token
+    @param username User name
+    @param token Token
+    """
+    update_token_status()
+
+    del_flag = revoke_token(token)
+
+    if del_flag:
+        result = query_one_token_info(token)
+        print_token_result(result)
+        prometheus_logger.info(f'{username} deleted a token, which is {token}')
+    if not del_flag:
+        print('Target token does not exist.')
+        prometheus_logger.warning(f'Target token ({token}) does not exist.')
+
+def token_is_valid(token):
+    """
+    @brief Check if the token is valid and update its expiration time
+    @param token Token
+    @return Token is valid
+    """
+    update_token_status()
+
+    # Connect to the prometheus_token database as the root user
+    with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+        with conn.cursor() as cur:
+            token_ttl = update_client_config.get_token_ttl()
+            interval_str = f"CURRENT_TIMESTAMP + INTERVAL '{token_ttl} minute'"
+            update_query = f"""
+            UPDATE tokens 
+            SET 
+                expires_at = {interval_str}, 
+                last_used_at = CURRENT_TIMESTAMP 
+            WHERE 
+                token = %s 
+                AND is_expired = FALSE;
+            """
+            try:
+                cur.execute(update_query, (token,))
+            except Exception as e:
+                pass
+            if cur.rowcount > 0:
+                conn.commit()
+                return True
+    return False
+
+def update_token_status():
+    """
+    @brief Check if the valid token has expired and update it
+    """
+    # Connect to the prometheus_token database as the root user
+    with psycopg.connect(POSTGRESQL_CONNECT) as conn:
+        with conn.cursor() as cur:
+            update_query = 'UPDATE tokens SET is_expired = TRUE WHERE is_expired = FALSE AND expires_at IS NOT NULL AND expires_at <= CURRENT_TIMESTAMP;'
+            cur.execute(update_query)
+            if cur.rowcount > 0:
+                conn.commit()
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
Index: /branches/rel_apv_10_7/usr/click/tools/upgrade_hooks.sh
===================================================================
--- /branches/rel_apv_10_7/usr/click/tools/upgrade_hooks.sh	(revision 38821)
+++ /branches/rel_apv_10_7/usr/click/tools/upgrade_hooks.sh	(working copy)
@@ -533,3 +533,21 @@
 # update webui graph db
 /usr/local/bin/python /caupgrade/ca/bin/webui_graphdb_update.py >> /var/log/webui_graphdb_update.log 2>&1
 
+# Copy PostgreSQL database and Prometheus config
+if [ -f /ca/bin/prometheus_client/server.crt ]; then
+	cp -f /ca/bin/prometheus_client/server.crt  /caupgrade/ca/bin/prometheus_client/server.crt
+fi
+
+if [ -f /ca/bin/prometheus_client/server.key ]; then
+	cp -f /ca/bin/prometheus_client/server.key  /caupgrade/ca/bin/prometheus_client/server.key
+fi
+
+if [ -f /ca/bin/prometheus_client/prometheus_client_config.json ]; then
+	cp -f /ca/bin/prometheus_client/prometheus_client_config.json  /caupgrade/ca/bin/prometheus_client/prometheus_client_config.json
+fi
+
+if [ -d /ca/pgsql/ ]; then
+	# Due to permission issues, you need to dump the data first before moving it.
+	su -c "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib64/ pg_dumpall -U postgres -f /tmp/pgsql_backup.sql" postgres
+	mv /tmp/pgsql_backup.sql /caupgrade/ca/bin/prometheus_client/pgsql_backup.sql
+fi
