James Read il y a 8 mois
Parent
commit
a67b5b4e8f

+ 2 - 0
.gitignore

@@ -17,3 +17,5 @@ webui/
 server.log
 OliveTin
 integration-tests/configs/authRequireGuestsToLogin/sessions.yaml
+webui
+webui.dev

+ 54 - 12
config.yaml

@@ -5,21 +5,10 @@
 # Listen on all addresses available, port 1337
 listenAddressSingleHTTPFrontend: 0.0.0.0:1337
 
-bannerMessage: "This is an early alpha version of OliveTin 3000. Many thanks are broken, many things will change."
-bannerCss: "background-color: #b2e4b2; color: black; font-size: small; text-align: center; padding: .6em; border-radius: 0.5em;"
-
-insecureAllowDumpSos: true
-insecureAllowDumpVars: true
-
 # Choose from INFO (default), WARN and DEBUG
+# Docs: https://docs.olivetin.app/advanced_configuration/logs.html 
 logLevel: "INFO"
 
-# Checking for updates https://docs.olivetin.app/reference/updateChecks.html
-checkForUpdates: false
-
-authLocalUsers:
-  enabled: true
-
 # Actions are commands that are executed by OliveTin, and normally show up as
 # buttons on the WebUI.
 #
@@ -55,6 +44,7 @@ actions:
   # You can also rate-limit actions too.
   - title: date
     shell: date
+    id: date
     timeout: 6
     icon: clock
     popupOnStart: execution-button
@@ -322,3 +312,55 @@ dashboards:
 
           - title: 'Start {{ .CurrentEntity.Names }}'
           - title: 'Stop {{ .CurrentEntity.Names }}'
+
+
+# Security - Authentication
+
+# This setting effectively enables or disables guests. 
+# If set to "true", then users will have to login to do anything.
+authRequireGuestsToLogin: false
+
+# This form of auth is the simplest to setup - just define users and passwords
+# in the config. OliveTin also supports header-based auth, OAuth2,
+# and JWT authentication which are documented separately.
+#
+# Docs: https://docs.olivetin.app/security/local.html
+# 
+# How to get a hashed password:
+# Docs: https://docs.olivetin.app/security/local.html#_get_a_argon2id_hashed_password
+authLocalUsers:
+  enabled: true
+#  users:
+#    - username: alice
+#      usergroup: admins
+#      password: "$argon2id$v=19$m=65536,t=4,p=2$puyxA0s555TSFx7hnFLCXA$PyhLGpZtvpMMvc2DgMWkM8OJMKO55euwV5gm//1iwx4"
+
+# Security - Access Control
+
+# Policies affect the whole app (eg: ability to view the log list).
+# Docs: https://docs.olivetin.app/security/acl.html
+defaultPolicy:
+  showDiagnostics: true
+  showLogList: true
+
+# Permissions affect actions (eg: ability to view a specific log).
+# Docs: https://docs.olivetin.app/security/acl.html
+defaultPermissions:
+  view: true
+  exec: true
+  logs: true
+
+# OliveTin uses access control lists to match up policy and permissions to users.
+# Docs: https://docs.olivetin.app/security/acl.html
+accessControlLists:
+  - name: admin_acl
+    matchUsergroups: ["admins"]
+    policy:
+      showDiagnostics: true
+    permissions:
+      view: true
+      exec: true
+      logs: true
+
+# OliveTin contains many more configuration options not in this default config.
+# Check out docs.olivetin.app for a setting if you feel like you're missing something.

+ 4 - 4
frontend/package-lock.json

@@ -17,7 +17,7 @@
 				"@xterm/addon-fit": "^0.10.0",
 				"@xterm/xterm": "^5.5.0",
 				"iconify-icon": "^3.0.2",
-				"picocrank": "^1.6.4",
+				"picocrank": "^1.8.0",
 				"unplugin-vue-components": "^30.0.0",
 				"vite": "^7.1.12",
 				"vue-router": "^4.6.3"
@@ -2362,9 +2362,9 @@
 			"license": "ISC"
 		},
 		"node_modules/picocrank": {
-			"version": "1.6.4",
-			"resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.6.4.tgz",
-			"integrity": "sha512-zD1wnkoUDAXZOUs9zKqS4rqz9mljeqFwM7QWx4ykXJsmH6iOLAIKh2AVlxa384oeXJXIWM9VLiySEnhQZmQmjA==",
+			"version": "1.8.0",
+			"resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.8.0.tgz",
+			"integrity": "sha512-YPGmXvw7vvjIcgrAe3io87kZDM+NUa+aiEYxk8CVqBzgI4koXeF+2VEGPHBwknZBBEbJfXsSdnxVwXrLKpWKfw==",
 			"license": "ISC",
 			"dependencies": {
 				"@hugeicons/core-free-icons": "^1.0.16",

+ 1 - 1
frontend/package.json

@@ -30,7 +30,7 @@
 		"@xterm/addon-fit": "^0.10.0",
 		"@xterm/xterm": "^5.5.0",
 		"iconify-icon": "^3.0.2",
-		"picocrank": "^1.6.4",
+		"picocrank": "^1.8.0",
 		"unplugin-vue-components": "^30.0.0",
 		"vite": "^7.1.12",
 		"vue-router": "^4.6.3"

+ 25 - 21
frontend/resources/vue/App.vue

@@ -1,5 +1,5 @@
 <template>
-    <Header title="OliveTin" :logoUrl="logoUrl" @toggleSidebar="toggleSidebar">
+    <Header title="OliveTin" :logoUrl="logoUrl" @toggleSidebar="toggleSidebar" :sidebarEnabled="showNavigation">
         <template #toolbar>
             <div id="banner" v-if="bannerMessage" :style="bannerCss">
                 <p>{{ bannerMessage }}</p>
@@ -86,7 +86,9 @@ const initError = ref(false)
 const initErrorMessage = ref('')
 
 function toggleSidebar() {
-    sidebar.value.toggle()
+    if (sidebar.value && showNavigation.value) {
+        sidebar.value.toggle()
+    }
 }
 
 function updateHeaderFromInit() {
@@ -132,25 +134,27 @@ async function requestInit() {
         showLogs.value = initResponse.showLogList
         showDiagnostics.value = initResponse.showDiagnostics
 
-        for (const rootDashboard of initResponse.rootDashboards) {
-            sidebar.value.addNavigationLink({
-                id: rootDashboard,
-                name: rootDashboard,
-                title: rootDashboard,
-                path: rootDashboard === 'Actions' ? '/' : `/dashboards/${rootDashboard}`,
-                icon: DashboardSquare01Icon,
-            })
-        }
-
-        sidebar.value.addSeparator()
-        sidebar.value.addRouterLink('Entities')
-
-        if (showLogs.value) {
-            sidebar.value.addRouterLink('Logs')
-        }
-
-        if (showDiagnostics.value) {
-            sidebar.value.addRouterLink('Diagnostics')
+        if (showNavigation.value && sidebar.value) {
+            for (const rootDashboard of initResponse.rootDashboards) {
+                sidebar.value.addNavigationLink({
+                    id: rootDashboard,
+                    name: rootDashboard,
+                    title: rootDashboard,
+                    path: rootDashboard === 'Actions' ? '/' : `/dashboards/${rootDashboard}`,
+                    icon: DashboardSquare01Icon,
+                })
+            }
+
+            sidebar.value.addSeparator()
+            sidebar.value.addRouterLink('Entities')
+
+            if (showLogs.value) {
+                sidebar.value.addRouterLink('Logs')
+            }
+
+            if (showDiagnostics.value) {
+                sidebar.value.addRouterLink('Diagnostics')
+            }
         }
 
         hasLoaded.value = true;

+ 2 - 3
proto/buf.gen.yaml

@@ -11,8 +11,7 @@ plugins:
   - remote: buf.build/bufbuild/es
     out: ../frontend/resources/scripts/gen/
 
-#  - name: swagger
-#    out: reports/swagger
-
+#  - local: ["go", "run", "github.com/sudorandom/protoc-gen-connect-openapi@latest"]
+#    out: gen
 #  - local: protoc-gen-openapiv2
 #    out: reports/openapiv2

+ 26 - 3
service/internal/entities/storage.go

@@ -60,15 +60,38 @@ func GetAll() *variableBase {
 }
 
 func GetEntities() entitiesByClass {
-	return contents.Entities
+	rwmutex.RLock()
+
+	copiedEntities := make(entitiesByClass, len(contents.Entities))
+
+	for entityName, entityInstances := range contents.Entities {
+		copiedInstances := make(entityInstancesByKey, len(entityInstances))
+
+		for key, entity := range entityInstances {
+			copiedInstances[key] = entity
+		}
+		copiedEntities[entityName] = copiedInstances
+	}
+
+	rwmutex.RUnlock()
+
+	return copiedEntities
 }
 
 func GetEntityInstances(entityName string) entityInstancesByKey {
+	rwmutex.RLock()
+	defer rwmutex.RUnlock()
+
 	if entities, ok := contents.Entities[entityName]; ok {
-		return entities
+		copiedInstances := make(entityInstancesByKey, len(entities))
+
+		for key, entity := range entities {
+			copiedInstances[key] = entity
+		}
+		return copiedInstances
 	}
 
-	return nil
+	return make(entityInstancesByKey, 0)
 }
 
 func AddEntity(entityName string, entityKey string, data any) {

+ 4 - 1
service/internal/entities/templates.go

@@ -91,7 +91,7 @@ func ParseTemplateWithArgs(source string, ent *Entity, args map[string]string) s
 	}
 
 	templateVariables := &variableBase{
-		OliveTin:      contents.OliveTin,
+		OliveTin:      GetAll().OliveTin,
 		Arguments:     args,
 		CurrentEntity: entdata,
 	}
@@ -126,5 +126,8 @@ func ParseTemplateBoolWith(source string, ent *Entity) bool {
 }
 
 func ClearEntities(entityType string) {
+	rwmutex.Lock()
+	defer rwmutex.Unlock()
+
 	delete(contents.Entities, entityType)
 }