diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnBriefDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnBriefDto.java
index 38ba0bff019836a9b00f128a75004740139dd385..7863806cb538886c81db162729493ad7d85b7888 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnBriefDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnBriefDto.java
@@ -1,5 +1,6 @@
 package at.tuwien.api.database.table.columns;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
@@ -20,6 +21,14 @@ public class ColumnBriefDto {
     @NotNull(message = "id is required")
     private Long id;
 
+    @JsonProperty("database_id")
+    @NotNull(message = "database id is required")
+    private Long databaseId;
+
+    @JsonProperty("table_id")
+    @NotNull(message = "table id is required")
+    private Long tableId;
+
     @NotBlank(message = "name is required")
     @Schema(example = "date")
     private String name;
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/concepts/ConceptDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/concepts/ConceptDto.java
index 68b98535aacb462719a1b08f245e582950fcec88..e031cf594496b99d68c58dc44088f177ec32c091 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/concepts/ConceptDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/concepts/ConceptDto.java
@@ -1,5 +1,7 @@
 package at.tuwien.api.database.table.columns.concepts;
 
+import at.tuwien.api.database.table.columns.ColumnBriefDto;
+import at.tuwien.api.database.table.columns.ColumnDto;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
@@ -9,6 +11,7 @@ import jakarta.validation.constraints.NotNull;
 import lombok.extern.jackson.Jacksonized;
 
 import java.time.Instant;
+import java.util.List;
 
 @Getter
 @Setter
@@ -26,7 +29,9 @@ public class ConceptDto {
     private String name;
 
     @NotNull
-    @Schema(required = true)
     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC")
     private Instant created;
+
+    @NotNull
+    private List<ColumnBriefDto> columns;
 }
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/concepts/UnitDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/concepts/UnitDto.java
index a5429ceb9b56c9a0f687020a7f6809faf9252704..65b6c2a0bdcdbeb10c47975c1a43e9523259f773 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/concepts/UnitDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/concepts/UnitDto.java
@@ -1,7 +1,7 @@
 package at.tuwien.api.database.table.columns.concepts;
 
+import at.tuwien.api.database.table.columns.ColumnBriefDto;
 import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
 
 import jakarta.validation.constraints.NotBlank;
@@ -9,6 +9,7 @@ import jakarta.validation.constraints.NotNull;
 import lombok.extern.jackson.Jacksonized;
 
 import java.time.Instant;
+import java.util.List;
 
 @Getter
 @Setter
@@ -26,7 +27,9 @@ public class UnitDto {
     private String name;
 
     @NotNull
-    @Schema(required = true)
     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC")
     private Instant created;
+
+    @NotNull
+    private List<ColumnBriefDto> columns;
 }
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumnConcept.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumnConcept.java
index 78e37ced97555cb71d2ff90efb62fd73791a64ed..fa2ae5a0666c3d289fadae19996f968b194b3ae0 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumnConcept.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumnConcept.java
@@ -36,4 +36,15 @@ public class TableColumnConcept {
     @Column(nullable = false, updatable = false, columnDefinition = "TIMESTAMP")
     @CreatedDate
     private Instant created;
+
+    @ToString.Exclude
+    @OneToMany(fetch = FetchType.LAZY)
+    @JoinTable(name = "mdb_columns_concepts",
+            inverseJoinColumns = {
+                    @JoinColumn(name = "cid", referencedColumnName = "id", insertable = false, updatable = false),
+                    @JoinColumn(name = "tid", referencedColumnName = "tid", insertable = false, updatable = false),
+                    @JoinColumn(name = "cdbid", referencedColumnName = "cdbid", insertable = false, updatable = false)
+            },
+            joinColumns = @JoinColumn(name = "uri", referencedColumnName = "uri"))
+    private List<TableColumn> columns;
 }
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumnUnit.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumnUnit.java
index 1b93dea177774c038f734176e3402d12a7b0e192..aecbcd9dd354fc8d49b47a8021322dcadedf3598 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumnUnit.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/columns/TableColumnUnit.java
@@ -36,4 +36,15 @@ public class TableColumnUnit {
     @Column(nullable = false, updatable = false, columnDefinition = "TIMESTAMP")
     @CreatedDate
     private Instant created;
+
+    @ToString.Exclude
+    @OneToMany(fetch = FetchType.LAZY)
+    @JoinTable(name = "mdb_columns_concepts",
+            inverseJoinColumns = {
+                    @JoinColumn(name = "cid", referencedColumnName = "id", insertable = false, updatable = false),
+                    @JoinColumn(name = "tid", referencedColumnName = "tid", insertable = false, updatable = false),
+                    @JoinColumn(name = "cdbid", referencedColumnName = "cdbid", insertable = false, updatable = false)
+            },
+            joinColumns = @JoinColumn(name = "uri", referencedColumnName = "uri"))
+    private List<TableColumn> columns;
 }
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/semantics/Ontology.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/semantics/Ontology.java
index bb25ecd58a46a0ef1afacdd032c019fd566ac043..df63a7acf52ca59e6b80dbaeab10af5c8ce0135e 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/semantics/Ontology.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/semantics/Ontology.java
@@ -29,10 +29,10 @@ public class Ontology {
     @Column(updatable = false, nullable = false)
     private Long id;
 
-    @Column(nullable = false)
+    @Column(nullable = false, unique = true)
     private String uri;
 
-    @Column(nullable = false, columnDefinition = "VARCHAR(8)")
+    @Column(nullable = false, unique = true, columnDefinition = "VARCHAR(8)")
     private String prefix;
 
     @Column
diff --git a/dbrepo-metadata-db/setup-schema.sql b/dbrepo-metadata-db/setup-schema.sql
index f57b6aad248b749c6aec92ca5aa740dd2d39c13f..9642c98f6152a0e691dffdbc4fd1332badced085 100644
--- a/dbrepo-metadata-db/setup-schema.sql
+++ b/dbrepo-metadata-db/setup-schema.sql
@@ -353,6 +353,8 @@ CREATE TABLE IF NOT EXISTS `fda`.`mdb_ontologies`
     last_modified   timestamp,
     created         timestamp              NOT NULL DEFAULT NOW(),
     created_by      character varying(255) NULL,
+    UNIQUE (prefix),
+    UNIQUE (uri),
     PRIMARY KEY (id)
 ) WITH SYSTEM VERSIONING;
 
diff --git a/dbrepo-semantics-service/rest-service/src/main/java/at/tuwien/endpoints/SemanticsEndpoint.java b/dbrepo-semantics-service/rest-service/src/main/java/at/tuwien/endpoints/SemanticsEndpoint.java
index 0e942b22e5ba264488ca3edf1a355ee9fb50ed96..6567d5ee38424c65946292583f8ef0002ef0e993 100644
--- a/dbrepo-semantics-service/rest-service/src/main/java/at/tuwien/endpoints/SemanticsEndpoint.java
+++ b/dbrepo-semantics-service/rest-service/src/main/java/at/tuwien/endpoints/SemanticsEndpoint.java
@@ -5,6 +5,7 @@ import at.tuwien.api.database.table.columns.concepts.ConceptSaveDto;
 import at.tuwien.api.database.table.columns.concepts.UnitDto;
 import at.tuwien.api.database.table.columns.concepts.UnitSaveDto;
 import at.tuwien.mapper.OntologyMapper;
+import at.tuwien.mapper.SemanticMapper;
 import at.tuwien.service.SemanticService;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
@@ -12,33 +13,62 @@ import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotNull;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+
 @Log4j2
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/semantic")
 public class SemanticsEndpoint {
 
+    private final SemanticMapper semanticMapper;
     private final OntologyMapper ontologyMapper;
     private final SemanticService semanticService;
 
     @Autowired
-    public SemanticsEndpoint(OntologyMapper ontologyMapper, SemanticService semanticService) {
+    public SemanticsEndpoint(SemanticMapper semanticMapper, OntologyMapper ontologyMapper, SemanticService semanticService) {
+        this.semanticMapper = semanticMapper;
         this.ontologyMapper = ontologyMapper;
         this.semanticService = semanticService;
     }
 
+    @GetMapping("/concept")
+    @Transactional(readOnly = true)
+    @Timed(value = "semantics.concept.list", description = "Time needed to find all semantic concepts")
+    @Operation(summary = "List semantic concepts")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200",
+                    description = "Find all semantic concepts",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ConceptDto[].class))}),
+    })
+    public ResponseEntity<List<ConceptDto>> findAllConcepts() {
+        log.debug("endpoint list concepts");
+        final List<ConceptDto> dtos = semanticService.findAllConcepts()
+                .stream()
+                .map(semanticMapper::tableColumnConceptToConceptDto)
+                .toList();
+        log.trace("Find all concepts resulted in dtos {}", dtos);
+        return ResponseEntity.ok()
+                .body(dtos);
+    }
+
     @PostMapping("/concept")
+    @Transactional
     @PreAuthorize("hasAuthority('create-semantic-concept')")
     @Timed(value = "semantics.concept.save", description = "Time needed to create or update a semantic concept")
-    @Operation(summary = "Create or update a semantic concept")
+    @Operation(summary = "Create or update a semantic concept", security = @SecurityRequirement(name = "bearerAuth"))
     @ApiResponses(value = {
             @ApiResponse(responseCode = "202",
                     description = "Created or updated a semantic concept",
@@ -50,14 +80,57 @@ public class SemanticsEndpoint {
         log.debug("endpoint save or update concept, data={}", data);
         final ConceptDto dto = ontologyMapper.tableColumnConceptToConceptDto(semanticService.saveConcept(data));
         log.trace("save or update concept resulted in dto {}", dto);
-        return ResponseEntity.accepted()
+        return ResponseEntity.ok()
+                .body(dto);
+    }
+
+    @PutMapping("/concept")
+    @Transactional
+    @PreAuthorize("hasAuthority('create-semantic-concept')")
+    @Timed(value = "semantics.concept.save", description = "Time needed to create or update a semantic concept")
+    @Operation(summary = "Create or update a semantic concept", security = @SecurityRequirement(name = "bearerAuth"))
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "202",
+                    description = "Created or updated a semantic concept",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ConceptDto.class))}),
+    })
+    public ResponseEntity<ConceptDto> saveConcept(@NotNull @Valid @RequestBody ConceptSaveDto data) {
+        log.debug("endpoint save or update concept, data={}", data);
+        final ConceptDto dto = ontologyMapper.tableColumnConceptToConceptDto(semanticService.saveConcept(data));
+        log.trace("save or update concept resulted in dto {}", dto);
+        return ResponseEntity.ok()
                 .body(dto);
     }
 
+    @GetMapping("/unit")
+    @Transactional(readOnly = true)
+    @Timed(value = "semantics.concept.list", description = "Time needed to find all semantic units")
+    @Operation(summary = "List semantic units")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200",
+                    description = "Find all semantic units",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = UnitDto[].class))}),
+    })
+    public ResponseEntity<List<UnitDto>> findAllUnits() {
+        log.debug("endpoint list units");
+        final List<UnitDto> dtos = semanticService.findAllUnits()
+                .stream()
+                .map(semanticMapper::tableColumnUnitToUnitDto)
+                .toList();
+        log.trace("Find all units resulted in dtos {}", dtos);
+        return ResponseEntity.ok()
+                .body(dtos);
+    }
+
     @PostMapping("/unit")
+    @Transactional
     @PreAuthorize("hasAuthority('create-semantic-unit')")
     @Timed(value = "semantics.unit.save", description = "Time needed to create or update a semantic unit")
-    @Operation(summary = "Create or update a semantic unit")
+    @Operation(summary = "Create or update a semantic unit", security = @SecurityRequirement(name = "bearerAuth"))
     @ApiResponses(value = {
             @ApiResponse(responseCode = "202",
                     description = "Created or updated a semantic unit",
diff --git a/dbrepo-semantics-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/dbrepo-semantics-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
index 3bd2ce6956ac3c52b301dbeacad582b6f68f9022..8b4729dcf0ff8a056f66a1881fff2e66813a2176 100644
--- a/dbrepo-semantics-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
+++ b/dbrepo-semantics-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
@@ -45,7 +45,9 @@ public class WebSecurityConfig {
                 new AntPathRequestMatcher("/swagger-ui.html")
         );
         final OrRequestMatcher publicEndpoints = new OrRequestMatcher(
-                new AntPathRequestMatcher("/api/semantic/ontology/**", "GET")
+                new AntPathRequestMatcher("/api/semantic/ontology/**", "GET"),
+                new AntPathRequestMatcher("/api/semantic/concept", "GET"),
+                new AntPathRequestMatcher("/api/semantic/unit", "GET")
         );
         /* enable CORS and disable CSRF */
         http = http.cors().and().csrf().disable();
diff --git a/dbrepo-semantics-service/services/src/main/java/at/tuwien/mapper/SemanticMapper.java b/dbrepo-semantics-service/services/src/main/java/at/tuwien/mapper/SemanticMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..95c56e0a5c53be6d910b33a637de29ec43da5a32
--- /dev/null
+++ b/dbrepo-semantics-service/services/src/main/java/at/tuwien/mapper/SemanticMapper.java
@@ -0,0 +1,18 @@
+package at.tuwien.mapper;
+
+import at.tuwien.api.database.table.columns.concepts.ConceptDto;
+import at.tuwien.api.database.table.columns.concepts.UnitDto;
+import at.tuwien.entities.database.table.columns.TableColumnConcept;
+import at.tuwien.entities.database.table.columns.TableColumnUnit;
+import org.mapstruct.Mapper;
+
+
+@Mapper(componentModel = "spring", uses = {TableMapper.class})
+public interface SemanticMapper {
+
+    org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SemanticMapper.class);
+
+    ConceptDto tableColumnConceptToConceptDto(TableColumnConcept data);
+
+    UnitDto tableColumnUnitToUnitDto(TableColumnUnit data);
+}
diff --git a/dbrepo-semantics-service/services/src/main/java/at/tuwien/mapper/TableMapper.java b/dbrepo-semantics-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
index 4f79866fcb749bd31c828d182dcd7b7235eb64f3..e5ae2d291b1fb12b437509a38c456af736a042fc 100644
--- a/dbrepo-semantics-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
+++ b/dbrepo-semantics-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
@@ -1,7 +1,12 @@
 package at.tuwien.mapper;
 
+import at.tuwien.api.database.table.columns.ColumnBriefDto;
+import at.tuwien.api.database.table.columns.ColumnDto;
+import at.tuwien.entities.database.table.columns.TableColumn;
 import at.tuwien.entities.database.table.columns.TableColumnKey;
 import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
 
 
 @Mapper(componentModel = "spring")
@@ -9,6 +14,18 @@ public interface TableMapper {
 
     org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TableMapper.class);
 
+    @Mappings({
+            @Mapping(source = "tid", target = "tableId"),
+            @Mapping(source = "cdbid", target = "databaseId"),
+    })
+    ColumnBriefDto tableColumnToColumnBriefDto(TableColumn data);
+
+    @Mappings({
+            @Mapping(source = "tid", target = "tableId"),
+            @Mapping(source = "cdbid", target = "databaseId"),
+    })
+    ColumnDto tableColumnToColumnDto(TableColumn data);
+
     default TableColumnKey toTableColumnKey(Long databaseId, Long tableId, Long columnId) {
         return TableColumnKey.builder()
                 .cdbid(databaseId)
diff --git a/dbrepo-semantics-service/services/src/main/java/at/tuwien/service/SemanticService.java b/dbrepo-semantics-service/services/src/main/java/at/tuwien/service/SemanticService.java
index 34fd117fcbe9e23370b4e14cf75c055cc97182e8..564b739af08341c7c1684e343ab36898bed354b7 100644
--- a/dbrepo-semantics-service/services/src/main/java/at/tuwien/service/SemanticService.java
+++ b/dbrepo-semantics-service/services/src/main/java/at/tuwien/service/SemanticService.java
@@ -5,7 +5,13 @@ import at.tuwien.api.database.table.columns.concepts.UnitSaveDto;
 import at.tuwien.entities.database.table.columns.TableColumnConcept;
 import at.tuwien.entities.database.table.columns.TableColumnUnit;
 
+import java.util.List;
+
 public interface SemanticService {
+    List<TableColumnConcept> findAllConcepts();
+
+    List<TableColumnUnit> findAllUnits();
+
     TableColumnConcept saveConcept(ConceptSaveDto data);
 
     TableColumnUnit saveUnit(UnitSaveDto data);
diff --git a/dbrepo-semantics-service/services/src/main/java/at/tuwien/service/impl/SemanticServiceImpl.java b/dbrepo-semantics-service/services/src/main/java/at/tuwien/service/impl/SemanticServiceImpl.java
index 66d9f751315b34f3ba2ef5343ba23b8451af391b..8c8cab046939d9c9a90a5b24b7dde8b1748ea537 100644
--- a/dbrepo-semantics-service/services/src/main/java/at/tuwien/service/impl/SemanticServiceImpl.java
+++ b/dbrepo-semantics-service/services/src/main/java/at/tuwien/service/impl/SemanticServiceImpl.java
@@ -11,6 +11,9 @@ import at.tuwien.service.SemanticService;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
 
 @Log4j2
 @Service
@@ -29,6 +32,21 @@ public class SemanticServiceImpl implements SemanticService {
     }
 
     @Override
+    @Transactional(readOnly = true)
+    public List<TableColumnConcept> findAllConcepts() {
+        final List<TableColumnConcept> concepts = tableColumnConceptRepository.findAll();
+        return concepts;
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public List<TableColumnUnit> findAllUnits() {
+        final List<TableColumnUnit> units = tableColumnUnitRepository.findAll();
+        return units;
+    }
+
+    @Override
+    @Transactional
     public TableColumnConcept saveConcept(ConceptSaveDto data) {
         final TableColumnConcept entity = ontologyMapper.conceptSaveDtoToTableColumnConcept(data);
         final TableColumnConcept concept = tableColumnConceptRepository.save(entity);
@@ -37,6 +55,7 @@ public class SemanticServiceImpl implements SemanticService {
     }
 
     @Override
+    @Transactional
     public TableColumnUnit saveUnit(UnitSaveDto data) {
         final TableColumnUnit entity = ontologyMapper.unitSaveDtoToTableColumnUnit(data);
         final TableColumnUnit unit = tableColumnUnitRepository.save(entity);
diff --git a/dbrepo-ui/api/semantic.service.js b/dbrepo-ui/api/semantic.service.js
index 750dbd2082e175ba72ddf53d7e6aff723daca7a9..0c9f01b7db6973e64ebe4f7c65f170deea8c7bb8 100644
--- a/dbrepo-ui/api/semantic.service.js
+++ b/dbrepo-ui/api/semantic.service.js
@@ -19,6 +19,40 @@ class SemanticService {
     })
   }
 
+  findAllConcepts () {
+    return new Promise((resolve, reject) => {
+      api.get('/api/semantic/concept', { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const concepts = response.data
+          console.debug('response concepts', concepts)
+          resolve(concepts)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load concepts', error)
+          Vue.$toast.error(`[${code}] Failed to load concepts: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  findAllUnits () {
+    return new Promise((resolve, reject) => {
+      api.get('/api/semantic/unit', { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const units = response.data
+          console.debug('response units', units)
+          resolve(units)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load units', error)
+          Vue.$toast.error(`[${code}] Failed to load units: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
   findOntology (id) {
     return new Promise((resolve, reject) => {
       api.get(`/api/semantic/ontology/${id}`, { headers: { Accept: 'application/json' } })
diff --git a/dbrepo-ui/components/dialogs/EditSemantics.vue b/dbrepo-ui/components/dialogs/EditSemantics.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5debbc5840e681825688d2de715b9a0f7e943cb1
--- /dev/null
+++ b/dbrepo-ui/components/dialogs/EditSemantics.vue
@@ -0,0 +1,96 @@
+<template>
+  <div>
+    <v-card>
+      <v-card-title>Edit</v-card-title>
+      <v-card-text>
+        <v-form ref="form" v-model="valid" autocomplete="off" @submit.prevent="submit">
+          <v-row>
+            <v-col>
+              <v-text-field
+                v-model="semanticDto.name"
+                clearable
+                label="Name" />
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col>
+              <v-text-field
+                v-model="semanticDto.uri"
+                clearable
+                label="URI"
+                :rules="[v => !!v || $t('Required')]"
+                required />
+            </v-col>
+          </v-row>
+        </v-form>
+      </v-card-text>
+      <v-card-actions>
+        <v-spacer />
+        <v-btn
+          class="mb-2"
+          @click="cancel">
+          Cancel
+        </v-btn>
+        <v-btn
+          color="primary"
+          class="mb-2 mr-2"
+          :disabled="!valid"
+          :loading="loadingSave"
+          @click="save">
+          Save
+        </v-btn>
+      </v-card-actions>
+    </v-card>
+  </div>
+</template>
+
+<script>
+import SemanticService from '@/api/semantic.service'
+
+export default {
+  props: {
+    entity: {
+      type: Object,
+      default () {
+        return {}
+      }
+    }
+  },
+  data () {
+    return {
+      valid: false,
+      localEntity: null,
+      loadingSave: false,
+      semanticDto: {
+        name: null,
+        uri: null
+      }
+    }
+  },
+  computed: {
+  },
+  watch: {
+    entity () {
+      this.semanticDto.name = this.entity.name
+      this.semanticDto.uri = this.entity.uri
+    }
+  },
+  mounted () {
+    this.semanticDto.name = this.entity.name
+    this.semanticDto.uri = this.entity.uri
+  },
+  methods: {
+    cancel () {
+      this.$emit('close', { success: false, action: 'cancel' })
+    },
+    save () {
+      SemanticService.u
+    },
+    submit () {
+      this.$refs.form.validate()
+    }
+  }
+}
+</script>
+<style scoped>
+</style>
diff --git a/dbrepo-ui/pages/semantic/index.vue b/dbrepo-ui/pages/semantic/index.vue
index 6b924a606374f525e816b79c4ba71aaf92b37ddb..a9362fb6d3fd8dc25184604e3da5934f6d9dd117 100644
--- a/dbrepo-ui/pages/semantic/index.vue
+++ b/dbrepo-ui/pages/semantic/index.vue
@@ -5,19 +5,76 @@
       <v-spacer />
       <v-toolbar-title>
         <v-btn v-if="canListOntologies" to="/semantic/ontology" color="secondary">
-          Ontologies
+          {{ ontologies.length }} Ontologies
         </v-btn>
       </v-toolbar-title>
     </v-toolbar>
+    <v-tabs v-model="tab">
+      <v-tab>Concepts</v-tab>
+      <v-tab>Units</v-tab>
+    </v-tabs>
+    <v-card flat>
+      <v-card-text>
+        <v-data-table
+          :headers="headers"
+          :items="rows"
+          :options.sync="options"
+          :server-items-length="total"
+          :footer-props="footerProps">
+          <template v-slot:item.uri="{ item }">
+            <a :href="item.uri" target="_blank" v-text="item.uri" />
+          </template>
+          <template v-slot:item.action="{ item }">
+            <v-btn small @click="edit(item)">
+              Edit
+            </v-btn>
+          </template>
+        </v-data-table>
+      </v-card-text>
+    </v-card>
+    <v-dialog
+      v-model="editSemanticDialog"
+      persistent
+      max-width="640">
+      <EditSemantics :entity="entity" @close="close" />
+    </v-dialog>
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
 </template>
 <script>
+import SemanticService from '@/api/semantic.service'
+import EditSemantics from '@/components/dialogs/EditSemantics.vue'
+
 export default {
   components: {
+    EditSemantics
   },
   data () {
     return {
+      loadingConcepts: false,
+      loadingUnits: false,
+      entity: null,
+      editSemanticDialog: false,
+      headers: [
+        { text: 'Name', value: 'name' },
+        { text: 'URI', value: 'uri' },
+        { text: 'Usages', value: 'usages' },
+        { text: null, value: 'action' }
+      ],
+      options: {
+        page: 1,
+        itemsPerPage: 10
+      },
+      total: -1,
+      footerProps: {
+        'items-per-page-options': [10, 20, 30, 40, 50]
+      },
+      tab: 0,
+      tabs: [
+        'concepts', 'units'
+      ],
+      concepts: [],
+      units: [],
       createOntologyDialog: false,
       items: [
         { text: `${this.$t('layout.semantics', { name: 'vue-i18n' })}`, to: '/semantic', activeClass: '' }
@@ -34,6 +91,12 @@ export default {
     roles () {
       return this.$store.state.roles
     },
+    ontologies () {
+      return this.$store.state.ontologies
+    },
+    rows () {
+      return this.tab === 0 ? this.concepts : this.units
+    },
     canListOntologies () {
       if (!this.roles) {
         return false
@@ -41,7 +104,46 @@ export default {
       return this.roles.includes('list-ontologies')
     }
   },
+  mounted () {
+    this.loadUnits()
+    this.loadConcepts()
+  },
   methods: {
+    loadConcepts () {
+      this.loadingConcepts = true
+      SemanticService.findAllConcepts()
+        .then((concepts) => {
+          concepts = concepts.map((column) => {
+            column.usages = column.columns.length
+            return column
+          })
+          this.concepts = concepts
+        })
+        .finally(() => {
+          this.loadingConcepts = false
+        })
+    },
+    loadUnits () {
+      this.loadingUnits = true
+      SemanticService.findAllUnits()
+        .then((units) => {
+          units = units.map((unit) => {
+            unit.usages = unit.columns.length
+            return unit
+          })
+          this.units = units
+        })
+        .finally(() => {
+          this.loadingUnits = false
+        })
+    },
+    edit (entity) {
+      this.entity = entity
+      this.editSemanticDialog = true
+    },
+    close (event) {
+      this.editSemanticDialog = false
+    }
   }
 }
 </script>
diff --git a/dbrepo-ui/pages/semantic/ontology/index.vue b/dbrepo-ui/pages/semantic/ontology/index.vue
index f49d55364ac8645026e4329389dfb23aee806fbb..dcb79ccc929815d55a6e30ea4f1b21b4c46d5ab6 100644
--- a/dbrepo-ui/pages/semantic/ontology/index.vue
+++ b/dbrepo-ui/pages/semantic/ontology/index.vue
@@ -1,7 +1,7 @@
 <template>
   <div v-if="canListOntologies">
     <v-toolbar flat>
-      <v-toolbar-title>{{ $t('layout.ontologies', { name: 'vue-i18n' }) }}</v-toolbar-title>
+      <v-toolbar-title>{{ ontologies.length }} {{ $t('layout.ontologies', { name: 'vue-i18n' }) }}</v-toolbar-title>
       <v-spacer />
       <v-toolbar-title>
         <v-btn v-if="canCreateOntology" color="primary" name="create-ontology" @click.stop="createOntologyDialog = true">
@@ -47,6 +47,9 @@ export default {
     roles () {
       return this.$store.state.roles
     },
+    ontologies () {
+      return this.$store.state.ontologies
+    },
     canListOntologies () {
       if (!this.roles) {
         return false