diff --git a/pool/db.php b/pool/db.php
index 146749b9..1902cc7b 100644
--- a/pool/db.php
+++ b/pool/db.php
@@ -103,13 +103,23 @@ function checkpass($user, $pass)
  return $rep;
 }
 #
+function getAllUsers()
+{
+ $flds = array();
+ $msg = msgEncode('allusers', 'all', $flds);
+ $rep = sendsockreply('getAllUsers', $msg);
+ if (!$rep)
+	dbdown();
+ return $rep;
+}
+#
 function getWorkers($user)
 {
  if ($user == false)
 	showIndex();
  $flds = array('username' => $user, 'stats' => 'Y');
  $msg = msgEncode('workers', 'work', $flds);
- $rep = sendsockreply('getworkers', $msg);
+ $rep = sendsockreply('getWorkers', $msg);
  if (!$rep)
 	dbdown();
  return $rep;
@@ -121,7 +131,7 @@ function getPayments($user)
 	showIndex();
  $flds = array('username' => $user);
  $msg = msgEncode('payments', 'pay', $flds);
- $rep = sendsockreply('getpayments', $msg);
+ $rep = sendsockreply('getPayments', $msg);
  if (!$rep)
 	dbdown();
  return $rep;
diff --git a/pool/page_stats.php b/pool/page_stats.php
new file mode 100644
index 00000000..11a4e5ce
--- /dev/null
+++ b/pool/page_stats.php
@@ -0,0 +1,71 @@
+Pool Stats';
+
+ $rep = getAllUsers();
+ $ans = repDecode($rep);
+
+ $pg .= "
\n";
+ $pg .= "";
+ $pg .= "| Username";
+ $pg .= " | Hash Rate";
+ $pg .= " | 
\n";
+ if ($ans['STATUS'] == 'ok')
+ {
+	$all = array();
+	$count = $ans['rows'];
+	for ($i = 0; $i < $count; $i++)
+	{
+		$all[] = array('username' => $ans['username'.$i],
+				'userid' => $ans['userid'.$i],
+				'u_hashrate1hr' => $ans['u_hashrate1hr'.$i]);
+	}
+
+	usort($all, 'allusersort');
+
+	foreach ($all as $arr)
+	for ($i = 0; $i < $count; $i++)
+	{
+		if (($i % 2) == 0)
+			$row = 'even';
+		else
+			$row = 'odd';
+
+		$pg .= "";
+		$pg .= '| '.$all[$i]['username'].'';
+		$uhr = $all[$i]['u_hashrate1hr'];
+		if ($uhr == '?')
+			$uhr = '?GHs';
+		else
+		{
+			$uhr /= 10000000;
+			if ($uhr < 100000)
+				$uhr = (round($uhr)/100).'GHs';
+			else
+				$uhr = (round($uhr/1000)/100).'THs';
+		}
+		$pg .= " | $uhr";
+		$pg .= " | 
\n";
+	}
+ }
+ $pg .= "
\n";
+
+ return $pg;
+}
+#
+function show_stats($menu, $name, $user)
+{
+ gopage(NULL, 'dostats', $menu, $name, $user);
+}
+#
+?>
diff --git a/src/ckdb.c b/src/ckdb.c
index a10089a9..a1255fca 100644
--- a/src/ckdb.c
+++ b/src/ckdb.c
@@ -946,6 +946,9 @@ static K_STORE *userstats_eos_store;
  * any worker with newest stats being older than USERSTATS_PER_S */
 #define USERSTATS_PER_S (int)(600 * 1.5)
 
+/* on the allusers page, show any with stats in the last 1hr */
+#define ALLUSERS_LIMIT_S 3600
+
 static char logname[512];
 #define LOGFILE(_msg) rotating_log(logname, _msg)
 
@@ -1526,7 +1529,6 @@ static K_ITEM *find_users(char *username)
 	return find_in_ktree(users_root, &look, cmp_users, ctx);
 }
 
-/* unused
 static K_ITEM *find_userid(int64_t userid)
 {
 	USERS users;
@@ -1540,7 +1542,6 @@ static K_ITEM *find_userid(int64_t userid)
 	look.data = (void *)(&users);
 	return find_in_ktree(userid_root, &look, cmp_userid, ctx);
 }
-*/
 
 static bool users_add(PGconn *conn, char *username, char *emailaddress, char *passwordhash,
 			tv_t *now, char *by, char *code, char *inet)
@@ -4926,6 +4927,112 @@ static char *cmd_workers(char *cmd, char *id, __maybe_unused tv_t *now, __maybe_
 	return buf;
 }
 
+static char *cmd_allusers(char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by,
+				__maybe_unused char *code, __maybe_unused char *inet)
+{
+	K_TREE *userstats_workername_root = new_ktree();
+	K_ITEM *us_item, *usw_item, *tmp_item, *u_item;
+	K_TREE_CTX usctx[1], uswctx[1];
+	char reply[1024] = "";
+	char tmp[1024];
+	char *buf;
+	size_t len, off;
+	int rows;
+	int64_t userid = -1;
+	double u_hashrate1hr = 0.0;
+
+	LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
+
+	// Find last records for each user/worker in ALLUSERS_LIMIT_S
+	// TODO: include pool_instance
+	us_item = last_in_ktree(userstats_root, usctx);
+	while (us_item && tvdiff(now, &(DATA_USERSTATS(us_item)->createdate)) < ALLUSERS_LIMIT_S) {
+		usw_item = find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, uswctx);
+		if (!usw_item) {
+			K_WLOCK(userstats_list);
+			usw_item = k_unlink_head(userstats_list);
+			K_WUNLOCK(userstats_list);
+
+			DATA_USERSTATS(usw_item)->userid = DATA_USERSTATS(us_item)->userid;
+			strcpy(DATA_USERSTATS(usw_item)->workername, DATA_USERSTATS(us_item)->workername);
+			DATA_USERSTATS(usw_item)->hashrate1hr = DATA_USERSTATS(us_item)->hashrate1hr;
+
+			userstats_workername_root = add_to_ktree(userstats_workername_root, usw_item, cmp_userstats_workername);
+		}
+		us_item = us_item->prev;
+	}
+
+	APPEND_REALLOC_INIT(buf, off, len);
+	APPEND_REALLOC(buf, off, len, "ok.");
+	rows = 0;
+	// Add up per user
+	usw_item = first_in_ktree(userstats_workername_root, uswctx);
+	while (usw_item) {
+		if (DATA_USERSTATS(usw_item)->userid != userid) {
+			if (userid != -1) {
+				u_item = find_userid(userid);
+				if (!u_item) {
+					LOGERR("%s() userid %"PRId64" ignored - userstats but not users",
+					       __func__, userid);
+				} else {
+					str_to_buf(DATA_USERS(u_item)->username, reply, sizeof(reply));
+					snprintf(tmp, sizeof(tmp), "username%d=%s%c", rows, reply, FLDSEP);
+					APPEND_REALLOC(buf, off, len, tmp);
+
+					bigint_to_buf(userid, reply, sizeof(reply));
+					snprintf(tmp, sizeof(tmp), "userid%d=%s%c", rows, reply, FLDSEP);
+					APPEND_REALLOC(buf, off, len, tmp);
+
+					double_to_buf(u_hashrate1hr, reply, sizeof(reply));
+					snprintf(tmp, sizeof(tmp), "u_hashrate1hr%d=%s%c", rows, reply, FLDSEP);
+					APPEND_REALLOC(buf, off, len, tmp);
+
+					rows++;
+				}
+			}
+			userid = DATA_USERSTATS(usw_item)->userid;
+			u_hashrate1hr = 0;
+		}
+		u_hashrate1hr += DATA_USERSTATS(usw_item)->hashrate1hr;
+
+		tmp_item = usw_item;
+		usw_item = tmp_item->next;
+
+		K_WLOCK(userstats_list);
+		k_add_head(userstats_list, tmp_item);
+		K_WUNLOCK(userstats_list);
+	}
+	if (userid != -1) {
+		u_item = find_userid(userid);
+		if (!u_item) {
+			LOGERR("%s() userid %"PRId64" ignored - userstats but not users",
+			       __func__, userid);
+		} else {
+			str_to_buf(DATA_USERS(u_item)->username, reply, sizeof(reply));
+			snprintf(tmp, sizeof(tmp), "username%d=%s%c", rows, reply, FLDSEP);
+			APPEND_REALLOC(buf, off, len, tmp);
+
+			bigint_to_buf(userid, reply, sizeof(reply));
+			snprintf(tmp, sizeof(tmp), "userid%d=%s%c", rows, reply, FLDSEP);
+			APPEND_REALLOC(buf, off, len, tmp);
+
+			double_to_buf(u_hashrate1hr, reply, sizeof(reply));
+			snprintf(tmp, sizeof(tmp), "u_hashrate1hr%d=%s%c", rows, reply, FLDSEP);
+			APPEND_REALLOC(buf, off, len, tmp);
+
+			rows++;
+		}
+	}
+
+	userstats_workername_root = free_ktree(userstats_workername_root, NULL);
+
+	snprintf(tmp, sizeof(tmp), "rows=%d", rows);
+	APPEND_REALLOC(buf, off, len, tmp);
+
+	LOGDEBUG("%s.ok.allusers", id);
+	return buf;
+}
+
 static char *cmd_sharelog(char *cmd, char *id, tv_t *now, char *by, char *code, char *inet)
 {
 	char reply[1024] = "";
@@ -5459,6 +5566,7 @@ enum cmd_values {
 	CMD_NEWID,
 	CMD_PAYMENTS,
 	CMD_WORKERS,
+	CMD_ALLUSERS,
 	CMD_HOMEPAGE,
 	CMD_DSP,
 	CMD_END
@@ -5490,6 +5598,7 @@ static struct CMDS {
 	{ CMD_NEWID,	"newid",	cmd_newid,	ACCESS_SYSTEM },
 	{ CMD_PAYMENTS,	"payments",	cmd_payments,	ACCESS_WEB },
 	{ CMD_WORKERS,	"workers",	cmd_workers,	ACCESS_WEB },
+	{ CMD_ALLUSERS,	"allusers",	cmd_allusers,	ACCESS_WEB },
 	{ CMD_HOMEPAGE,	"homepage",	cmd_homepage,	ACCESS_WEB },
 	{ CMD_DSP,	"dsp",		cmd_dsp,	ACCESS_SYSTEM },
 	{ CMD_END,	NULL,		NULL,		NULL }