diff --git a/model/model.go b/model/model.go
index ad1ade9..35e2dc2 100644
--- a/model/model.go
+++ b/model/model.go
@@ -56,10 +56,10 @@ type JobEventQueue struct {
 	JobEvent *JobEvent
 }
 type Worker struct {
-	Name      string
-	Ip        string
-	QueueName string
-	LastSeen  time.Time
+	Name      string    `json:"name"`
+	Ip        string    `json:"id"`
+	QueueName string    `json:"queue_name"`
+	LastSeen  time.Time `json:"last_seen"`
 }
 
 type ControlEvent struct {
diff --git a/server/repository/repository.go b/server/repository/repository.go
index 63844e9..883c6fb 100644
--- a/server/repository/repository.go
+++ b/server/repository/repository.go
@@ -32,6 +32,7 @@ type Repository interface {
 	AddVideo(ctx context.Context, video *model.Video) error
 	WithTransaction(ctx context.Context, transactionFunc func(ctx context.Context, tx Repository) error) error
 	GetWorker(ctx context.Context, name string) (*model.Worker, error)
+	GetWorkers(ctx context.Context) (*[]model.Worker, error)
 }
 
 type Transaction interface {
@@ -154,9 +155,9 @@ func (S *SQLRepository) GetWorker(ctx context.Context, name string) (worker *mod
 	if err != nil {
 		return nil, err
 	}
-	worker, err = S.getWorker(ctx, db, name)
-	return worker, err
+	return S.getWorker(ctx, db, name)
 }
+
 func (S *SQLRepository) getWorker(ctx context.Context, db Transaction, name string) (*model.Worker, error) {
 	rows, err := db.QueryContext(ctx, "SELECT * FROM workers WHERE name=$1", name)
 	if err != nil {
@@ -175,6 +176,31 @@ func (S *SQLRepository) getWorker(ctx context.Context, db Transaction, name stri
 	return &worker, err
 }
 
+func (S *SQLRepository) GetWorkers(ctx context.Context) (*[]model.Worker, error) {
+	db, err := S.getConnection(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return S.getWorkers(ctx, db)
+}
+
+func (S *SQLRepository) getWorkers(ctx context.Context, db Transaction) (*[]model.Worker, error) {
+	rows, err := db.QueryContext(ctx, "SELECT name, ip, queue_name, last_seen FROM workers")
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	workers := []model.Worker{}
+	for rows.Next() {
+		worker := model.Worker{}
+		rows.Scan(&worker.Name, &worker.Ip, &worker.QueueName, &worker.LastSeen)
+		workers = append(workers, worker)
+	}
+
+	return &workers, nil
+}
+
 func (S *SQLRepository) GetJob(ctx context.Context, uuid string) (video *model.Video, returnError error) {
 	db, err := S.getConnection(ctx)
 	if err != nil {
diff --git a/server/scheduler/scheduler.go b/server/scheduler/scheduler.go
index 783c962..28ccd3b 100644
--- a/server/scheduler/scheduler.go
+++ b/server/scheduler/scheduler.go
@@ -36,6 +36,7 @@ type Scheduler interface {
 	GetUploadJobWriter(ctx context.Context, uuid string) (*UploadJobStream, error)
 	GetDownloadJobWriter(ctx context.Context, uuid string) (*DownloadJobStream, error)
 	GetChecksum(ctx context.Context, uuid string) (string, error)
+	GetWorkers(ctx context.Context) (*[]model.Worker, error)
 }
 
 type SchedulerConfig struct {
@@ -424,6 +425,10 @@ func (R *RuntimeScheduler) GetChecksum(ctx context.Context, uuid string) (string
 	return checksum, nil
 }
 
+func (R *RuntimeScheduler) GetWorkers(ctx context.Context) (*[]model.Worker, error) {
+	return R.repo.GetWorkers(ctx)
+}
+
 func (S *RuntimeScheduler) stop() {
 
 }
diff --git a/server/web/ui/src/App.tsx b/server/web/ui/src/App.tsx
index 28f7321..ec7374f 100644
--- a/server/web/ui/src/App.tsx
+++ b/server/web/ui/src/App.tsx
@@ -2,6 +2,7 @@
 
 import React, { useState } from 'react';
 import JobTable from './JobTable';
+import WorkerTable from './WorkerTable';
 import useMedia from './hooks/useMedia';
 import Navigation from './Navbar';
 
@@ -37,6 +38,12 @@ const App: React.FC = () => {
     </div>
   );
 
+  const Workers: React.FC = () => (
+    <div className="content-container">
+      {showJobTable && <WorkerTable token={token} setShowJobTable={setShowJobTable} />}
+    </div>
+  );
+
   const [userTheme, setUserTheme] = useLocalStorage<themeSetting>(themeLocalStorageKey, 'auto');
   const browserHasThemes = useMedia('(prefers-color-scheme)');
   const browserWantsDarkTheme = useMedia('(prefers-color-scheme: dark)');
@@ -86,6 +93,7 @@ const App: React.FC = () => {
             <Routes>
               <Route path="/" element={<Navigate to="/jobs" replace />} />
               <Route path="/jobs" element={<Jobs />} />
+              <Route path="/workers" element={<Workers />} />
             </Routes>
           </div>
       </Router>
diff --git a/server/web/ui/src/Navbar.tsx b/server/web/ui/src/Navbar.tsx
index a128aea..91de760 100644
--- a/server/web/ui/src/Navbar.tsx
+++ b/server/web/ui/src/Navbar.tsx
@@ -25,6 +25,11 @@ const CollapseNav: React.FC<CollapseNavProps> = ({ isOpen, className, id }) => (
           Jobs
         </NavLink>
       </NavItem>
+      <NavItem>
+        <NavLink tag={Link} to="/workers">
+          Workers
+        </NavLink>
+      </NavItem>
       <NavItem>
         <NavLink href="https://github.com/pando85/transcoder" title="GitHub">
           <GitHubIcon />
diff --git a/server/web/ui/src/WorkerTable.tsx b/server/web/ui/src/WorkerTable.tsx
new file mode 100644
index 0000000..cd8cdcb
--- /dev/null
+++ b/server/web/ui/src/WorkerTable.tsx
@@ -0,0 +1,77 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import {
+  Table,
+  TableBody,
+  TableCell,
+  TableHead,
+  TableRow,
+  CircularProgress,
+} from '@mui/material';
+
+interface Worker {
+  name: string;
+  id: string;
+  queue_name: string;
+  last_seen: string; // Assuming 'last_seen' is a string for simplicity
+}
+
+interface WorkerTableProps {
+  token: string;
+  setShowJobTable: React.Dispatch<React.SetStateAction<boolean>>;
+}
+
+const WorkersTable: React.FC<WorkerTableProps> = ({ token, setShowJobTable }) => {
+  const [workers, setWorkers] = useState<Worker[]>([]);
+  const [loading, setLoading] = useState<boolean>(false);
+
+  useEffect(() => {
+    const fetchWorkers = async () => {
+      try {
+        setLoading(true);
+        const response = await axios.get('/api/v1/workers',
+          {
+            headers: {
+              Authorization: `Bearer ${token}`,
+            },
+          });
+        setWorkers(response.data);
+      } catch (error) {
+        console.error('Error fetching workers:', error);
+        setShowJobTable(false);
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchWorkers();
+  }, []);
+
+  return (
+    <div>
+      <Table>
+        <TableHead>
+          <TableRow>
+            <TableCell>Name</TableCell>
+            <TableCell>ID</TableCell>
+            <TableCell>Queue Name</TableCell>
+            <TableCell>Last Seen</TableCell>
+          </TableRow>
+        </TableHead>
+        <TableBody>
+          {workers.map((worker) => (
+            <TableRow key={worker.id}>
+              <TableCell>{worker.name}</TableCell>
+              <TableCell>{worker.id}</TableCell>
+              <TableCell>{worker.queue_name}</TableCell>
+              <TableCell>{worker.last_seen}</TableCell>
+            </TableRow>
+          ))}
+        </TableBody>
+      </Table>
+      {loading && <CircularProgress />}
+    </div>
+  );
+};
+
+export default WorkersTable;
diff --git a/server/web/web.go b/server/web/web.go
index c5daa1a..c78c4d3 100644
--- a/server/web/web.go
+++ b/server/web/web.go
@@ -187,6 +187,16 @@ loop:
 	}
 }
 
+func (w *WebServer) getWorkers(c *gin.Context) {
+	workers, err := w.scheduler.GetWorkers(w.ctx)
+	if err != nil {
+		webError(c, err, http.StatusInternalServerError)
+		return
+	}
+
+	c.JSON(http.StatusOK, workers)
+}
+
 func (w *WebServer) checksum(c *gin.Context) {
 	id := c.Param("id")
 	if id == "" {
@@ -234,6 +244,8 @@ func NewWebServer(config WebServerConfig, scheduler scheduler.Scheduler) *WebSer
 	api.GET("/job/:id/checksum", webServer.checksum)
 	api.POST("/job/:id/upload", webServer.upload)
 
+	api.GET("/workers/", webServer.AuthFunc(webServer.getWorkers))
+
 	ui.AddRoutes(r)
 
 	return webServer