Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b039b0703 | ||
|
|
6d31ef9732 | ||
|
|
678fb9938c | ||
|
|
df73b29fb1 | ||
|
|
690a6d55f9 | ||
|
|
0ef35fdb36 | ||
|
|
cf4e0e303c | ||
|
|
ab4bfcbcfb | ||
|
|
6499e3cc63 | ||
|
|
d4e4ae0b43 | ||
|
|
de6578d9e8 | ||
|
|
0950c9ce38 | ||
|
|
f881e1c13c | ||
|
|
069a999297 | ||
|
|
a97f54d92f |
47
.github/workflows/docs.yml
vendored
Normal file
47
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Build / Deploy docs
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: |
|
||||
npm install vitepress@1.3.4 tailwindcss@3.4.10
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
cd docs
|
||||
npx tailwindcss -i .vitepress/theme/style.css -o .vitepress/theme/theme.css -c .vitepress/tailwind.config.js
|
||||
npm run docs:build
|
||||
|
||||
- name: Deploy to server
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SERVER_USERNAME }}
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
source: "docs/.vitepress/dist/*"
|
||||
target: ${{ secrets.SERVER_PATH }}
|
||||
|
||||
- name: Update remote docs
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SERVER_USERNAME }}
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
script: |
|
||||
${{ secrets.UPDATE_DOCS }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@ public/assets/*
|
||||
public/manifest.json
|
||||
opengist
|
||||
build/
|
||||
docs/.vitepress/dist/
|
||||
docs/.vitepress/cache/
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## [1.7.5](https://github.com/thomiceli/opengist/compare/v1.7.4...v1.7.5) - 2024-09-12
|
||||
See here how to [update](/docs/update.md) Opengist.
|
||||
|
||||
### Added
|
||||
- New website for documentation using Vitepress [https://opengist.io](https://opengist.io) (#326)
|
||||
- Ukrainian localization (#325)
|
||||
- Dummy /metrics endpoint (#327)
|
||||
|
||||
## [1.7.4](https://github.com/thomiceli/opengist/compare/v1.7.3...v1.7.4) - 2024-09-09
|
||||
See here how to [update](/docs/update.md) Opengist.
|
||||
|
||||
### Added
|
||||
- More translations strings (#294) (#304)
|
||||
- Hide change password form when login via password disabled (#314)
|
||||
- File delete button on create editor (#320)
|
||||
- Assets cache header
|
||||
- Hide secret values in admin config page
|
||||
- Atomic pointer for indexer (#321)
|
||||
|
||||
### Fixed
|
||||
- Fatal error using `cases.Title()` (#313)
|
||||
- Search unlisted gist (#319)
|
||||
|
||||
### Other
|
||||
- Removed logger `trace` and `fatal` levels (#322)
|
||||
|
||||
## [1.7.3](https://github.com/thomiceli/opengist/compare/v1.7.2...v1.7.3) - 2024-06-03
|
||||
See here how to [update](/docs/update.md) Opengist.
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -1,12 +1,12 @@
|
||||
# Opengist
|
||||
|
||||
<img height="108px" src="https://raw.githubusercontent.com/thomiceli/opengist/a9dd531f676d01b93bb6bd70751a69382ca563b0/public/opengist.svg" alt="Opengist" align="right" />
|
||||
<img height="108px" src="https://raw.githubusercontent.com/thomiceli/opengist/master/public/opengist.svg" alt="Opengist" align="right" />
|
||||
|
||||
Opengist is a **self-hosted** pastebin **powered by Git**. All snippets are stored in a Git repository and can be
|
||||
read and/or modified using standard Git commands, or with the web interface.
|
||||
It is similiar to [GitHub Gist](https://gist.github.com/), but open-source and could be self-hosted.
|
||||
|
||||
[Documentation](/docs) • [Discord](https://discord.gg/9Pm3X5scZT) • [Demo](https://demo.opengist.io)
|
||||
[Home Page](https://opengist.io) • [Documentation](https://opengist.io/docs) • [Discord](https://discord.gg/9Pm3X5scZT) • [Demo](https://demo.opengist.io)
|
||||
|
||||
|
||||

|
||||
@@ -78,9 +78,9 @@ Download the archive for your system from the release page [here](https://github
|
||||
|
||||
```shell
|
||||
# example for linux amd64
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.7.3/opengist1.7.3-linux-amd64.tar.gz
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.7.5/opengist1.7.5-linux-amd64.tar.gz
|
||||
|
||||
tar xzvf opengist1.7.3-linux-amd64.tar.gz
|
||||
tar xzvf opengist1.7.5-linux-amd64.tar.gz
|
||||
cd opengist
|
||||
chmod +x opengist
|
||||
./opengist # with or without `--config config.yml`
|
||||
@@ -103,11 +103,11 @@ Opengist is now running on port 6157, you can browse http://localhost:6157
|
||||
|
||||
---
|
||||
|
||||
To create and run a development environment, see [run-development.md](/docs/contributing/run-development.md).
|
||||
To create and run a development environment, see [run-development.md](/docs/contributing/development.md).
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation is available in [/docs](/docs) directory.
|
||||
The documentation is available at [https://opengist.io/](https://opengist.io/) or in the [/docs](/docs) directory.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# https://github.com/thomiceli/opengist/blob/master/docs/configuration/index.md
|
||||
# https://github.com/thomiceli/opengist/blob/master/docs/configuration/cheat-sheet.md
|
||||
|
||||
# Set the log level to one of the following: trace, debug, info, warn, error, fatal, panic. Default: warn
|
||||
# Set the log level to one of the following: debug, info, warn, error, fatal. Default: warn
|
||||
log-level: warn
|
||||
|
||||
# Set the log output to one or more of the following: `stdout`, `file`. Default: stdout,file
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace: opengist
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- https://github.com/thomiceli/opengist/deploy/?ref:v1.7.3
|
||||
- https://github.com/thomiceli/opengist/deploy/?ref:v1.7.5
|
||||
|
||||
images:
|
||||
- name: ghcr.io/thomiceli/opengist
|
||||
newTag: 1.7.3
|
||||
newTag: 1.7.5
|
||||
|
||||
patches:
|
||||
# Add your ingress
|
||||
|
||||
89
docs/.vitepress/config.mts
Normal file
89
docs/.vitepress/config.mts
Normal file
@@ -0,0 +1,89 @@
|
||||
import {defineConfig} from 'vitepress'
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: "Opengist",
|
||||
description: "Documention for Opengist",
|
||||
rewrites: {
|
||||
'index.md': 'index.md',
|
||||
'introduction.md': 'docs/index.md',
|
||||
':path(.*)': 'docs/:path'
|
||||
},
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
logo: 'https://raw.githubusercontent.com/thomiceli/opengist/master/public/opengist.svg',
|
||||
logoLink: '/',
|
||||
nav: [
|
||||
{ text: 'Demo', link: 'https://demo.opengist.io' },
|
||||
{ text: 'Translate', link: 'https://tr.opengist.io' }
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
'/docs/': [
|
||||
{
|
||||
text: '', items: [
|
||||
{text: 'Introduction', link: '/docs'},
|
||||
{text: 'Installation', link: '/docs/installation', items: [
|
||||
{text: 'Docker', link: '/docs/installation/docker'},
|
||||
{text: 'Binary', link: '/docs/installation/binary'},
|
||||
{text: 'Source', link: '/docs/installation/source'},
|
||||
],
|
||||
collapsed: true
|
||||
},
|
||||
{text: 'Update', link: '/docs/update'},
|
||||
], collapsed: false
|
||||
},
|
||||
{
|
||||
text: 'Configuration', base: '/docs/configuration', items: [
|
||||
{text: 'Configure Opengist', link: '/configure'},
|
||||
{text: 'Admin panel', link: '/admin-panel'},
|
||||
{text: 'OAuth Providers', link: '/oauth-providers'},
|
||||
{text: 'Custom assets', link: '/custom-assets'},
|
||||
{text: 'Custom links', link: '/custom-links'},
|
||||
{text: 'Cheat Sheet', link: '/cheat-sheet'},
|
||||
], collapsed: false
|
||||
},
|
||||
{
|
||||
text: 'Usage', base: '/docs/usage', items: [
|
||||
{text: 'Init via Git', link: '/init-via-git'},
|
||||
{text: 'Embed Gist', link: '/embed'},
|
||||
{text: 'Gist as JSON', link: '/gist-json'},
|
||||
{text: 'Import Gists from Github', link: '/import-from-github-gist'},
|
||||
{text: 'Git push options', link: '/git-push-options'},
|
||||
], collapsed: false
|
||||
},
|
||||
{
|
||||
text: 'Administration', base: '/docs/administration', items: [
|
||||
{text: 'Run with systemd', link: '/run-with-systemd'},
|
||||
{text: 'Reverse proxy', items: [
|
||||
{text: 'Nginx', link: '/nginx-reverse-proxy'},
|
||||
{text: 'Traefik', link: '/traefik-reverse-proxy'},
|
||||
], collapsed: true},
|
||||
{text: 'Fail2ban', link: '/fail2ban-setup'},
|
||||
{text: 'Healthcheck', link: '/healthcheck'},
|
||||
], collapsed: false
|
||||
},
|
||||
{
|
||||
text: 'Contributing', base: '/docs/contributing', items: [
|
||||
{text: 'Community', link: '/community'},
|
||||
{text: 'Development', link: '/development'},
|
||||
], collapsed: false
|
||||
},
|
||||
|
||||
]},
|
||||
|
||||
socialLinks: [
|
||||
{icon: 'github', link: 'https://github.com/thomiceli/opengist'}
|
||||
],
|
||||
editLink: {
|
||||
pattern: 'https://github.com/thomiceli/opengist/edit/stable/docs/:path'
|
||||
},
|
||||
// @ts-ignore
|
||||
lastUpdated: true,
|
||||
|
||||
},
|
||||
head: [
|
||||
['link', {rel: 'icon', href: '/favicon.svg'}],
|
||||
],
|
||||
ignoreDeadLinks: true
|
||||
})
|
||||
37
docs/.vitepress/tailwind.config.js
vendored
Normal file
37
docs/.vitepress/tailwind.config.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
const colors = require('tailwindcss/colors')
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./.vitepress/theme/*.vue",
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
current: 'currentColor',
|
||||
white: colors.white,
|
||||
black: colors.black,
|
||||
gray: {
|
||||
50: "#EEEFF1",
|
||||
100: "#DEDFE3",
|
||||
200: "#BABCC5",
|
||||
300: "#999CA8",
|
||||
400: "#75798A",
|
||||
500: "#585B68",
|
||||
600: "#464853",
|
||||
700: "#363840",
|
||||
800: "#232429",
|
||||
900: "#131316"
|
||||
},
|
||||
indigo: colors.indigo,
|
||||
|
||||
},
|
||||
extend: {
|
||||
borderWidth: {
|
||||
'1': '1px',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
darkMode: 'class',
|
||||
}
|
||||
101
docs/.vitepress/theme/Home.vue
Normal file
101
docs/.vitepress/theme/Home.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<script>
|
||||
import { withBase } from 'vitepress';
|
||||
import './theme.css'
|
||||
|
||||
export default {
|
||||
|
||||
setup() {
|
||||
return { withBase };
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<main class="home">
|
||||
<header class="hero">
|
||||
<div class="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div class="mx-auto lg:text-center">
|
||||
<img class="rotating h-36 mx-auto my-8 " src="https://raw.githubusercontent.com/thomiceli/opengist/master/public/opengist.svg" alt="" >
|
||||
<a target="_blank" href="https://github.com/thomiceli/opengist/releases" class="inline-flex items-center rounded-full bg-indigo-100 hover:bg-indigo-200 px-4 py-1.5 text-lg font-medium text-indigo-700">
|
||||
<span class="pr-1">Released 1.7.5</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
|
||||
</svg>
|
||||
</a>
|
||||
<h1 class="mt-5 text-4xl font-bold tracking-tight sm:text-5xl">Opengist</h1>
|
||||
<h2 class="mt-4 text-xl">Self-hosted pastebin powered by Git, open-source alternative to Github Gist.</h2>
|
||||
</div>
|
||||
<div class="space-x-2 my-12">
|
||||
<a href="/docs" class="rounded-md bg-indigo-600 mt-6 px-5 py-3 text-xl font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Docs</a>
|
||||
<a target="_blank" href="https://demo.opengist.io" class="rounded-md bg-indigo-400 mt-6 px-5 py-3 text-xl border-white font-semibold text-white shadow-sm hover:bg-indigo-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Live demo</a>
|
||||
<a target="_blank" href="https://github.com/thomiceli/opengist" class="rounded-md bg-gray-800 mt-6 px-3 py-3 text-xl dark:border dark:border-1 dark:border-gray-400 font-semibold text-white shadow-sm hover:bg-gray-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
|
||||
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16" class="w-7 h-auto inline" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border border-1 mt-6 px-5 py-3 rounded-md shadow-sm ">
|
||||
<code class="select-all ">docker run --name <span class="text-indigo-700 dark:text-indigo-300 font-bold">opengist</span> -p <span class="text-indigo-700 dark:text-indigo-300 font-bold">6157</span>:6157 -v "<span class="text-indigo-700 dark:text-indigo-300 font-bold">$HOME/.opengist</span>:/opengist" ghcr.io/thomiceli/opengist:1</code>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="relative w-full sm:max-w-7xl mx-auto overflow-auto">
|
||||
<img class="block w-[200vw] max-w-none sm:w-full h-auto" :src="withBase('/opengist-demo.png')" alt="demo-opengist-screenshot" />
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
@-webkit-keyframes rotating /* Safari and Chrome */ {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes rotating {
|
||||
from {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.home {
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rotating {
|
||||
-webkit-animation: rotating 8s linear infinite;
|
||||
-moz-animation: rotating 4s linear infinite;
|
||||
-ms-animation: rotating 4s linear infinite;
|
||||
-o-animation: rotating 4s linear infinite;
|
||||
animation: rotating 12s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
16
docs/.vitepress/theme/Layout.vue
Normal file
16
docs/.vitepress/theme/Layout.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
import { useData } from 'vitepress'
|
||||
import Home from './Home.vue'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
|
||||
const { Layout } = DefaultTheme
|
||||
const { frontmatter } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout>
|
||||
<template v-if="frontmatter.layout === 'home'" #home-hero-after>
|
||||
<Home />
|
||||
</template>
|
||||
</Layout>
|
||||
</template>
|
||||
12
docs/.vitepress/theme/index.ts
Normal file
12
docs/.vitepress/theme/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { h } from 'vue'
|
||||
import type { Theme } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import Layout from "./Layout.vue";
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
Layout,
|
||||
enhanceApp({ app, router, siteData }) {
|
||||
// ...
|
||||
}
|
||||
} satisfies Theme
|
||||
147
docs/.vitepress/theme/style.css
Normal file
147
docs/.vitepress/theme/style.css
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Customize default theme styling by overriding CSS variables:
|
||||
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
|
||||
*/
|
||||
|
||||
/**
|
||||
* Colors
|
||||
*
|
||||
* Each colors have exact same color scale system with 3 levels of solid
|
||||
* colors with different brightness, and 1 soft color.
|
||||
*
|
||||
* - `XXX-1`: The most solid color used mainly for colored text. It must
|
||||
* satisfy the contrast ratio against when used on top of `XXX-soft`.
|
||||
*
|
||||
* - `XXX-2`: The color used mainly for hover state of the button.
|
||||
*
|
||||
* - `XXX-3`: The color for solid background, such as bg color of the button.
|
||||
* It must satisfy the contrast ratio with pure white (#ffffff) text on
|
||||
* top of it.
|
||||
*
|
||||
* - `XXX-soft`: The color used for subtle background such as custom container
|
||||
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
|
||||
* on top of it.
|
||||
*
|
||||
* The soft color must be semi transparent alpha channel. This is crucial
|
||||
* because it allows adding multiple "soft" colors on top of each other
|
||||
* to create a accent, such as when having inline code block inside
|
||||
* custom containers.
|
||||
*
|
||||
* - `default`: The color used purely for subtle indication without any
|
||||
* special meanings attched to it such as bg color for menu hover state.
|
||||
*
|
||||
* - `brand`: Used for primary brand colors, such as link text, button with
|
||||
* brand theme, etc.
|
||||
*
|
||||
* - `tip`: Used to indicate useful information. The default theme uses the
|
||||
* brand color for this by default.
|
||||
*
|
||||
* - `warning`: Used to indicate warning to the users. Used in custom
|
||||
* container, badges, etc.
|
||||
*
|
||||
* - `danger`: Used to show error, or dangerous message to the users. Used
|
||||
* in custom container, badges, etc.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-c-default-1: var(--vp-c-gray-1);
|
||||
--vp-c-default-2: var(--vp-c-gray-2);
|
||||
--vp-c-default-3: var(--vp-c-gray-3);
|
||||
--vp-c-default-soft: var(--vp-c-gray-soft);
|
||||
|
||||
--vp-c-brand-1: var(--vp-c-indigo-1);
|
||||
--vp-c-brand-2: var(--vp-c-indigo-2);
|
||||
--vp-c-brand-3: var(--vp-c-indigo-3);
|
||||
--vp-c-brand-soft: var(--vp-c-indigo-soft);
|
||||
|
||||
--vp-c-tip-1: var(--vp-c-brand-1);
|
||||
--vp-c-tip-2: var(--vp-c-brand-2);
|
||||
--vp-c-tip-3: var(--vp-c-brand-3);
|
||||
--vp-c-tip-soft: var(--vp-c-brand-soft);
|
||||
|
||||
--vp-c-warning-1: var(--vp-c-yellow-1);
|
||||
--vp-c-warning-2: var(--vp-c-yellow-2);
|
||||
--vp-c-warning-3: var(--vp-c-yellow-3);
|
||||
--vp-c-warning-soft: var(--vp-c-yellow-soft);
|
||||
|
||||
--vp-c-danger-1: var(--vp-c-red-1);
|
||||
--vp-c-danger-2: var(--vp-c-red-2);
|
||||
--vp-c-danger-3: var(--vp-c-red-3);
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Home
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
#0f0513 30%,
|
||||
#7e8b90
|
||||
);
|
||||
|
||||
--vp-home-hero-image-background-image: linear-gradient(
|
||||
-45deg,
|
||||
#bd34fe 50%,
|
||||
#47caff 50%
|
||||
);
|
||||
--vp-home-hero-image-filter: blur(44px);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(68px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Custom Block
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-custom-block-tip-border: transparent;
|
||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Algolia
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
.content img {
|
||||
padding-left: 20px;
|
||||
height: 108px;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
48
docs/administration/traefik-reverse-proxy.md
Normal file
48
docs/administration/traefik-reverse-proxy.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Use Traefik as a reverse proxy
|
||||
|
||||
You can set up Traefik in two ways:
|
||||
|
||||
<details>
|
||||
<summary>Using Docker labels</summary>
|
||||
|
||||
Add these labels to your `docker-compose.yml` file:
|
||||
|
||||
```yml
|
||||
labels:
|
||||
- traefik.http.routers.opengist.rule=Host(`opengist.example.com`) # Change to your subdomain
|
||||
# Uncomment the line below if you run Opengist in a subdirectory
|
||||
# - traefik.http.routers.app1.rule=PathPrefix(`/opengist{regex:$$|/.*}`) # Change opentist in the regex to yuor subdirectory name
|
||||
- traefik.http.routers.opengist.entrypoints=websecure # Change to the name of your 443 port entrypoint
|
||||
- traefik.http.routers.opengist.tls.certresolver=lets-encrypt # Change to certresolver's name
|
||||
- traefik.http.routers.opengist.service=opengist
|
||||
- traefik.http.services.opengist.loadBalancer.server.port=6157
|
||||
```
|
||||
</details>
|
||||
<details>
|
||||
<summary>Using a <code>yml</code> file</summary>
|
||||
|
||||
> [!Note]
|
||||
> Don't forget to change the `<server-address>` to your server's IP
|
||||
|
||||
`traefik_dynamic.yml`
|
||||
```yml
|
||||
http:
|
||||
routers:
|
||||
opengist:
|
||||
entrypoints: websecure
|
||||
rule: Host(`opengist.example.com`) # Comment this line and uncomment the line below if using a subpath
|
||||
# rule: PathPrefix(`/opengist{regex:$$|/.*}`) # Change opentist in the regex to yuor subdirectory name
|
||||
# middlewares:
|
||||
# - opengist-fail2ban
|
||||
service: opengist
|
||||
tls:
|
||||
certresolver: lets-encrypt
|
||||
services:
|
||||
opengist:
|
||||
loadbalancer:
|
||||
servers:
|
||||
- url: "http://<server-address>:6157"
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
53
docs/configuration/admin-panel.md
Normal file
53
docs/configuration/admin-panel.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Admin panel
|
||||
|
||||
The first user created on your Opengist instance has access to the Admin panel.
|
||||
|
||||
To access the Admin panel:
|
||||
|
||||
1. Log in
|
||||
2. Click your username in the upper right corner
|
||||
3. Select `Admin`
|
||||
|
||||
## Usage
|
||||
|
||||
### General
|
||||
|
||||
Here you can see some basic information, like Opengist version, alongside some stats.
|
||||
|
||||
You can also start some actions like forcing synchronization of gists,
|
||||
starting garbage collection, etc.
|
||||
|
||||
### Users
|
||||
|
||||
Here you can see your users and delete them.
|
||||
|
||||
### Gists
|
||||
|
||||
Here you can see all the gists and some basic information about them. You also have an option
|
||||
to delete them.
|
||||
|
||||
|
||||
### Invitations
|
||||
|
||||
Here you can create invitation links with some options like limiting the number of signed up
|
||||
users or setting an expiration date.
|
||||
|
||||
> [!Note]
|
||||
> Invitation links override the `Disable signup` option but not the `Disable login form` option.
|
||||
>
|
||||
> Users will see only the OAuth providers when `Disable login form` is enabled.
|
||||
|
||||
### Configuration
|
||||
|
||||
Here you can change a limited number of settings without restarting the instance.
|
||||
|
||||
- Disable signup
|
||||
- Forbid the creation of new accounts.
|
||||
- Require login
|
||||
- Enforce users to be logged in to see gists.
|
||||
- Allow individual gists without login
|
||||
- Allow individual gists to be viewed and downloaded without login, while requiring login for discovering gists.
|
||||
- Disable login form
|
||||
- Forbid logging in via the login form to force using OAuth providers instead.
|
||||
- Disable Gravatar
|
||||
- Disable the usage of Gravatar as an avatar provider.
|
||||
@@ -1,8 +1,12 @@
|
||||
---
|
||||
aside: false
|
||||
---
|
||||
|
||||
# Configuration Cheat Sheet
|
||||
|
||||
| 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: `debug`, `info`, `warn`, `error`, `fatal`. |
|
||||
| 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. |
|
||||
| opengist-home | OG_OPENGIST_HOME | home directory | Path to the directory where Opengist stores its data. |
|
||||
|
||||
@@ -27,7 +27,7 @@ Usage via command line :
|
||||
./opengist --config /path/to/config.yml
|
||||
```
|
||||
|
||||
You can start by copying and/or modifying the provided [config.yml](/config.yml) file.
|
||||
You can start by copying and/or modifying the provided [config.yml](https://github.com/thomiceli/opengist/blob/stable/config.yml) file.
|
||||
|
||||
|
||||
## Configuration via Environment Variables
|
||||
@@ -52,11 +52,11 @@ If you want your custom page to integrate well into the existing theme, you can
|
||||
</header>
|
||||
<main>
|
||||
<h3 class="text-xl font-bold leading-tight mt-4">Sub-Heading</h3>
|
||||
<p class="mt-4 ml-1"><!-- content --></p>
|
||||
<p class="mt-4 ml-1"><!-- my content --></p>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{{ template "footer" . }}
|
||||
```
|
||||
|
||||
You can adjust above as needed. Opengist uses Tailwind CSS classes.
|
||||
You can adjust above as needed. Opengist uses TailwindCSS classes.
|
||||
|
||||
@@ -2,51 +2,76 @@
|
||||
|
||||
Opengist can be configured to use OAuth to authenticate users, with GitHub, Gitea, or OpenID Connect.
|
||||
|
||||
## Github
|
||||
## GitHub
|
||||
|
||||
* Add a new OAuth app in your [GitHub account settings](https://github.com/settings/applications/new)
|
||||
* Set 'Authorization callback URL' to `http://opengist.url/oauth/github/callback`
|
||||
* Copy the 'Client ID' and 'Client Secret' and add them to the [configuration](/docs/configuration/cheat-sheet.md) :
|
||||
* Copy the 'Client ID' and 'Client Secret' and add them to the [configuration](cheat-sheet.md) :
|
||||
```yaml
|
||||
github.client-key: <key>
|
||||
github.secret: <secret>
|
||||
```
|
||||
```shell
|
||||
OG_GITHUB_CLIENT_KEY=<key>
|
||||
OG_GITHUB_SECRET=<secret>
|
||||
```
|
||||
|
||||
|
||||
## GitLab
|
||||
|
||||
* Add a new OAuth app in Application settings from the [GitLab instance](https://gitlab.com/-/user_settings/applications)
|
||||
* Set 'Redirect URI' to `http://opengist.url/oauth/gitlab/callback`
|
||||
* Copy the 'Client ID' and 'Client Secret' and add them to the [configuration](/docs/configuration/cheat-sheet.md) :
|
||||
* Copy the 'Client ID' and 'Client Secret' and add them to the [configuration](cheat-sheet.md) :
|
||||
```yaml
|
||||
gitlab.client-key: <key>
|
||||
gitlab.secret: <secret>
|
||||
# URL of the GitLab instance. Default: https://gitlab.com/
|
||||
gitlab.url: https://gitlab.com/
|
||||
```
|
||||
```shell
|
||||
OG_GITLAB_CLIENT_KEY=<key>
|
||||
OG_GITLAB_SECRET=<secret>
|
||||
# URL of the GitLab instance. Default: https://gitlab.com/
|
||||
OG_GITLAB_URL=https://gitlab.com/
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Gitea
|
||||
|
||||
* Add a new OAuth app in Application settings from the [Gitea instance](https://gitea.com/user/settings/applications)
|
||||
* Set 'Redirect URI' to `http://opengist.url/oauth/gitea/callback`
|
||||
* Copy the 'Client ID' and 'Client Secret' and add them to the [configuration](/docs/configuration/cheat-sheet.md) :
|
||||
* Copy the 'Client ID' and 'Client Secret' and add them to the [configuration](cheat-sheet.md) :
|
||||
```yaml
|
||||
gitea.client-key: <key>
|
||||
gitea.secret: <secret>
|
||||
# URL of the Gitea instance. Default: https://gitea.com/
|
||||
gitea.url: http://localhost:3000
|
||||
```
|
||||
```shell
|
||||
OG_GITEA_CLIENT_KEY=<key>
|
||||
OG_GITEA_SECRET=<secret>
|
||||
# URL of the Gitea instance. Default: https://gitea.com/
|
||||
OG_GITEA_URL=http://localhost:3000
|
||||
```
|
||||
|
||||
|
||||
|
||||
## OpenID Connect
|
||||
|
||||
* Add a new OAuth app in Application settings of your OIDC provider
|
||||
* Set 'Redirect URI' to `http://opengist.url/oauth/openid-connect/callback`
|
||||
* Copy the 'Client ID', 'Client Secret', and the discovery endpoint, and add them to the [configuration](/docs/configuration/cheat-sheet.md) :
|
||||
* Copy the 'Client ID', 'Client Secret', and the discovery endpoint, and add them to the [configuration](cheat-sheet.md) :
|
||||
```yaml
|
||||
oidc.client-key: <key>
|
||||
oidc.secret: <secret>
|
||||
# Discovery endpoint of the OpenID provider. Generally something like http://auth.example.com/.well-known/openid-configuration
|
||||
oidc.discovery-url: http://auth.example.com/.well-known/openid-configuration
|
||||
```
|
||||
```shell
|
||||
OG_OIDC_CLIENT_KEY=<key>
|
||||
OG_OIDC_SECRET=<secret>
|
||||
# Discovery endpoint of the OpenID provider. Generally something like http://auth.example.com/.well-known/openid-configuration
|
||||
OG_OIDC_DISCOVERY_URL=http://auth.example.com/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
6
docs/contributing/community.md
Normal file
6
docs/contributing/community.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Community
|
||||
|
||||
The following is a list of resources made by happy users of Opengist. Feel free to make a PR add your own!
|
||||
|
||||
- [Aetherinox/opengist-debian](https://github.com/Aetherinox/opengist-debian) - A Debian package for Opengist
|
||||
- [How to Install Opengist on Your Synology NAS](https://mariushosting.com/how-to-install-opengist-on-your-synology-nas/) - A guide to install Opengist on a Synology NAS
|
||||
@@ -1,54 +1,4 @@
|
||||
# Opengist
|
||||
|
||||
Opengist is a **self-hosted** pastebin **powered by Git**. All snippets are stored in a Git repository and can be
|
||||
read and/or modified using standard Git commands, or with the web interface.
|
||||
It is similiar to [GitHub Gist](https://gist.github.com/), but open-source and could be self-hosted.
|
||||
|
||||
Written in [Go](https://go.dev), Opengist aims to be fast and easy to deploy.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Create public, unlisted or private snippets
|
||||
* [Init](/docs/usage/init-via-git.md) / Clone / Pull / Push snippets **via Git** over HTTP or SSH
|
||||
* Syntax highlighting ; markdown & CSV support
|
||||
* Search code in snippets ; browse users snippets, likes and forks
|
||||
* Embed snippets in other websites
|
||||
* Revisions history
|
||||
* Like / Fork snippets
|
||||
* Editor with indentation mode & size ; drag and drop files
|
||||
* Download raw files or as a ZIP archive
|
||||
* Retrieve snippet data/metadata via a JSON API
|
||||
* OAuth2 login with GitHub, GitLab, Gitea, and OpenID Connect
|
||||
* Avatars via Gravatar or OAuth2 providers
|
||||
* Light/Dark mode
|
||||
* Responsive UI
|
||||
* Enable or disable signups
|
||||
* Restrict or unrestrict snippets visibility to anonymous users
|
||||
* Admin panel :
|
||||
* delete users/gists;
|
||||
* clean database/filesystem by syncing gists
|
||||
* run `git gc` for all repositories
|
||||
* SQLite database
|
||||
* Logging
|
||||
* Docker support
|
||||
|
||||
|
||||
## System requirements
|
||||
|
||||
[Git](https://git-scm.com/download) is obviously required to run Opengist, as it's the main feature of the app.
|
||||
Version **2.28** or later is recommended as the app has not been tested with older Git versions and some features would not work.
|
||||
|
||||
[OpenSSH](https://www.openssh.com/) suite if you wish to use Git over SSH.
|
||||
|
||||
|
||||
## Components
|
||||
|
||||
* Backend Web Framework: [Echo](https://echo.labstack.com/)
|
||||
* ORM: [GORM](https://gorm.io/)
|
||||
* Frontend libraries:
|
||||
* [Tailwind CSS](https://tailwindcss.com/)
|
||||
* [CodeMirror](https://codemirror.net/)
|
||||
* [Day.js](https://day.js.org/)
|
||||
* [highlight.js](https://highlightjs.org/)
|
||||
* and [others](/package.json)
|
||||
---
|
||||
layout: home
|
||||
navbar: false
|
||||
---
|
||||
|
||||
@@ -1,74 +1,7 @@
|
||||
# Installation
|
||||
# Install Opengist
|
||||
|
||||
## With Docker
|
||||
There are several ways to install Opengist, depending on your preferences and your environment.
|
||||
|
||||
Docker [images](https://github.com/thomiceli/opengist/pkgs/container/opengist) are available for each release :
|
||||
|
||||
```shell
|
||||
docker pull ghcr.io/thomiceli/opengist:1
|
||||
```
|
||||
|
||||
It can be used in a `docker-compose.yml` file :
|
||||
|
||||
1. Create a `docker-compose.yml` file with the following content
|
||||
2. Run `docker compose up -d`
|
||||
3. Opengist is now running on port 6157, you can browse http://localhost:6157
|
||||
|
||||
```yml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
opengist:
|
||||
image: ghcr.io/thomiceli/opengist:1
|
||||
container_name: opengist
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6157:6157" # HTTP port
|
||||
- "2222:2222" # SSH port, can be removed if you don't use SSH
|
||||
volumes:
|
||||
- "$HOME/.opengist:/opengist"
|
||||
```
|
||||
|
||||
You can define which user/group should run the container and own the files by setting the `UID` and `GID` environment
|
||||
variables :
|
||||
|
||||
```yml
|
||||
services:
|
||||
opengist:
|
||||
# ...
|
||||
environment:
|
||||
UID: 1001
|
||||
GID: 1001
|
||||
```
|
||||
|
||||
## Via binary
|
||||
|
||||
Download the archive for your system from the release page [here](https://github.com/thomiceli/opengist/releases/latest), and extract it.
|
||||
|
||||
```shell
|
||||
# example for linux amd64
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.7.3/opengist1.7.3-linux-amd64.tar.gz
|
||||
|
||||
tar xzvf opengist1.7.3-linux-amd64.tar.gz
|
||||
cd opengist
|
||||
chmod +x opengist
|
||||
./opengist # with or without `--config config.yml`
|
||||
```
|
||||
|
||||
|
||||
## From source
|
||||
|
||||
Requirements:
|
||||
* [Git](https://git-scm.com/downloads) (2.28+)
|
||||
* [Go](https://go.dev/doc/install) (1.22+)
|
||||
* [Node.js](https://nodejs.org/en/download/) (16+)
|
||||
* [Make](https://linux.die.net/man/1/make) (optional, but easier)
|
||||
|
||||
```shell
|
||||
git clone https://github.com/thomiceli/opengist
|
||||
cd opengist
|
||||
make
|
||||
./opengist
|
||||
```
|
||||
|
||||
Opengist is now running on port 6157, you can browse http://localhost:6157
|
||||
- [Docker](installation/docker.md)
|
||||
- [Source](installation/source.md)
|
||||
- [Binary](installation/binary.md)
|
||||
|
||||
14
docs/installation/binary.md
Normal file
14
docs/installation/binary.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Install from binary
|
||||
|
||||
Download the archive for your system from the release page [here](https://github.com/thomiceli/opengist/releases/latest), and extract it.
|
||||
|
||||
```shell
|
||||
# example for linux amd64
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.7.5/opengist1.7.5-linux-amd64.tar.gz
|
||||
|
||||
tar xzvf opengist1.7.5-linux-amd64.tar.gz
|
||||
cd opengist
|
||||
chmod +x opengist
|
||||
./opengist # with or without `--config config.yml`
|
||||
```
|
||||
|
||||
43
docs/installation/docker.md
Normal file
43
docs/installation/docker.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Install with Docker
|
||||
|
||||
Docker [images](https://github.com/thomiceli/opengist/pkgs/container/opengist) are available for each release :
|
||||
|
||||
```shell
|
||||
docker pull ghcr.io/thomiceli/opengist:1
|
||||
```
|
||||
|
||||
It can be used in a `docker-compose.yml` file :
|
||||
|
||||
1. Create a `docker-compose.yml` file with the following content
|
||||
2. Run `docker compose up -d`
|
||||
3. Opengist is now running on port 6157, you can browse http://localhost:6157
|
||||
|
||||
```yml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
opengist:
|
||||
image: ghcr.io/thomiceli/opengist:1
|
||||
container_name: opengist
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6157:6157" # HTTP port
|
||||
- "2222:2222" # SSH port, can be removed if you don't use SSH
|
||||
volumes:
|
||||
- "$HOME/.opengist:/opengist"
|
||||
environment:
|
||||
# OG_LOG_LEVEL: info
|
||||
# other configuration options
|
||||
```
|
||||
|
||||
You can define which user/group should run the container and own the files by setting the `UID` and `GID` environment
|
||||
variables :
|
||||
|
||||
```yml
|
||||
services:
|
||||
opengist:
|
||||
# ...
|
||||
environment:
|
||||
UID: 1001
|
||||
GID: 1001
|
||||
```
|
||||
19
docs/installation/source.md
Normal file
19
docs/installation/source.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Installation from source
|
||||
|
||||
Requirements:
|
||||
* [Git](https://git-scm.com/downloads) (2.28+)
|
||||
* [Go](https://go.dev/doc/install) (1.22+)
|
||||
* [Node.js](https://nodejs.org/en/download/) (16+)
|
||||
* [Make](https://linux.die.net/man/1/make) (optional, but easier)
|
||||
|
||||
```shell
|
||||
git clone https://github.com/thomiceli/opengist
|
||||
cd opengist
|
||||
|
||||
git checkout v1.7.5 # optional, to checkout the latest release
|
||||
|
||||
make
|
||||
./opengist
|
||||
```
|
||||
|
||||
Opengist is now running on port 6157, you can browse http://localhost:6157
|
||||
55
docs/introduction.md
Normal file
55
docs/introduction.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Opengist
|
||||
|
||||
<img height="108px" src="https://raw.githubusercontent.com/thomiceli/opengist/master/public/opengist.svg" alt="Opengist" align="right" />
|
||||
|
||||
Opengist is a **self-hosted** pastebin **powered by Git**. All snippets are stored in a Git repository and can be
|
||||
read and/or modified using standard Git commands, or with the web interface.
|
||||
It is similiar to [GitHub Gist](https://gist.github.com/), but open-source and could be self-hosted.
|
||||
|
||||
Written in [Go](https://go.dev), Opengist aims to be fast and easy to deploy.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Create public, unlisted or private snippets
|
||||
* [Init](usage/init-via-git.md) / Clone / Pull / Push snippets **via Git** over HTTP or SSH
|
||||
* Syntax highlighting ; markdown & CSV support
|
||||
* Search code in snippets ; browse users snippets, likes and forks
|
||||
* Embed snippets in other websites
|
||||
* Revisions history
|
||||
* Like / Fork snippets
|
||||
* Editor with indentation mode & size ; drag and drop files
|
||||
* Download raw files or as a ZIP archive
|
||||
* Retrieve snippet data/metadata via a JSON API
|
||||
* OAuth2 login with GitHub, GitLab, Gitea, and OpenID Connect
|
||||
* Avatars via Gravatar or OAuth2 providers
|
||||
* Light/Dark mode
|
||||
* Responsive UI
|
||||
* Enable or disable signups
|
||||
* Restrict or unrestrict snippets visibility to anonymous users
|
||||
* Admin panel :
|
||||
* delete users/gists;
|
||||
* clean database/filesystem by syncing gists
|
||||
* run `git gc` for all repositories
|
||||
* SQLite database
|
||||
* Logging
|
||||
* Docker support
|
||||
|
||||
|
||||
## System requirements
|
||||
|
||||
[Git](https://git-scm.com/download) is obviously required to run Opengist, as it's the main feature of the app.
|
||||
Version **2.28** or later is recommended as the app has not been tested with older Git versions and some features would not work.
|
||||
|
||||
[OpenSSH](https://www.openssh.com/) suite if you wish to use Git over SSH.
|
||||
|
||||
|
||||
## Components
|
||||
|
||||
* Backend Web Framework: [Echo](https://echo.labstack.com/)
|
||||
* ORM: [GORM](https://gorm.io/)
|
||||
* Frontend libraries:
|
||||
* [TailwindCSS](https://tailwindcss.com/)
|
||||
* [CodeMirror](https://codemirror.net/)
|
||||
* [Day.js](https://day.js.org/)
|
||||
* and [others](/package.json)
|
||||
17
docs/public/favicon.svg
Normal file
17
docs/public/favicon.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="document" transform="scale(1.6666666666666667 1.6666666666666667) translate(150.0 150.0)">
|
||||
<path class="st0" d="M131.3,24.3c13.7-71-33.9-139.5-106.4-152.9C-47.7-142-117.6-95.3-131.3-24.3s33.9,139.5,106.4,152.9 C47.7,142,117.6,95.3,131.3,24.3z"/>
|
||||
<path class="st0" d="M128.9,0c0,55.7-36.8,103-88,119.8c0.2-1.2,0.3-2.5,0.3-4c0.1-22.3,0.2-36.2,0.2-52.8 c0-11.7-0.2-18.1-0.2-18.1c1.8,0,21.1-6,29.9-12.1S89.2,15.1,90.5-1.4c1.3-16.6-6-36.2-12.4-47.8C65.3-72.4,54.7-86.6,45.4-94.5 c-9.3-7.8-16.1-6.1-22.1-1.4S8.5-76.9,2.2-71.2c-3,2.8-10.6,12-20.4,3.3C-21-70.3-38-93.6-48.5-90.6c-13.1,3.7-28.1,27.3-35.1,43.8 c-9,21-10.8,33.6-6.1,63.5c4.7,29.9,7.5,60,11.8,76.4c1,4,2.3,7.4,4,10.4c-33.2-22.8-55-60.7-55-103.5 c0-69.7,57.7-126.3,128.9-126.3S128.9-69.7,128.9,0z"/>
|
||||
<path d="M0-145c-81.8,0-148.1,64.9-148.1,145S-81.8,145,0,145S148.1,80.1,148.1,0S81.8-145,0-145z M40.9,119.8 c0.2-1.2,0.3-2.5,0.3-4c0.1-22.3,0.2-36.2,0.2-52.8c0-11.7-0.2-18.1-0.2-18.1c1.8,0,21.1-6,29.9-12.1S89.2,15.1,90.5-1.4 c1.3-16.6-6-36.2-12.4-47.8C65.3-72.4,54.7-86.6,45.4-94.5c-9.3-7.8-16.1-6.1-22.1-1.4S8.5-76.9,2.2-71.2c-3,2.8-10.6,12-20.4,3.3 C-21-70.3-38-93.6-48.5-90.6c-13.1,3.7-28.1,27.3-35.1,43.8c-9,21-10.8,33.6-6.1,63.5c4.7,29.9,7.5,60,11.8,76.4 c1,4,2.3,7.4,4,10.4c-33.2-22.8-55-60.7-55-103.5c0-69.7,57.7-126.3,128.9-126.3S128.9-69.7,128.9,0 C128.9,55.7,92.1,103,40.9,119.8z"/>
|
||||
<path class="st0" d="M-102.8-7.2l91.2-9.4l-0.3-7l-91.2,9.4L-102.8-7.2z"/>
|
||||
<path class="st0" d="M12-17.3c0.8-9.6-6.5-18-16.3-18.8s-18.4,6.4-19.2,16S-17-2.1-7.2-1.3S11.2-7.7,12-17.3z"/>
|
||||
<path class="st0" d="M62.9-24.6c0.8-9.6-6.5-18-16.3-18.8c-9.8-0.8-18.4,6.4-19.2,16c-0.8,9.6,6.5,18,16.3,18.8S62.1-15,62.9-24.6z "/>
|
||||
<path class="st0" d="M-11.8-16.8l67.6-7.3l-0.5-6.3l-67.5,7.3L-11.8-16.8z"/>
|
||||
<path class="st0" d="M53.1-23.6l49.5-12.2l-0.6-6.3L52.5-29.9L53.1-23.6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
BIN
docs/public/opengist-demo.png
Normal file
BIN
docs/public/opengist-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
@@ -1,4 +1,4 @@
|
||||
# Update
|
||||
# Update Opengist
|
||||
|
||||
## Make a backup
|
||||
|
||||
@@ -27,9 +27,9 @@ Stop the running instance; then like your first installation of Opengist, downlo
|
||||
|
||||
```shell
|
||||
# example for linux amd64
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.7.3/opengist1.7.3-linux-amd64.tar.gz
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.7.5/opengist1.7.5-linux-amd64.tar.gz
|
||||
|
||||
tar xzvf opengist1.7.3-linux-amd64.tar.gz
|
||||
tar xzvf opengist1.7.5-linux-amd64.tar.gz
|
||||
cd opengist
|
||||
chmod +x opengist
|
||||
./opengist # with or without `--config config.yml`
|
||||
@@ -40,6 +40,7 @@ chmod +x opengist
|
||||
Stop the running instance; then pull the last changes from the master branch, and build the new version.
|
||||
|
||||
```shell
|
||||
git switch master
|
||||
git pull
|
||||
make
|
||||
./opengist
|
||||
|
||||
@@ -39,4 +39,4 @@ To http://localhost:6157/init
|
||||
* [new branch] master -> master
|
||||
```
|
||||
|
||||
https://github.com/thomiceli/opengist/assets/27960254/3fe1a0ba-b638-4928-83a1-f38e46fea066
|
||||
<video controls="controls" src="https://github.com/thomiceli/opengist/assets/27960254/3fe1a0ba-b638-4928-83a1-f38e46fea066" />
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"github.com/thomiceli/opengist/internal/web"
|
||||
"github.com/urfave/cli/v2"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var CmdVersion = cli.Command{
|
||||
@@ -29,10 +31,17 @@ var CmdStart = cli.Command{
|
||||
Name: "start",
|
||||
Usage: "Start Opengist server",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
stopCtx, stop := signal.NotifyContext(ctx.Context, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
Initialize(ctx)
|
||||
|
||||
go web.NewServer(os.Getenv("OG_DEV") == "1", path.Join(config.GetHomeDir(), "sessions")).Start()
|
||||
go ssh.Start()
|
||||
select {}
|
||||
|
||||
<-stopCtx.Done()
|
||||
shutdown()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -110,12 +119,24 @@ func Initialize(ctx *cli.Context) {
|
||||
|
||||
if config.C.IndexEnabled {
|
||||
log.Info().Msg("Index directory: " + filepath.Join(homePath, config.C.IndexDirname))
|
||||
if err := index.Open(filepath.Join(homePath, config.C.IndexDirname)); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to open index")
|
||||
}
|
||||
index.Init(filepath.Join(homePath, config.C.IndexDirname))
|
||||
}
|
||||
}
|
||||
|
||||
func shutdown() {
|
||||
log.Info().Msg("Shutting down database...")
|
||||
if err := db.Close(); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to close database")
|
||||
}
|
||||
|
||||
if config.C.IndexEnabled {
|
||||
log.Info().Msg("Shutting down index...")
|
||||
index.Close()
|
||||
}
|
||||
|
||||
log.Info().Msg("Shutdown complete")
|
||||
}
|
||||
|
||||
func createSymlink(homePath string, configPath string) error {
|
||||
if err := os.MkdirAll(filepath.Join(homePath, "symlinks"), 0755); err != nil {
|
||||
return err
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -153,6 +154,21 @@ func InitLog() {
|
||||
logOutputTypes := utils.RemoveDuplicates[string](
|
||||
strings.Split(strings.ToLower(C.LogOutput), ","),
|
||||
)
|
||||
|
||||
consoleWriter := zerolog.NewConsoleWriter(
|
||||
func(w *zerolog.ConsoleWriter) {
|
||||
w.TimeFormat = time.TimeOnly
|
||||
w.FormatCaller = func(i interface{}) string {
|
||||
file := i.(string)
|
||||
index := strings.Index(file, "internal")
|
||||
if index == -1 {
|
||||
return file
|
||||
}
|
||||
return file[index:]
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
for _, logOutputType := range logOutputTypes {
|
||||
logOutputType = strings.TrimSpace(logOutputType)
|
||||
if !slices.Contains([]string{"stdout", "file"}, logOutputType) {
|
||||
@@ -162,7 +178,7 @@ func InitLog() {
|
||||
|
||||
switch logOutputType {
|
||||
case "stdout":
|
||||
logWriters = append(logWriters, zerolog.NewConsoleWriter())
|
||||
logWriters = append(logWriters, consoleWriter)
|
||||
defer func() { log.Debug().Msg("Logging to stdout") }()
|
||||
case "file":
|
||||
file, err := os.OpenFile(filepath.Join(GetHomeDir(), "log", "opengist.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
@@ -174,14 +190,14 @@ func InitLog() {
|
||||
}
|
||||
}
|
||||
if len(logWriters) == 0 {
|
||||
logWriters = append(logWriters, zerolog.NewConsoleWriter())
|
||||
logWriters = append(logWriters, consoleWriter)
|
||||
defer func() { log.Warn().Msg("No valid log outputs, defaulting to stdout") }()
|
||||
}
|
||||
|
||||
multi := zerolog.MultiLevelWriter(logWriters...)
|
||||
log.Logger = zerolog.New(multi).Level(level).With().Timestamp().Logger()
|
||||
log.Logger = zerolog.New(multi).Level(level).With().Caller().Timestamp().Logger()
|
||||
|
||||
if !slices.Contains([]string{"trace", "debug", "info", "warn", "error", "fatal", "panic"}, strings.ToLower(C.LogLevel)) {
|
||||
if !slices.Contains([]string{"debug", "info", "warn", "error", "fatal"}, strings.ToLower(C.LogLevel)) {
|
||||
log.Warn().Msg("Invalid log level: " + C.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var title = cases.Title(language.English)
|
||||
var Locales = NewLocaleStore()
|
||||
|
||||
type LocaleStore struct {
|
||||
@@ -59,7 +58,7 @@ func (store *LocaleStore) loadLocaleFromYAML(localeCode, path string) error {
|
||||
|
||||
locale := &Locale{
|
||||
Code: localeCode,
|
||||
Name: title.String(name),
|
||||
Name: cases.Title(language.English).String(name),
|
||||
Messages: make(map[string]string),
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,11 @@ gist.new.add-file: 'Datei hinzufügen'
|
||||
gist.new.create-public-button: 'Öffentliche Gist erstellen'
|
||||
gist.new.create-unlisted-button: 'Nicht gelistete Gist erstellen'
|
||||
gist.new.create-private-button: 'Private Gist erstellen'
|
||||
gist.new.preview: 'Vorschau'
|
||||
gist.new.create-a-new-gist: 'Neue Gist erstellen'
|
||||
|
||||
gist.edit.editing: 'Bearbeiten'
|
||||
gist.edit.edit-gist: '%s bearbeiten'
|
||||
gist.edit.change-visibility: 'Sichtbarkeit ändern'
|
||||
gist.edit.delete: 'Löschen'
|
||||
gist.edit.cancel: 'Abbrechen'
|
||||
@@ -67,6 +70,9 @@ gist.list.forks: 'Forks'
|
||||
gist.list.files: 'Dateien'
|
||||
gist.list.last-active: 'Zuletzt aktiv'
|
||||
gist.list.no-gists: 'Keine Gists'
|
||||
gist.list.all-liked-by: 'Alle Gists favorisiert von %s'
|
||||
gist.list.all-forked-by: 'Alle Gists geforked von %s'
|
||||
gist.list.all-from: 'Alle Gists von %s'
|
||||
|
||||
gist.search.found: 'Gists gefunden'
|
||||
gist.search.no-results: 'Keine Gists gefunden'
|
||||
@@ -79,9 +85,11 @@ gist.search.help.language: 'Gists in Sprache'
|
||||
gist.forks: 'Forks'
|
||||
gist.forks.view: 'Fork ansehen'
|
||||
gist.forks.no: 'Keine öffentlichen Forks'
|
||||
gist.forks.for: 'Fork für %s'
|
||||
|
||||
gist.likes: 'Favoriten'
|
||||
gist.likes.no: 'Keine Favorisierungen'
|
||||
gist.likes.for: 'Favortitisiert für %s'
|
||||
|
||||
gist.revisions: 'Revisionen'
|
||||
gist.revision.revised: 'hat die Gist bearbeitet'
|
||||
@@ -94,6 +102,7 @@ gist.revision.file-renamed-no-changes: 'Datei ohne Änderung umbenannt'
|
||||
gist.revision.empty-file: 'Leere Datei'
|
||||
gist.revision.no-changes: 'Keine Änderungen'
|
||||
gist.revision.no-revisions: 'Keine Änderungen zum Anzeigen'
|
||||
gist.revision-of: 'Änderungen von %s'
|
||||
|
||||
settings: 'Einstellungen'
|
||||
settings.email: 'Email'
|
||||
@@ -117,6 +126,7 @@ settings.delete-ssh-key-confirm: 'Entfernen von SSH-Schlüssel bestätigen'
|
||||
settings.ssh-key-added-at: 'Hinzugefügt'
|
||||
settings.ssh-key-never-used: 'Nie benutzt'
|
||||
settings.ssh-key-last-used: 'Zuletzt benutzt'
|
||||
settings.ssh-key-exists: 'SSH Schlüssel existiert bereits'
|
||||
settings.change-username: 'Benutzername ändern'
|
||||
settings.create-password: 'Password erstellen'
|
||||
settings.create-password-help: 'Passwort erstellen'
|
||||
@@ -132,13 +142,24 @@ auth.username: 'Benutzername'
|
||||
auth.password: 'Passwort'
|
||||
auth.register-instead: 'Stattdessen registrieren'
|
||||
auth.login-instead: 'Stattdessen anmelden'
|
||||
auth.oauth: 'Weiter mit %s Account'
|
||||
|
||||
error: 'Fehler'
|
||||
error.page-not-found: 'Seite nicht gefunden'
|
||||
error.bad-request: 'Ungültige Anfrage'
|
||||
error.signup-disabled: 'Registrierung ist deaktivert'
|
||||
error.signup-disabled-form: 'Registrierung über das Formular ist deaktiviert'
|
||||
error.login-disabled-form: 'Anmeldung über das Formular ist deaktiviert'
|
||||
error.complete-oauth-login: 'Anmeldung kann nicht abgeschlossen werden: %s'
|
||||
error.oauth-unsupported: 'Nicht unterstützer Anbieter'
|
||||
error.cannot-bind-data: 'Daten können nicht gebunden werden'
|
||||
error.invalid-number: 'Ungültige Nummer'
|
||||
error.invalid-character-unescaped: 'Ungültiges Zeichen unescapped'
|
||||
|
||||
header.menu.all: 'Alle'
|
||||
header.menu.new: 'Neu'
|
||||
header.menu.search: 'Suchen'
|
||||
header.menu.my-gists: 'meine Gists'
|
||||
header.menu.my-gists: 'Meine Gists'
|
||||
header.menu.liked: 'Favorisiert'
|
||||
header.menu.admin: 'Admin'
|
||||
header.menu.settings: 'Einstellungen'
|
||||
@@ -160,6 +181,8 @@ admin.general: 'Allgemein'
|
||||
admin.users: 'Benutzer'
|
||||
admin.gists: 'Gists'
|
||||
admin.configuration: 'Konfiguration'
|
||||
admin.invitations: 'Einladungen'
|
||||
admin.invitations.create: 'Einladung erstellen'
|
||||
admin.versions: 'Versionen'
|
||||
admin.ssh_keys: 'SSH Schlüssel'
|
||||
admin.stats: 'Statistiken'
|
||||
@@ -181,80 +204,66 @@ admin.disable-signup: 'Registrierung deaktivieren'
|
||||
admin.disable-signup_help: 'Die Erstellung neuer Accounts verbieten.'
|
||||
admin.require-login: 'Anmeldung nötig'
|
||||
admin.require-login_help: 'Benutzer müssen sich anmelden, bevor sie Gists ansehen können.'
|
||||
admin.allow-gists-without-login: 'Erlaube einzelne Gists ohne login'
|
||||
admin.allow-gists-without-login_help: 'Einzelne Gists können ohne Anmeldung angesehen und heruntergeladen werden, während für das Auffinden von Gists eine Anmeldung erforderlich ist.'
|
||||
admin.disable-login: 'Login-Maske deaktivieren'
|
||||
admin.disable-login_help: 'Login über Login-Maske verbieten und Benutzung von OAuth Providern erzwingen.'
|
||||
admin.disable-gravatar: 'Gravatar deaktivieren'
|
||||
admin.disable-gravatar_help: 'Gravatar als Avatar-Anbieter deaktivieren.'
|
||||
admin.allow-gists-without-login:
|
||||
admin.allow-gists-without-login_help:
|
||||
|
||||
admin.users.delete_confirm: 'Willst du diesen Benutzer löschen?'
|
||||
|
||||
admin.gists.title: 'Titel'
|
||||
admin.gists.private: 'privat?'
|
||||
admin.gists.private: 'Privat?'
|
||||
admin.gists.nb-files: 'Anz. Dateien'
|
||||
admin.gists.nb-likes: 'Anz. Favoriten'
|
||||
admin.gists.delete_confirm: 'Willst du diese Gist löschen?'
|
||||
auth.oauth: ''
|
||||
gist.new.preview: ''
|
||||
gist.new.create-a-new-gist: ''
|
||||
gist.edit.edit-gist: ''
|
||||
gist.list.all-liked-by: ''
|
||||
gist.list.all-forked-by: ''
|
||||
gist.list.all-from: ''
|
||||
gist.forks.for: ''
|
||||
gist.likes.for: ''
|
||||
gist.revision-of: ''
|
||||
error.page-not-found: ''
|
||||
error.bad-request: ''
|
||||
error.signup-disabled: ''
|
||||
error.signup-disabled-form: ''
|
||||
error.login-disabled-form: ''
|
||||
error.complete-oauth-login: ''
|
||||
error.oauth-unsupported: ''
|
||||
error.cannot-bind-data: ''
|
||||
error.invalid-number: ''
|
||||
error.invalid-character-unescaped: ''
|
||||
admin.invitations: ''
|
||||
admin.invitations.create: ''
|
||||
admin.invitations.help: ''
|
||||
admin.invitations.max_uses: ''
|
||||
admin.invitations.expires_at: ''
|
||||
admin.invitations.code: ''
|
||||
admin.invitations.copy_link: ''
|
||||
admin.invitations.uses: ''
|
||||
admin.invitations.expired: ''
|
||||
flash.admin.user-deleted: ''
|
||||
flash.admin.gist-deleted: ''
|
||||
flash.admin.invitation-created: ''
|
||||
flash.admin.invitation-deleted: ''
|
||||
flash.admin.sync-fs: ''
|
||||
flash.admin.sync-db: ''
|
||||
flash.admin.git-gc: ''
|
||||
flash.admin.sync-previews: ''
|
||||
flash.admin.reset-hooks: ''
|
||||
flash.admin.index-gists: ''
|
||||
flash.auth.username-exists: ''
|
||||
flash.auth.invalid-credentials: ''
|
||||
flash.auth.account-linked-oauth: ''
|
||||
flash.auth.account-unlinked-oauth: ''
|
||||
flash.auth.user-sshkeys-not-retrievable: ''
|
||||
flash.auth.user-sshkeys-not-created: ''
|
||||
flash.auth.must-be-logged-in: ''
|
||||
flash.gist.visibility-changed: ''
|
||||
flash.gist.deleted: ''
|
||||
flash.gist.fork-own-gist: ''
|
||||
flash.gist.forked: ''
|
||||
flash.user.email-updated: ''
|
||||
flash.user.invalid-ssh-key: ''
|
||||
flash.user.ssh-key-added: ''
|
||||
flash.user.ssh-key-deleted: ''
|
||||
flash.user.password-updated: ''
|
||||
flash.user.username-updated: ''
|
||||
validation.is-too-long: ''
|
||||
validation.should-not-be-empty: ''
|
||||
validation.should-not-include-sub-directory: ''
|
||||
validation.should-only-contain-alphanumeric-characters: ''
|
||||
validation.should-only-contain-alphanumeric-characters-and-dashes: ''
|
||||
validation.not-enough: ''
|
||||
validation.invalid: ''
|
||||
html.title.admin-panel: ''
|
||||
|
||||
admin.invitations.help: 'Einladungen können zur Erstellung eines Kontos verwendet werden, auch wenn die Anmeldung deaktiviert ist.'
|
||||
admin.invitations.max_uses: 'Maximale Verwendungen'
|
||||
admin.invitations.expires_at: 'Läuft ab am'
|
||||
admin.invitations.code: 'Code'
|
||||
admin.invitations.copy_link: 'Link kopieren'
|
||||
admin.invitations.uses: 'Verwendungen'
|
||||
admin.invitations.expired: 'Abgelaufen'
|
||||
|
||||
flash.admin.user-deleted: 'Benutzer wurde gelöscht'
|
||||
flash.admin.gist-deleted: 'Gist wurde gelöscht'
|
||||
flash.admin.invitation-created: 'Einladung wurde erstellt'
|
||||
flash.admin.invitation-deleted: 'Einladung wurde gelöscht'
|
||||
flash.admin.sync-fs: 'Synchronisiere Repositories vom Dateisystem...'
|
||||
flash.admin.sync-db: 'Synchronisiere Repositories aus der Datenbank...'
|
||||
flash.admin.git-gc: 'Sammle Repositories...'
|
||||
flash.admin.sync-previews: 'Synchronisiere Gist-Vorschauen...'
|
||||
flash.admin.reset-hooks: 'Setze Git-Server-Hooks für alle Repositories zurück...'
|
||||
flash.admin.index-gists: 'Indiziere alle Gists...'
|
||||
|
||||
flash.auth.username-exists: 'Benutzername existiert bereits'
|
||||
flash.auth.invalid-credentials: 'Ungültige Anmeldeinformationen'
|
||||
flash.auth.account-linked-oauth: 'Konto verknüpft mit %s'
|
||||
flash.auth.account-unlinked-oauth: 'Konto getrennt von %s'
|
||||
flash.auth.user-sshkeys-not-retrievable: 'Benutzerschlüssel konnten nicht abgerufen werden'
|
||||
flash.auth.user-sshkeys-not-created: 'SSH-Schlüssel konnte nicht erstellt werden'
|
||||
flash.auth.must-be-logged-in: 'Sie müssen eingeloggt sein, um auf Gists zuzugreifen'
|
||||
|
||||
flash.gist.visibility-changed: 'Gist-Sichtbarkeit wurde geändert'
|
||||
flash.gist.deleted: 'Gist wurde gelöscht'
|
||||
flash.gist.fork-own-gist: 'Eigene Gists können nicht geforkt werden'
|
||||
flash.gist.forked: 'Gist wurde geforkt'
|
||||
|
||||
flash.user.email-updated: 'E-Mail wurde aktualisiert'
|
||||
flash.user.invalid-ssh-key: 'Ungültiger SSH-Schlüssel'
|
||||
flash.user.ssh-key-added: 'SSH-Schlüssel hinzugefügt'
|
||||
flash.user.ssh-key-deleted: 'SSH-Schlüssel gelöscht'
|
||||
flash.user.password-updated: 'Passwort wurde aktualisiert'
|
||||
flash.user.username-updated: 'Benutzername wurde aktualisiert'
|
||||
|
||||
validation.is-too-long: 'Feld %s ist zu lang'
|
||||
validation.should-not-be-empty: 'Feld %s darf nicht leer sein'
|
||||
validation.should-not-include-sub-directory: 'Feld %s darf kein Unterverzeichnis enthalten'
|
||||
validation.should-only-contain-alphanumeric-characters: 'Feld %s darf nur alphanumerische Zeichen enthalten'
|
||||
validation.should-only-contain-alphanumeric-characters-and-dashes: 'Feld %s darf nur alphanumerische Zeichen und Bindestriche enthalten'
|
||||
validation.not-enough: 'Nicht genug %s'
|
||||
validation.invalid: 'Ungültiges %s'
|
||||
|
||||
html.title.admin-panel: 'Admin-Panel'
|
||||
|
||||
@@ -166,8 +166,8 @@ admin.disable-login: Désactiver le formulaire de connexion
|
||||
admin.disable-login_help: Interdire la connexion via le formulaire de connexion pour forcer l'utilisation des fournisseurs OAuth à la place.
|
||||
admin.disable-gravatar: Désactiver Gravatar
|
||||
admin.disable-gravatar_help: Désactiver l'utilisation de Gravatar comme fournisseur d'avatar.
|
||||
admin.allow-gists-without-login:
|
||||
admin.allow-gists-without-login_help:
|
||||
admin.allow-gists-without-login: Autoriser les gists individuelles sans login
|
||||
admin.allow-gists-without-login_help: Autoriser la visualisation et le téléchargement de gists individuels sans connexion, tout en exigeant une connexion pour la découverte de gists.
|
||||
admin.users.delete_confirm: Voulez-vous supprimer cet utilisateur ?
|
||||
|
||||
admin.gists.title: Titre
|
||||
@@ -194,66 +194,67 @@ gist.new.url: URL
|
||||
gist.search.no-results: Aucun gist trouvé
|
||||
settings.unlink-gitlab-account: Détacher le compte GitLab
|
||||
admin.actions.index-gists: Indexer tous les gists
|
||||
gist.new.preview: ''
|
||||
gist.new.create-a-new-gist: ''
|
||||
gist.edit.edit-gist: ''
|
||||
gist.list.all-liked-by: ''
|
||||
gist.list.all-forked-by: ''
|
||||
gist.list.all-from: ''
|
||||
gist.forks.for: ''
|
||||
gist.likes.for: ''
|
||||
gist.revision-of: ''
|
||||
error.page-not-found: ''
|
||||
error.bad-request: ''
|
||||
error.signup-disabled: ''
|
||||
error.signup-disabled-form: ''
|
||||
error.login-disabled-form: ''
|
||||
error.complete-oauth-login: ''
|
||||
error.oauth-unsupported: ''
|
||||
error.cannot-bind-data: ''
|
||||
error.invalid-number: ''
|
||||
error.invalid-character-unescaped: ''
|
||||
admin.invitations: ''
|
||||
admin.invitations.create: ''
|
||||
admin.invitations.help: ''
|
||||
admin.invitations.max_uses: ''
|
||||
admin.invitations.expires_at: ''
|
||||
admin.invitations.code: ''
|
||||
admin.invitations.copy_link: ''
|
||||
admin.invitations.uses: ''
|
||||
admin.invitations.expired: ''
|
||||
flash.admin.user-deleted: ''
|
||||
flash.admin.gist-deleted: ''
|
||||
flash.admin.invitation-created: ''
|
||||
flash.admin.invitation-deleted: ''
|
||||
flash.admin.sync-fs: ''
|
||||
flash.admin.sync-db: ''
|
||||
flash.admin.git-gc: ''
|
||||
flash.admin.sync-previews: ''
|
||||
flash.admin.reset-hooks: ''
|
||||
flash.admin.index-gists: ''
|
||||
flash.auth.username-exists: ''
|
||||
flash.auth.invalid-credentials: ''
|
||||
flash.auth.account-linked-oauth: ''
|
||||
flash.auth.account-unlinked-oauth: ''
|
||||
flash.auth.user-sshkeys-not-retrievable: ''
|
||||
flash.auth.user-sshkeys-not-created: ''
|
||||
flash.auth.must-be-logged-in: ''
|
||||
flash.gist.visibility-changed: ''
|
||||
flash.gist.deleted: ''
|
||||
flash.gist.fork-own-gist: ''
|
||||
flash.gist.forked: ''
|
||||
flash.user.email-updated: ''
|
||||
flash.user.invalid-ssh-key: ''
|
||||
flash.user.ssh-key-added: ''
|
||||
flash.user.ssh-key-deleted: ''
|
||||
flash.user.password-updated: ''
|
||||
flash.user.username-updated: ''
|
||||
validation.is-too-long: ''
|
||||
validation.should-not-be-empty: ''
|
||||
validation.should-not-include-sub-directory: ''
|
||||
validation.should-only-contain-alphanumeric-characters: ''
|
||||
validation.should-only-contain-alphanumeric-characters-and-dashes: ''
|
||||
validation.not-enough: ''
|
||||
validation.invalid: ''
|
||||
html.title.admin-panel: ''
|
||||
gist.new.preview: 'Aperçu'
|
||||
gist.new.create-a-new-gist: 'Créer un nouveau gist'
|
||||
gist.edit.edit-gist: 'Modifier %s'
|
||||
gist.list.all-liked-by: 'Tous les gists aimés par %s'
|
||||
gist.list.all-forked-by: 'Tous les gists forkées par %s'
|
||||
gist.list.all-from: 'Tous les gists de %S'
|
||||
gist.forks.for: 'Forks pour %s'
|
||||
gist.likes.for: 'J''aimes pour %s'
|
||||
gist.revision-of: 'Révisions pour %s'
|
||||
error.page-not-found: 'Page non trouvée'
|
||||
error.bad-request: 'Requête erronée'
|
||||
error.signup-disabled: 'L''inscription est désactivée'
|
||||
error.signup-disabled-form: 'L''inscription via le formulaire d''enregistrement est désactivée'
|
||||
error.login-disabled-form: 'La connexion via le formulaire de connexion est désactivée'
|
||||
error.complete-oauth-login: 'Impossible de terminer l''authentification de l''utilisateur : %s'
|
||||
error.oauth-unsupported: 'Fournisseur d''authentification non supporté'
|
||||
error.cannot-bind-data: 'Impossible de lier les données'
|
||||
error.invalid-number: 'Nombre invalide'
|
||||
error.invalid-character-unescaped: 'Caractère non protégé invalide'
|
||||
admin.invitations: 'Invitations'
|
||||
admin.invitations.create: 'Créer une invitation'
|
||||
admin.invitations.help: 'Les invitations peuvent être utilisées pour créer un compte même si l''inscription est désactivée.'
|
||||
admin.invitations.max_uses: 'Utilisations maximales'
|
||||
admin.invitations.expires_at: 'Expire le'
|
||||
admin.invitations.code: 'Code'
|
||||
admin.invitations.copy_link: 'Copier le lien'
|
||||
admin.invitations.uses: 'Utilisations'
|
||||
admin.invitations.expired: 'Expiré'
|
||||
flash.admin.user-deleted: 'L''utilisateur a été supprimé'
|
||||
flash.admin.gist-deleted: 'Le gist a été supprimée'
|
||||
flash.admin.invitation-created: 'L''invitation a été créée'
|
||||
flash.admin.invitation-deleted: 'L''invitation a été supprimée'
|
||||
flash.admin.sync-fs: 'Synchronisation des dépôts à partir du système de fichiers...'
|
||||
flash.admin.sync-db: 'Synchronisation des dépôts à partir de la base de données...'
|
||||
flash.admin.git-gc: 'Nettoyage des dépôts...'
|
||||
flash.admin.sync-previews: 'Synchronisation des aperçus du Gist...'
|
||||
flash.admin.reset-hooks: 'Réinitialisation des hooks du serveur Git pour tous les dépôts...'
|
||||
flash.admin.index-gists: 'Indexation de tous les gists...'
|
||||
flash.auth.username-exists: 'Nom d''utilisateur déjà utilisé'
|
||||
flash.auth.invalid-credentials: 'Identifiants non valides'
|
||||
flash.auth.account-linked-oauth: 'Compte lié à %s'
|
||||
flash.auth.account-unlinked-oauth: 'Compte dissocié de %s'
|
||||
flash.auth.user-sshkeys-not-retrievable: 'Impossible d''obtenir les clés de l''utilisateur'
|
||||
flash.auth.user-sshkeys-not-created: 'Impossible de créer une clé ssh'
|
||||
flash.auth.must-be-logged-in: 'Vous devez être connecté pour accéder aux gists'
|
||||
flash.gist.visibility-changed: 'La visibilité du gist a été modifiée'
|
||||
flash.gist.deleted: 'Le gist a été supprimé'
|
||||
flash.gist.fork-own-gist: 'Impossible de forker ses propres gists'
|
||||
flash.gist.forked: 'Le gist a été forké'
|
||||
flash.user.email-updated: 'Email mis à jour'
|
||||
flash.user.invalid-ssh-key: 'Clé SSH invalide'
|
||||
flash.user.ssh-key-added: 'Clé SSH ajoutée'
|
||||
flash.user.ssh-key-deleted: 'Clé SSH supprimée'
|
||||
flash.user.password-updated: 'Mot de passe mis à jour'
|
||||
flash.user.username-updated: 'Nom d''utilisateur mis à jour'
|
||||
validation.is-too-long: 'Le champ %s est trop long'
|
||||
validation.should-not-be-empty: 'Le champ %s ne doit pas être vide'
|
||||
validation.should-not-include-sub-directory: 'Le champ %s ne doit pas inclure de sous-répertoire'
|
||||
validation.should-only-contain-alphanumeric-characters: 'Le champ %s ne doit contenir que des caractères alphanumériques.'
|
||||
validation.should-only-contain-alphanumeric-characters-and-dashes: 'Le champ %s ne doit contenir que des caractères alphanumériques et des tirets.'
|
||||
validation.not-enough: 'Pas assez de %s'
|
||||
validation.invalid: '%s non valide'
|
||||
html.title.admin-panel: 'Administration'
|
||||
settings.ssh-key-exists: La clé SSH existe déjà
|
||||
|
||||
@@ -17,8 +17,8 @@ gist.header.clone-http: Клонировать с помощью %s
|
||||
gist.header.clone-http-help: Клонировать с помощью Git используя аутентификацию HTTP.
|
||||
gist.header.clone-ssh: Клонировать c помощью SSH
|
||||
gist.header.clone-ssh-help: Клонировать c помощью Git используя ключ SSH.
|
||||
gist.header.embed: ''
|
||||
gist.header.embed-help: ''
|
||||
gist.header.embed: 'Встроить'
|
||||
gist.header.embed-help: 'Встроить этот фрагмент в ваш веб-сайт.'
|
||||
gist.header.download-zip: Скачать ZIP-архив
|
||||
|
||||
gist.raw: Исходник
|
||||
@@ -175,16 +175,16 @@ admin.gists.private: Приватный
|
||||
admin.gists.nb-files: Файлов
|
||||
admin.gists.nb-likes: Понравилось
|
||||
admin.gists.delete_confirm: Вы уверены что хотите удалить этот фрагмент?
|
||||
gist.new.url: ''
|
||||
gist.new.preview: ''
|
||||
gist.new.create-a-new-gist: ''
|
||||
gist.edit.edit-gist: ''
|
||||
gist.list.all-liked-by: ''
|
||||
gist.list.all-forked-by: ''
|
||||
gist.list.all-from: ''
|
||||
gist.search.found: ''
|
||||
gist.search.no-results: ''
|
||||
gist.search.help.user: ''
|
||||
gist.new.url: 'URL'
|
||||
gist.new.preview: 'Предпросмотр'
|
||||
gist.new.create-a-new-gist: 'Создать новый фрагмент'
|
||||
gist.edit.edit-gist: 'Редактировать %s'
|
||||
gist.list.all-liked-by: 'Все фрагменты, понравившиеся %s'
|
||||
gist.list.all-forked-by: 'Все фрагменты, ответвлённые %s'
|
||||
gist.list.all-from: 'Все фрагменты от %s'
|
||||
gist.search.found: 'фрагментов найдено'
|
||||
gist.search.no-results: 'Не найден ни один фрагмент'
|
||||
gist.search.help.user: 'фрагментов создано пользователем'
|
||||
gist.search.help.title: ''
|
||||
gist.search.help.filename: ''
|
||||
gist.search.help.extension: ''
|
||||
|
||||
269
internal/i18n/locales/uk-UK.yml
Normal file
269
internal/i18n/locales/uk-UK.yml
Normal file
@@ -0,0 +1,269 @@
|
||||
gist.public: Публічний
|
||||
gist.unlisted: Прихований
|
||||
gist.private: Приватний
|
||||
|
||||
gist.header.like: Подобається
|
||||
gist.header.unlike: Не подобається
|
||||
gist.header.fork: Створити форк
|
||||
gist.header.edit: Редагувати
|
||||
gist.header.delete: Видалити
|
||||
gist.header.forked-from: Форк з
|
||||
gist.header.last-active: Остання активність
|
||||
gist.header.select-tab: Перейти
|
||||
gist.header.code: Код
|
||||
gist.header.revisions: Версії
|
||||
gist.header.revision: Версія
|
||||
gist.header.clone-http: Клонувати за допомогою %s
|
||||
gist.header.clone-http-help: Клонувати за допомогою Git з використанням аутентифікації HTTP.
|
||||
gist.header.clone-ssh: Клонувати за допомогою SSH
|
||||
gist.header.clone-ssh-help: Клонувати за допомогою Git з використанням ключа SSH.
|
||||
gist.header.embed: 'Вбудувати'
|
||||
gist.header.embed-help: 'Вбудувати цей gist до вашого веб-сайту.'
|
||||
gist.header.download-zip: Скачати ZIP-архів
|
||||
|
||||
gist.raw: Неформатований
|
||||
gist.file-truncated: Цей файл було обрізано.
|
||||
gist.watch-full-file: Перегляд всього файла.
|
||||
gist.file-not-valid: Невалідний CSV.
|
||||
gist.no-content: Немає даних
|
||||
|
||||
gist.new.new_gist: Новий gist
|
||||
gist.new.title: Назва
|
||||
gist.new.description: Опис
|
||||
gist.new.url: URL
|
||||
gist.new.filename-with-extension: Ім'я файла з розширенням
|
||||
gist.new.indent-mode: Режим відступів
|
||||
gist.new.indent-mode-space: Пробіли
|
||||
gist.new.indent-mode-tab: Табуляція
|
||||
gist.new.indent-size: Розмір відступа
|
||||
gist.new.wrap-mode: Переноси рядків
|
||||
gist.new.wrap-mode-no: Без переносів
|
||||
gist.new.wrap-mode-soft: М'які переноси
|
||||
gist.new.add-file: Додати файл
|
||||
gist.new.create-public-button: Створити публічний gist
|
||||
gist.new.create-unlisted-button: Створити прихований gist
|
||||
gist.new.create-private-button: Створити приватний gist
|
||||
gist.new.preview: Перегляд
|
||||
gist.new.create-a-new-gist: Створити новий gist
|
||||
|
||||
gist.edit.editing: Редагування
|
||||
gist.edit.edit-gist: Редагувати %s
|
||||
gist.edit.change-visibility: Зробити
|
||||
gist.edit.delete: Видалити
|
||||
gist.edit.cancel: Скасувати
|
||||
gist.edit.save: Зберегти
|
||||
|
||||
gist.list.joined: Зареєстрован
|
||||
gist.list.all: Всі gist
|
||||
gist.list.search-results: Результати пошуку
|
||||
gist.list.sort: Сортування
|
||||
gist.list.sort-by-created: створені
|
||||
gist.list.sort-by-updated: оновлені
|
||||
gist.list.order-by-asc: Нещодавні знизу
|
||||
gist.list.order-by-desc: Нещодавні зверху
|
||||
gist.list.select-tab: Оберіть вкладку
|
||||
gist.list.liked: Вподобані
|
||||
gist.list.likes: вподобань
|
||||
gist.list.forked: Форки
|
||||
gist.list.forked-from: Форки з
|
||||
gist.list.forks: форк(-ів)
|
||||
gist.list.files: файл(-ів)
|
||||
gist.list.last-active: Остання активність
|
||||
gist.list.no-gists: Немає gists
|
||||
gist.list.all-liked-by: Всі gists вподобані %s
|
||||
gist.list.all-forked-by: Всі gists форкнуті by %s
|
||||
gist.list.all-from: Всі gists від %s
|
||||
|
||||
gist.search.found: gists знайдено
|
||||
gist.search.no-results: Не знайдено gists
|
||||
gist.search.help.user: gists створені користувачем
|
||||
gist.search.help.title: gists з наданим ім'ям
|
||||
gist.search.help.filename: gists мають файли з наданим ім'ям
|
||||
gist.search.help.extension: gists мають файли з наданим розширенням
|
||||
gist.search.help.language: gists мають файли з наданою мовою
|
||||
|
||||
gist.forks: Форки
|
||||
gist.forks.view: Подивитися форк
|
||||
gist.forks.no: Немає форків
|
||||
gist.forks.for: Форки для %s
|
||||
|
||||
gist.likes: Подобається
|
||||
gist.likes.no: Ще немає вподобань
|
||||
gist.likes.for: Вподобання для %s
|
||||
|
||||
gist.revisions: Ревизії
|
||||
gist.revision.revised: ревизій цього gist
|
||||
gist.revision.go-to-revision: До ревизії
|
||||
gist.revision.file-created: файл створено
|
||||
gist.revision.file-deleted: файл видалено
|
||||
gist.revision.file-renamed: перейменовано в
|
||||
gist.revision.diff-truncated: Різниця завелика для відображення
|
||||
gist.revision.file-renamed-no-changes: Файл перейменовано без змін
|
||||
gist.revision.empty-file: Пустий файл
|
||||
gist.revision.no-changes: Без змін
|
||||
gist.revision.no-revisions: Немає ревізій для відображення
|
||||
gist.revision-of: Ревізії %s
|
||||
|
||||
settings: Налаштування
|
||||
settings.email: Адреса електронної пошти
|
||||
settings.email-help: Використовується для коммітів та Gravatar
|
||||
settings.email-set: Зберегти адресу електронної пошти
|
||||
settings.link-accounts: Підключення акаунтів
|
||||
settings.link-github-account: Підключити GitHub акаунт
|
||||
settings.link-gitlab-account: Підключити GitLab акаунт
|
||||
settings.link-gitea-account: Підключити Gitea акаунт
|
||||
settings.unlink-github-account: Відключити GitHub акаунт
|
||||
settings.unlink-gitlab-account: Відключити GitLab акаунт
|
||||
settings.unlink-gitea-account: Відключити Gitea акаунт
|
||||
settings.delete-account: Видалити акаунт
|
||||
settings.delete-account-confirm: Ви впевненні, що хочете видалити свій акаунт?
|
||||
settings.add-ssh-key: Додати SSH ключ
|
||||
settings.add-ssh-key-help: Використовується только для pull/push gists при використанні Git з SSH
|
||||
settings.add-ssh-key-title: Назва
|
||||
settings.add-ssh-key-content: Ключ
|
||||
settings.delete-ssh-key: Видалити
|
||||
settings.delete-ssh-key-confirm: Підтвердьте видалення ключа SSH
|
||||
settings.ssh-key-added-at: Додано
|
||||
settings.ssh-key-never-used: Не використовувася
|
||||
settings.ssh-key-last-used: Останнє використання
|
||||
settings.ssh-key-exists: SSH ключ вже існує
|
||||
settings.change-username: Змінити им'я користувача
|
||||
settings.create-password: Створити пароль
|
||||
settings.create-password-help: Створити ваш пароль для логіну в Opengist через HTTP
|
||||
settings.change-password: Створити пароль
|
||||
settings.change-password-help: Змінити ваш пароль для логіну в Opengist через HTTP
|
||||
settings.password-label-title: Пароль
|
||||
|
||||
auth.signup-disabled: Адміністратор відключив реєстрацію
|
||||
auth.login: Вхід
|
||||
auth.signup: Реєстрація
|
||||
auth.new-account: Новий акаунт
|
||||
auth.username: Им'я користувача
|
||||
auth.password: Пароль
|
||||
auth.register-instead: Зареєструватися
|
||||
auth.login-instead: Увійти
|
||||
auth.oauth: Продовжити з %s акаунтом
|
||||
|
||||
error: Помилка
|
||||
error.page-not-found: Сторінка не знайдена
|
||||
error.bad-request: Невірний запрос
|
||||
error.signup-disabled: Реєстрацію вимкнено
|
||||
error.signup-disabled-form: Реєстрацію через форму вимкнено
|
||||
error.login-disabled-form: Логін через форму логіна вимкнено
|
||||
error.complete-oauth-login: "Неможливо виконати авторизацію користувача: %s"
|
||||
error.oauth-unsupported: Провайдер не підтримується
|
||||
error.cannot-bind-data: Не вдається зв'язати дані
|
||||
error.invalid-number: Недійсний номер
|
||||
error.invalid-character-unescaped: Неправильний символ не екранований
|
||||
|
||||
header.menu.all: Все
|
||||
header.menu.new: Новий
|
||||
header.menu.search: Пошук
|
||||
header.menu.my-gists: Мої gists
|
||||
header.menu.liked: Сподобалися
|
||||
header.menu.admin: Адміністрування
|
||||
header.menu.settings: Налаштування
|
||||
header.menu.logout: Вийти
|
||||
header.menu.register: Реєстрація
|
||||
header.menu.login: Увійти
|
||||
header.menu.light: Світла
|
||||
header.menu.dark: Темна
|
||||
header.menu.system: Системна
|
||||
footer.powered-by: Працює на %s
|
||||
|
||||
pagination.older: Пізніше
|
||||
pagination.newer: Новіше
|
||||
pagination.previous: Попередня
|
||||
pagination.next: Наступна
|
||||
|
||||
admin.admin_panel: Панель управління
|
||||
admin.general: Загальні
|
||||
admin.users: Користувачі
|
||||
admin.gists: Gists
|
||||
admin.configuration: Конфігурація
|
||||
admin.invitations: Запрошення
|
||||
admin.invitations.create: Створити запрошення
|
||||
admin.versions: Версії
|
||||
admin.ssh_keys: Ключі SSH
|
||||
admin.stats: Статистика
|
||||
admin.actions: Дії
|
||||
admin.actions.sync-fs: Синхронізувати gists з файлової системи
|
||||
admin.actions.sync-db: Синхронізувати gists з базою даних
|
||||
admin.actions.git-gc: Збір сміття з репозиторіїв Git
|
||||
admin.actions.sync-previews: Синхронізувати всі gists перегляди
|
||||
admin.actions.reset-hooks: Скинути серверні Git hooks для всіх репозиторіїв
|
||||
admin.actions.index-gists: Проіндексувати всі gists
|
||||
admin.id: ID
|
||||
admin.user: Користувач
|
||||
admin.delete: Видалити
|
||||
admin.created_at: Створено
|
||||
|
||||
admin.config-link: Ця конфігурація може бути %s за допомогою YAML файла та/або змінних середовища
|
||||
admin.config-link-overriden: перевизначено
|
||||
admin.disable-signup: Вимкнути реєстрацію
|
||||
admin.disable-signup_help: Заборонити створення нових акаунтів
|
||||
admin.require-login: Вимагати авторизацію
|
||||
admin.require-login_help: Вимагати авторизації для перегляду gists
|
||||
admin.allow-gists-without-login: Дозволити перегляд індивідуальних gists без авторизації
|
||||
admin.allow-gists-without-login_help: Дозволити перегляд і скачування індивідуальних gists без авторизації, але вимагати авторизацію для перегляду переліку gists.
|
||||
admin.disable-login: Вимкнути форму авторизації
|
||||
admin.disable-login_help: Заборонити авторизацію по паролю та замість цього примушувати використовувати провайдерів OAuth.
|
||||
admin.disable-gravatar: Заборонити Gravatar
|
||||
admin.disable-gravatar_help: Вимкнути використання Gravatar як провайдера аватарів.
|
||||
|
||||
admin.users.delete_confirm: Ви впевнені, що хочете видалити цього користувача?
|
||||
|
||||
admin.gists.title: Назва
|
||||
admin.gists.private: Чи приватний
|
||||
admin.gists.nb-files: Файлів
|
||||
admin.gists.nb-likes: Вподобань
|
||||
admin.gists.delete_confirm: Ви впевнені, що хочете видалити цей gist?
|
||||
|
||||
admin.invitations.help: Запрошення можуть бути використані навіть якщо реєстрація вимкнена.
|
||||
admin.invitations.max_uses: Максимальна кількість використань
|
||||
admin.invitations.expires_at: Спливає
|
||||
admin.invitations.code: Код
|
||||
admin.invitations.copy_link: Копіювати посилання
|
||||
admin.invitations.uses: Використовується
|
||||
admin.invitations.expired: Сплинув
|
||||
|
||||
flash.admin.user-deleted: Користувач був видалений
|
||||
flash.admin.gist-deleted: Gist був видалений
|
||||
flash.admin.invitation-created: Запрошення було створено
|
||||
flash.admin.invitation-deleted: Запрошення було видалено
|
||||
flash.admin.sync-fs: Синхронізація репозиторіїв за файловою системою...
|
||||
flash.admin.sync-db: Синхронізація репозиторіїв за базою даних...
|
||||
flash.admin.git-gc: Збір сміття з репозиторіїв...
|
||||
flash.admin.sync-previews: Синхронізація Gist переглядів...
|
||||
flash.admin.reset-hooks: Скидання cерверниз Git hooks для всіх репозиторіїв...
|
||||
flash.admin.index-gists: Індексація всіх gists...
|
||||
|
||||
flash.auth.username-exists: Це ім'я користувача вже існує
|
||||
flash.auth.invalid-credentials: Недійсні облікові дані
|
||||
flash.auth.account-linked-oauth: Акаунт підключено до %s
|
||||
flash.auth.account-unlinked-oauth: Акаунт відключено від %s
|
||||
flash.auth.user-sshkeys-not-retrievable: Не зміг отримати ключі користувача
|
||||
flash.auth.user-sshkeys-not-created: Не зміг створити ssh ключ
|
||||
flash.auth.must-be-logged-in: Ви маєте бути авторизовані для доступу до gists
|
||||
|
||||
flash.gist.visibility-changed: Параметри перегляду gist змінено
|
||||
flash.gist.deleted: Gist було видалено
|
||||
flash.gist.fork-own-gist: Неможливо форкнути власні gists
|
||||
flash.gist.forked: Gist було форкнуто
|
||||
|
||||
flash.user.email-updated: Email оновлено
|
||||
flash.user.invalid-ssh-key: Недійсний SSH ключ
|
||||
flash.user.ssh-key-added: SSH ключ додано
|
||||
flash.user.ssh-key-deleted: SSH key видалено
|
||||
flash.user.password-updated: Пароль оновлено
|
||||
flash.user.username-updated: Ім'я користувача оновлено
|
||||
|
||||
validation.is-too-long: Поле %s занадто велике
|
||||
validation.should-not-be-empty: Поле %s не має бути пустим
|
||||
validation.should-not-include-sub-directory: Поле %s неповинно включати піддиректорії
|
||||
validation.should-only-contain-alphanumeric-characters: Поле %s має містити лише буквено-цифрові символи
|
||||
validation.should-only-contain-alphanumeric-characters-and-dashes: Поле %s має містити лише буквено-цифрові символи та тире
|
||||
validation.not-enough: Недостатньо %s
|
||||
validation.invalid: Недійсний %s
|
||||
|
||||
html.title.admin-panel: Панель адміністратора
|
||||
@@ -9,25 +9,44 @@ import (
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
|
||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
||||
"github.com/blevesearch/bleve/v2/search/query"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var bleveIndex bleve.Index
|
||||
var atomicIndexer atomic.Pointer[Indexer]
|
||||
|
||||
type Indexer struct {
|
||||
Index bleve.Index
|
||||
}
|
||||
|
||||
func Enabled() bool {
|
||||
return config.C.IndexEnabled
|
||||
}
|
||||
|
||||
func Open(indexFilename string) error {
|
||||
var err error
|
||||
bleveIndex, err = bleve.Open(indexFilename)
|
||||
func Init(indexFilename string) {
|
||||
atomicIndexer.Store(&Indexer{Index: nil})
|
||||
|
||||
go func() {
|
||||
bleveIndex, err := open(indexFilename)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to open index")
|
||||
(*atomicIndexer.Load()).close()
|
||||
}
|
||||
atomicIndexer.Store(&Indexer{Index: bleveIndex})
|
||||
log.Info().Msg("Indexer initialized")
|
||||
}()
|
||||
}
|
||||
|
||||
func open(indexFilename string) (bleve.Index, error) {
|
||||
bleveIndex, err := bleve.Open(indexFilename)
|
||||
if err == nil {
|
||||
return nil
|
||||
return bleveIndex, nil
|
||||
}
|
||||
|
||||
if !errors.Is(err, bleve.ErrorIndexPathDoesNotExist) {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docMapping := bleve.NewDocumentMapping()
|
||||
@@ -40,7 +59,7 @@ func Open(indexFilename string) error {
|
||||
"type": unicodenorm.Name,
|
||||
"form": unicodenorm.NFC,
|
||||
}); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = mapping.AddCustomAnalyzer("gistAnalyser", map[string]interface{}{
|
||||
@@ -49,43 +68,71 @@ func Open(indexFilename string) error {
|
||||
"tokenizer": unicode.Name,
|
||||
"token_filters": []string{"unicodeNormalize", camelcase.Name, lowercase.Name},
|
||||
}); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docMapping.DefaultAnalyzer = "gistAnalyser"
|
||||
|
||||
bleveIndex, err = bleve.New(indexFilename, mapping)
|
||||
|
||||
return err
|
||||
return bleve.New(indexFilename, mapping)
|
||||
}
|
||||
|
||||
func Close() error {
|
||||
return bleveIndex.Close()
|
||||
func Close() {
|
||||
(*atomicIndexer.Load()).close()
|
||||
}
|
||||
|
||||
func (i *Indexer) close() {
|
||||
if i == nil || i.Index == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := i.Index.Close()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to close bleve index")
|
||||
}
|
||||
log.Info().Msg("Indexer closed")
|
||||
atomicIndexer.Store(&Indexer{Index: nil})
|
||||
}
|
||||
|
||||
func checkForIndexer() error {
|
||||
if (*atomicIndexer.Load()).Index == nil {
|
||||
return errors.New("indexer is not initialized")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddInIndex(gist *Gist) error {
|
||||
if !Enabled() {
|
||||
return nil
|
||||
}
|
||||
if err := checkForIndexer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gist == nil {
|
||||
return errors.New("failed to add nil gist to index")
|
||||
}
|
||||
return bleveIndex.Index(strconv.Itoa(int(gist.GistID)), gist)
|
||||
return (*atomicIndexer.Load()).Index.Index(strconv.Itoa(int(gist.GistID)), gist)
|
||||
}
|
||||
|
||||
func RemoveFromIndex(gistID uint) error {
|
||||
if !Enabled() {
|
||||
return nil
|
||||
}
|
||||
if err := checkForIndexer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bleveIndex.Delete(strconv.Itoa(int(gistID)))
|
||||
return (*atomicIndexer.Load()).Index.Delete(strconv.Itoa(int(gistID)))
|
||||
}
|
||||
|
||||
func SearchGists(queryStr string, queryMetadata SearchGistMetadata, gistsIds []uint, page int) ([]uint, uint64, map[string]int, error) {
|
||||
if !Enabled() {
|
||||
return nil, 0, nil, nil
|
||||
}
|
||||
if err := checkForIndexer(); err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
var indexerQuery query.Query
|
||||
@@ -98,20 +145,18 @@ func SearchGists(queryStr string, queryMetadata SearchGistMetadata, gistsIds []u
|
||||
indexerQuery = contentQuery
|
||||
}
|
||||
|
||||
if len(gistsIds) > 0 {
|
||||
repoQueries := make([]query.Query, 0, len(gistsIds))
|
||||
repoQueries := make([]query.Query, 0, len(gistsIds))
|
||||
|
||||
truee := true
|
||||
for _, id := range gistsIds {
|
||||
f := float64(id)
|
||||
qq := bleve.NewNumericRangeInclusiveQuery(&f, &f, &truee, &truee)
|
||||
qq.SetField("GistID")
|
||||
repoQueries = append(repoQueries, qq)
|
||||
}
|
||||
|
||||
indexerQuery = bleve.NewConjunctionQuery(bleve.NewDisjunctionQuery(repoQueries...), indexerQuery)
|
||||
truee := true
|
||||
for _, id := range gistsIds {
|
||||
f := float64(id)
|
||||
qq := bleve.NewNumericRangeInclusiveQuery(&f, &f, &truee, &truee)
|
||||
qq.SetField("GistID")
|
||||
repoQueries = append(repoQueries, qq)
|
||||
}
|
||||
|
||||
indexerQuery = bleve.NewConjunctionQuery(bleve.NewDisjunctionQuery(repoQueries...), indexerQuery)
|
||||
|
||||
addQuery := func(field, value string) {
|
||||
if value != "" && value != "." {
|
||||
q := bleve.NewMatchPhraseQuery(value)
|
||||
@@ -136,7 +181,7 @@ func SearchGists(queryStr string, queryMetadata SearchGistMetadata, gistsIds []u
|
||||
s.Fields = []string{"GistID"}
|
||||
s.IncludeLocations = false
|
||||
|
||||
results, err := bleveIndex.Search(s)
|
||||
results, err := (*atomicIndexer.Load()).Index.Search(s)
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func validateReservedKeywords(fl validator.FieldLevel) bool {
|
||||
name := fl.Field().String()
|
||||
|
||||
restrictedNames := map[string]struct{}{}
|
||||
for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search", "init", "healthcheck", "preview"} {
|
||||
for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search", "init", "healthcheck", "preview", "metrics"} {
|
||||
restrictedNames[restrictedName] = struct{}{}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ const (
|
||||
OpenIDConnect = "openid-connect"
|
||||
)
|
||||
|
||||
var title = cases.Title(language.English)
|
||||
|
||||
func register(ctx echo.Context) error {
|
||||
disableSignup := getData(ctx, "DisableSignup")
|
||||
disableForm := getData(ctx, "DisableLoginForm")
|
||||
@@ -188,10 +186,10 @@ func oauthCallback(ctx echo.Context) error {
|
||||
updateUserProviderInfo(currUser, user.Provider, user)
|
||||
|
||||
if err = currUser.Update(); err != nil {
|
||||
return errorRes(500, "Cannot update user "+title.String(user.Provider)+" id", err)
|
||||
return errorRes(500, "Cannot update user "+cases.Title(language.English).String(user.Provider)+" id", err)
|
||||
}
|
||||
|
||||
addFlash(ctx, tr(ctx, "flash.auth.account-linked-oauth", title.String(user.Provider)), "success")
|
||||
addFlash(ctx, tr(ctx, "flash.auth.account-linked-oauth", cases.Title(language.English).String(user.Provider)), "success")
|
||||
return redirect(ctx, "/settings")
|
||||
}
|
||||
|
||||
@@ -358,10 +356,10 @@ func oauth(ctx echo.Context) error {
|
||||
// Means that the user wants to unlink the account
|
||||
if checkFunc, exists := providerIDCheckMap[provider]; exists && checkFunc() {
|
||||
if err := currUser.DeleteProviderID(provider); err != nil {
|
||||
return errorRes(500, "Cannot unlink account from "+title.String(provider), err)
|
||||
return errorRes(500, "Cannot unlink account from "+cases.Title(language.English).String(provider), err)
|
||||
}
|
||||
|
||||
addFlash(ctx, tr(ctx, "flash.auth.account-unlinked-oauth", title.String(provider)), "success")
|
||||
addFlash(ctx, tr(ctx, "flash.auth.account-unlinked-oauth", cases.Title(language.English).String(provider)), "success")
|
||||
return redirect(ctx, "/settings")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,3 +23,9 @@ func healthcheck(ctx echo.Context) error {
|
||||
"time": time.Now().Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
// metrics is a dummy handler to satisfy the /metrics endpoint (for Prometheus, Openmetrics, etc.)
|
||||
// until we have a proper metrics endpoint
|
||||
func metrics(ctx echo.Context) error {
|
||||
return ctx.String(200, "")
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ func NewServer(isDev bool, sessionsPath string) *Server {
|
||||
e.Pre(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||
LogURI: true, LogStatus: true, LogMethod: true,
|
||||
LogValuesFunc: func(ctx echo.Context, v middleware.RequestLoggerValues) error {
|
||||
log.Info().Str("URI", v.URI).Int("status", v.Status).Str("method", v.Method).
|
||||
Str("ip", ctx.RealIP()).
|
||||
log.Info().Str("uri", v.URI).Int("status", v.Status).Str("method", v.Method).
|
||||
Str("ip", ctx.RealIP()).TimeDiff("duration", time.Now(), v.StartTime).
|
||||
Msg("HTTP")
|
||||
return nil
|
||||
},
|
||||
@@ -216,10 +216,6 @@ func NewServer(isDev bool, sessionsPath string) *Server {
|
||||
|
||||
e.HTTPErrorHandler = func(er error, ctx echo.Context) {
|
||||
if err, ok := er.(*echo.HTTPError); ok {
|
||||
if err.Code >= 500 {
|
||||
log.Error().Int("code", err.Code).Err(err.Internal).Msg("HTTP: " + err.Message.(string))
|
||||
}
|
||||
|
||||
setData(ctx, "error", err)
|
||||
if errHtml := htmlWithCode(ctx, err.Code, "error.html"); errHtml != nil {
|
||||
log.Fatal().Err(errHtml).Send()
|
||||
@@ -255,6 +251,7 @@ func NewServer(isDev bool, sessionsPath string) *Server {
|
||||
g1.GET("/preview", preview, logged)
|
||||
|
||||
g1.GET("/healthcheck", healthcheck)
|
||||
g1.GET("/metrics", metrics)
|
||||
|
||||
g1.GET("/register", register)
|
||||
g1.POST("/register", processRegister)
|
||||
@@ -332,6 +329,9 @@ func NewServer(isDev bool, sessionsPath string) *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 {
|
||||
ctx.Response().Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
ctx.Response().Header().Set("Expires", time.Now().AddDate(1, 0, 0).Format(http.TimeFormat))
|
||||
|
||||
return echo.WrapHandler(http.FileServer(http.FS(public.Files)))(ctx)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ func userSettings(ctx echo.Context) error {
|
||||
setData(ctx, "email", user.Email)
|
||||
setData(ctx, "sshKeys", keys)
|
||||
setData(ctx, "hasPassword", user.Password != "")
|
||||
setData(ctx, "disableForm", getData(ctx, "DisableLoginForm"))
|
||||
setData(ctx, "htmlTitle", trH(ctx, "settings"))
|
||||
return html(ctx, "settings.html")
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@ import (
|
||||
"errors"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/i18n"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -56,6 +59,11 @@ func notFound(message string) error {
|
||||
}
|
||||
|
||||
func errorRes(code int, message string, err error) error {
|
||||
if code >= 500 {
|
||||
var skipLogger = log.With().CallerWithSkipFrameCount(3).Logger()
|
||||
skipLogger.Error().Err(err).Msg(message)
|
||||
}
|
||||
|
||||
return &echo.HTTPError{Code: code, Message: message, Internal: err}
|
||||
}
|
||||
|
||||
@@ -116,7 +124,7 @@ func loadSettings(ctx echo.Context) error {
|
||||
|
||||
for key, value := range settings {
|
||||
s := strings.ReplaceAll(key, "-", " ")
|
||||
s = title.String(s)
|
||||
s = cases.Title(language.English).String(s)
|
||||
setData(ctx, strings.ReplaceAll(s, " ", ""), value == "1")
|
||||
}
|
||||
return nil
|
||||
|
||||
1894
package-lock.json
generated
1894
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,10 @@
|
||||
"scripts": {
|
||||
"dev": "node_modules/.bin/vite -c public/vite.config.js",
|
||||
"build": "node_modules/.bin/vite -c public/vite.config.js build",
|
||||
"preview": "node_modules/.bin/vite -c public/vite.config.js preview"
|
||||
"preview": "node_modules/.bin/vite -c public/vite.config.js preview",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codemirror/commands": "^6.2.2",
|
||||
|
||||
@@ -111,6 +111,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
deleteBtns.onclick = () => {
|
||||
editorsjs.splice(editorsjs.indexOf(editor), 1);
|
||||
dom.remove();
|
||||
checkForFirstDeleteButton();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,6 +192,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
editorsjs.push(currEditor);
|
||||
});
|
||||
|
||||
checkForFirstDeleteButton();
|
||||
|
||||
document.getElementById("add-file")!.onclick = () => {
|
||||
let newEditorDom = firstEditordom.cloneNode(true) as HTMLElement;
|
||||
|
||||
@@ -204,6 +207,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// creating the new codemirror editor and append it in the editor div
|
||||
editorsjs.push(newEditor(newEditorDom));
|
||||
editorsParentdom.append(newEditorDom);
|
||||
showDeleteButton(newEditorDom);
|
||||
};
|
||||
|
||||
document.querySelector<HTMLFormElement>("form#create")!.onsubmit = () => {
|
||||
@@ -226,6 +230,27 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
}
|
||||
|
||||
function checkForFirstDeleteButton() {
|
||||
let deleteBtn = editorsParentdom.querySelector<HTMLButtonElement>("button.delete-file")!;
|
||||
if (editorsjs.length === 1) {
|
||||
deleteBtn.classList.add("hidden");
|
||||
deleteBtn.previousElementSibling.classList.remove("rounded-l-md");
|
||||
deleteBtn.previousElementSibling.classList.add("rounded-md");
|
||||
} else {
|
||||
deleteBtn.classList.remove("hidden");
|
||||
deleteBtn.previousElementSibling.classList.add("rounded-l-md");
|
||||
deleteBtn.previousElementSibling.classList.remove("rounded-md");
|
||||
}
|
||||
}
|
||||
|
||||
function showDeleteButton(editorDom: HTMLElement) {
|
||||
let deleteBtn = editorDom.querySelector<HTMLButtonElement>("button.delete-file")!;
|
||||
deleteBtn.classList.remove("hidden");
|
||||
deleteBtn.previousElementSibling.classList.add("rounded-l-md");
|
||||
deleteBtn.previousElementSibling.classList.remove("rounded-md");
|
||||
checkForFirstDeleteButton();
|
||||
}
|
||||
|
||||
document.onsubmit = () => {
|
||||
window.onbeforeunload = null;
|
||||
};
|
||||
|
||||
18
templates/pages/admin_config.html
vendored
18
templates/pages/admin_config.html
vendored
@@ -54,19 +54,19 @@
|
||||
<span class="bg-gray-50 dark:bg-gray-800 px-2 text-sm text-slate-700 dark:text-slate-300 font-bold">OAuth</span>
|
||||
</div>
|
||||
</div>
|
||||
<dt>Github Client key</dt><dd>{{ .c.GithubClientKey }}</dd>
|
||||
<dt>Github Secret</dt><dd>{{ .c.GithubSecret }}</dd>
|
||||
<dt>GitLab client Key</dt><dd>{{ .c.GitlabClientKey }}</dd>
|
||||
<dt>GitLab Secret</dt><dd>{{ .c.GitlabSecret }}</dd>
|
||||
<dt>Github Client key</dt><dd>{{ if .c.GithubClientKey }}<defined>{{ end }}</dd>
|
||||
<dt>Github Secret</dt><dd>{{ if .c.GithubSecret }}<defined>{{ end }}</dd>
|
||||
<dt>GitLab client Key</dt><dd>{{ if .c.GitlabClientKey }}<defined>{{ end }}</dd>
|
||||
<dt>GitLab Secret</dt><dd>{{ if .c.GitlabSecret }}<defined>{{ end }}</dd>
|
||||
<dt>GitLab URL</dt><dd>{{ .c.GitlabUrl }}</dd>
|
||||
<dt>GitLab Name</dt><dd>{{ .c.GitlabName }}</dd>
|
||||
<dt>Gitea client Key</dt><dd>{{ .c.GiteaClientKey }}</dd>
|
||||
<dt>Gitea Secret</dt><dd>{{ .c.GiteaSecret }}</dd>
|
||||
<dt>Gitea client Key</dt><dd>{{ if .c.GiteaClientKey }}<defined>{{ end }}</dd>
|
||||
<dt>Gitea Secret</dt><dd>{{ if .c.GiteaSecret }}<defined>{{ end }}</dd>
|
||||
<dt>Gitea URL</dt><dd>{{ .c.GiteaUrl }}</dd>
|
||||
<dt>Gitea Name</dt><dd>{{ .c.GiteaName }}</dd>
|
||||
<dt>OIDC client Key</dt><dd>{{ .c.OIDCClientKey }}</dd>
|
||||
<dt>OIDC Secret</dt><dd>{{ .c.OIDCSecret }}</dd>
|
||||
<dt>OIDC Discovery URL</dt><dd>{{ .c.OIDCDiscoveryUrl }}</dd>
|
||||
<dt>OIDC client Key</dt><dd>{{ if .c.OIDCClientKey }}<defined>{{ end }}</dd>
|
||||
<dt>OIDC Secret</dt><dd>{{ if .c.OIDCSecret }}<defined>{{ end }}</dd>
|
||||
<dt>OIDC Discovery URL</dt><dd>{{ if .c.OIDCDiscoveryUrl }}<defined>{{ end }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
5
templates/pages/create.html
vendored
5
templates/pages/create.html
vendored
@@ -32,6 +32,11 @@
|
||||
<div class="border-b-1 border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 my-auto flex">
|
||||
<p class="mx-2 my-2 inline-flex">
|
||||
<input type="text" name="name" placeholder="{{ .locale.Tr "gist.new.filename-with-extension" }}" style="line-height: 0.05em" class="form-filename bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-md gist-title">
|
||||
<button style="line-height: 0.05em" class="hidden delete-file -ml-px relative inline-flex items-center space-x-2 px-4 py-2 border border-gray-200 dark:border-gray-700 text-sm font-medium rounded-r-md text-slate-700 dark:text-slate-300 bg-gray-50 dark:bg-gray-800 hover:bg-white dark:hover:bg-gray-900 focus:outline-none" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</p>
|
||||
<button type="button" class="md-preview hidden whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900 my-2 px-2 text-xs font-medium shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">{{ .locale.Tr "gist.new.preview" }}</button>
|
||||
<div class="hidden mx-2 my-2 sm:inline-flex ml-auto space-x-2">
|
||||
|
||||
4
templates/pages/edit.html
vendored
4
templates/pages/edit.html
vendored
@@ -65,8 +65,8 @@
|
||||
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 editor">
|
||||
<div class="border-b-1 border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 my-auto flex">
|
||||
<p class="mx-2 my-2 inline-flex">
|
||||
<input type="text" value="{{ $file.Filename }}" name="name" placeholder="Filename with extension" style="line-height: 0.05em; z-index: 99999" class="form-filename bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-l-md gist-title">
|
||||
<button style="line-height: 0.05em" class="delete-file -ml-px relative inline-flex items-center space-x-2 px-4 py-2 border border-gray-200 dark:border-gray-700 text-sm font-medium rounded-r-md text-slate-700 dark:text-slate-300 bg-gray-50 dark:bg-gray-800 hover:bg-white dark:hover:bg-gray-900 focus:outline-none" type="button">
|
||||
<input type="text" value="{{ $file.Filename }}" name="name" placeholder="{{ $.locale.Tr "gist.new.filename-with-extension" }}" style="line-height: 0.05em; z-index: 99999" class="form-filename bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 {{ if eq (len $.files) 1 }}rounded-md{{ else }}rounded-l-md{{ end }} gist-title">
|
||||
<button style="line-height: 0.05em" class="{{ if eq (len $.files) 1 }}hidden{{ end }} delete-file -ml-px relative inline-flex items-center space-x-2 px-4 py-2 border border-gray-200 dark:border-gray-700 text-sm font-medium rounded-r-md text-slate-700 dark:text-slate-300 bg-gray-50 dark:bg-gray-800 hover:bg-white dark:hover:bg-gray-900 focus:outline-none" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
|
||||
4
templates/pages/settings.html
vendored
4
templates/pages/settings.html
vendored
@@ -7,7 +7,7 @@
|
||||
</header>
|
||||
<main>
|
||||
<div class="relative mx-auto max-w-[40rem] space-y-8">
|
||||
<div class="sm:grid grid-cols-2 gap-x-4 md:gap-x-8 space-y-8 md:space-y-0">
|
||||
<div class="sm:grid {{ if not .disableForm }}grid-cols-2{{ else }}grid-cols-1{{ end }} gap-x-4 md:gap-x-8 space-y-8 md:space-y-0">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10 h-full">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
@@ -28,6 +28,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ if not .disableForm }}
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
@@ -64,6 +65,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
|
||||
Reference in New Issue
Block a user