diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml
index 2a03519ab9..0498765b02 100644
--- a/.woodpecker/docs.yml
+++ b/.woodpecker/docs.yml
@@ -1,4 +1,5 @@
when:
+ - event: tag
- event: pull_request
- event: push
path: &when_path
@@ -59,7 +60,7 @@ steps:
secrets:
- BOT_PRIVATE_KEY
commands:
- - apk add openssh-client git rsync
+ - apk add openssh-client git rsync jq
- mkdir -p $HOME/.ssh
- ssh-keyscan -t rsa github.com >> $HOME/.ssh/known_hosts
- echo "$BOT_PRIVATE_KEY" > $HOME/.ssh/id_rsa
@@ -67,8 +68,11 @@ steps:
- git config --global user.email "woodpecker-bot@obermui.de"
- git config --global user.name "woodpecker-bot"
- git clone --depth 1 --single-branch git@github.com:woodpecker-ci/woodpecker-ci.github.io.git /repo
+ # update latest and next version
+ - if [ "$CI_PIPELINE_EVENT" == "tag" ] ; then jq '.latest = ${CI_COMMIT_TAG}' repo/version.json > repo/version.json.tmp && mv repo/version.json.tmp repo/version.json ; fi
+ - if [ "$CI_PIPELINE_EVENT" == "push" ] ; then jq "next-" '.next = ${CI_COMMIT_SHA:0:10}' repo/version.json > repo/version.json.tmp && mv repo/version.json.tmp repo/version.json ; fi
# copy all docs files and delete all old ones, but leave CNAME and index.yaml untouched
- - rsync -r --exclude .git --exclude CNAME --exclude index.yaml --exclude README.md --delete docs/build/ /repo
+ - rsync -r --exclude .git --exclude CNAME --exclude index.yaml --exclude README.md --exclude version.json --delete docs/build/ /repo
- cd /repo
- git add .
# exit successfully if nothing changed
@@ -76,5 +80,6 @@ steps:
- git commit -m "Deploy website - based on ${CI_COMMIT_SHA}"
- git push
when:
- event: [push, cron]
- path: *when_path
+ - event: [push, cron]
+ path: *when_path
+ - event: tag
diff --git a/web/components.d.ts b/web/components.d.ts
index fe8f5baecb..d2cdd02957 100644
--- a/web/components.d.ts
+++ b/web/components.d.ts
@@ -10,6 +10,7 @@ declare module 'vue' {
ActionsTab: typeof import('./src/components/repo/settings/ActionsTab.vue')['default']
ActivePipelines: typeof import('./src/components/layout/header/ActivePipelines.vue')['default']
AdminAgentsTab: typeof import('./src/components/admin/settings/AdminAgentsTab.vue')['default']
+ AdminInfoTab: typeof import('./src/components/admin/settings/AdminInfoTab.vue')['default']
AdminOrgsTab: typeof import('./src/components/admin/settings/AdminOrgsTab.vue')['default']
AdminQueueStats: typeof import('./src/components/admin/settings/queue/AdminQueueStats.vue')['default']
AdminQueueTab: typeof import('./src/components/admin/settings/AdminQueueTab.vue')['default']
@@ -22,6 +23,7 @@ declare module 'vue' {
Checkbox: typeof import('./src/components/form/Checkbox.vue')['default']
CheckboxesField: typeof import('./src/components/form/CheckboxesField.vue')['default']
Container: typeof import('./src/components/layout/Container.vue')['default']
+ copy: typeof import('./src/components/admin/settings/AdminAgentsTab copy.vue')['default']
CronTab: typeof import('./src/components/repo/settings/CronTab.vue')['default']
DeployPipelinePopup: typeof import('./src/components/layout/popups/DeployPipelinePopup.vue')['default']
DocsLink: typeof import('./src/components/atomic/DocsLink.vue')['default']
diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json
index 3ca1936a3f..b3f05a1daf 100644
--- a/web/src/assets/locales/en.json
+++ b/web/src/assets/locales/en.json
@@ -501,5 +501,8 @@
"internal_error": "Some internal error occurred",
"access_denied": "You are not allowed to login"
},
- "default": "default"
+ "default": "default",
+ "info": "Info",
+ "running_version": "You are running woodpecker {0}",
+ "update_woodpecker": "Please update your Woodpecker instance to {0}"
}
diff --git a/web/src/components/admin/settings/AdminInfoTab.vue b/web/src/components/admin/settings/AdminInfoTab.vue
new file mode 100644
index 0000000000..e8f530c368
--- /dev/null
+++ b/web/src/components/admin/settings/AdminInfoTab.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/web/src/components/layout/header/Navbar.vue b/web/src/components/layout/header/Navbar.vue
index f664571845..322a90f77a 100644
--- a/web/src/components/layout/header/Navbar.vue
+++ b/web/src/components/layout/header/Navbar.vue
@@ -1,47 +1,41 @@
-
@@ -55,9 +49,11 @@ import Button from '~/components/atomic/Button.vue';
import IconButton from '~/components/atomic/IconButton.vue';
import useAuthentication from '~/compositions/useAuthentication';
import useConfig from '~/compositions/useConfig';
+import { useVersion } from '~/compositions/useVersion';
import ActivePipelines from './ActivePipelines.vue';
+const version = useVersion();
const config = useConfig();
const route = useRoute();
const authentication = useAuthentication();
@@ -68,7 +64,6 @@ function doLogin() {
authentication.authenticate(route.fullPath);
}
-const version = config.version?.startsWith('next') ? 'next' : config.version;
const { enableSwagger } = config;
diff --git a/web/src/compositions/useVersion.ts b/web/src/compositions/useVersion.ts
new file mode 100644
index 0000000000..6243966359
--- /dev/null
+++ b/web/src/compositions/useVersion.ts
@@ -0,0 +1,84 @@
+import { onMounted, ref } from 'vue';
+
+import useAuthentication from './useAuthentication';
+import useConfig from './useConfig';
+
+type VersionInfo = {
+ latest: string;
+ next: string;
+};
+
+const version = ref<{
+ latest: string | undefined;
+ current: string;
+ currentShort: string;
+ needsUpdate: boolean;
+}>();
+
+async function fetchVersion(): Promise {
+ try {
+ const resp = await fetch('https://woodpecker-ci.org/version.json');
+ const json = await resp.json();
+ return json;
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('Failed to fetch version info', error);
+ return undefined;
+ }
+}
+
+const isInitialised = ref(false);
+
+export function useVersion() {
+ if (isInitialised.value) {
+ return version;
+ }
+ isInitialised.value = true;
+
+ const config = useConfig();
+ const current = config.version as string;
+ const usesNext = config.version?.startsWith('next');
+
+ const { user } = useAuthentication();
+ if (!user?.admin) {
+ version.value = {
+ latest: undefined,
+ current,
+ currentShort: usesNext ? 'next' : current,
+ needsUpdate: false,
+ };
+ return version;
+ }
+
+ if (current === 'dev') {
+ version.value = {
+ latest: undefined,
+ current,
+ currentShort: current,
+ needsUpdate: false,
+ };
+ return version;
+ }
+
+ onMounted(async () => {
+ const versionInfo = await fetchVersion();
+
+ let needsUpdate = false;
+ if (versionInfo) {
+ if (usesNext) {
+ needsUpdate = versionInfo.next !== current;
+ } else {
+ needsUpdate = versionInfo.latest !== current;
+ }
+ }
+
+ version.value = {
+ latest: usesNext ? versionInfo?.next : versionInfo?.latest,
+ current,
+ currentShort: usesNext ? 'next' : current,
+ needsUpdate,
+ };
+ });
+
+ return version;
+}
diff --git a/web/src/views/admin/AdminSettings.vue b/web/src/views/admin/AdminSettings.vue
index fc15dd377d..3cb032888a 100644
--- a/web/src/views/admin/AdminSettings.vue
+++ b/web/src/views/admin/AdminSettings.vue
@@ -3,6 +3,9 @@
{{ $t('repo.settings.settings') }}
+
+
+
@@ -30,6 +33,7 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import AdminAgentsTab from '~/components/admin/settings/AdminAgentsTab.vue';
+import AdminInfoTab from '~/components/admin/settings/AdminInfoTab.vue';
import AdminOrgsTab from '~/components/admin/settings/AdminOrgsTab.vue';
import AdminQueueTab from '~/components/admin/settings/AdminQueueTab.vue';
import AdminReposTab from '~/components/admin/settings/AdminReposTab.vue';