mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 20:42:40 +00:00
Add custom static links (#234)
This commit is contained in:
parent
c185cb8933
commit
3f5f4e01f1
7 changed files with 209 additions and 52 deletions
10
config.yml
10
config.yml
|
@ -97,6 +97,14 @@ oidc.discovery-url:
|
||||||
|
|
||||||
|
|
||||||
# Custom assets
|
# Custom assets
|
||||||
# Add your own custom assets to $opengist-home/custom/
|
# Add your own custom assets, that are files relatives to $opengist-home/custom/
|
||||||
custom.logo:
|
custom.logo:
|
||||||
custom.favicon:
|
custom.favicon:
|
||||||
|
|
||||||
|
# Static pages in footer (like legal notices, privacy policy, etc.)
|
||||||
|
# The path can be a URL or a relative path to a file in the $opengist-home/custom/ directory
|
||||||
|
custom.static-links:
|
||||||
|
# - name: Gitea
|
||||||
|
# path: https://gitea.com
|
||||||
|
# - name: Legal notices
|
||||||
|
# path: legal.html
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Configuration Cheat Sheet
|
# Configuration Cheat Sheet
|
||||||
|
|
||||||
| YAML Config Key | Environment Variable | Default value | Description |
|
| YAML Config Key | Environment Variable | Default value | Description |
|
||||||
|-----------------------|--------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------|-------------------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| log-level | OG_LOG_LEVEL | `warn` | Set the log level to one of the following: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`. |
|
| log-level | OG_LOG_LEVEL | `warn` | Set the log level to one of the following: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`. |
|
||||||
| log-output | OG_LOG_OUTPUT | `stdout,file` | Set the log output to one or more of the following: `stdout`, `file`. |
|
| log-output | OG_LOG_OUTPUT | `stdout,file` | Set the log output to one or more of the following: `stdout`, `file`. |
|
||||||
| external-url | OG_EXTERNAL_URL | none | Public URL to access to Opengist. |
|
| external-url | OG_EXTERNAL_URL | none | Public URL to access to Opengist. |
|
||||||
|
@ -32,5 +32,6 @@
|
||||||
| oidc.client-key | OG_OIDC_CLIENT_KEY | none | The client key for the OpenID application. |
|
| oidc.client-key | OG_OIDC_CLIENT_KEY | none | The client key for the OpenID application. |
|
||||||
| oidc.secret | OG_OIDC_SECRET | none | The secret for the OpenID application. |
|
| oidc.secret | OG_OIDC_SECRET | none | The secret for the OpenID application. |
|
||||||
| oidc.discovery-url | OG_OIDC_DISCOVERY_URL | none | Discovery endpoint of the OpenID provider. |
|
| oidc.discovery-url | OG_OIDC_DISCOVERY_URL | none | Discovery endpoint of the OpenID provider. |
|
||||||
| custom.logo | OG_CUSTOM_LOGO | none | Path to an image, relative to $opengist-home/custom |
|
| custom.logo | OG_CUSTOM_LOGO | none | Path to an image, relative to $opengist-home/custom. |
|
||||||
| custom.favicon | OG_CUSTOM_FAVICON | none | Path to an image, relative to $opengist-home/custom |
|
| custom.favicon | OG_CUSTOM_FAVICON | none | Path to an image, relative to $opengist-home/custom. |
|
||||||
|
| custom.static-links | OG_CUSTOM_STATIC_LINK_#_(PATH,NAME) | none | Path and name to custom links, more info [here](custom-links.md). |
|
||||||
|
|
31
docs/configuration/custom-assets.md
Normal file
31
docs/configuration/custom-assets.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Custom assets
|
||||||
|
|
||||||
|
To add custom assets to your Opengist instance, you can use the `$opengist-home/custom` directory (where `$opengist-home` is the directory where Opengist stores its data).
|
||||||
|
|
||||||
|
### Logo / Favicon
|
||||||
|
|
||||||
|
To add a custom logo or favicon, you can add your own image file to the `$opengist-home/custom` directory, then define the relative path in the config.
|
||||||
|
|
||||||
|
For example, if you have a logo file `logo.png` in the `$opengist-home/custom` directory, you can set the logo path in the config as follows:
|
||||||
|
|
||||||
|
#### YAML
|
||||||
|
```yaml
|
||||||
|
custom.logo: logo.png
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Environment variable
|
||||||
|
```sh
|
||||||
|
export OG_CUSTOM_LOGO=logo.png
|
||||||
|
```
|
||||||
|
|
||||||
|
Same as the favicon:
|
||||||
|
|
||||||
|
#### YAML
|
||||||
|
```yaml
|
||||||
|
custom.favicon: favicon.png
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Environment variable
|
||||||
|
```sh
|
||||||
|
export OG_CUSTOM_FAVICON=favicon.png
|
||||||
|
```
|
38
docs/configuration/custom-links.md
Normal file
38
docs/configuration/custom-links.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Custom links
|
||||||
|
|
||||||
|
On the footer of your Opengist instance, you can add links to custom static templates or any other website you want to link to.
|
||||||
|
This can be useful for legal information, privacy policy, or any other information you want to provide to your users.
|
||||||
|
|
||||||
|
To add one or more links, you can add your own file to the `$opengist-home/custom` directory or set a URL, then define the relative path and its name in the config.
|
||||||
|
|
||||||
|
For example, if you have a legal information file `legal.html` in the `$opengist-home/custom` directory, and also wish to add a link to a Gitea instance, you can set the link in the config as follows:
|
||||||
|
|
||||||
|
#### YAML
|
||||||
|
```yaml
|
||||||
|
custom.static-links:
|
||||||
|
- name: Legal notices
|
||||||
|
path: legal.html
|
||||||
|
- name: Gitea
|
||||||
|
path: https://gitea.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Environment variable
|
||||||
|
```sh
|
||||||
|
OG_CUSTOM_STATIC_LINK_0_NAME="Legal Notices" \
|
||||||
|
OG_CUSTOM_STATIC_LINK_0_PATH=legal.html \
|
||||||
|
OG_CUSTOM_STATIC_LINK_1_NAME=Gitea \
|
||||||
|
OG_CUSTOM_STATIC_LINK_1_PATH=https://gitea.com \
|
||||||
|
./opengist
|
||||||
|
```
|
||||||
|
|
||||||
|
## Templating custom HTML pages
|
||||||
|
|
||||||
|
In the start and end of the custom HTML files, you can use the syntax to include the header and footer of the Opengist instance:
|
||||||
|
|
||||||
|
```html
|
||||||
|
{{ template "header" . }}
|
||||||
|
|
||||||
|
<!-- my content -->
|
||||||
|
|
||||||
|
{{ template "footer" . }}
|
||||||
|
```
|
|
@ -65,6 +65,12 @@ type config struct {
|
||||||
|
|
||||||
CustomLogo string `yaml:"custom.logo" env:"OG_CUSTOM_LOGO"`
|
CustomLogo string `yaml:"custom.logo" env:"OG_CUSTOM_LOGO"`
|
||||||
CustomFavicon string `yaml:"custom.favicon" env:"OG_CUSTOM_FAVICON"`
|
CustomFavicon string `yaml:"custom.favicon" env:"OG_CUSTOM_FAVICON"`
|
||||||
|
StaticLinks []StaticLink `yaml:"custom.static-links" env:"OG_CUSTOM_STATIC_LINK"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StaticLink struct {
|
||||||
|
Name string `yaml:"name" env:"OG_CUSTOM_STATIC_LINK_#_NAME"`
|
||||||
|
Path string `yaml:"path" env:"OG_CUSTOM_STATIC_LINK_#_PATH"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func configWithDefaults() (*config, error) {
|
func configWithDefaults() (*config, error) {
|
||||||
|
@ -129,7 +135,6 @@ func InitConfig(configPath string, out io.Writer) error {
|
||||||
if err = os.Setenv("OG_OPENGIST_HOME_INTERNAL", GetHomeDir()); err != nil {
|
if err = os.Setenv("OG_OPENGIST_HOME_INTERNAL", GetHomeDir()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,22 +251,63 @@ func loadConfigFromEnv(c *config, out io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
envValue := os.Getenv(strings.ToUpper(tag))
|
envValue := os.Getenv(strings.ToUpper(tag))
|
||||||
if envValue == "" {
|
if envValue == "" && v.Field(i).Kind() != reflect.Slice {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v.Field(i).Kind() {
|
switch v.Field(i).Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
v.Field(i).SetString(envValue)
|
v.Field(i).SetString(envValue)
|
||||||
|
envVars = append(envVars, tag)
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
boolVal, err := strconv.ParseBool(envValue)
|
boolVal, err := strconv.ParseBool(envValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.Field(i).SetBool(boolVal)
|
v.Field(i).SetBool(boolVal)
|
||||||
|
envVars = append(envVars, tag)
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Type().Field(i).Type.Elem().Kind() == reflect.Struct {
|
||||||
|
prefix := strings.ToUpper(tag) + "_"
|
||||||
|
var sliceValue reflect.Value
|
||||||
|
elemType := v.Type().Field(i).Type.Elem()
|
||||||
|
|
||||||
|
for index := 0; ; index++ {
|
||||||
|
allFieldsPresent := true
|
||||||
|
elemValue := reflect.New(elemType).Elem()
|
||||||
|
|
||||||
|
for j := 0; j < elemValue.NumField() && allFieldsPresent; j++ {
|
||||||
|
elemField := elemValue.Type().Field(j)
|
||||||
|
envName := fmt.Sprintf("%s%d_%s", prefix, index, strings.ToUpper(elemField.Name))
|
||||||
|
envValue, present := os.LookupEnv(envName)
|
||||||
|
|
||||||
|
if !present {
|
||||||
|
allFieldsPresent = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
envVars = append(envVars, envName)
|
||||||
|
elemValue.Field(j).SetString(envValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allFieldsPresent {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if sliceValue.Kind() != reflect.Slice {
|
||||||
|
sliceValue = reflect.MakeSlice(v.Type().Field(i).Type, 0, index+1)
|
||||||
|
}
|
||||||
|
sliceValue = reflect.Append(sliceValue, elemValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sliceValue.IsValid() {
|
||||||
|
v.Field(i).Set(sliceValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type: %s", v.Field(i).Kind())
|
||||||
}
|
}
|
||||||
|
|
||||||
envVars = append(envVars, tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(envVars) > 0 {
|
if len(envVars) > 0 {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/thomiceli/opengist/internal/index"
|
"github.com/thomiceli/opengist/internal/index"
|
||||||
"github.com/thomiceli/opengist/internal/utils"
|
"github.com/thomiceli/opengist/internal/utils"
|
||||||
|
"github.com/thomiceli/opengist/templates"
|
||||||
htmlpkg "html"
|
htmlpkg "html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
@ -30,7 +31,6 @@ import (
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
"github.com/thomiceli/opengist/internal/i18n"
|
"github.com/thomiceli/opengist/internal/i18n"
|
||||||
"github.com/thomiceli/opengist/public"
|
"github.com/thomiceli/opengist/public"
|
||||||
"github.com/thomiceli/opengist/templates"
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -138,6 +138,10 @@ var (
|
||||||
},
|
},
|
||||||
"addMetadataToSearchQuery": addMetadataToSearchQuery,
|
"addMetadataToSearchQuery": addMetadataToSearchQuery,
|
||||||
"indexEnabled": index.Enabled,
|
"indexEnabled": index.Enabled,
|
||||||
|
"isUrl": func(s string) bool {
|
||||||
|
_, err := url.ParseRequestURI(s)
|
||||||
|
return err == nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -186,9 +190,22 @@ func NewServer(isDev bool) *Server {
|
||||||
e.Use(middleware.Recover())
|
e.Use(middleware.Recover())
|
||||||
e.Use(middleware.Secure())
|
e.Use(middleware.Secure())
|
||||||
|
|
||||||
e.Renderer = &Template{
|
t := template.Must(template.New("t").Funcs(fm).ParseFS(templates.Files, "*/*.html"))
|
||||||
templates: template.Must(template.New("t").Funcs(fm).ParseFS(templates.Files, "*/*.html")),
|
customPattern := filepath.Join(config.GetHomeDir(), "custom", "*.html")
|
||||||
|
matches, err := filepath.Glob(customPattern)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to check for custom templates")
|
||||||
}
|
}
|
||||||
|
if len(matches) > 0 {
|
||||||
|
t, err = t.ParseGlob(customPattern)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to parse custom templates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.Renderer = &Template{
|
||||||
|
templates: t,
|
||||||
|
}
|
||||||
|
|
||||||
e.HTTPErrorHandler = func(er error, ctx echo.Context) {
|
e.HTTPErrorHandler = func(er error, ctx echo.Context) {
|
||||||
if err, ok := er.(*echo.HTTPError); ok {
|
if err, ok := er.(*echo.HTTPError); ok {
|
||||||
if err.Code >= 500 {
|
if err.Code >= 500 {
|
||||||
|
@ -211,14 +228,6 @@ func NewServer(isDev bool) *Server {
|
||||||
if !dev {
|
if !dev {
|
||||||
parseManifestEntries()
|
parseManifestEntries()
|
||||||
}
|
}
|
||||||
customFs := os.DirFS(filepath.Join(config.GetHomeDir(), "custom"))
|
|
||||||
e.GET("/assets/*", func(c echo.Context) error {
|
|
||||||
if _, err := public.Files.Open(path.Join("assets", c.Param("*"))); !dev && err == nil {
|
|
||||||
return echo.WrapHandler(http.FileServer(http.FS(public.Files)))(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return echo.WrapHandler(http.StripPrefix("/assets/", http.FileServer(http.FS(customFs))))(c)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Web based routes
|
// Web based routes
|
||||||
g1 := e.Group("")
|
g1 := e.Group("")
|
||||||
|
@ -309,6 +318,23 @@ func NewServer(isDev bool) *Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customFs := os.DirFS(filepath.Join(config.GetHomeDir(), "custom"))
|
||||||
|
e.GET("/assets/*", func(ctx echo.Context) error {
|
||||||
|
if _, err := public.Files.Open(path.Join("assets", ctx.Param("*"))); !dev && err == nil {
|
||||||
|
return echo.WrapHandler(http.FileServer(http.FS(public.Files)))(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the custom file is an .html template, render it
|
||||||
|
if strings.HasSuffix(ctx.Param("*"), ".html") {
|
||||||
|
if err := html(ctx, ctx.Param("*")); err != nil {
|
||||||
|
return notFound("Page not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return echo.WrapHandler(http.StripPrefix("/assets/", http.FileServer(http.FS(customFs))))(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
// Git HTTP routes
|
// Git HTTP routes
|
||||||
if config.C.HttpGit {
|
if config.C.HttpGit {
|
||||||
e.Any("/:user/:gistname/*", gitHttp, gistSoftInit)
|
e.Any("/:user/:gistname/*", gitHttp, gistSoftInit)
|
||||||
|
|
9
templates/base/base_footer.html
vendored
9
templates/base/base_footer.html
vendored
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
{{ define "footer" }}
|
{{ define "footer" }}
|
||||||
<div class="inline-flex py-8">
|
<div class="inline-flex py-8">
|
||||||
<p class="text-slate-600 dark:text-slate-400 [&>*]:mx-1.5 flex">
|
<p class="text-slate-600 dark:text-slate-400 [&>*]:mx-1.5 -ml-1.5 flex">
|
||||||
<span>
|
<span>
|
||||||
<a target="_blank" style="margin-left: 0 !important;" class="text-slate-600 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200 inline-flex" href="https://github.com/thomiceli/opengist">
|
<a target="_blank" style="margin-left: 0 !important;" class="text-slate-600 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200 inline-flex" href="https://github.com/thomiceli/opengist">
|
||||||
<span class="mr-1">{{ .locale.Tr "footer.powered-by" "<span class=\"font-bold dark:text-slate-300\">Opengist</span>" }} </span>
|
<span class="mr-1">{{ .locale.Tr "footer.powered-by" "<span class=\"font-bold dark:text-slate-300\">Opengist</span>" }} </span>
|
||||||
|
@ -28,6 +28,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if ne (len .c.StaticLinks) 0 }}
|
||||||
|
<div class="ml-1.5">
|
||||||
|
{{ range $index, $value := .c.StaticLinks }}
|
||||||
|
⋅ <a href="{{ if isUrl .Path }}{{ .Path }}{{ else }}{{ $.c.ExternalUrl }}/assets/{{ .Path }}{{ end }}" class="text-slate-600 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200 inline-flex">{{ .Name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue