diff --git a/src/App.vue b/src/App.vue
index 30e6887148b2cffac09f1b9dd14a71a5b549b457..65c7b0dfe57e1c361aa9ea7d36cad82395b59ca6 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -63,6 +63,13 @@ onBeforeMount(() => {
       !(store.rewiewer || store.admin)
     ) {
       return { name: "dashboard" };
+    } else if (
+      to.meta.requiresMaintainerRole &&
+      !(store.resourceMaintainer || store.admin)
+    ) {
+      return { name: "dashboard" };
+    } else if (to.meta.adminRole && !store.admin) {
+      return { name: "dashboard" };
     }
   });
   nameRepository.loadNameMapping();
diff --git a/src/components/NavbarTop.vue b/src/components/NavbarTop.vue
index 20cad27ce06b4af13c5c6cc83fc880ba6005b955..37a3540c874d27e715d88a1b9e3b8e32cb2acbf1 100644
--- a/src/components/NavbarTop.vue
+++ b/src/components/NavbarTop.vue
@@ -214,7 +214,13 @@ watch(
               <li><a class="dropdown-item disabled" href="#">User</a></li>
               <li><a class="dropdown-item disabled" href="#">Bucket</a></li>
               <li><a class="dropdown-item disabled" href="#">Workflow</a></li>
-              <li><a class="dropdown-item disabled" href="#">Resource</a></li>
+              <li>
+                <router-link
+                  class="dropdown-item"
+                  :to="{ name: 'admin-resources' }"
+                  >Resources
+                </router-link>
+              </li>
             </ul>
           </li>
         </ul>
@@ -271,7 +277,7 @@ watch(
     </nav>
   </header>
   <bootstrap-modal
-    modal-i-d="advancedUsageModal"
+    modal-id="advancedUsageModal"
     modal-label="Advanced Usage Modal"
     v-if="store.authenticated"
     size-modifier="lg"
diff --git a/src/components/admin/ResourceVersionInfoModal.vue b/src/components/admin/ResourceVersionInfoModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1a774636382d2cf6d2ef52adb925e5f138041593
--- /dev/null
+++ b/src/components/admin/ResourceVersionInfoModal.vue
@@ -0,0 +1,262 @@
+<script setup lang="ts">
+import BootstrapModal from "@/components/modals/BootstrapModal.vue";
+import type { ResourceVersionOut, ResourceOut } from "@/client/resource";
+import CopyToClipboardIcon from "@/components/CopyToClipboardIcon.vue";
+import dayjs from "dayjs";
+import { computed } from "vue";
+import { useNameStore } from "@/stores/names";
+
+const props = defineProps<{
+  modalId: string;
+  resourceVersionIndex: number;
+  resource?: ResourceOut;
+}>();
+
+const nameRepository = useNameStore();
+
+const resourceVersion = computed<ResourceVersionOut | undefined>(
+  () => props.resource?.versions[props.resourceVersionIndex],
+);
+</script>
+
+<template>
+  <bootstrap-modal
+    :modal-id="props.modalId"
+    modal-label="Resource Version Info Modal"
+    size-modifier="lg"
+  >
+    <template #header
+      >Resource Version
+      <span v-if="resourceVersion">{{ resourceVersion.release }}</span>
+    </template>
+    <template #body>
+      <h5>Resource</h5>
+      <div class="mb-3 row">
+        <div class="col-8">
+          <label
+            for="resource-version-info-modal-resource-id"
+            class="form-label"
+            >ID</label
+          >
+          <div class="input-group">
+            <input
+              type="text"
+              class="form-control"
+              id="resource-version-info-modal-resource-id"
+              readonly
+              :value="props.resource?.resource_id"
+            />
+            <span class="input-group-text"
+              ><copy-to-clipboard-icon
+                :text="props.resource?.resource_id ?? ''"
+            /></span>
+          </div>
+        </div>
+        <div class="col-4">
+          <label
+            for="resource-version-info-modal-resource-name"
+            class="form-label"
+            >Name</label
+          >
+          <div class="input-group">
+            <input
+              type="text"
+              class="form-control"
+              id="resource-version-info-modal-resource-name"
+              readonly
+              :value="props.resource?.name"
+            />
+          </div>
+        </div>
+      </div>
+      <div class="mb-3 row">
+        <div class="col-8">
+          <label
+            for="resource-version-info-modal-maintainer-id"
+            class="form-label"
+            >Maintainer ID</label
+          >
+          <div class="input-group">
+            <input
+              type="text"
+              class="form-control"
+              id="resource-version-info-modal-maintainer-id"
+              readonly
+              :value="props.resource?.maintainer_id"
+            />
+            <span class="input-group-text"
+              ><copy-to-clipboard-icon
+                :text="props.resource?.maintainer_id ?? ''"
+            /></span>
+          </div>
+        </div>
+        <div class="col-4">
+          <label
+            for="resource-version-info-modal-maintainer-name"
+            class="form-label"
+            >Maintainer Name</label
+          >
+          <div class="input-group">
+            <input
+              type="text"
+              class="form-control"
+              id="resource-version-info-modal-maintainer-name"
+              readonly
+              :value="nameRepository.getName(props.resource?.maintainer_id)"
+            />
+          </div>
+        </div>
+      </div>
+      <div class="mb-3">
+        <label
+          for="resource-version-info-modal-resource-description"
+          class="form-label"
+          >Description</label
+        >
+        <div class="input-group">
+          <textarea
+            class="form-control"
+            id="resource-version-info-modal-resource-description"
+            readonly
+            rows="2"
+            :value="props.resource?.description"
+          />
+        </div>
+      </div>
+      <div class="mb-5">
+        <label
+          for="resource-version-info-modal-resource-source"
+          class="form-label"
+          >Source</label
+        >
+        <div class="input-group">
+          <input
+            type="text"
+            class="form-control"
+            id="resource-version-info-modal-resource-source"
+            readonly
+            :value="props.resource?.source"
+          />
+        </div>
+      </div>
+      <h5>Resource Version</h5>
+      <div class="mb-3 row">
+        <div class="col-8">
+          <label
+            for="resource-version-info-modal-resource-version-id"
+            class="form-label"
+            >Resource Version ID</label
+          >
+          <div class="input-group">
+            <input
+              type="text"
+              class="form-control"
+              id="resource-version-info-modal-resource-version-id"
+              readonly
+              :value="resourceVersion?.resource_version_id"
+            />
+            <span class="input-group-text"
+              ><copy-to-clipboard-icon
+                :text="resourceVersion?.resource_version_id ?? ''"
+            /></span>
+          </div>
+        </div>
+        <div class="col-4">
+          <label
+            for="resource-version-info-modal-resource-version-release"
+            class="form-label"
+            >Resource Version Release</label
+          >
+          <div class="input-group">
+            <input
+              type="text"
+              class="form-control"
+              id="resource-version-info-modal-resource-version-release"
+              readonly
+              :value="resourceVersion?.release"
+            />
+          </div>
+        </div>
+      </div>
+      <div class="mb-3 row">
+        <div class="col-4">
+          <label
+            for="resource-version-info-modal-resource-version-status"
+            class="form-label"
+            >Status</label
+          >
+          <div class="input-group">
+            <input
+              type="text"
+              class="form-control"
+              id="resource-version-info-modal-resource-version-status"
+              readonly
+              :value="resourceVersion?.status"
+            />
+          </div>
+        </div>
+        <div class="col-4">
+          <label
+            for="resource-version-info-modal-resource-version-timestamp"
+            class="form-label"
+            >Created At</label
+          >
+          <div class="input-group">
+            <input
+              type="datetime-local"
+              class="form-control"
+              id="resource-version-info-modal-resource-version-timestamp"
+              readonly
+              :value="
+                dayjs
+                  .unix(resourceVersion?.created_at ?? 0)
+                  .format('YYYY-MM-DDTHH:mm')
+              "
+            />
+          </div>
+        </div>
+      </div>
+      <div class="mb-3">
+        <label
+          for="resource-version-info-modal-resource-version-s3-path"
+          class="form-label"
+          >S3 Path</label
+        >
+        <div class="input-group">
+          <input
+            type="text"
+            class="form-control"
+            id="resource-version-info-modal-resource-version-s3-path"
+            readonly
+            :value="resourceVersion?.s3_path"
+          />
+          <span class="input-group-text"
+            ><copy-to-clipboard-icon :text="resourceVersion?.s3_path ?? ''"
+          /></span>
+        </div>
+      </div>
+      <div class="mb-3">
+        <label
+          for="resource-version-info-modal-resource-version-cluster-path"
+          class="form-label"
+          >Cluster Path</label
+        >
+        <div class="input-group">
+          <input
+            type="text"
+            class="form-control"
+            id="resource-version-info-modal-resource-version-cluster-path"
+            readonly
+            :value="resourceVersion?.cluster_path"
+          />
+          <span class="input-group-text"
+            ><copy-to-clipboard-icon
+              :text="resourceVersion?.cluster_path ?? ''"
+          /></span>
+        </div>
+      </div>
+    </template>
+  </bootstrap-modal>
+</template>
+
+<style scoped></style>
diff --git a/src/components/modals/BootstrapModal.vue b/src/components/modals/BootstrapModal.vue
index 93e2a516247693235ce2f35e8001cd6c250d0a9e..bb4940543ce0dbab9669e28f299d0a443ab74989 100644
--- a/src/components/modals/BootstrapModal.vue
+++ b/src/components/modals/BootstrapModal.vue
@@ -2,7 +2,7 @@
 import { computed } from "vue";
 
 const props = defineProps<{
-  modalID: string;
+  modalId: string;
   modalLabel: string;
   staticBackdrop?: boolean;
   sizeModifier?: string; // https://getbootstrap.com/docs/5.3/components/modal/#optional-sizes, e.g. sm, lg and xl
@@ -19,7 +19,7 @@ const modalSizeClass = computed<string>(() => {
 <template>
   <div
     class="modal"
-    :id="modalID"
+    :id="modalId"
     tabindex="-1"
     :aria-labelledby="modalLabel"
     aria-hidden="true"
diff --git a/src/components/modals/DeleteModal.vue b/src/components/modals/DeleteModal.vue
index b51c55f02d5f6c357fc758f0d5311cc453eaa30c..0a7c662f06a0e2e73e43cf3d81b8719d846c54ed 100644
--- a/src/components/modals/DeleteModal.vue
+++ b/src/components/modals/DeleteModal.vue
@@ -34,7 +34,7 @@ onMounted(() => {
 
 <template>
   <bootstrap-modal
-    :modalID="props.modalID"
+    :modalId="props.modalID"
     :static-backdrop="true"
     modal-label="Confirm Delete Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/modals/SearchUserModal.vue b/src/components/modals/SearchUserModal.vue
index 56c269a501af76531de09aa60dd1553571f0f0f6..414369508f1be90c59a4dab83446ddddb89450b7 100644
--- a/src/components/modals/SearchUserModal.vue
+++ b/src/components/modals/SearchUserModal.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { reactive, watch } from "vue";
+import { reactive, ref, watch } from "vue";
 import BootstrapModal from "@/components/modals/BootstrapModal.vue";
 import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
 import { UserService } from "@/client/auth";
@@ -8,13 +8,15 @@ import { useAuthStore } from "@/stores/users";
 import { useNameStore } from "@/stores/names";
 
 const props = defineProps<{
-  modalID: string;
+  modalId: string;
   backModalId?: string;
+  filterUserSelf?: boolean;
 }>();
 const randomIDSuffix = Math.random().toString(16).substring(2, 8);
 
 const store = useAuthStore();
 const nameStore = useNameStore();
+const textInputElement = ref<HTMLInputElement | undefined>(undefined);
 
 const formState = reactive<{
   searchString: string;
@@ -55,13 +57,21 @@ function modalClosed() {
   formState.potentialUsers = [];
 }
 
+function modalShown() {
+  textInputElement.value?.focus();
+}
+
 function searchUser(name: string) {
   formState.error = false;
   UserService.userListUsers(name)
     .then((userSuggestions) => {
-      formState.potentialUsers = userSuggestions.filter(
-        (user) => store.currentUID != user.uid,
-      );
+      if (props.filterUserSelf) {
+        formState.potentialUsers = userSuggestions.filter(
+          (user) => store.currentUID != user.uid,
+        );
+      } else {
+        formState.potentialUsers = userSuggestions;
+      }
       for (const user of userSuggestions) {
         nameStore.addNameToMapping(user.uid, user.display_name);
       }
@@ -78,10 +88,10 @@ function searchUser(name: string) {
 
 <template>
   <bootstrap-modal
-    :modalID="props.modalID"
+    :modalId="props.modalId"
     :static-backdrop="true"
     modal-label="Search User Modal"
-    v-on="{ 'hidden.bs.modal': modalClosed }"
+    v-on="{ 'hidden.bs.modal': modalClosed, 'shown.bs.modal': modalShown }"
   >
     <template v-slot:header>Search User</template>
     <template v-slot:body>
@@ -94,6 +104,7 @@ function searchUser(name: string) {
           :id="'searchUserInput' + randomIDSuffix"
           placeholder="Search for a user"
           v-model.trim="formState.searchString"
+          ref="textInputElement"
         />
       </div>
       <div v-if="formState.loading" class="text-center">
@@ -118,8 +129,9 @@ function searchUser(name: string) {
           :key="user.uid"
           type="button"
           class="list-group-item list-group-item-action"
-          :data-bs-target="'#' + props.backModalId"
-          data-bs-toggle="modal"
+          :data-bs-target="props.backModalId ? '#' + props.backModalId : null"
+          :data-bs-toggle="props.backModalId ? 'modal' : null"
+          :data-bs-dismiss="props.backModalId ? null : 'modal'"
           @click="emit('user-found', user)"
         >
           {{ user.display_name }}
diff --git a/src/components/object-storage/modals/BucketDetailModal.vue b/src/components/object-storage/modals/BucketDetailModal.vue
index 09788df9d94e1e290c501bc98cc7e53aadbf1bd5..49eef5918ab7e1e2f26d7a49e876d9a95f386be1 100644
--- a/src/components/object-storage/modals/BucketDetailModal.vue
+++ b/src/components/object-storage/modals/BucketDetailModal.vue
@@ -12,7 +12,7 @@ const props = defineProps<{
 
 <template>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="false"
     modal-label="Bucket Detail Modal"
   >
diff --git a/src/components/object-storage/modals/CopyObjectModal.vue b/src/components/object-storage/modals/CopyObjectModal.vue
index b51e17dad74e5b6eeb21f8094126dc7f33cec902..68f8b3d1a386ca24b0b712836b728e7eeaec85d1 100644
--- a/src/components/object-storage/modals/CopyObjectModal.vue
+++ b/src/components/object-storage/modals/CopyObjectModal.vue
@@ -95,7 +95,7 @@ onMounted(() => {
     Try again later
   </bootstrap-toast>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Copy Object Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/object-storage/modals/CreateBucketModal.vue b/src/components/object-storage/modals/CreateBucketModal.vue
index ae030960e3ab025834d85ba26ef3f56a17d297a4..9de88c11559748cd7fc22d701c42ef294b879af9 100644
--- a/src/components/object-storage/modals/CreateBucketModal.vue
+++ b/src/components/object-storage/modals/CreateBucketModal.vue
@@ -82,7 +82,7 @@ function modalClosed() {
 
 <template>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Create Bucket Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/object-storage/modals/CreateFolderModal.vue b/src/components/object-storage/modals/CreateFolderModal.vue
index 12400a92ddfd07598ff23c6f69779616f207fc1a..7791c9c7a62de44f7b7e290a7be4d0b2fbd9c76d 100644
--- a/src/components/object-storage/modals/CreateFolderModal.vue
+++ b/src/components/object-storage/modals/CreateFolderModal.vue
@@ -79,7 +79,7 @@ onMounted(() => {
     Try again later
   </bootstrap-toast>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Create Folder Modal"
   >
diff --git a/src/components/object-storage/modals/ObjectDetailModal.vue b/src/components/object-storage/modals/ObjectDetailModal.vue
index 08f5280a86aac2400e20909bae8723377ac92916..c1c2eaf0b1f2006db22acb8f893daf221af573c8 100644
--- a/src/components/object-storage/modals/ObjectDetailModal.vue
+++ b/src/components/object-storage/modals/ObjectDetailModal.vue
@@ -53,7 +53,7 @@ onMounted(() => {
 
 <template>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="false"
     modal-label="Object Detail Modal"
   >
diff --git a/src/components/object-storage/modals/PermissionListModal.vue b/src/components/object-storage/modals/PermissionListModal.vue
index 1d5c4d020a74e2bf737026c03ad5610cb095b41e..8d74a24c67c91f2a10dfa910c6f522e94c4c1cf6 100644
--- a/src/components/object-storage/modals/PermissionListModal.vue
+++ b/src/components/object-storage/modals/PermissionListModal.vue
@@ -6,8 +6,13 @@ import { onBeforeMount, watch } from "vue";
 import BootstrapModal from "@/components/modals/BootstrapModal.vue";
 import PermissionModal from "@/components/object-storage/modals/PermissionModal.vue";
 import { useBucketStore } from "@/stores/buckets";
+import { useNameStore } from "@/stores/names";
+import { useAuthStore } from "@/stores/users";
 
 const bucketRepository = useBucketStore();
+const nameRepository = useNameStore();
+const userRepository = useAuthStore();
+
 // Props
 // -----------------------------------------------------------------------------
 const props = defineProps<{
@@ -46,7 +51,9 @@ watch(
 // Function
 // -----------------------------------------------------------------------------
 function updateBucketPermissions(bucketName: string) {
-  bucketRepository.fetchBucketPermissions(bucketName);
+  bucketRepository.fetchBucketPermissions(bucketName).then((permissions) => {
+    userRepository.fetchUsernames(permissions.map((p) => p.uid));
+  });
 }
 
 // Lifecycle Hooks
@@ -68,7 +75,7 @@ onBeforeMount(() => {
     :modalID="'permission-list-edit-modal' + randomIDSuffix"
   />
   <bootstrap-modal
-    :modalID="props.modalID"
+    :modalId="props.modalID"
     :static-backdrop="true"
     modal-label="Permission List Modal"
   >
@@ -99,7 +106,7 @@ onBeforeMount(() => {
                 permission.permission
               }}</span>
               <span class="col-9 text-center">
-                {{ permission.grantee_display_name }}</span
+                {{ nameRepository.getName(permission.uid) }}</span
               >
             </div>
           </button>
diff --git a/src/components/object-storage/modals/PermissionModal.vue b/src/components/object-storage/modals/PermissionModal.vue
index 4ce35b44058b6658ec624818b2f30e85736fdd0a..846aad4522e2f640ef46e01b4f6eec3086a798df 100644
--- a/src/components/object-storage/modals/PermissionModal.vue
+++ b/src/components/object-storage/modals/PermissionModal.vue
@@ -17,6 +17,7 @@ import { Toast } from "bootstrap";
 import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
 import { useBucketStore } from "@/stores/buckets";
 import BootstrapToast from "@/components/BootstrapToast.vue";
+import { useNameStore } from "@/stores/names";
 
 // Props
 // -----------------------------------------------------------------------------
@@ -32,6 +33,7 @@ const props = defineProps<{
 }>();
 
 const bucketRepository = useBucketStore();
+const nameRepository = useNameStore();
 
 const emit = defineEmits<{ (e: "permission-deleted"): void }>();
 // Variables
@@ -45,12 +47,10 @@ let successToast: Toast | null = null;
 // eslint-disable-next-line vue/no-setup-props-destructure
 const formState = reactive<{
   loading: boolean;
-  grantee_name: string;
   error: boolean;
   readonly: boolean;
 }>({
   loading: false,
-  grantee_name: "",
   error: false,
   readonly: props.readonly,
 });
@@ -133,7 +133,6 @@ function updateLocalPermission() {
     permission.bucket_name = props.editUserPermission.bucket_name;
     permission.file_prefix = props.editUserPermission.file_prefix;
     permission.uid = props.editUserPermission.uid;
-    formState.grantee_name = props.editUserPermission.grantee_display_name;
     permission.from_timestamp = props.editUserPermission.from_timestamp;
     permission.to_timestamp = props.editUserPermission.to_timestamp;
     permission.permission = props.editUserPermission.permission;
@@ -143,7 +142,6 @@ function updateLocalPermission() {
     permission.from_timestamp = undefined;
     permission.to_timestamp = undefined;
     permission.permission = undefined;
-    formState.grantee_name = "";
   }
 }
 
@@ -234,7 +232,6 @@ function confirmedDeletePermission(bucketName: string, uid: string) {
 
 function updateUser(user: User) {
   permission.uid = user.uid;
-  formState.grantee_name = user.display_name;
 }
 
 // Lifecycle Hooks
@@ -267,9 +264,10 @@ function toTimestampChanged(target?: HTMLInputElement | null) {
       confirmedDeletePermission(permission.bucket_name, permission.uid)
     "
   />
-  <SearchUserModal
-    :modalID="'search-user-modal' + randomIDSuffix"
+  <search-user-modal
+    :modal-id="'search-user-modal' + randomIDSuffix"
     :back-modal-id="modalID"
+    filter-user-self
     @user-found="updateUser"
   />
   <bootstrap-toast
@@ -283,7 +281,7 @@ function toTimestampChanged(target?: HTMLInputElement | null) {
     Permission
   </bootstrap-toast>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Permission Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
@@ -336,32 +334,23 @@ function toTimestampChanged(target?: HTMLInputElement | null) {
           <label for="permissionGranteeInput" class="col-2 col-form-label">
             User<span v-if="!formState.readonly">*</span>
           </label>
-          <div
-            :class="{
-              'col-10': permissionUserReadonly,
-              'col-9': !permissionUserReadonly,
-            }"
-          >
+          <div class="col-10">
             <input
               type="text"
               class="form-control"
               id="permissionGranteeInput"
               required
               placeholder="Search for a user"
-              v-model.trim="formState.grantee_name"
+              :value="nameRepository.getName(permission.uid)"
               readonly
+              :data-bs-toggle="permissionUserReadonly ? null : 'modal'"
+              :data-bs-target="
+                permissionUserReadonly
+                  ? null
+                  : '#search-user-modal' + randomIDSuffix
+              "
             />
           </div>
-          <div v-if="!permissionUserReadonly" class="col-1">
-            <button
-              type="button"
-              class="btn btn-secondary btn-sm float-end"
-              data-bs-toggle="modal"
-              :data-bs-target="'#search-user-modal' + randomIDSuffix"
-            >
-              <font-awesome-icon icon="fa-solid fa-magnifying-glass" />
-            </button>
-          </div>
         </div>
         <div class="mb-3 row">
           <label for="permissionTypeInput" class="col-3 col-form-label">
diff --git a/src/components/object-storage/modals/UploadObjectModal.vue b/src/components/object-storage/modals/UploadObjectModal.vue
index a6ec1f38483c07d5132be7674cb7fc9de31705d9..331fbf62a255527b1e1220c4d4b9db089eb554c6 100644
--- a/src/components/object-storage/modals/UploadObjectModal.vue
+++ b/src/components/object-storage/modals/UploadObjectModal.vue
@@ -113,7 +113,7 @@ onMounted(() => {
     Try again later
   </bootstrap-toast>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Upload Object Modal"
   >
diff --git a/src/components/resources/modals/CreateResourceModal.vue b/src/components/resources/modals/CreateResourceModal.vue
index 73dff643642779f6db6ea04235e1571fbc5a711d..93f6cab3fb1e9395ab03d9c945daebfad117ef56 100644
--- a/src/components/resources/modals/CreateResourceModal.vue
+++ b/src/components/resources/modals/CreateResourceModal.vue
@@ -88,7 +88,7 @@ onMounted(() => {
 
 <template>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     static-backdrop
     modal-label="Create Resource Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/resources/modals/UpdateResourceModal.vue b/src/components/resources/modals/UpdateResourceModal.vue
index f40a8d1f4a3268abcb434fdd20a96cb580614b08..f0e73c341af5384ae0b64216b26eb02a86e782c9 100644
--- a/src/components/resources/modals/UpdateResourceModal.vue
+++ b/src/components/resources/modals/UpdateResourceModal.vue
@@ -60,7 +60,7 @@ onMounted(() => {
 
 <template>
   <bootstrap-modal
-    :modalID="modalId"
+    :modalId="modalId"
     static-backdrop
     modal-label="Update Resource Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/resources/modals/UploadResourceInfoModal.vue b/src/components/resources/modals/UploadResourceInfoModal.vue
index 5d72c70b9be927fa586bfb3cca16eb778bc03771..10463ea778ce2f0f11991e61470337f786da86aa 100644
--- a/src/components/resources/modals/UploadResourceInfoModal.vue
+++ b/src/components/resources/modals/UploadResourceInfoModal.vue
@@ -134,7 +134,7 @@ onMounted(() => {
 
 <template>
   <bootstrap-modal
-    :modalID="props.modalId"
+    :modalId="props.modalId"
     modal-label="Upload Resource Info Modal"
     sizeModifier="lg"
   >
diff --git a/src/components/workflows/modals/ArbitraryWorkflowModal.vue b/src/components/workflows/modals/ArbitraryWorkflowModal.vue
index 18dedf30b4120162f9e23b1d71bc5e1d0536cc1a..c33d00d11f856eeb47d9f0dba00045f31215723a 100644
--- a/src/components/workflows/modals/ArbitraryWorkflowModal.vue
+++ b/src/components/workflows/modals/ArbitraryWorkflowModal.vue
@@ -186,7 +186,7 @@ onMounted(() => {
 
 <template>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="false"
     modal-label="Create Workflow Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/workflows/modals/CreateWorkflowModal.vue b/src/components/workflows/modals/CreateWorkflowModal.vue
index 29c41c3d872c296de6de777bc4181336c41ddd0a..3d7d1bcca8a6ed80cb6532d6c2b3628d52897e04 100644
--- a/src/components/workflows/modals/CreateWorkflowModal.vue
+++ b/src/components/workflows/modals/CreateWorkflowModal.vue
@@ -324,7 +324,7 @@ onMounted(() => {
     Successfully created Workflow
   </bootstrap-toast>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Create Workflow Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/workflows/modals/ParameterModal.vue b/src/components/workflows/modals/ParameterModal.vue
index 00dc11ee4c904b8988840e3480b4ed20b2dc7958..76f0d35f2ae516fd616ec0baf486931dc183fd79 100644
--- a/src/components/workflows/modals/ParameterModal.vue
+++ b/src/components/workflows/modals/ParameterModal.vue
@@ -139,7 +139,7 @@ onMounted(() => {
 
 <template>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="false"
     modal-label="Workflow Execution Parameters Modal"
   >
diff --git a/src/components/workflows/modals/UpdateWorkflowCredentialsModal.vue b/src/components/workflows/modals/UpdateWorkflowCredentialsModal.vue
index 130f275792f865f75104b080ecb3b2a37bc6b9b9..ea0a1d22b232ebe9b4fb9e4186dffb73e3e3a95c 100644
--- a/src/components/workflows/modals/UpdateWorkflowCredentialsModal.vue
+++ b/src/components/workflows/modals/UpdateWorkflowCredentialsModal.vue
@@ -136,7 +136,7 @@ onMounted(() => {
     Successfully updated credentials
   </bootstrap-toast>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Update Workflow Version Icon Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/workflows/modals/UpdateWorkflowModal.vue b/src/components/workflows/modals/UpdateWorkflowModal.vue
index 78ca17fc52d89f9421472de599a8b836615ead4c..2961a8d5db685984b352fc55fb49f81b66bc88de 100644
--- a/src/components/workflows/modals/UpdateWorkflowModal.vue
+++ b/src/components/workflows/modals/UpdateWorkflowModal.vue
@@ -312,7 +312,7 @@ onMounted(() => {
     Successfully updated Workflow
   </bootstrap-toast>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Update Workflow Modal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/components/workflows/modals/UpdateWorkflowVersionIconModal.vue b/src/components/workflows/modals/UpdateWorkflowVersionIconModal.vue
index 420dfc32dab305f901663891a52bf6f6e6a77864..b820af5335b0021bfbd7538ce8e0c20d664e0d0c 100644
--- a/src/components/workflows/modals/UpdateWorkflowVersionIconModal.vue
+++ b/src/components/workflows/modals/UpdateWorkflowVersionIconModal.vue
@@ -146,7 +146,7 @@ onMounted(() => {
     <div v-else>Successfully deleted icon</div>
   </bootstrap-toast>
   <bootstrap-modal
-    :modalID="modalID"
+    :modalId="modalID"
     :static-backdrop="true"
     modal-label="Update Workflow Version IconModal"
     v-on="{ 'hidden.bs.modal': modalClosed }"
diff --git a/src/router/adminRoutes.ts b/src/router/adminRoutes.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a7a4384781a425879efc121e17f1c2ea24253195
--- /dev/null
+++ b/src/router/adminRoutes.ts
@@ -0,0 +1,12 @@
+import type { RouteRecordRaw } from "vue-router";
+
+export const adminRoutes: RouteRecordRaw[] = [
+  {
+    path: "admin/resources",
+    name: "admin-resources",
+    component: () => import("../views/admin/AdminResourcesView.vue"),
+    meta: {
+      requiresAdminRole: true,
+    },
+  },
+];
diff --git a/src/router/index.ts b/src/router/index.ts
index 1154704c207ec7c034944a1e8858640413d14b42..21ae88be87f65a9832c6fcdbb7b83d8dc62a6214 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -4,6 +4,7 @@ import LoginView from "../views/LoginView.vue";
 import { workflowRoutes } from "@/router/workflowRoutes";
 import { s3Routes } from "@/router/s3Routes";
 import { resourceRoutes } from "@/router/resourceRoutes";
+import { adminRoutes } from "@/router/adminRoutes";
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
@@ -12,7 +13,12 @@ const router = createRouter({
       path: "/dashboard",
       name: "dashboard",
       component: DashboardView,
-      children: [...resourceRoutes, ...s3Routes, ...workflowRoutes],
+      children: [
+        ...resourceRoutes,
+        ...s3Routes,
+        ...workflowRoutes,
+        ...adminRoutes,
+      ],
     },
     {
       path: "/login",
diff --git a/src/router/resourceRoutes.ts b/src/router/resourceRoutes.ts
index 6b2ed067d9693568979aa2670847191ba3d3297e..69cd0cffc0282ff9dc6063bc6bd47e551bd50178 100644
--- a/src/router/resourceRoutes.ts
+++ b/src/router/resourceRoutes.ts
@@ -7,13 +7,19 @@ export const resourceRoutes: RouteRecordRaw[] = [
     component: () => import("../views/resources/ListResourcesView.vue"),
   },
   {
-    path: "developer/resources",
+    path: "maintainer/resources",
     name: "resource-maintainer",
     component: () => import("../views/resources/MyResourcesView.vue"),
+    meta: {
+      requiresMaintainerRole: true,
+    },
   },
   {
     path: "reviewer/resources",
     name: "resource-review",
     component: () => import("../views/resources/ReviewResourceView.vue"),
+    meta: {
+      requiresReviewerRole: true,
+    },
   },
 ];
diff --git a/src/stores/names.ts b/src/stores/names.ts
index 4dc30f76e0e34b41de204796741a41b5a5277eed..e2b76aa70e58f926db013beb7b5f106950fd6cdd 100644
--- a/src/stores/names.ts
+++ b/src/stores/names.ts
@@ -9,9 +9,13 @@ export const useNameStore = defineStore({
       nameMapping: Record<string, string>;
     },
   getters: {
-    getName(): (objectID: string) => string | undefined {
-      return (objectID) =>
-        this.nameMapping[objectID] ?? localStorage.getItem(objectID);
+    getName(): (objectID?: string) => string | undefined {
+      return (objectID) => {
+        if (objectID) {
+          return this.nameMapping[objectID] ?? localStorage.getItem(objectID);
+        }
+        return undefined;
+      };
     },
   },
   actions: {
diff --git a/src/stores/resources.ts b/src/stores/resources.ts
index 986b90f7f7f0020be830ac6b0de110f1453c9204..662c5139bfc64336aab6c478bfc568adfd9ce6bb 100644
--- a/src/stores/resources.ts
+++ b/src/stores/resources.ts
@@ -40,8 +40,25 @@ export const useResourceStore = defineStore({
     fetchResources(
       maintainerId?: string,
       versionStatus?: Status[],
+      searchString?: string,
     ): Promise<ResourceOut[]> {
-      return ResourceService.resourceListResources(maintainerId, versionStatus);
+      return ResourceService.resourceListResources(
+        maintainerId,
+        versionStatus,
+        searchString,
+      ).then((resources) => {
+        const nameStore = useNameStore();
+        for (const resource of resources) {
+          nameStore.addNameToMapping(resource.resource_id, resource.name);
+          for (const version of resource.versions) {
+            nameStore.addNameToMapping(
+              version.resource_version_id,
+              version.release,
+            );
+          }
+        }
+        return resources;
+      });
     },
     fetchReviewableResources(onFinally?: () => void): Promise<ResourceOut[]> {
       if (Object.keys(this.reviewableResourceMapping).length > 0) {
@@ -53,16 +70,8 @@ export const useResourceStore = defineStore({
       ])
         .then((resources) => {
           const newMapping: Record<string, ResourceOut> = {};
-          const nameStore = useNameStore();
           for (const resource of resources) {
             newMapping[resource.resource_id] = resource;
-            nameStore.addNameToMapping(resource.resource_id, resource.name);
-            for (const version of resource.versions) {
-              nameStore.addNameToMapping(
-                version.resource_version_id,
-                version.release,
-              );
-            }
           }
           this.reviewableResourceMapping = newMapping;
           return resources;
@@ -76,16 +85,8 @@ export const useResourceStore = defineStore({
       return this.fetchResources()
         .then((resources) => {
           const newMapping: Record<string, ResourceOut> = {};
-          const nameStore = useNameStore();
           for (const resource of resources) {
             newMapping[resource.resource_id] = resource;
-            nameStore.addNameToMapping(resource.resource_id, resource.name);
-            for (const version of resource.versions) {
-              nameStore.addNameToMapping(
-                version.resource_version_id,
-                version.release,
-              );
-            }
           }
           this.resourceMapping = newMapping;
           return resources;
@@ -125,16 +126,8 @@ export const useResourceStore = defineStore({
       return this.fetchResources(authStore.currentUID, Object.values(Status))
         .then((resources) => {
           const newMapping: Record<string, ResourceOut> = {};
-          const nameStore = useNameStore();
           for (const resource of resources) {
             newMapping[resource.resource_id] = resource;
-            nameStore.addNameToMapping(resource.resource_id, resource.name);
-            for (const version of resource.versions) {
-              nameStore.addNameToMapping(
-                version.resource_version_id,
-                version.release,
-              );
-            }
           }
           this.ownResourceMapping = newMapping;
           return resources;
diff --git a/src/views/admin/AdminResourcesView.vue b/src/views/admin/AdminResourcesView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..380187ca549819e46b7a5cf52e7dbb167515f2bf
--- /dev/null
+++ b/src/views/admin/AdminResourcesView.vue
@@ -0,0 +1,245 @@
+<script setup lang="ts">
+import { useResourceStore } from "@/stores/resources";
+import { reactive } from "vue";
+import {
+  type ResourceOut,
+  Status,
+} from "@/client/resource";
+import SearchUserModal from "@/components/modals/SearchUserModal.vue";
+import type { User } from "@/client/auth";
+import { useNameStore } from "@/stores/names";
+import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
+import dayjs from "dayjs";
+import ResourceVersionInfoModal from "@/components/admin/ResourceVersionInfoModal.vue";
+
+const resourceRepository = useResourceStore();
+const nameRepository = useNameStore();
+
+const resourceState = reactive<{
+  loading: boolean;
+  resources: ResourceOut[];
+  maintainerId: string;
+  searchString: string;
+  resourceStatus: Status[];
+  inspectVersionIndex: number;
+  inspectResource?: ResourceOut;
+}>({
+  loading: false,
+  resources: [],
+  maintainerId: "",
+  searchString: "",
+  resourceStatus: [],
+  inspectVersionIndex: 0,
+  inspectResource: undefined,
+});
+
+function updateUser(user: User) {
+  resourceState.maintainerId = user.uid;
+}
+
+function searchResources() {
+  resourceState.loading = true;
+  resourceRepository
+    .fetchResources(
+      resourceState.maintainerId ? resourceState.maintainerId : undefined,
+      resourceState.resourceStatus ? resourceState.resourceStatus : undefined,
+      resourceState.searchString ? resourceState.searchString : undefined,
+    )
+    .then((resources) => {
+      resourceState.resources = resources;
+    })
+    .finally(() => {
+      resourceState.loading = false;
+    });
+}
+
+function resetForm() {
+  resourceState.maintainerId = "";
+  resourceState.searchString = "";
+  resourceState.resourceStatus = [];
+  resourceState.resources = [];
+}
+</script>
+
+<template>
+  <search-user-modal
+    modal-id="admin-resource-search-user-modal"
+    @user-found="updateUser"
+  />
+  <resource-version-info-modal
+    modal-id="admin-resource-version-info-modal"
+    :resource-version-index="resourceState.inspectVersionIndex"
+    :resource="resourceState.inspectResource"
+  />
+  <div
+    class="row m-2 border-bottom mb-4 justify-content-between align-items-center"
+  >
+    <h2>Manage Resources</h2>
+  </div>
+  <form @submit.prevent="searchResources" id="admin-resource-search-form">
+    <div class="d-flex justify-content-evenly align-content-center">
+      <div class="mx-2">
+        <label for="admin-resource-state-select" class="form-label"
+          >Status of Resource Versions</label
+        >
+        <select
+          v-model="resourceState.resourceStatus"
+          multiple
+          class="form-select mb-4 w-fit"
+          id="admin-resource-state-select"
+        >
+          <option v-for="state in Object.values(Status)" :key="state">
+            {{ state }}
+          </option>
+        </select>
+      </div>
+      <div class="flex-fill mx-2">
+        <label for="admin-resource-name-search" class="form-label"
+          >Name of the Resource</label
+        >
+        <div class="input-group">
+          <input
+            id="admin-resource-name-search"
+            type="text"
+            class="form-control"
+            maxlength="32"
+            v-model="resourceState.searchString"
+            placeholder="Search for resource name"
+          />
+        </div>
+      </div>
+      <div class="flex-fill mx-2">
+        <label for="admin-resource-user-search" class="form-label"
+          >Name of the Maintainer</label
+        >
+        <div class="input-group">
+          <div class="input-group-text">
+            <font-awesome-icon icon="fa-solid fa-user" />
+          </div>
+          <input
+            id="admin-resource-user-search"
+            type="text"
+            class="form-control"
+            readonly
+            :value="nameRepository.getName(resourceState.maintainerId)"
+            placeholder="Search for maintainer"
+            data-bs-toggle="modal"
+            data-bs-target="#admin-resource-search-user-modal"
+          />
+        </div>
+      </div>
+    </div>
+    <button
+      type="submit"
+      class="btn btn-primary w-fit"
+      :disabled="resourceState.loading"
+    >
+      Search
+    </button>
+    <button type="button" class="btn-primary btn w-fit ms-4" @click="resetForm">
+      Reset
+    </button>
+  </form>
+  <table class="table table-striped" v-if="resourceState.resources">
+    <thead>
+      <tr>
+        <th scope="col"><b>Resource ID</b></th>
+        <th scope="col">Name</th>
+        <th scope="col">Source</th>
+        <th scope="col">Maintainer</th>
+      </tr>
+    </thead>
+    <tbody v-if="resourceState.resources.length === 0">
+      <tr>
+        <td colspan="5" class="text-center fst-italic fw-light">
+          Select a filter and search for Resources
+        </td>
+      </tr>
+    </tbody>
+    <tbody v-else>
+      <template
+        v-for="resource in resourceState.resources"
+        :key="resource.resource_id"
+      >
+        <tr>
+          <th scope="row">{{ resource.resource_id }}</th>
+          <td>{{ resource.name }}</td>
+          <td>{{ resource.source }}</td>
+          <td>{{ nameRepository.getName(resource.maintainer_id) }}</td>
+        </tr>
+        <tr>
+          <td colspan="5">
+            <table class="table mb-0 table-hover">
+              <thead>
+                <tr>
+                  <th scope="col"><b>Version ID</b></th>
+                  <th scope="col">Release</th>
+                  <th scope="col">Created At</th>
+                  <th scope="col">State</th>
+                  <th scope="col" class="text-end">Action</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr
+                  v-for="(version, index) in resource.versions"
+                  :key="version.resource_version_id"
+                >
+                  <th scope="row">{{ version.resource_version_id }}</th>
+                  <th>{{ version.release }}</th>
+                  <th>
+                    {{
+                      dayjs
+                        .unix(version.created_at)
+                        .format("DD.MM.YYYY HH:mm:ss")
+                    }}
+                  </th>
+                  <th>{{ version.status }}</th>
+                  <th class="text-end">
+                    <div class="btn-group">
+                      <button
+                        type="button"
+                        class="btn btn-secondary"
+                        data-bs-toggle="modal"
+                        data-bs-target="#admin-resource-version-info-modal"
+                        @click="
+                          resourceState.inspectResource = resource;
+                          resourceState.inspectVersionIndex = index;
+                        "
+                      >
+                        Inspect
+                      </button>
+                      <button
+                        type="button"
+                        class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
+                        data-bs-toggle="dropdown"
+                        aria-expanded="false"
+                      >
+                        <span class="visually-hidden">Toggle Dropdown</span>
+                      </button>
+                      <ul class="dropdown-menu">
+                        <li><a class="dropdown-item" href="#">Action</a></li>
+                        <li>
+                          <a class="dropdown-item" href="#">Another action</a>
+                        </li>
+                        <li>
+                          <a class="dropdown-item" href="#"
+                            >Something else here</a
+                          >
+                        </li>
+                        <li>
+                          <a class="dropdown-item" href="#">Separated link</a>
+                        </li>
+                      </ul>
+                    </div>
+                  </th>
+                </tr>
+              </tbody>
+            </table>
+          </td>
+        </tr>
+      </template>
+    </tbody>
+  </table>
+</template>
+
+<style scoped></style>