├── l10n
├── .gitkeep
├── km.json
├── bn_BD.json
├── kn.json
├── km.js
├── az.json
├── lb.json
├── nn_NO.json
├── bn_BD.js
├── hy.json
├── ia.json
├── kn.js
├── kab.json
├── az.js
├── lb.js
├── nn_NO.js
├── si.json
├── th.json
├── af.json
├── hy.js
├── ia.js
├── kab.js
├── oc.json
├── si.js
├── th.js
├── af.js
├── mn.json
├── cy_GB.json
├── oc.js
├── mn.js
├── cy_GB.js
├── id.json
├── id.js
├── vi.json
├── vi.js
├── ro.json
├── ro.js
├── sq.json
├── ka_GE.json
├── ka_GE.js
├── sq.js
├── es_419.json
├── es_CL.json
├── es_CO.json
├── es_CR.json
├── es_DO.json
├── es_GT.json
├── es_HN.json
├── es_NI.json
├── es_PA.json
├── es_PE.json
├── es_PR.json
├── es_PY.json
├── es_SV.json
├── es_UY.json
├── br.json
├── es_CL.js
├── es_CO.js
├── es_CR.js
├── es_DO.js
├── es_GT.js
├── es_HN.js
├── es_NI.js
├── es_PA.js
├── es_PE.js
├── es_PR.js
├── es_PY.js
├── es_SV.js
├── es_UY.js
├── es_419.js
├── br.js
├── be.json
├── be.js
├── eo.json
├── he.json
├── eo.js
├── he.js
├── es_AR.json
├── sc.json
├── fi.json
├── es_AR.js
├── sc.js
├── fi.js
├── nl.json
├── nl.js
├── hr.json
├── pt_PT.json
├── hr.js
├── pt_PT.js
├── bg.json
├── bg.js
├── es_EC.json
├── es_EC.js
├── lt_LT.json
├── lt_LT.js
├── mk.json
├── mk.js
├── lv.json
├── lv.js
├── ko.json
├── ko.js
├── sl.json
└── sl.js
├── .l10nignore
├── .github
├── CODEOWNERS
├── dependabot.yml
├── workflows
│ ├── reuse.yml
│ ├── fixup.yml
│ ├── block-unconventional-commits.yml
│ ├── lint-php-cs.yml
│ ├── block-merge-eol.yml
│ ├── dependabot-approve-merge.yml
│ ├── block-merge-freeze.yml
│ ├── update-nextcloud-ocp-approve-merge.yml
│ ├── lint-php.yml
│ ├── pr-feedback.yml
│ ├── npm-audit-fix.yml
│ ├── lint-eslint.yml
│ └── psalm-matrix.yml
├── contributing.md
└── issue_template.md
├── screenshots
└── reader.png
├── css
└── logreader-main.css
├── .gitattributes
├── templates
└── index.php
├── src
├── utils
│ ├── logger.ts
│ ├── debounce.ts
│ ├── logger.spec.ts
│ ├── clipboard.ts
│ ├── clipboard.spec.ts
│ ├── debounce.spec.ts
│ ├── format.ts
│ ├── format.spec.ts
│ ├── logfile.spec.ts
│ ├── splitter.ts
│ ├── exception.spec.ts
│ └── logfile.ts
├── interfaces
│ ├── index.ts
│ ├── IAppSettings.ts
│ └── ILogEntry.ts
├── index.ts
├── constants.spec.ts
├── components
│ ├── exception
│ │ ├── StackTrace.vue
│ │ ├── TraceLine.vue
│ │ └── LogException.vue
│ ├── IntersectionObserver.vue
│ ├── settings
│ │ ├── SettingsLiveView.vue
│ │ ├── SettingsSetLogLevel.vue
│ │ ├── AppSettingsDialog.vue
│ │ ├── SettingsLogLevels.vue
│ │ ├── SettingsActions.vue
│ │ └── SettingsDatetimeFormat.vue
│ ├── LogSearch.vue
│ └── table
│ │ └── LogTableHeader.vue
├── vue-shim.d.ts
├── constants.ts
├── api.ts
└── api.spec.ts
├── tsconfig.json
├── .gitignore
├── .tx
└── config
├── AUTHORS.md
├── codecov.yml
├── lib
├── Exception
│ └── UnsupportedLogTypeException.php
├── Controller
│ ├── PageController.php
│ └── SettingsController.php
├── Listener
│ └── LogListener.php
├── AppInfo
│ └── Application.php
├── Constants.php
├── Settings
│ ├── Section.php
│ └── Admin.php
├── Log
│ ├── LogIteratorFactory.php
│ ├── SearchFilter.php
│ ├── Console.php
│ └── LogIterator.php
├── SetupChecks
│ └── LogErrors.php
├── Command
│ └── Tail.php
└── Service
│ └── SettingsService.php
├── img
├── app.svg
└── app-dark.svg
├── tests
├── bootstrap.php
├── stubs
│ └── stub.phpstub
├── phpunit.xml
└── Unit
│ └── SetupChecks
│ └── LogErrorsTest.php
├── .php-cs-fixer.dist.php
├── appinfo
├── routes.php
└── info.xml
├── eslint.config.mjs
├── LICENSES
├── ISC.txt
├── MIT.txt
└── BSD-3-Clause.txt
├── vite.config.ts
├── psalm.xml
├── composer.json
├── REUSE.toml
├── package.json
├── Makefile
└── README.md
/l10n/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.l10nignore:
--------------------------------------------------------------------------------
1 | js/
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | /appinfo/info.xml @Antreesy @icewind1991
2 |
3 |
--------------------------------------------------------------------------------
/screenshots/reader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextcloud/logreader/HEAD/screenshots/reader.png
--------------------------------------------------------------------------------
/css/logreader-main.css:
--------------------------------------------------------------------------------
1 | /* extracted by css-entry-points-plugin */
2 | @import './main-BM0QfzKZ.chunk.css';
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
2 | # SPDX-License-Identifier: CC0-1.0
3 |
4 | js/* -diff
5 |
--------------------------------------------------------------------------------
/l10n/km.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Time" : "ម៉ោង",
3 | "Info" : "Info",
4 | "Warning" : "បម្រាម",
5 | "Error" : "កំហុស"
6 | },"pluralForm" :"nplurals=1; plural=0;"
7 | }
--------------------------------------------------------------------------------
/l10n/bn_BD.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Time" : "সময়",
3 | "Info" : "Info",
4 | "Warning" : "সতর্কবাণী",
5 | "Error" : "সমস্যা"
6 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
7 | }
--------------------------------------------------------------------------------
/l10n/kn.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Time" : "ಸಮಯ",
3 | "Info" : "Info",
4 | "Warning" : "ಎಚ್ಚರಿಕೆ",
5 | "Error" : "ತಪ್ಪಾಗಿದೆ"
6 | },"pluralForm" :"nplurals=2; plural=(n > 1);"
7 | }
--------------------------------------------------------------------------------
/l10n/km.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Time" : "ម៉ោង",
5 | "Info" : "Info",
6 | "Warning" : "បម្រាម",
7 | "Error" : "កំហុស"
8 | },
9 | "nplurals=1; plural=0;");
10 |
--------------------------------------------------------------------------------
/l10n/az.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "App" : "Tətbiq",
3 | "Time" : "Vaxt",
4 | "Info" : "Info",
5 | "Warning" : "Xəbərdarlıq",
6 | "Error" : "Səhv"
7 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
8 | }
--------------------------------------------------------------------------------
/l10n/lb.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Time" : "Zäit",
3 | "Relative" : "Relativ",
4 | "Info" : "Info",
5 | "Warning" : "Warnung",
6 | "Error" : "Fehler"
7 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
8 | }
--------------------------------------------------------------------------------
/l10n/nn_NO.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "App" : "Applikasjon",
3 | "Time" : "Tid",
4 | "Info" : "Info",
5 | "Warning" : "Åtvaring",
6 | "Error" : "Feil"
7 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
8 | }
--------------------------------------------------------------------------------
/l10n/bn_BD.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Time" : "সময়",
5 | "Info" : "Info",
6 | "Warning" : "সতর্কবাণী",
7 | "Error" : "সমস্যা"
8 | },
9 | "nplurals=2; plural=(n != 1);");
10 |
--------------------------------------------------------------------------------
/l10n/hy.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Time" : "Ժամ",
3 | "Relative" : "Հարաբերական",
4 | "Info" : "Info",
5 | "Warning" : "Զգուշացում",
6 | "Error" : "Սխալ"
7 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
8 | }
--------------------------------------------------------------------------------
/l10n/ia.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Time" : "Tempore",
3 | "Relative" : "Relative",
4 | "Info" : "Info",
5 | "Warning" : "Aviso",
6 | "Error" : "Error"
7 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
8 | }
--------------------------------------------------------------------------------
/l10n/kn.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Time" : "ಸಮಯ",
5 | "Info" : "Info",
6 | "Warning" : "ಎಚ್ಚರಿಕೆ",
7 | "Error" : "ತಪ್ಪಾಗಿದೆ"
8 | },
9 | "nplurals=2; plural=(n > 1);");
10 |
--------------------------------------------------------------------------------
/l10n/kab.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Ctrl" : "Ctrl",
3 | "Message" : "Izen",
4 | "Show details" : "Sken talqayt",
5 | "Warning" : "Alɣu",
6 | "Error" : "Erreur"
7 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
8 | }
--------------------------------------------------------------------------------
/l10n/az.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "App" : "Tətbiq",
5 | "Time" : "Vaxt",
6 | "Info" : "Info",
7 | "Warning" : "Xəbərdarlıq",
8 | "Error" : "Səhv"
9 | },
10 | "nplurals=2; plural=(n != 1);");
11 |
--------------------------------------------------------------------------------
/l10n/lb.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Time" : "Zäit",
5 | "Relative" : "Relativ",
6 | "Info" : "Info",
7 | "Warning" : "Warnung",
8 | "Error" : "Fehler"
9 | },
10 | "nplurals=2; plural=(n != 1);");
11 |
--------------------------------------------------------------------------------
/l10n/nn_NO.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "App" : "Applikasjon",
5 | "Time" : "Tid",
6 | "Info" : "Info",
7 | "Warning" : "Åtvaring",
8 | "Error" : "Feil"
9 | },
10 | "nplurals=2; plural=(n != 1);");
11 |
--------------------------------------------------------------------------------
/l10n/si.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "App" : "යෙදුම",
3 | "Time" : "වේලාව",
4 | "Message" : "පණිවිඩය",
5 | "Info" : "තොරතුරු",
6 | "Warning" : "අවවාදයයි",
7 | "Error" : "දෝෂය"
8 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
9 | }
--------------------------------------------------------------------------------
/l10n/th.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "App" : "แอป",
3 | "Time" : "เวลา",
4 | "Show details" : "แสดงข้อมูล",
5 | "Info" : "ข้อมูล",
6 | "Warning" : "คำเตือน",
7 | "Error" : "ข้อผิดพลาด"
8 | },"pluralForm" :"nplurals=1; plural=0;"
9 | }
--------------------------------------------------------------------------------
/l10n/af.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "App" : "Toep",
3 | "Time" : "Tyd",
4 | "Relative" : "Relatief",
5 | "Info" : "Inligting",
6 | "Warning" : "Waarskuwing",
7 | "Error" : "Fout"
8 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
9 | }
--------------------------------------------------------------------------------
/l10n/hy.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Time" : "Ժամ",
5 | "Relative" : "Հարաբերական",
6 | "Info" : "Info",
7 | "Warning" : "Զգուշացում",
8 | "Error" : "Սխալ"
9 | },
10 | "nplurals=2; plural=(n != 1);");
11 |
--------------------------------------------------------------------------------
/l10n/ia.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Time" : "Tempore",
5 | "Relative" : "Relative",
6 | "Info" : "Info",
7 | "Warning" : "Aviso",
8 | "Error" : "Error"
9 | },
10 | "nplurals=2; plural=(n != 1);");
11 |
--------------------------------------------------------------------------------
/l10n/kab.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Ctrl" : "Ctrl",
5 | "Message" : "Izen",
6 | "Show details" : "Sken talqayt",
7 | "Warning" : "Alɣu",
8 | "Error" : "Erreur"
9 | },
10 | "nplurals=2; plural=(n != 1);");
11 |
--------------------------------------------------------------------------------
/l10n/oc.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Time" : "Ora",
3 | "Relative" : "Relatiu",
4 | "Show details" : "Mostrar detalhs",
5 | "Info" : "Info",
6 | "Warning" : "Avertiment",
7 | "Error" : "Error"
8 | },"pluralForm" :"nplurals=2; plural=(n > 1);"
9 | }
--------------------------------------------------------------------------------
/templates/index.php:
--------------------------------------------------------------------------------
1 |
8 |
9 |
--------------------------------------------------------------------------------
/l10n/si.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "App" : "යෙදුම",
5 | "Time" : "වේලාව",
6 | "Message" : "පණිවිඩය",
7 | "Info" : "තොරතුරු",
8 | "Warning" : "අවවාදයයි",
9 | "Error" : "දෝෂය"
10 | },
11 | "nplurals=2; plural=(n != 1);");
12 |
--------------------------------------------------------------------------------
/l10n/th.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "App" : "แอป",
5 | "Time" : "เวลา",
6 | "Show details" : "แสดงข้อมูล",
7 | "Info" : "ข้อมูล",
8 | "Warning" : "คำเตือน",
9 | "Error" : "ข้อผิดพลาด"
10 | },
11 | "nplurals=1; plural=0;");
12 |
--------------------------------------------------------------------------------
/l10n/af.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "App" : "Toep",
5 | "Time" : "Tyd",
6 | "Relative" : "Relatief",
7 | "Info" : "Inligting",
8 | "Warning" : "Waarskuwing",
9 | "Error" : "Fout"
10 | },
11 | "nplurals=2; plural=(n != 1);");
12 |
--------------------------------------------------------------------------------
/l10n/mn.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Level" : "түвшин",
3 | "App" : "Аппликэйшин",
4 | "Time" : "Цаг",
5 | "Relative" : "Хамаарал",
6 | "Info" : "Info",
7 | "Warning" : "Warning",
8 | "Error" : "Алдаа"
9 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
10 | }
--------------------------------------------------------------------------------
/l10n/cy_GB.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "App" : "Ap",
3 | "Time" : "Amser",
4 | "Relative" : "Perthynas",
5 | "Info" : "Info",
6 | "Warning" : "Rhybudd",
7 | "Error" : "Gwall"
8 | },"pluralForm" :"nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;"
9 | }
--------------------------------------------------------------------------------
/l10n/oc.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Time" : "Ora",
5 | "Relative" : "Relatiu",
6 | "Show details" : "Mostrar detalhs",
7 | "Info" : "Info",
8 | "Warning" : "Avertiment",
9 | "Error" : "Error"
10 | },
11 | "nplurals=2; plural=(n > 1);");
12 |
--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { getLoggerBuilder } from '@nextcloud/logger'
7 |
8 | export const logger = getLoggerBuilder().setApp(appName).build()
9 |
--------------------------------------------------------------------------------
/l10n/mn.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Level" : "түвшин",
5 | "App" : "Аппликэйшин",
6 | "Time" : "Цаг",
7 | "Relative" : "Хамаарал",
8 | "Info" : "Info",
9 | "Warning" : "Warning",
10 | "Error" : "Алдаа"
11 | },
12 | "nplurals=2; plural=(n != 1);");
13 |
--------------------------------------------------------------------------------
/l10n/cy_GB.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "App" : "Ap",
5 | "Time" : "Amser",
6 | "Relative" : "Perthynas",
7 | "Info" : "Info",
8 | "Warning" : "Rhybudd",
9 | "Error" : "Gwall"
10 | },
11 | "nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;");
12 |
--------------------------------------------------------------------------------
/l10n/id.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "App" : "Apl",
3 | "Time" : "Waktu",
4 | "Line {line}" : "Baris {line}",
5 | "Message" : "Pesan",
6 | "Show details" : "Tampilkan detail",
7 | "Info" : "Info",
8 | "Warning" : "Peringatan",
9 | "Error" : "Galat"
10 | },"pluralForm" :"nplurals=1; plural=0;"
11 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig",
3 | "compilerOptions": {
4 | "moduleResolution": "Bundler",
5 | "outDir": "./js/",
6 | "lib": [
7 | "DOM",
8 | "ESNext"
9 | ],
10 | "types": [
11 | "vite/client"
12 | ],
13 | "noImplicitAny": true,
14 | "allowSyntheticDefaultImports": true,
15 | },
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
2 | # SPDX-License-Identifier: CC0-1.0
3 |
4 | # Code coverge
5 | .nyc_output/
6 | coverage/
7 |
8 | # Test output
9 | cypress/screenshots/
10 | cypress/videos/
11 |
12 | # Locally installed packages
13 | node_modules/
14 | vendor/
15 |
16 | build/
17 | *.cache
18 |
--------------------------------------------------------------------------------
/l10n/id.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "App" : "Apl",
5 | "Time" : "Waktu",
6 | "Line {line}" : "Baris {line}",
7 | "Message" : "Pesan",
8 | "Show details" : "Tampilkan detail",
9 | "Info" : "Info",
10 | "Warning" : "Peringatan",
11 | "Error" : "Galat"
12 | },
13 | "nplurals=1; plural=0;");
14 |
--------------------------------------------------------------------------------
/l10n/vi.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Level" : "Cấp độ",
3 | "App" : "Ứng dụng",
4 | "Time" : "Thời gian",
5 | "Ctrl" : "Ctrl",
6 | "Relative" : "Liên quan",
7 | "Show details" : "Hiển thị chi tiết",
8 | "Info" : "Thông tin",
9 | "Warning" : "Cảnh báo",
10 | "Error" : "Lỗi"
11 | },"pluralForm" :"nplurals=1; plural=0;"
12 | }
--------------------------------------------------------------------------------
/.tx/config:
--------------------------------------------------------------------------------
1 | [main]
2 | host = https://www.transifex.com
3 | lang_map = th_TH: th, ja_JP: ja, bg_BG: bg, cs_CZ: cs, fi_FI: fi, hu_HU: hu, nb_NO: nb, sk_SK: sk
4 |
5 | [o:nextcloud:p:nextcloud:r:logreader]
6 | file_filter = translationfiles//logreader.po
7 | source_file = translationfiles/templates/logreader.pot
8 | source_lang = en
9 | type = PO
10 |
11 |
--------------------------------------------------------------------------------
/src/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | export type * from './IAppSettings'
7 | export type * from './ILogEntry'
8 |
9 | /**
10 | * Options how to sort rows
11 | */
12 | export type ISortingOptions = '' | 'ascending' | 'descending'
13 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Ferdinand Thiessen
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { createPinia } from 'pinia'
7 | import { createApp } from 'vue'
8 | import App from './App.vue'
9 |
10 | const pinia = createPinia()
11 |
12 | createApp(App)
13 | .use(pinia)
14 | .mount('#app-content')
15 |
--------------------------------------------------------------------------------
/l10n/vi.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Level" : "Cấp độ",
5 | "App" : "Ứng dụng",
6 | "Time" : "Thời gian",
7 | "Ctrl" : "Ctrl",
8 | "Relative" : "Liên quan",
9 | "Show details" : "Hiển thị chi tiết",
10 | "Info" : "Thông tin",
11 | "Warning" : "Cảnh báo",
12 | "Error" : "Lỗi"
13 | },
14 | "nplurals=1; plural=0;");
15 |
--------------------------------------------------------------------------------
/AUTHORS.md:
--------------------------------------------------------------------------------
1 |
5 | # Authors
6 |
7 | - Carl Schwan
8 | - Côme Chilliet
9 | - Ferdinand Thiessen
10 | - Robin Appelman
11 | - Nextcloud GmbH
12 | - ownCloud, Inc.
13 |
--------------------------------------------------------------------------------
/l10n/ro.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Level" : "Nivel",
3 | "App" : "Aplicație",
4 | "Time" : "Timp",
5 | "Relative" : "Relativ",
6 | "Message" : "Mesaj",
7 | "Show details" : "Arată detaliile",
8 | "Info" : "Info",
9 | "Warning" : "Atenție",
10 | "Error" : "Eroare"
11 | },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"
12 | }
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
2 | # SPDX-License-Identifier: AGPL-3.0-or-later
3 | codecov:
4 | branch: master
5 | comment: false
6 |
7 | # Ignore project status check as our CI only runs PHP tests for PHP PRs and JS tests for JS PRs
8 | # Otherwise it will warn about project coverage drops
9 | coverage:
10 | status:
11 | project: off
12 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
2 | # SPDX-License-Identifier: CC0-1.0
3 |
4 | version: 2
5 | updates:
6 | - package-ecosystem: npm
7 | directory: "/"
8 | schedule:
9 | interval: weekly
10 | day: saturday
11 | time: "03:00"
12 | timezone: Europe/Paris
13 | open-pull-requests-limit: 10
14 | versioning-strategy: increase
15 |
--------------------------------------------------------------------------------
/src/constants.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { expect, test } from 'vitest'
7 | import { LOGGING_LEVEL, LOGGING_LEVEL_NAMES } from './constants'
8 |
9 | test('constants - every level has a name', () => {
10 | expect(LOGGING_LEVEL.length).toBe(LOGGING_LEVEL_NAMES.length)
11 | })
12 |
--------------------------------------------------------------------------------
/l10n/ro.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Level" : "Nivel",
5 | "App" : "Aplicație",
6 | "Time" : "Timp",
7 | "Relative" : "Relativ",
8 | "Message" : "Mesaj",
9 | "Show details" : "Arată detaliile",
10 | "Info" : "Info",
11 | "Warning" : "Atenție",
12 | "Error" : "Eroare"
13 | },
14 | "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));");
15 |
--------------------------------------------------------------------------------
/l10n/sq.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Duke hyrë",
3 | "Log Reader" : "Lexuesi i Regjistrit",
4 | "Level" : "Nivel",
5 | "App" : "Aplikacion",
6 | "Time" : "Kohë",
7 | "Download logs" : "Regjistrimet e shkarkuara",
8 | "Relative" : "Relative",
9 | "Message" : "Mesazh",
10 | "Info" : "Info",
11 | "Warning" : "Kujdes",
12 | "Error" : "Gabim"
13 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
14 | }
--------------------------------------------------------------------------------
/l10n/ka_GE.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "იწერება ლოგი",
3 | "Log Reader" : "ლოგის მკითხველი",
4 | "Level" : "დონე",
5 | "App" : "აპლიკაცია",
6 | "Time" : "დრო",
7 | "Download logs" : "ლოგების გადმოწერა",
8 | "Relative" : "ფარდობითი",
9 | "Message" : "წერილი",
10 | "Info" : "ინფორმაცია",
11 | "Warning" : "გაფრთხილება",
12 | "Error" : "შეცდომა"
13 | },"pluralForm" :"nplurals=2; plural=(n!=1);"
14 | }
--------------------------------------------------------------------------------
/l10n/ka_GE.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "იწერება ლოგი",
5 | "Log Reader" : "ლოგის მკითხველი",
6 | "Level" : "დონე",
7 | "App" : "აპლიკაცია",
8 | "Time" : "დრო",
9 | "Download logs" : "ლოგების გადმოწერა",
10 | "Relative" : "ფარდობითი",
11 | "Message" : "წერილი",
12 | "Info" : "ინფორმაცია",
13 | "Warning" : "გაფრთხილება",
14 | "Error" : "შეცდომა"
15 | },
16 | "nplurals=2; plural=(n!=1);");
17 |
--------------------------------------------------------------------------------
/l10n/sq.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Duke hyrë",
5 | "Log Reader" : "Lexuesi i Regjistrit",
6 | "Level" : "Nivel",
7 | "App" : "Aplikacion",
8 | "Time" : "Kohë",
9 | "Download logs" : "Regjistrimet e shkarkuara",
10 | "Relative" : "Relative",
11 | "Message" : "Mesazh",
12 | "Info" : "Info",
13 | "Warning" : "Kujdes",
14 | "Error" : "Gabim"
15 | },
16 | "nplurals=2; plural=(n != 1);");
17 |
--------------------------------------------------------------------------------
/lib/Exception/UnsupportedLogTypeException.php:
--------------------------------------------------------------------------------
1 | 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);"
7 | }
--------------------------------------------------------------------------------
/img/app.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/l10n/es_CL.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_CO.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_CR.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_DO.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_GT.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_HN.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_NI.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_PA.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_PE.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_PR.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_PY.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_SV.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/es_UY.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/img/app-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/l10n/es_419.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar bitácoras",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Info" : "Info",
13 | "Warning" : "Advertencia",
14 | "Error" : "Error"
15 | },
16 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
17 |
--------------------------------------------------------------------------------
/l10n/br.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Show details" : "Diskouel ar munudoù",
5 | "Info" : "Titouroù",
6 | "Warning" : "Kemenadenn",
7 | "Error" : "Fazi"
8 | },
9 | "nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);");
10 |
--------------------------------------------------------------------------------
/l10n/be.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Level" : "Узровень",
3 | "App" : "Праграма",
4 | "Time" : "Час",
5 | "Ctrl" : "Ctrl",
6 | "Application" : "Праграма",
7 | "Message" : "Паведамленне",
8 | "Show details" : "Паказаць падрабязнасці",
9 | "Info" : "Інфармацыя",
10 | "Warning" : "Папярэджанне",
11 | "Error" : "Памылка"
12 | },"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"
13 | }
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | addPsr4('Test\\', OC::$SERVERROOT . '/tests/lib/', true);
15 | \OC::$composerAutoloader->addPsr4('Tests\\', OC::$SERVERROOT . '/tests/', true);
16 |
17 | OC_App::loadApp('logreader');
18 |
19 | OC_Hook::clear();
20 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | getFinder()
16 | ->ignoreVCSIgnored(true)
17 | ->notPath('build')
18 | ->notPath('l10n')
19 | ->notPath('lib/Vendor')
20 | ->notPath('src')
21 | ->notPath('vendor')
22 | ->in(__DIR__);
23 | return $config;
24 |
--------------------------------------------------------------------------------
/l10n/be.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Level" : "Узровень",
5 | "App" : "Праграма",
6 | "Time" : "Час",
7 | "Ctrl" : "Ctrl",
8 | "Application" : "Праграма",
9 | "Message" : "Паведамленне",
10 | "Show details" : "Паказаць падрабязнасці",
11 | "Info" : "Інфармацыя",
12 | "Warning" : "Папярэджанне",
13 | "Error" : "Памылка"
14 | },
15 | "nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);");
16 |
--------------------------------------------------------------------------------
/src/utils/debounce.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | /**
7 | * Debounce a function call for specified amount of time
8 | *
9 | * @param func The function to debounce
10 | * @param timeout Amount of time (ms) to wait
11 | */
12 | export function debounce(func: Function, timeout = 300) {
13 | let timer: number
14 | return (...args: unknown[]) => {
15 | clearTimeout(timer)
16 | timer = window.setTimeout(() => {
17 | func.apply(this, args)
18 | }, timeout)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/l10n/eo.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Protokolo",
3 | "Log Reader" : "Protokola legilo",
4 | "A log reader for Nextcloud" : "Protokola legilo por Nextcloud",
5 | "Log reader for Nextcloud" : "Protokola legilo por Nextcloud",
6 | "Level" : "Nivelo",
7 | "App" : "Aplikaĵo",
8 | "Time" : "Dato",
9 | "Download logs" : "Elŝuti prokolon",
10 | "Relative" : "Relative",
11 | "Message" : "Mesaĝo",
12 | "Show details" : "Montri la detalojn",
13 | "Info" : "Info",
14 | "Warning" : "Averto",
15 | "Error" : "Eraro"
16 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
17 | }
--------------------------------------------------------------------------------
/l10n/he.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "רישום",
3 | "Log Reader" : "קורא רישומים",
4 | "A log reader for Nextcloud" : "קורא רישומים ל־Nextcloud",
5 | "Log reader for Nextcloud" : "קורא רישומים ל־Nextcloud",
6 | "Level" : "רמה",
7 | "App" : "יישומון",
8 | "Time" : "זמן",
9 | "Download logs" : "הורדת רישומים",
10 | "Relative" : "יחסי",
11 | "Message" : "הודעה",
12 | "Show details" : "הצגת פרטים",
13 | "Info" : "פרטים",
14 | "Warning" : "אזהרה",
15 | "Error" : "שגיאה"
16 | },"pluralForm" :"nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;"
17 | }
--------------------------------------------------------------------------------
/l10n/eo.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Protokolo",
5 | "Log Reader" : "Protokola legilo",
6 | "A log reader for Nextcloud" : "Protokola legilo por Nextcloud",
7 | "Log reader for Nextcloud" : "Protokola legilo por Nextcloud",
8 | "Level" : "Nivelo",
9 | "App" : "Aplikaĵo",
10 | "Time" : "Dato",
11 | "Download logs" : "Elŝuti prokolon",
12 | "Relative" : "Relative",
13 | "Message" : "Mesaĝo",
14 | "Show details" : "Montri la detalojn",
15 | "Info" : "Info",
16 | "Warning" : "Averto",
17 | "Error" : "Eraro"
18 | },
19 | "nplurals=2; plural=(n != 1);");
20 |
--------------------------------------------------------------------------------
/l10n/he.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "רישום",
5 | "Log Reader" : "קורא רישומים",
6 | "A log reader for Nextcloud" : "קורא רישומים ל־Nextcloud",
7 | "Log reader for Nextcloud" : "קורא רישומים ל־Nextcloud",
8 | "Level" : "רמה",
9 | "App" : "יישומון",
10 | "Time" : "זמן",
11 | "Download logs" : "הורדת רישומים",
12 | "Relative" : "יחסי",
13 | "Message" : "הודעה",
14 | "Show details" : "הצגת פרטים",
15 | "Info" : "פרטים",
16 | "Warning" : "אזהרה",
17 | "Error" : "שגיאה"
18 | },
19 | "nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;");
20 |
--------------------------------------------------------------------------------
/appinfo/routes.php:
--------------------------------------------------------------------------------
1 | [
9 | // page
10 | ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
11 | ['name' => 'log#get', 'url' => '/api/log', 'verb' => 'GET'],
12 | ['name' => 'log#poll', 'url' => '/api/poll', 'verb' => 'GET'],
13 | // app settings
14 | ['name' => 'settings#getAppConfig', 'url' => '/api/settings', 'verb' => 'GET'],
15 | ['name' => 'settings#updateAppConfig', 'url' => '/api/settings', 'verb' => 'PUT']
16 | ]];
17 |
--------------------------------------------------------------------------------
/l10n/es_AR.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Registro",
3 | "Log Reader" : "Lector de registro",
4 | "A log reader for Nextcloud" : "Un lector de registro para Nextcloud",
5 | "Log reader for Nextcloud" : "Lector de registro para Nextcloud",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Download logs" : "Descargar registros",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensaje",
12 | "Show details" : "Mostrar detalles",
13 | "Info" : "Info",
14 | "Warning" : "Advertencia",
15 | "Error" : "Error"
16 | },"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
17 | }
--------------------------------------------------------------------------------
/l10n/sc.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Registratzione",
3 | "Log Reader" : "Letore de registros",
4 | "A log reader for Nextcloud" : "Unu letore de registros pro Nextcloud",
5 | "Log reader for Nextcloud" : "Letore de registros pro Nextcloud",
6 | "Level" : "Livellu",
7 | "App" : "Aplicatzione",
8 | "Time" : "Ora",
9 | "Download logs" : "Iscàrriga registros",
10 | "Message" : "Messàgiu",
11 | "Show details" : "Mustra detàllios",
12 | "Debug" : "Currigi",
13 | "Info" : "Informatziones",
14 | "Warning" : "Avisu",
15 | "Error" : "Errore",
16 | "Fatal" : "Fatale"
17 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
18 | }
--------------------------------------------------------------------------------
/l10n/fi.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Loki",
3 | "Log Reader" : "Lokilukija",
4 | "A log reader for Nextcloud" : "Nextcloudin lokilukija",
5 | "Log reader for Nextcloud" : "Nextcloudin lokilukija",
6 | "Level" : "Taso",
7 | "App" : "Sovellus",
8 | "Time" : "Aika",
9 | "Download logs" : "Lataa lokit",
10 | "Relative" : "Suhteellinen",
11 | "Application" : "Sovellus",
12 | "Message" : "Viesti",
13 | "Show details" : "Näytä lisätiedot",
14 | "Debug" : "Vianjäljitys",
15 | "Info" : "Tietoa",
16 | "Warning" : "Varoitus",
17 | "Error" : "Virhe",
18 | "Fatal" : "Kriittinen"
19 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
20 | }
--------------------------------------------------------------------------------
/l10n/es_AR.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registro",
5 | "Log Reader" : "Lector de registro",
6 | "A log reader for Nextcloud" : "Un lector de registro para Nextcloud",
7 | "Log reader for Nextcloud" : "Lector de registro para Nextcloud",
8 | "Level" : "Nivel",
9 | "App" : "Aplicación",
10 | "Time" : "Hora",
11 | "Download logs" : "Descargar registros",
12 | "Relative" : "Relativo",
13 | "Message" : "Mensaje",
14 | "Show details" : "Mostrar detalles",
15 | "Info" : "Info",
16 | "Warning" : "Advertencia",
17 | "Error" : "Error"
18 | },
19 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
20 |
--------------------------------------------------------------------------------
/l10n/sc.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registratzione",
5 | "Log Reader" : "Letore de registros",
6 | "A log reader for Nextcloud" : "Unu letore de registros pro Nextcloud",
7 | "Log reader for Nextcloud" : "Letore de registros pro Nextcloud",
8 | "Level" : "Livellu",
9 | "App" : "Aplicatzione",
10 | "Time" : "Ora",
11 | "Download logs" : "Iscàrriga registros",
12 | "Message" : "Messàgiu",
13 | "Show details" : "Mustra detàllios",
14 | "Debug" : "Currigi",
15 | "Info" : "Informatziones",
16 | "Warning" : "Avisu",
17 | "Error" : "Errore",
18 | "Fatal" : "Fatale"
19 | },
20 | "nplurals=2; plural=(n != 1);");
21 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { recommended } from '@nextcloud/eslint-config'
7 |
8 | export default [
9 | ...recommended,
10 | {
11 | name: 'logreader/disabled-during-migration',
12 | rules: {
13 | '@typescript-eslint/no-unsafe-function-type': 'off',
14 | '@typescript-eslint/no-unused-vars': 'off',
15 | '@typescript-eslint/no-use-before-define': 'off',
16 | 'import-extensions/extensions': 'off',
17 | 'vue/no-boolean-default': 'off',
18 | 'vue/no-required-prop-with-default': 'off',
19 | 'vue/no-unused-properties': 'off',
20 | },
21 | },
22 | ]
23 |
--------------------------------------------------------------------------------
/l10n/fi.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Loki",
5 | "Log Reader" : "Lokilukija",
6 | "A log reader for Nextcloud" : "Nextcloudin lokilukija",
7 | "Log reader for Nextcloud" : "Nextcloudin lokilukija",
8 | "Level" : "Taso",
9 | "App" : "Sovellus",
10 | "Time" : "Aika",
11 | "Download logs" : "Lataa lokit",
12 | "Relative" : "Suhteellinen",
13 | "Application" : "Sovellus",
14 | "Message" : "Viesti",
15 | "Show details" : "Näytä lisätiedot",
16 | "Debug" : "Vianjäljitys",
17 | "Info" : "Tietoa",
18 | "Warning" : "Varoitus",
19 | "Error" : "Virhe",
20 | "Fatal" : "Kriittinen"
21 | },
22 | "nplurals=2; plural=(n != 1);");
23 |
--------------------------------------------------------------------------------
/src/components/exception/StackTrace.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
22 |
23 |
30 |
--------------------------------------------------------------------------------
/l10n/nl.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Logs",
3 | "Log Reader" : "Logboek lezer",
4 | "A log reader for Nextcloud" : "Een loglezer voor Nextcloud",
5 | "Log reader for Nextcloud" : "Loglezer voor Nextcloud",
6 | "Level" : "Niveau",
7 | "App" : "App",
8 | "Time" : "Tijd",
9 | "Ctrl" : "Ctrl",
10 | "Download logs" : "Logboek downloaden",
11 | "Relative" : "Relatief",
12 | "Application" : "Applicatie",
13 | "Message" : "Bericht",
14 | "Show details" : "Tonen details",
15 | "Debug" : "Debug",
16 | "Info" : "Info",
17 | "Warning" : "Waarschuwing",
18 | "Error" : "Fout",
19 | "Fatal" : "Fataal"
20 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
21 | }
--------------------------------------------------------------------------------
/l10n/nl.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Logs",
5 | "Log Reader" : "Logboek lezer",
6 | "A log reader for Nextcloud" : "Een loglezer voor Nextcloud",
7 | "Log reader for Nextcloud" : "Loglezer voor Nextcloud",
8 | "Level" : "Niveau",
9 | "App" : "App",
10 | "Time" : "Tijd",
11 | "Ctrl" : "Ctrl",
12 | "Download logs" : "Logboek downloaden",
13 | "Relative" : "Relatief",
14 | "Application" : "Applicatie",
15 | "Message" : "Bericht",
16 | "Show details" : "Tonen details",
17 | "Debug" : "Debug",
18 | "Info" : "Info",
19 | "Warning" : "Waarschuwing",
20 | "Error" : "Fout",
21 | "Fatal" : "Fataal"
22 | },
23 | "nplurals=2; plural=(n != 1);");
24 |
--------------------------------------------------------------------------------
/src/utils/logger.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { afterEach, describe, expect, it, vi } from 'vitest'
7 | import { logger } from './logger'
8 |
9 | describe('utils:logger', () => {
10 | afterEach(() => {
11 | vi.restoreAllMocks()
12 | })
13 |
14 | it('creates a logger instance', () => {
15 | expect(typeof logger).toBe('object')
16 | expect(typeof logger.warn).toBe('function')
17 |
18 | const consoleSpy = vi.spyOn(window.console, 'warn').mockImplementation(() => {})
19 | logger.warn('foo')
20 | expect(consoleSpy).toBeCalled()
21 | expect(consoleSpy.mock.calls[0][0]).toMatch('[WARN] logreader:')
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/src/vue-shim.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Ferdinand Thiessen
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | declare module 'vue-material-design-icons/*.vue' {
7 | import type { DefineComponent } from 'vue'
8 |
9 | const IconVue: DefineComponent<{
10 | /** @default 24 */
11 | size?: number
12 | /** @default 'currentColor' */
13 | fillColor?: string
14 | title?: string
15 | }>
16 |
17 | export default IconVue
18 | }
19 |
20 | declare module '@nextcloud/vue/dist/Components/*.js' {
21 | import Vue from 'vue'
22 | export default Vue
23 | }
24 |
25 | declare module 'json-string-splitter' {
26 | function splitter(input: string): { jsons: string[], remainder: string }
27 | export = splitter
28 | }
29 |
--------------------------------------------------------------------------------
/l10n/hr.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Zapisivanje",
3 | "Log Reader" : "Čitač zapisa",
4 | "A log reader for Nextcloud" : "Čitač zapisa za Nextcloud",
5 | "Log reader for Nextcloud" : "Čitač zapisa za Nextcloud",
6 | "Level" : "Razina",
7 | "App" : "Aplikacija",
8 | "Time" : "Vrijeme",
9 | "Download logs" : "Preuzmi zapise",
10 | "Relative" : "Relativno",
11 | "Message" : "Poruka",
12 | "Show details" : "Prikaži pojedinosti",
13 | "Debug" : "Otklanjanje pogrešaka",
14 | "Info" : "Informacije",
15 | "Warning" : "Upozorenje",
16 | "Error" : "Pogreška",
17 | "Fatal" : "Fatalno"
18 | },"pluralForm" :"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;"
19 | }
--------------------------------------------------------------------------------
/l10n/pt_PT.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Registos",
3 | "_%n warning in the logs since %s_::_%n warnings in the logs since %s_" : ["%n aviso nos registos desde %s","%n avisos nos registos desde %s","%n avisos nos registos desde %s"],
4 | "Level" : "Nível",
5 | "App" : "Aplicação",
6 | "Time" : "Tempo",
7 | "Download/Upload logs" : "Registos de Transferir/Enviar",
8 | "Ctrl" : "Ctrl",
9 | "Download logs" : "Transferir registos",
10 | "Relative" : "Relativo",
11 | "Message" : "Mensagem",
12 | "Show details" : "Mostrar detalhes",
13 | "Info" : "Informação",
14 | "Warning" : "Aviso",
15 | "Error" : "Erro"
16 | },"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
17 | }
--------------------------------------------------------------------------------
/tests/stubs/stub.phpstub:
--------------------------------------------------------------------------------
1 |
6 | * SPDX-License-Identifier: AGPL-3.0-or-later
7 | */
8 |
9 | namespace OC\Core\Command {
10 | use Symfony\Component\Console\Command\Command;
11 |
12 | class Base extends Command {
13 | protected function abortIfInterrupted(){
14 | }
15 | }
16 |
17 | class InterruptedException extends \Exception {
18 | }
19 | }
20 |
21 | namespace OC {
22 | class SystemConfig {
23 | }
24 | }
25 |
26 | namespace OC\Log {
27 | use OC\SystemConfig;
28 | class LogDetails {
29 | public function __construct(SystemConfig $config) {
30 | }
31 | public function logDetails(string $app, $message, int $level): array {
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/l10n/hr.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Zapisivanje",
5 | "Log Reader" : "Čitač zapisa",
6 | "A log reader for Nextcloud" : "Čitač zapisa za Nextcloud",
7 | "Log reader for Nextcloud" : "Čitač zapisa za Nextcloud",
8 | "Level" : "Razina",
9 | "App" : "Aplikacija",
10 | "Time" : "Vrijeme",
11 | "Download logs" : "Preuzmi zapise",
12 | "Relative" : "Relativno",
13 | "Message" : "Poruka",
14 | "Show details" : "Prikaži pojedinosti",
15 | "Debug" : "Otklanjanje pogrešaka",
16 | "Info" : "Informacije",
17 | "Warning" : "Upozorenje",
18 | "Error" : "Pogreška",
19 | "Fatal" : "Fatalno"
20 | },
21 | "nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;");
22 |
--------------------------------------------------------------------------------
/l10n/pt_PT.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registos",
5 | "_%n warning in the logs since %s_::_%n warnings in the logs since %s_" : ["%n aviso nos registos desde %s","%n avisos nos registos desde %s","%n avisos nos registos desde %s"],
6 | "Level" : "Nível",
7 | "App" : "Aplicação",
8 | "Time" : "Tempo",
9 | "Download/Upload logs" : "Registos de Transferir/Enviar",
10 | "Ctrl" : "Ctrl",
11 | "Download logs" : "Transferir registos",
12 | "Relative" : "Relativo",
13 | "Message" : "Mensagem",
14 | "Show details" : "Mostrar detalhes",
15 | "Info" : "Informação",
16 | "Warning" : "Aviso",
17 | "Error" : "Erro"
18 | },
19 | "nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
20 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { translate as t } from '@nextcloud/l10n'
7 |
8 | /**
9 | * Mapping from numeric log level (0 - 4) to localized names
10 | */
11 | export const LOGGING_LEVEL_NAMES = [
12 | t('logreader', 'Debug'),
13 | t('logreader', 'Info'),
14 | t('logreader', 'Warning'),
15 | t('logreader', 'Error'),
16 | t('logreader', 'Fatal'),
17 | ]
18 |
19 | /**
20 | * Mapping from numeric log level to string
21 | */
22 | export const LOGGING_LEVEL = [
23 | 'debug',
24 | 'info',
25 | 'warning',
26 | 'error',
27 | 'fatal',
28 | ] as const
29 |
30 | /**
31 | * Interval for polling in ms
32 | */
33 | export const POLLING_INTERVAL = 10000
34 |
--------------------------------------------------------------------------------
/l10n/bg.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Регистри",
3 | "Log Reader" : "Четец на журнал",
4 | "A log reader for Nextcloud" : "Четец на журнал за Nextcloud",
5 | "Log reader for Nextcloud" : "Четец на журнал за Nextcloud",
6 | "Level" : "Ниво",
7 | "App" : "Приложение",
8 | "Time" : "Време",
9 | "Line {line}" : "Ред {line}",
10 | "Ctrl" : "Ctrl /бутон/",
11 | "Download logs" : "Изтегляне на журнали",
12 | "Relative" : "Относително",
13 | "Application" : "Приложение",
14 | "Message" : "Съобщение",
15 | "Show details" : "Показване на подробности",
16 | "Debug" : "Отстраняване на грешки",
17 | "Info" : "Информация",
18 | "Warning" : "Внимание",
19 | "Error" : "Грешка",
20 | "Fatal" : "Фатална"
21 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
22 | }
--------------------------------------------------------------------------------
/src/utils/clipboard.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { translate as t } from '@nextcloud/l10n'
7 |
8 | /**
9 | * Copy text to clipboard if it fails (e.g. not secure context (https, localhost...))
10 | * a prompt will be opened for the user to copy the text manually
11 | *
12 | * @param text The text to copy
13 | * @return true if automatic copy suceeded, false if prompt was used
14 | */
15 | export async function copyToCipboard(text: string) {
16 | try {
17 | await window.navigator.clipboard.writeText(text)
18 | return true
19 | } catch (e) {
20 | window.prompt(
21 | t('logreader', 'Could not copy to clipboard, please copy manually:'),
22 | text,
23 | )
24 | }
25 | return false
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSES/ISC.txt:
--------------------------------------------------------------------------------
1 | ISC License:
2 |
3 | Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
4 | Copyright (c) 1995-2003 by Internet Software Consortium
5 |
6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
9 |
--------------------------------------------------------------------------------
/l10n/bg.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Регистри",
5 | "Log Reader" : "Четец на журнал",
6 | "A log reader for Nextcloud" : "Четец на журнал за Nextcloud",
7 | "Log reader for Nextcloud" : "Четец на журнал за Nextcloud",
8 | "Level" : "Ниво",
9 | "App" : "Приложение",
10 | "Time" : "Време",
11 | "Line {line}" : "Ред {line}",
12 | "Ctrl" : "Ctrl /бутон/",
13 | "Download logs" : "Изтегляне на журнали",
14 | "Relative" : "Относително",
15 | "Application" : "Приложение",
16 | "Message" : "Съобщение",
17 | "Show details" : "Показване на подробности",
18 | "Debug" : "Отстраняване на грешки",
19 | "Info" : "Информация",
20 | "Warning" : "Внимание",
21 | "Error" : "Грешка",
22 | "Fatal" : "Фатална"
23 | },
24 | "nplurals=2; plural=(n != 1);");
25 |
--------------------------------------------------------------------------------
/l10n/es_EC.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Registrando",
3 | "Log Reader" : "Lector de Bitácoras",
4 | "A log reader for Nextcloud" : "Un lector de registros para Nextcloud.",
5 | "Log reader for Nextcloud" : "Lector de registros para Nextcloud.",
6 | "Level" : "Nivel",
7 | "App" : "Aplicación",
8 | "Time" : "Hora",
9 | "Line {line}" : "Línea {line}",
10 | "Ctrl" : "Ctrl",
11 | "Download logs" : "Descargar bitácoras",
12 | "Relative" : "Relativo",
13 | "Application" : "Aplicación",
14 | "Message" : "Mensaje",
15 | "Show details" : "Mostrar detalles",
16 | "Debug" : "Depuración",
17 | "Info" : "Info",
18 | "Warning" : "Advertencia",
19 | "Error" : "Error",
20 | "Fatal" : "Fatal"
21 | },"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
22 | }
--------------------------------------------------------------------------------
/tests/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 | ./Unit
16 |
17 |
18 |
19 |
20 | ../lib
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3 | * SPDX-License-Identifier: CC0-1.0
4 | */
5 |
6 | import type { UserConfig } from 'vitest/config'
7 |
8 | import { createAppConfig } from '@nextcloud/vite-config'
9 |
10 | const config = createAppConfig({
11 | main: 'src/index.ts',
12 | }, {
13 | // Build the css/logreader-style.css instead of inlineing the styles in the js bundle
14 | inlineCSS: false,
15 | assetFileNames: (info) => info.name === 'index.css' ? 'css/logreader-main.css' : undefined,
16 | // Configuration for vitest unit tests
17 | config: {
18 | test: {
19 | coverage: {
20 | include: ['src/**'],
21 | provider: 'istanbul',
22 | reporter: ['lcov', 'text'],
23 | },
24 | environment: 'happy-dom',
25 | },
26 | } as UserConfig,
27 | })
28 |
29 | export default config
30 |
--------------------------------------------------------------------------------
/l10n/es_EC.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registrando",
5 | "Log Reader" : "Lector de Bitácoras",
6 | "A log reader for Nextcloud" : "Un lector de registros para Nextcloud.",
7 | "Log reader for Nextcloud" : "Lector de registros para Nextcloud.",
8 | "Level" : "Nivel",
9 | "App" : "Aplicación",
10 | "Time" : "Hora",
11 | "Line {line}" : "Línea {line}",
12 | "Ctrl" : "Ctrl",
13 | "Download logs" : "Descargar bitácoras",
14 | "Relative" : "Relativo",
15 | "Application" : "Aplicación",
16 | "Message" : "Mensaje",
17 | "Show details" : "Mostrar detalles",
18 | "Debug" : "Depuración",
19 | "Info" : "Info",
20 | "Warning" : "Advertencia",
21 | "Error" : "Error",
22 | "Fatal" : "Fatal"
23 | },
24 | "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
25 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/l10n/lt_LT.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Registravimas",
3 | "Log Reader" : "Žurnalų skaitytuvė",
4 | "A log reader for Nextcloud" : "Žurnalo skaitytuvė, skirta Nextcloud",
5 | "Log reader for Nextcloud" : "Žurnalo skaitytuvė, skirta Nextcloud",
6 | "Level" : "Lygmuo",
7 | "App" : "Programėlė",
8 | "Time" : "Laikas",
9 | "Ctrl" : "Vald",
10 | "Download logs" : "Atsisiųsti žurnalus",
11 | "Relative" : "Santykinis laikas",
12 | "Message" : "Žinutė",
13 | "Show details" : "Rodyti išsamiau",
14 | "Debug" : "Derinimas",
15 | "Info" : "Informacija",
16 | "Warning" : "Įspėjimas",
17 | "Error" : "Klaida",
18 | "Fatal" : "Lemtinga klaida"
19 | },"pluralForm" :"nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);"
20 | }
--------------------------------------------------------------------------------
/.github/workflows/reuse.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 |
6 | # SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V.
7 | #
8 | # SPDX-License-Identifier: CC0-1.0
9 |
10 | name: REUSE Compliance Check
11 |
12 | on: [pull_request]
13 |
14 | permissions:
15 | contents: read
16 |
17 | jobs:
18 | reuse-compliance-check:
19 | runs-on: ubuntu-latest-low
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
23 | with:
24 | persist-credentials: false
25 |
26 | - name: REUSE Compliance Check
27 | uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0
28 |
--------------------------------------------------------------------------------
/l10n/lt_LT.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Registravimas",
5 | "Log Reader" : "Žurnalų skaitytuvė",
6 | "A log reader for Nextcloud" : "Žurnalo skaitytuvė, skirta Nextcloud",
7 | "Log reader for Nextcloud" : "Žurnalo skaitytuvė, skirta Nextcloud",
8 | "Level" : "Lygmuo",
9 | "App" : "Programėlė",
10 | "Time" : "Laikas",
11 | "Ctrl" : "Vald",
12 | "Download logs" : "Atsisiųsti žurnalus",
13 | "Relative" : "Santykinis laikas",
14 | "Message" : "Žinutė",
15 | "Show details" : "Rodyti išsamiau",
16 | "Debug" : "Derinimas",
17 | "Info" : "Informacija",
18 | "Warning" : "Įspėjimas",
19 | "Error" : "Klaida",
20 | "Fatal" : "Lemtinga klaida"
21 | },
22 | "nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);");
23 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextcloud/logreader",
3 | "type": "project",
4 | "license": "AGPLv3",
5 | "require-dev": {
6 | "nextcloud/coding-standard": "^1.3",
7 | "nextcloud/ocp": "dev-master",
8 | "phpunit/phpunit": "^9",
9 | "vimeo/psalm": "^5.15"
10 | },
11 | "config": {
12 | "optimize-autoloader": true,
13 | "classmap-authoritative": true,
14 | "platform": {
15 | "php": "8.1"
16 | },
17 | "sort-packages": true
18 | },
19 | "scripts": {
20 | "lint": "find . -name \\*.php -not -path './vendor/*' -not -path './build/*' -not -path './tests/integration/vendor/*' -print0 | xargs -0 -n1 php -l",
21 | "cs:check": "php-cs-fixer fix --dry-run --diff",
22 | "cs:fix": "php-cs-fixer fix",
23 | "test:unit": "phpunit -c tests/phpunit.xml",
24 | "psalm": "psalm --threads=1"
25 | },
26 | "autoload-dev": {
27 | "psr-4": {
28 | "OCP\\": "vendor/nextcloud/ocp/OCP"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/interfaces/IAppSettings.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | /**
7 | * Logreader app settings
8 | */
9 | export interface IAppSettings {
10 | /**
11 | * Logging levels to display messages for
12 | */
13 | shownLevels: Array<0 | 1 | 2 | 3 | 4>
14 | /**
15 | * How the log time should be displayed
16 | * - `local` Show in client local time format
17 | * - `utc` Same as `local` but in UTC timezone
18 | * - `relative`Same as `local` but show near times as relative (e.g. "2 seconds ago")
19 | * - `raw` The same as in the logfile
20 | */
21 | dateTimeFormat: 'local' | 'raw' | 'utc' | 'relative'
22 | /**
23 | * Wether the log should be polled
24 | */
25 | liveLog: boolean
26 | /**
27 | * Wether backend is enable = logging is set to file
28 | */
29 | enabled: boolean
30 | /**
31 | * The loglevel which is currently set on the server
32 | */
33 | logLevel: 0 | 1 | 2 | 3 | 4
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSES/MIT.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/src/utils/clipboard.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7 | import { copyToCipboard } from './clipboard'
8 |
9 | describe('utils:clipboard', () => {
10 | let originalWindowPrompt: window.prompt
11 |
12 | beforeEach(() => {
13 | window.prompt = vi.fn(() => '')
14 | })
15 |
16 | afterEach(() => {
17 | window.prompt = originalWindowPrompt
18 | })
19 |
20 | it('writes to clipboard', () => {
21 | const spy = vi.spyOn(window.navigator.clipboard, 'writeText')
22 | copyToCipboard('foo bar')
23 | expect(spy).toBeCalledWith('foo bar')
24 | expect(window.prompt).not.toBeCalled()
25 | })
26 |
27 | it('opens promp if not supported clipboard', () => {
28 | window.navigator.clipboard.writeText = vi.fn(() => {
29 | throw Error('no secure context')
30 | })
31 | copyToCipboard('foo bar')
32 | expect(window.navigator.clipboard.writeText).toBeCalledWith('foo bar')
33 | expect(window.prompt).toBeCalled()
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/.github/workflows/fixup.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Block fixup and squash commits
10 |
11 | on:
12 | pull_request:
13 | types: [opened, ready_for_review, reopened, synchronize]
14 |
15 | permissions:
16 | contents: read
17 |
18 | concurrency:
19 | group: fixup-${{ github.head_ref || github.run_id }}
20 | cancel-in-progress: true
21 |
22 | jobs:
23 | commit-message-check:
24 | if: github.event.pull_request.draft == false
25 |
26 | permissions:
27 | pull-requests: write
28 | name: Block fixup and squash commits
29 |
30 | runs-on: ubuntu-latest-low
31 |
32 | steps:
33 | - name: Run check
34 | uses: skjnldsv/block-fixup-merge-action@c138ea99e45e186567b64cf065ce90f7158c236a # v2
35 | with:
36 | repo-token: ${{ secrets.GITHUB_TOKEN }}
37 |
--------------------------------------------------------------------------------
/lib/Controller/PageController.php:
--------------------------------------------------------------------------------
1 | appName, 'logreader-main');
36 | Util::addStyle($this->appName, 'logreader-main');
37 | $this->initialState->provideInitialState('settings', $this->settingsService->getAppSettings());
38 |
39 | return new TemplateResponse($this->appName, 'index');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.github/workflows/block-unconventional-commits.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Block unconventional commits
10 |
11 | on:
12 | pull_request:
13 | types: [opened, ready_for_review, reopened, synchronize]
14 |
15 | permissions:
16 | contents: read
17 |
18 | concurrency:
19 | group: block-unconventional-commits-${{ github.head_ref || github.run_id }}
20 | cancel-in-progress: true
21 |
22 | jobs:
23 | block-unconventional-commits:
24 | name: Block unconventional commits
25 |
26 | runs-on: ubuntu-latest-low
27 |
28 | steps:
29 | - name: Checkout
30 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
31 | with:
32 | persist-credentials: false
33 |
34 | - uses: webiny/action-conventional-commits@8bc41ff4e7d423d56fa4905f6ff79209a78776c7 # v1.3.0
35 | with:
36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37 |
--------------------------------------------------------------------------------
/l10n/mk.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Евидентирање",
3 | "Errors in the log" : "Грешки во записникот",
4 | "No errors in the logs since %s" : "Не постојат грешки во записникот од %s",
5 | "_%n error in the logs since %s_::_%n errors in the logs since %s_" : ["%n грешка во записникот од %s","%n грешки во записникот од %s"],
6 | "_%n warning in the logs since %s_::_%n warnings in the logs since %s_" : ["%n предупредување во записникот од %s","%n предупредувања во записникот од %s"],
7 | "Log Reader" : "Читач на записи",
8 | "A log reader for Nextcloud" : "Читач на записи со евиденција за Nextcloud",
9 | "Log reader for Nextcloud" : "Читач на записи со евиденција за Nextcloud",
10 | "Log reader" : "Читач на записи",
11 | "Level" : "Ниво",
12 | "App" : "Апликација",
13 | "Time" : "Време",
14 | "Ctrl" : "Ctrl",
15 | "Download logs" : "Преземи записи",
16 | "Relative" : "Релативно",
17 | "Message" : "Порака",
18 | "Show details" : "Прикажи детали",
19 | "Debug" : "Дебаг",
20 | "Info" : "Инфо",
21 | "Warning" : "Предупредување",
22 | "Error" : "Грешка",
23 | "Fatal" : "Фатално"
24 | },"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"
25 | }
--------------------------------------------------------------------------------
/l10n/mk.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Евидентирање",
5 | "Errors in the log" : "Грешки во записникот",
6 | "No errors in the logs since %s" : "Не постојат грешки во записникот од %s",
7 | "_%n error in the logs since %s_::_%n errors in the logs since %s_" : ["%n грешка во записникот од %s","%n грешки во записникот од %s"],
8 | "_%n warning in the logs since %s_::_%n warnings in the logs since %s_" : ["%n предупредување во записникот од %s","%n предупредувања во записникот од %s"],
9 | "Log Reader" : "Читач на записи",
10 | "A log reader for Nextcloud" : "Читач на записи со евиденција за Nextcloud",
11 | "Log reader for Nextcloud" : "Читач на записи со евиденција за Nextcloud",
12 | "Log reader" : "Читач на записи",
13 | "Level" : "Ниво",
14 | "App" : "Апликација",
15 | "Time" : "Време",
16 | "Ctrl" : "Ctrl",
17 | "Download logs" : "Преземи записи",
18 | "Relative" : "Релативно",
19 | "Message" : "Порака",
20 | "Show details" : "Прикажи детали",
21 | "Debug" : "Дебаг",
22 | "Info" : "Инфо",
23 | "Warning" : "Предупредување",
24 | "Error" : "Грешка",
25 | "Fatal" : "Фатално"
26 | },
27 | "nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;");
28 |
--------------------------------------------------------------------------------
/lib/Listener/LogListener.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class LogListener implements IEventListener {
23 | private ?Console $console;
24 |
25 | public function __construct(Formatter $formatter, SystemConfig $config) {
26 | $level = getenv('OCC_LOG');
27 | if ($level) {
28 | $terminal = new Terminal();
29 | $this->console = new Console($formatter, $config, $level, $terminal->getWidth());
30 | } else {
31 | $this->console = null;
32 | }
33 | }
34 |
35 |
36 | public function handle(Event $event): void {
37 | if (!$event instanceof BeforeMessageLoggedEvent) {
38 | return;
39 | }
40 |
41 | if ($this->console) {
42 | $this->console->log($event->getLevel(), $event->getApp(), $event->getMessage());
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/utils/debounce.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
7 | import { debounce } from './debounce'
8 |
9 | describe('utils/debounce', () => {
10 | afterAll(() => {
11 | vi.useRealTimers()
12 | })
13 | beforeAll(() => {
14 | vi.useFakeTimers()
15 | })
16 |
17 | it('waits for the timeout', () => {
18 | const callback = vi.fn()
19 | const debounced = debounce(callback)
20 |
21 | debounced()
22 | expect(callback).not.toBeCalled()
23 | vi.advanceTimersByTime(300)
24 | expect(callback).toBeCalled()
25 | })
26 |
27 | it('passes arguments', () => {
28 | const callback = vi.fn()
29 | const debounced = debounce(callback, 50)
30 |
31 | debounced(1, 'two', { three: 3 })
32 | expect(callback).not.toBeCalled()
33 | vi.advanceTimersByTime(50)
34 | expect(callback).toBeCalledWith(1, 'two', { three: 3 })
35 | })
36 |
37 | it('dismisses calls before timeout', () => {
38 | const callback = vi.fn()
39 | const debounced = debounce(callback, 50)
40 |
41 | debounced()
42 | debounced()
43 | expect(callback).not.toBeCalled()
44 |
45 | vi.advanceTimersByTime(50)
46 | expect(callback).toBeCalledTimes(1)
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/lib/AppInfo/Application.php:
--------------------------------------------------------------------------------
1 | registerEventListener(BeforeMessageLoggedEvent::class, LogListener::class);
29 | }
30 | $context->registerService(Formatter::class, function (ContainerInterface $c) {
31 | return new Formatter(\OC::$SERVERROOT);
32 | });
33 |
34 | $context->registerSetupCheck(LogErrors::class);
35 | }
36 |
37 | public function boot(IBootContext $context): void {
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/IntersectionObserver.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
58 |
--------------------------------------------------------------------------------
/src/components/settings/SettingsLiveView.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | {{ t('logreader', 'Polling is disabled because server is not configured to log to file') }}
10 |
11 |
12 | {{
13 | t('logreader', 'Polling (live view)')
14 | }}
15 |
16 |
17 |
18 |
19 |
39 |
--------------------------------------------------------------------------------
/LICENSES/BSD-3-Clause.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12 |
--------------------------------------------------------------------------------
/lib/Constants.php:
--------------------------------------------------------------------------------
1 | {
21 | const dateFormat = Intl.DateTimeFormat(getCanonicalLocale(), {
22 | dateStyle: 'medium',
23 | timeStyle: 'medium',
24 | timeZone: settingsStore.dateTimeFormat === 'utc' ? 'UTC' : undefined,
25 | })
26 | return dateFormat.format(new Date(time))
27 | }
28 | /**
29 | * Format a log entry into a human readable text
30 | *
31 | * @param entry The log entry to format
32 | */
33 | const formatLogEntry = (entry: ILogEntry) => {
34 | return (
35 | `[${entry.app}] ${LOGGING_LEVEL_NAMES[entry.level]}: ${entry.message}\n`
36 | + (entry.method ? `\t${entry.method} ${entry.url}\n` : '')
37 | + t('logreader', '\tfrom {address} by {user} at {time}\n', {
38 | address: entry.remoteAddr || '?',
39 | user: entry.user || '?',
40 | time: formatTime(entry.time),
41 | })
42 | )
43 | }
44 | return {
45 | formatTime,
46 | formatLogEntry,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/Settings/Section.php:
--------------------------------------------------------------------------------
1 | l = $l;
21 | $this->url = $url;
22 | }
23 |
24 | /**
25 | * returns the ID of the section. It is supposed to be a lower case string,
26 | * e.g. 'ldap'
27 | *
28 | * @returns string
29 | */
30 | public function getID() {
31 | return 'logging';
32 | }
33 |
34 | /**
35 | * returns the translated name as it should be displayed, e.g. 'LDAP / AD
36 | * integration'. Use the L10N service to translate it.
37 | *
38 | * @return string
39 | */
40 | public function getName() {
41 | return $this->l->t('Logging');
42 | }
43 |
44 | /**
45 | * @return int whether the form should be rather on the top or bottom of
46 | * the settings navigation. The sections are arranged in ascending order of
47 | * the priority values. It is required to return a value between 0 and 99.
48 | *
49 | * E.g.: 70
50 | */
51 | public function getPriority() {
52 | return 90;
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function getIcon() {
59 | return $this->url->imagePath('logreader', 'app-dark.svg');
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/appinfo/info.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
9 | logreader
10 | Log Reader
11 | A log reader for Nextcloud
12 | Log reader for Nextcloud
13 | 6.0.0-dev.0
14 | agpl
15 | Robin Appelman
16 | Ferdinand Thiessen
17 | LogReader
18 |
19 |
20 |
21 |
22 |
23 |
24 | tools
25 | https://github.com/nextcloud/logreader
26 | https://github.com/nextcloud/logreader/issues
27 | https://github.com/nextcloud/logreader
28 |
29 | https://raw.githubusercontent.com/nextcloud/logreader/master/screenshots/reader.png
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | OCA\LogReader\Command\Tail
38 | OCA\LogReader\Command\Watch
39 |
40 |
41 |
42 | OCA\LogReader\Settings\Admin
43 | OCA\LogReader\Settings\Section
44 |
45 |
46 |
--------------------------------------------------------------------------------
/lib/Log/LogIteratorFactory.php:
--------------------------------------------------------------------------------
1 | config->getSystemValue('logdateformat', \DateTime::ATOM);
30 | $timezone = $this->config->getSystemValue('logtimezone', 'UTC');
31 | $logType = $this->config->getSystemValue('log_type', 'file');
32 | if ($logType !== 'file') {
33 | throw new UnsupportedLogTypeException();
34 | }
35 | $log = $this->logFactory->get('file');
36 | if ($log instanceof IFileBased) {
37 | $handle = fopen($log->getLogFilePath(), 'rb');
38 | if ($handle) {
39 | $iterator = new LogIterator($handle, $dateFormat, $timezone);
40 | return new \CallbackFilterIterator($iterator, function ($logItem) use ($levels) {
41 | return $logItem && in_array($logItem['level'], $levels);
42 | });
43 | } else {
44 | throw new \Exception('Error while opening ' . $log->getLogFilePath());
45 | }
46 | }
47 | throw new \Exception('Can\'t find log class');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/l10n/lv.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Žurnalēšana",
3 | "No errors in the logs since %s" : "Kopš %s žurnālos nav kļūdu",
4 | "_%n error in the logs since %s_::_%n errors in the logs since %s_" : ["Kopš %s žurnālos ir %n kļūdu","Kopš %s žurnālos ir %n kļūda","Kopš %s žurnālos ir %n kļūdas"],
5 | "Log Reader" : "Žurnālu lasītājs",
6 | "A log reader for Nextcloud" : "Nextcloud žurnālu lasītājs",
7 | "Log reader for Nextcloud" : "Nextcloud žurnālu lasītājs",
8 | "Log reader" : "Žurnālu lasītāj",
9 | "Open log reader settings" : "Atvērt žurnālu lasītāja iestatījumus",
10 | "Log reader settings" : "Žurnālu lasītāja iestatījumi",
11 | "Show server log" : "Parādīt servera žurnālu",
12 | "Level" : "Līmenis",
13 | "App" : "Lietotne",
14 | "Time" : "Laiks",
15 | "Copy raw entry" : "Ievietot starpliktuvē neapstrādātu ierakstu",
16 | "Copy formatted entry" : "Ievietot starpliktuvē formatētu ierakstu",
17 | "Time format" : "Laika formāts",
18 | "Download logs" : "Lejupielādēt žurnālierakstu",
19 | "Relative" : "Relatīvs",
20 | "Application" : "Lietotne",
21 | "Message" : "Ziņojums",
22 | "Collapse row" : "Sakļaut rindu",
23 | "Expand row" : "Izvērst rindu",
24 | "Show details" : "Rādīt informāciju",
25 | "Info" : "Informācija",
26 | "Warning" : "Brīdinājums",
27 | "Error" : "Kļūda",
28 | "Could not load log entries" : "Nevarēja ielādēt žurnāla ierakstus",
29 | "Could not fetch new log entries (server unavailable)" : "Neizdevās izgūt jaunus žurnāla ierakstus (serveris nav pieejams)"
30 | },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"
31 | }
--------------------------------------------------------------------------------
/l10n/lv.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Žurnalēšana",
5 | "No errors in the logs since %s" : "Kopš %s žurnālos nav kļūdu",
6 | "_%n error in the logs since %s_::_%n errors in the logs since %s_" : ["Kopš %s žurnālos ir %n kļūdu","Kopš %s žurnālos ir %n kļūda","Kopš %s žurnālos ir %n kļūdas"],
7 | "Log Reader" : "Žurnālu lasītājs",
8 | "A log reader for Nextcloud" : "Nextcloud žurnālu lasītājs",
9 | "Log reader for Nextcloud" : "Nextcloud žurnālu lasītājs",
10 | "Log reader" : "Žurnālu lasītāj",
11 | "Open log reader settings" : "Atvērt žurnālu lasītāja iestatījumus",
12 | "Log reader settings" : "Žurnālu lasītāja iestatījumi",
13 | "Show server log" : "Parādīt servera žurnālu",
14 | "Level" : "Līmenis",
15 | "App" : "Lietotne",
16 | "Time" : "Laiks",
17 | "Copy raw entry" : "Ievietot starpliktuvē neapstrādātu ierakstu",
18 | "Copy formatted entry" : "Ievietot starpliktuvē formatētu ierakstu",
19 | "Time format" : "Laika formāts",
20 | "Download logs" : "Lejupielādēt žurnālierakstu",
21 | "Relative" : "Relatīvs",
22 | "Application" : "Lietotne",
23 | "Message" : "Ziņojums",
24 | "Collapse row" : "Sakļaut rindu",
25 | "Expand row" : "Izvērst rindu",
26 | "Show details" : "Rādīt informāciju",
27 | "Info" : "Informācija",
28 | "Warning" : "Brīdinājums",
29 | "Error" : "Kļūda",
30 | "Could not load log entries" : "Nevarēja ielādēt žurnāla ierakstus",
31 | "Could not fetch new log entries (server unavailable)" : "Neizdevās izgūt jaunus žurnāla ierakstus (serveris nav pieejams)"
32 | },
33 | "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);");
34 |
--------------------------------------------------------------------------------
/src/utils/format.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import type { ILogEntry } from '../interfaces'
7 |
8 | import { createTestingPinia } from '@pinia/testing'
9 | import { beforeAll, describe, expect, it, vi } from 'vitest'
10 | import { useLogFormatting } from './format'
11 |
12 | describe('utils:format', () => {
13 | beforeAll(() => {
14 | createTestingPinia({
15 | fakeApp: true,
16 | createSpy: vi.fn,
17 | })
18 | })
19 |
20 | describe('formatLogEntry', () => {
21 | it('formats a log entry', () => {
22 | const { formatLogEntry } = useLogFormatting()
23 | const entry = { app: 'app', level: 1, message: 'msg', time: '2023-07-11T19:08:02.102Z' }
24 | expect(formatLogEntry(entry as ILogEntry)).toBe('[app] Info: msg\n\tfrom ? by ? at Jul 11, 2023, 6:08:02 PM\n')
25 | })
26 |
27 | it('formats a log entry with user and remoteAddr', () => {
28 | const { formatLogEntry } = useLogFormatting()
29 | const entry = { app: 'app', level: 1, message: 'msg', time: '2023-07-11T19:08:02.102Z', user: 'user1', remoteAddr: '0.0.0.0' }
30 | expect(formatLogEntry(entry as ILogEntry)).toBe('[app] Info: msg\n\tfrom 0.0.0.0 by user1 at Jul 11, 2023, 6:08:02 PM\n')
31 | })
32 |
33 | it('formats a log entry with url', () => {
34 | const { formatLogEntry } = useLogFormatting()
35 | const entry = { app: 'app', level: 1, message: 'msg', time: '2023-07-11T19:08:02.102Z', method: 'GET', url: 'foo/bar' }
36 | expect(formatLogEntry(entry as ILogEntry)).toBe('[app] Info: msg\n\tGET foo/bar\n\tfrom ? by ? at Jul 11, 2023, 6:08:02 PM\n')
37 | })
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/lib/Settings/Admin.php:
--------------------------------------------------------------------------------
1 | appName, 'logreader-main');
30 | Util::addStyle($this->appName, 'logreader-main');
31 |
32 | $this->initialState->provideInitialState('settings', $this->settingsService->getAppSettings());
33 |
34 | return new TemplateResponse($this->appName, 'index');
35 | }
36 |
37 | /**
38 | * @return string the section ID, e.g. 'sharing'
39 | */
40 | public function getSection() {
41 | return 'logging';
42 | }
43 |
44 | /**
45 | * @return int whether the form should be rather on the top or bottom of
46 | * the admin section. The forms are arranged in ascending order of the
47 | * priority values. It is required to return a value between 0 and 100.
48 | *
49 | * E.g.: 70
50 | */
51 | public function getPriority() {
52 | return 90;
53 | }
54 |
55 | public function getName(): ?string {
56 | return null;
57 | }
58 |
59 | public function getAuthorizedAppConfig(): array {
60 | return [];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/l10n/ko.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "로그",
3 | "No errors in the logs since %s" : "%s 이래로 로그에 아무런 오류가 기록되지 않았습니다",
4 | "_%n error in the logs since %s_::_%n errors in the logs since %s_" : ["로그에 %s 이래로 %n개의 오류가 기록되었습니다"],
5 | "Log Reader" : "로그 리더",
6 | "A log reader for Nextcloud" : "Nextcloud 로그 리더",
7 | "Log reader for Nextcloud" : "Nextcloud 로그 리더",
8 | "Log reader" : "로그 리더",
9 | "Open log reader settings" : "로그 리더 설정 열기",
10 | "Log reader settings" : "로그 리더 설정",
11 | "Level" : "단계",
12 | "App" : "앱",
13 | "Time" : "시간",
14 | "Filter log levels" : "로그 단계 필터링",
15 | "Set log level" : "로그 단계 설정",
16 | "Time format" : "시간 형식",
17 | "Live view" : "실시간 보기",
18 | "Download/Upload logs" : "로그 다운로드/업로드",
19 | "Ctrl" : "Ctrl",
20 | "Download logs" : "로그 다운로드",
21 | "Show local log file" : "로컬 로그 파일 보기",
22 | "Upload local log file to be displayed" : "표시할 로컬 로그 파일 업로드",
23 | "Time format used for displaying the timestamp" : "타임스탬프 표시에 사용되는 시간 형식",
24 | "(Local log files only support the \"raw\" time format)" : "(로컬 로그 파일은 \"raw\" 형식만 지원합니다)",
25 | "Could not change date time format." : "시간 형식을 변경할 수 없음",
26 | "Local time" : "로컬 시간 형식",
27 | "UTC time" : "UTC 시간 형식",
28 | "Relative" : "상대적",
29 | "Could not change live view setting." : "실시간 보기 설정을 변경할 수 없음",
30 | "Polling (live view)" : "폴링 (실시간 보기 활성화)",
31 | "Application" : "애플리케이션",
32 | "Message" : "메시지",
33 | "No older log entries available" : "사용 가능한 로그 항목 없음",
34 | "Show details" : "세부 정보",
35 | "Debug" : "디버그",
36 | "Info" : "정보",
37 | "Warning" : "경고",
38 | "Error" : "오류",
39 | "Fatal" : "치명적"
40 | },"pluralForm" :"nplurals=1; plural=0;"
41 | }
--------------------------------------------------------------------------------
/l10n/ko.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "로그",
5 | "No errors in the logs since %s" : "%s 이래로 로그에 아무런 오류가 기록되지 않았습니다",
6 | "_%n error in the logs since %s_::_%n errors in the logs since %s_" : ["로그에 %s 이래로 %n개의 오류가 기록되었습니다"],
7 | "Log Reader" : "로그 리더",
8 | "A log reader for Nextcloud" : "Nextcloud 로그 리더",
9 | "Log reader for Nextcloud" : "Nextcloud 로그 리더",
10 | "Log reader" : "로그 리더",
11 | "Open log reader settings" : "로그 리더 설정 열기",
12 | "Log reader settings" : "로그 리더 설정",
13 | "Level" : "단계",
14 | "App" : "앱",
15 | "Time" : "시간",
16 | "Filter log levels" : "로그 단계 필터링",
17 | "Set log level" : "로그 단계 설정",
18 | "Time format" : "시간 형식",
19 | "Live view" : "실시간 보기",
20 | "Download/Upload logs" : "로그 다운로드/업로드",
21 | "Ctrl" : "Ctrl",
22 | "Download logs" : "로그 다운로드",
23 | "Show local log file" : "로컬 로그 파일 보기",
24 | "Upload local log file to be displayed" : "표시할 로컬 로그 파일 업로드",
25 | "Time format used for displaying the timestamp" : "타임스탬프 표시에 사용되는 시간 형식",
26 | "(Local log files only support the \"raw\" time format)" : "(로컬 로그 파일은 \"raw\" 형식만 지원합니다)",
27 | "Could not change date time format." : "시간 형식을 변경할 수 없음",
28 | "Local time" : "로컬 시간 형식",
29 | "UTC time" : "UTC 시간 형식",
30 | "Relative" : "상대적",
31 | "Could not change live view setting." : "실시간 보기 설정을 변경할 수 없음",
32 | "Polling (live view)" : "폴링 (실시간 보기 활성화)",
33 | "Application" : "애플리케이션",
34 | "Message" : "메시지",
35 | "No older log entries available" : "사용 가능한 로그 항목 없음",
36 | "Show details" : "세부 정보",
37 | "Debug" : "디버그",
38 | "Info" : "정보",
39 | "Warning" : "경고",
40 | "Error" : "오류",
41 | "Fatal" : "치명적"
42 | },
43 | "nplurals=1; plural=0;");
44 |
--------------------------------------------------------------------------------
/REUSE.toml:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
2 | # SPDX-License-Identifier: AGPL-3.0-or-later
3 | version = 1
4 | SPDX-PackageName = "logreader"
5 | SPDX-PackageSupplier = "Nextcloud "
6 | SPDX-PackageDownloadLocation = "https://github.com/nextcloud/logreader"
7 |
8 | [[annotations]]
9 | path = [".github/issue_template.md", ".github/CODEOWNERS", "package-lock.json", "package.json", "**/package.json", "**/package-lock.json", "composer.json", "composer.lock", "**/composer.json", "**/composer.lock", ".jshintrc", ".l10nignore", ".tx/config", "tsconfig.json", "js/vendor.LICENSE.txt", "krankerl.toml", ".nextcloudignore", ".devcontainer/devcontainer.json", ".scrutinizer.yml", ".tx/backport"]
10 | precedence = "aggregate"
11 | SPDX-FileCopyrightText = "none"
12 | SPDX-License-Identifier = "CC0-1.0"
13 |
14 | [[annotations]]
15 | path = ["l10n/**.js", "l10n/**.json"]
16 | precedence = "aggregate"
17 | SPDX-FileCopyrightText = "2016 Nextcloud contributors"
18 | SPDX-License-Identifier = "AGPL-3.0-or-later"
19 |
20 | [[annotations]]
21 | path = ["js/**.js.map", "js/**.js", "js/**.mjs", "js/**.mjs.map", "js/templates/**.handlebars", "emptyTemplates/**", "css/**.css"]
22 | precedence = "aggregate"
23 | SPDX-FileCopyrightText = "2016 Nextcloud GmbH and Nextcloud contributors"
24 | SPDX-License-Identifier = "AGPL-3.0-or-later"
25 |
26 | [[annotations]]
27 | path = "screenshots/reader.png"
28 | precedence = "aggregate"
29 | SPDX-FileCopyrightText = "2016 Nextcloud GmbH and Nextcloud contributors"
30 | SPDX-License-Identifier = "CC0-1.0"
31 |
32 | [[annotations]]
33 | path = ["img/app-dark.svg", "img/app.svg"]
34 | precedence = "aggregate"
35 | SPDX-FileCopyrightText = "2018-2024 Google LLC"
36 | SPDX-License-Identifier = "Apache-2.0"
37 |
--------------------------------------------------------------------------------
/src/components/settings/SettingsSetLogLevel.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
20 |
21 |
22 |
53 |
54 |
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "logreader",
3 | "version": "6.0.0-dev.0",
4 | "license": "AGPL-3.0-or-later",
5 | "author": "Robin Appelman ",
6 | "type": "module",
7 | "scripts": {
8 | "build": "vite --mode production build",
9 | "dev": "vite --mode development build",
10 | "dev:watch": "vite --mode development build --watch",
11 | "lint": "eslint",
12 | "lint:fix": "eslint --fix",
13 | "test": "TZ='Etc/GMT+1' vitest run",
14 | "test:coverage": "TZ='Etc/GMT+1' vitest run --coverage",
15 | "test:watch": "TZ='Etc/GMT+1' vitest watch"
16 | },
17 | "browserslist": [
18 | "extends @nextcloud/browserslist-config"
19 | ],
20 | "dependencies": {
21 | "@highlightjs/vue-plugin": "^2.1.0",
22 | "@mdi/svg": "^7.4.47",
23 | "@nextcloud/axios": "^2.5.2",
24 | "@nextcloud/dialogs": "^7.1.0",
25 | "@nextcloud/initial-state": "^3.0.0",
26 | "@nextcloud/l10n": "^3.4.1",
27 | "@nextcloud/logger": "^3.0.3",
28 | "@nextcloud/router": "^3.1.0",
29 | "@nextcloud/typings": "^1.10.0",
30 | "@nextcloud/vue": "^9.3.0",
31 | "highlight.js": "^11.11.1",
32 | "pinia": "^3.0.4",
33 | "vue": "^3.5.24",
34 | "vue-material-design-icons": "^5.3.1"
35 | },
36 | "devDependencies": {
37 | "@nextcloud/browserslist-config": "^3.1.2",
38 | "@nextcloud/eslint-config": "^9.0.0-rc.6",
39 | "@nextcloud/vite-config": "^2.5.2",
40 | "@pinia/testing": "^1.0.3",
41 | "@vitest/coverage-istanbul": "^4.0.15",
42 | "@vue/test-utils": "^2.4.6",
43 | "@vue/tsconfig": "^0.8.1",
44 | "browserslist": "^4.28.1",
45 | "happy-dom": "^20.0.11",
46 | "typescript": "^5.9.3",
47 | "vite": "^7.2.7",
48 | "vitest": "^4.0.13"
49 | },
50 | "engines": {
51 | "node": "^24.0.0",
52 | "npm": "^11.3.0"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/exception/TraceLine.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | {{ line.file }}
10 | {{
11 | t('logreader', 'Line {line}', { line: line.line })
12 | }}
13 |
14 |
15 |
16 |
17 |
18 |
47 |
48 |
69 |
--------------------------------------------------------------------------------
/lib/Log/SearchFilter.php:
--------------------------------------------------------------------------------
1 | >
12 | */
13 | class SearchFilter extends \FilterIterator {
14 | /**
15 | * @var string
16 | */
17 | private $query;
18 |
19 | /**
20 | * @var string[]
21 | */
22 | private $levels;
23 |
24 | public function __construct(\Iterator $iterator, string $query) {
25 | parent::__construct($iterator);
26 | $this->rewind();
27 | $this->query = strtolower($query);
28 | $this->levels = ['Debug', 'Info', 'Warning', 'Error', 'Fatal'];
29 | }
30 |
31 | private function formatLevel(int $level): string {
32 | return isset($this->levels[$level]) ? $this->levels[$level] : 'Unknown';
33 | }
34 |
35 | public function accept(): bool {
36 | if (!$this->query) {
37 | return true;
38 | }
39 | $value = $this->current();
40 | return $this->inMessage($value['message'] ?? '', $this->query)
41 | || stripos($value['app'] ?? '', $this->query) !== false
42 | || stripos($value['reqId'] ?? '', $this->query) !== false
43 | || stripos($value['user'] ?? '', $this->query) !== false
44 | || stripos($value['url'] ?? '', $this->query) !== false
45 | || stripos($this->formatLevel($value['level'] ?? -1), $this->query) !== false;
46 | }
47 |
48 | private function inMessage($message, string $query): bool {
49 | if (is_string($message)) {
50 | return stripos($message, $query) !== false;
51 | } elseif (isset($message['Exception'])) {
52 | return stripos($message['Exception'], $query) !== false
53 | || stripos($message['Message'] ?? '', $query) !== false;
54 | }
55 | return false;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/.github/workflows/lint-php-cs.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Lint php-cs
10 |
11 | on: pull_request
12 |
13 | permissions:
14 | contents: read
15 |
16 | concurrency:
17 | group: lint-php-cs-${{ github.head_ref || github.run_id }}
18 | cancel-in-progress: true
19 |
20 | jobs:
21 | lint:
22 | runs-on: ubuntu-latest
23 |
24 | name: php-cs
25 |
26 | steps:
27 | - name: Checkout
28 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
29 | with:
30 | persist-credentials: false
31 |
32 | - name: Get php version
33 | id: versions
34 | uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
35 |
36 | - name: Set up php${{ steps.versions.outputs.php-min }}
37 | uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5
38 | with:
39 | php-version: ${{ steps.versions.outputs.php-min }}
40 | extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
41 | coverage: none
42 | ini-file: development
43 | env:
44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 |
46 | - name: Install dependencies
47 | run: |
48 | composer remove nextcloud/ocp --dev --no-scripts
49 | composer i
50 |
51 | - name: Lint
52 | run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 )
53 |
--------------------------------------------------------------------------------
/.github/contributing.md:
--------------------------------------------------------------------------------
1 | ## Submitting issues
2 |
3 | If you have questions about how to install or use Nextcloud, please direct these to our [forum][forum]. We are also available on [IRC][irc].
4 |
5 | ### Short version
6 |
7 | * The [**issue template can be found here**][template]. Please always use the issue template when reporting issues.
8 |
9 | ### Guidelines
10 | * Please search the existing issues first, it's likely that your issue was already reported or even fixed.
11 | - Go to one of the repositories, click "issues" and type any word in the top search/command bar.
12 | - You can also filter by appending e. g. "state:open" to the search string.
13 | - More info on [search syntax within github](https://help.github.com/articles/searching-issues)
14 | * This repository ([Log reader](https://github.com/nextcloud/logreader/issues)) is *only* for issues within the Log reader code.
15 | * __SECURITY__: Report any potential security bug to us via [our HackerOne page](https://hackerone.com/nextcloud) or security@nextcloud.com following our [security policy](https://nextcloud.com/security/) instead of filing an issue in our bug tracker
16 | * Report the issue using our [template][template], it includes all the information we need to track down the issue.
17 |
18 | Help us to maximize the effort we can spend fixing issues and adding new features, by not reporting duplicate issues.
19 |
20 | [template]: https://raw.github.com/nextcloud/logreader/master/.github/issue_template.md
21 | [forum]: https://help.nextcloud.com/
22 | [irc]: https://webchat.freenode.net/?channels=nextcloud
23 |
24 | ### Contribute Code and translations
25 | Please check [server's contribution guidelines](https://github.com/nextcloud/server/blob/master/CONTRIBUTING.md#contributing-to-source-code) for further information about contributing code and translations.
26 |
27 |
31 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 |
8 | ### Steps to reproduce
9 | 1.
10 | 2.
11 | 3.
12 |
13 | ### Expected behaviour
14 | Tell us what should happen
15 |
16 | ### Actual behaviour
17 | Tell us what happens instead
18 |
19 | ### Server configuration
20 |
23 |
24 | **Operating system**:
25 |
26 | **Web server:**
27 |
28 | **Database:**
29 |
30 | **PHP version:**
31 |
32 | **Nextcloud version:** (see Nextcloud admin page)
33 |
34 | **Where did you install Nextcloud from:**
35 |
36 |
37 | **List of activated apps:**
38 |
39 | ```
40 | If you have access to your command line run e.g.:
41 | sudo -u www-data php occ app:list
42 | from within your Nextcloud installation folder
43 | ```
44 |
45 | **Nextcloud configuration:**
46 |
47 | ```
48 | If you have access to your command line run e.g.:
49 | sudo -u www-data php occ config:list system
50 | from within your Nextcloud installation folder
51 |
52 | or
53 |
54 | Insert your config.php content here
55 | Make sure to remove all sensitive content such as passwords. (e.g. database password, passwordsalt, secret, smtp password, …)
56 | ```
57 |
58 | ### Client configuration
59 | **Browser:**
60 |
61 | **Operating system:**
62 |
63 | ### Logs
64 |
65 | #### Nextcloud log (data/owncloud.log)
66 | ```
67 | Insert your Nextcloud log here
68 | ```
69 |
70 | #### Browser log
71 | ```
72 | Insert your browser log here, this could for example include:
73 |
74 | a) The javascript console log
75 | b) The network log
76 | c) ...
77 | ```
78 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
2 | # SPDX-FileCopyrightText: 2015-2016 ownCloud, Inc.
3 | # SPDX-License-Identifier: AGPL-3.0-or-later
4 | app_name=logreader
5 | project_dir=$(CURDIR)/../$(app_name)
6 | build_dir=$(project_dir)/build
7 | appstore_dir=$(build_dir)/appstore
8 | package_name=$(app_name)
9 | cert_dir=$(HOME)/.nextcloud/certificates
10 | webpack=node_modules/.bin/webpack
11 |
12 | jssources=$(wildcard js/*) $(wildcard js/*/*) $(wildcard css/*/*) $(wildcard css/*)
13 | othersources=$(wildcard appinfo/*) $(wildcard css/*/*) $(wildcard controller/*/*) $(wildcard templates/*/*) $(wildcard log/*/*)
14 |
15 | all: build/main.js
16 |
17 | clean:
18 | rm -rf $(build_dir)
19 | rm -rf node_modules
20 |
21 | node_modules: package.json
22 | npm install --deps
23 |
24 | build/main.js: node_modules $(jssources)
25 | npm run build
26 |
27 | .PHONY: watch
28 | watch: node_modules
29 | node node_modules/.bin/webpack-dev-server --public localcloud.icewind.me:444 --inline --hot --port 3000 --config webpack/dev.config.js
30 |
31 | appstore: clean build/main.js package
32 |
33 | package: build/appstore/$(package_name).tar.gz
34 | build/appstore/$(package_name).tar.gz: build/main.js $(othersources)
35 | mkdir -p $(appstore_dir)
36 | tar --exclude-vcs \
37 | --exclude=$(appstore_dir) \
38 | --exclude=$(project_dir)/node_modules \
39 | --exclude=$(project_dir)/webpack \
40 | --exclude=$(project_dir)/.gitattributes \
41 | --exclude=$(project_dir)/.gitignore \
42 | --exclude=$(project_dir)/.travis.yml \
43 | --exclude=$(project_dir)/.tx \
44 | --exclude=$(project_dir)/.scrutinizer.yml \
45 | --exclude=$(project_dir)/CONTRIBUTING.md \
46 | --exclude=$(project_dir)/package.json \
47 | --exclude=$(project_dir)/screenshots \
48 | --exclude=$(project_dir)/Makefile \
49 | -cvzf $(appstore_dir)/$(package_name).tar.gz $(project_dir)
50 | openssl dgst -sha512 -sign $(cert_dir)/$(app_name).key $(appstore_dir)/$(app_name).tar.gz | openssl base64
51 |
52 |
--------------------------------------------------------------------------------
/.github/workflows/block-merge-eol.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Block merges for EOL
10 |
11 | on: pull_request
12 |
13 | permissions:
14 | contents: read
15 |
16 | concurrency:
17 | group: block-merge-eol-${{ github.head_ref || github.run_id }}
18 | cancel-in-progress: true
19 |
20 | jobs:
21 | block-merges-eol:
22 | name: Block merges for EOL branches
23 |
24 | # Only run on stableXX branches
25 | if: startsWith( github.base_ref, 'stable')
26 | runs-on: ubuntu-latest-low
27 |
28 | steps:
29 | - name: Set server major version environment
30 | uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
31 | with:
32 | github-token: ${{secrets.GITHUB_TOKEN}}
33 | script: |
34 | const regex = /^stable(\d+)$/
35 | const baseRef = context.payload.pull_request.base.ref
36 | const match = baseRef.match(regex)
37 | if (match) {
38 | console.log('Setting server_major to ' + match[1]);
39 | core.exportVariable('server_major', match[1]);
40 | console.log('Setting current_day to ' + (new Date()).toISOString().substr(0, 10));
41 | core.exportVariable('current_day', (new Date()).toISOString().substr(0, 10));
42 | }
43 |
44 | - name: Checking if server ${{ env.server_major }} is EOL
45 | if: ${{ env.server_major != '' }}
46 | run: |
47 | curl -s https://raw.githubusercontent.com/nextcloud-releases/updater_server/production/config/major_versions.json \
48 | | jq '.["${{ env.server_major }}"]["eol"] // "9999-99-99" | . >= "${{ env.current_day }}"' \
49 | | grep -q true
50 |
--------------------------------------------------------------------------------
/src/components/settings/AppSettingsDialog.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
51 |
--------------------------------------------------------------------------------
/src/components/settings/SettingsLogLevels.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
18 |
19 |
20 |
21 |
57 |
58 |
63 |
--------------------------------------------------------------------------------
/lib/Log/Console.php:
--------------------------------------------------------------------------------
1 | formatter = $formatter;
26 | $this->level = self::parseLogLevel($level);
27 | $this->terminalWidth = $terminalWidth;
28 | }
29 |
30 | public function log(int $level, string $app, array $entry) {
31 | if ($level >= $this->level) {
32 | $messageWidth = $this->terminalWidth - 8 - 18 - 6;
33 |
34 | $entry = $this->logDetails($app, $entry, $level);
35 |
36 | $lines = explode("\n", $this->formatter->formatMessage($entry, $messageWidth));
37 | $lines[0] = str_pad(Tail::LEVELS[$level], 8) . ' ' .
38 | str_pad(wordwrap($app, 18), 18) . ' ' .
39 | str_pad($lines[0], $messageWidth);
40 |
41 | for ($i = 1; $i < count($lines); $i++) {
42 | $lines[$i] = str_repeat(' ', 8 + 18 + 2) . $lines[$i];
43 | }
44 |
45 | foreach ($lines as $line) {
46 | fwrite(STDERR, $line . "\n");
47 | }
48 | fwrite(STDERR, "\n");
49 | }
50 | }
51 |
52 | private static function parseLogLevel(string $level): int {
53 | if (is_numeric($level)) {
54 | return (int)$level;
55 | }
56 |
57 | switch (strtoupper($level)) {
58 | case 'DEBUG':
59 | return 0;
60 | case 'INFO':
61 | return 1;
62 | case 'WARN':
63 | return 2;
64 | case 'ERROR':
65 | return 3;
66 | case 'FATAL':
67 | return 4;
68 | default:
69 | throw new \Exception("Unknown log level $level");
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/exception/LogException.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 | {{ exceptionTitleText }}
11 |
12 | {{ exceptionMessage }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
53 |
54 |
75 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot-approve-merge.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Auto approve Dependabot PRs
10 |
11 | on:
12 | pull_request_target: # zizmor: ignore[dangerous-triggers]
13 | branches:
14 | - main
15 | - master
16 | - stable*
17 |
18 | permissions:
19 | contents: read
20 |
21 | concurrency:
22 | group: dependabot-approve-merge-${{ github.head_ref || github.run_id }}
23 | cancel-in-progress: true
24 |
25 | jobs:
26 | auto-approve-merge:
27 | if: github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'renovate[bot]'
28 | runs-on: ubuntu-latest-low
29 | permissions:
30 | # for hmarr/auto-approve-action to approve PRs
31 | pull-requests: write
32 | # for alexwilson/enable-github-automerge-action to approve PRs
33 | contents: write
34 |
35 | steps:
36 | - name: Disabled on forks
37 | if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
38 | run: |
39 | echo 'Can not approve PRs from forks'
40 | exit 1
41 |
42 | - uses: mdecoleman/pr-branch-name@55795d86b4566d300d237883103f052125cc7508 # v3.0.0
43 | id: branchname
44 | with:
45 | repo-token: ${{ secrets.GITHUB_TOKEN }}
46 |
47 | # GitHub actions bot approve
48 | - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0
49 | if: startsWith(steps.branchname.outputs.branch, 'dependabot/')
50 | with:
51 | github-token: ${{ secrets.GITHUB_TOKEN }}
52 |
53 | # Enable GitHub auto merge
54 | - name: Auto merge
55 | uses: alexwilson/enable-github-automerge-action@56e3117d1ae1540309dc8f7a9f2825bc3c5f06ff # v2.0.0
56 | if: startsWith(steps.branchname.outputs.branch, 'dependabot/')
57 | with:
58 | github-token: ${{ secrets.GITHUB_TOKEN }}
59 |
--------------------------------------------------------------------------------
/src/utils/logfile.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { describe, expect, it } from 'vitest'
7 | import { parseLogFile, parseLogString } from './logfile'
8 |
9 | describe('utils:logfile', () => {
10 | it('can parse a file', async () => {
11 | // simply test that files are handled, the inner handling is checked by parseLogString
12 | const file = new File(['{ "app": "myApp" }'], 'log')
13 | expect(await parseLogFile(file)).toEqual([{ app: 'myApp' }])
14 | })
15 |
16 | it('can parse server style logs', async () => {
17 | const input = '{ "app": "myApp" }\n{ "app": "myApp2" }'
18 | const expected = [{ app: 'myApp' }, { app: 'myApp2' }]
19 |
20 | const output = await parseLogString(input)
21 | expect(output).toEqual(expected)
22 | })
23 |
24 | it('can parse logs without newline', async () => {
25 | const input = '{ "app": "myApp" }{ "app": "myApp2" }'
26 | const expected = [{ app: 'myApp' }, { app: 'myApp2' }]
27 |
28 | const output = await parseLogString(input)
29 | expect(output).toEqual(expected)
30 | })
31 |
32 | it('can parse formatted logs', async () => {
33 | const input = '{\n\t"app": "myApp"\n}{\n\t"app": "myApp2"\n}\n'
34 | const expected = [{ app: 'myApp' }, { app: 'myApp2' }]
35 |
36 | const output = await parseLogString(input)
37 | expect(output).toEqual(expected)
38 | })
39 |
40 | it('can parse quoted json', async () => {
41 | const input = '"{"app": "myApp"}"'
42 | const expected = [{ app: 'myApp' }]
43 |
44 | const output = await parseLogString(input)
45 | expect(output).toEqual(expected)
46 | })
47 |
48 | it('can parse quoted and csv escaped json', async () => {
49 | const input = '"{""app"": ""myApp""}"'
50 | const expected = [{ app: 'myApp' }]
51 |
52 | const output = await parseLogString(input)
53 | expect(output).toEqual(expected)
54 | })
55 |
56 | it('can parse unescaped message', async () => {
57 | const input = '{"app": "myApp", "message":""hello"","level": 1 }'
58 | const expected = [{ app: 'myApp', message: '"hello"', level: 1 }]
59 |
60 | const output = await parseLogString(input)
61 | expect(output).toEqual(expected)
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/.github/workflows/block-merge-freeze.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Block merges during freezes
10 |
11 | on:
12 | pull_request:
13 | types: [opened, ready_for_review, reopened, synchronize]
14 |
15 | permissions:
16 | contents: read
17 |
18 | concurrency:
19 | group: block-merge-freeze-${{ github.head_ref || github.run_id }}
20 | cancel-in-progress: true
21 |
22 | jobs:
23 | block-merges-during-freeze:
24 | name: Block merges during freezes
25 |
26 | if: github.event.pull_request.draft == false
27 |
28 | runs-on: ubuntu-latest-low
29 |
30 | steps:
31 | - name: Register server reference to fallback to master branch
32 | uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
33 | with:
34 | github-token: ${{secrets.GITHUB_TOKEN}}
35 | script: |
36 | const baseRef = context.payload.pull_request.base.ref
37 | if (baseRef === 'main' || baseRef === 'master') {
38 | core.exportVariable('server_ref', 'master');
39 | console.log('Setting server_ref to master');
40 | } else {
41 | const regex = /^stable(\d+)$/
42 | const match = baseRef.match(regex)
43 | if (match) {
44 | core.exportVariable('server_ref', match[0]);
45 | console.log('Setting server_ref to ' + match[0]);
46 | } else {
47 | console.log('Not based on master/main/stable*, so skipping freeze check');
48 | }
49 | }
50 |
51 | - name: Download version.php from ${{ env.server_ref }}
52 | if: ${{ env.server_ref != '' }}
53 | run: curl 'https://raw.githubusercontent.com/nextcloud/server/${{ env.server_ref }}/version.php' --output version.php
54 |
55 | - name: Run check
56 | if: ${{ env.server_ref != '' }}
57 | run: cat version.php | grep 'OC_VersionString' | grep -i -v 'RC'
58 |
--------------------------------------------------------------------------------
/tests/Unit/SetupChecks/LogErrorsTest.php:
--------------------------------------------------------------------------------
1 |
6 | * SPDX-License-Identifier: AGPL-3.0-or-later
7 | */
8 |
9 | namespace OCA\LogReader\Tests\Unit\SetupChecks;
10 |
11 | use OCA\LogReader\Log\LogIteratorFactory;
12 | use OCA\LogReader\SetupChecks\LogErrors;
13 | use OCP\IConfig;
14 | use OCP\IDateTimeFormatter;
15 | use OCP\IL10N;
16 | use Test\TestCase;
17 |
18 | class LogErrorsTest extends TestCase {
19 | private IL10N $l10n;
20 | private IConfig $config;
21 | private IDateTimeFormatter $dateFormatter;
22 | private LogIteratorFactory $logIteratorFactory;
23 | private LogErrors $logErrorsCheck;
24 |
25 | protected function setUp(): void {
26 | parent::setUp();
27 |
28 | $this->l10n = $this->createMock(IL10N::class);
29 | $this->config = $this->createMock(IConfig::class);
30 | $this->dateFormatter = $this->createMock(IDateTimeFormatter::class);
31 | $this->logIteratorFactory = $this->createMock(LogIteratorFactory::class);
32 | $this->logErrorsCheck = new LogErrors(
33 | $this->l10n,
34 | $this->config,
35 | $this->dateFormatter,
36 | $this->logIteratorFactory,
37 | );
38 | }
39 |
40 | public function logProvider(): array {
41 | $now = (new \DateTime())->format(\DateTime::ATOM);
42 | $tooOld = (new \DateTime('1 month ago'))->format(\DateTime::ATOM);
43 | return [
44 | [[], 'success'],
45 | [[['level' => 2, 'time' => $now]], 'info'],
46 | [[['level' => 3, 'time' => $now]], 'warning'],
47 | [[['level' => 2, 'time' => $now],['level' => 3, 'time' => $now]], 'warning'],
48 | [[['level' => 2, 'time' => $now],['level' => 3, 'time' => $tooOld]], 'info'],
49 | [[['level' => 2, 'time' => $tooOld],['level' => 3, 'time' => $tooOld]], 'success'],
50 | ];
51 | }
52 |
53 | /**
54 | * @dataProvider logProvider
55 | */
56 | public function testSetupCheck(array $logContent, string $severity): void {
57 | $logIterator = new \ArrayIterator($logContent);
58 | $this->logIteratorFactory
59 | ->expects(self::once())
60 | ->method('getLogIterator')
61 | ->willReturn($logIterator);
62 |
63 | $result = $this->logErrorsCheck->run();
64 |
65 | $this->assertEquals($severity, $result->getSeverity());
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/.github/workflows/update-nextcloud-ocp-approve-merge.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Auto approve nextcloud/ocp
10 |
11 | on:
12 | pull_request_target: # zizmor: ignore[dangerous-triggers]
13 | branches:
14 | - main
15 | - master
16 | - stable*
17 |
18 | permissions:
19 | contents: read
20 |
21 | concurrency:
22 | group: update-nextcloud-ocp-approve-merge-${{ github.head_ref || github.run_id }}
23 | cancel-in-progress: true
24 |
25 | jobs:
26 | auto-approve-merge:
27 | if: github.actor == 'nextcloud-command'
28 | runs-on: ubuntu-latest-low
29 | permissions:
30 | # for hmarr/auto-approve-action to approve PRs
31 | pull-requests: write
32 | # for alexwilson/enable-github-automerge-action to approve PRs
33 | contents: write
34 |
35 | steps:
36 | - name: Disabled on forks
37 | if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
38 | run: |
39 | echo 'Can not approve PRs from forks'
40 | exit 1
41 |
42 | - uses: mdecoleman/pr-branch-name@55795d86b4566d300d237883103f052125cc7508 # v3.0.0
43 | id: branchname
44 | with:
45 | repo-token: ${{ secrets.GITHUB_TOKEN }}
46 |
47 | # GitHub actions bot approve
48 | - uses: hmarr/auto-approve-action@b40d6c9ed2fa10c9a2749eca7eb004418a705501 # v2
49 | if: startsWith(steps.branchname.outputs.branch, 'automated/noid/') && endsWith(steps.branchname.outputs.branch, 'update-nextcloud-ocp')
50 | with:
51 | github-token: ${{ secrets.GITHUB_TOKEN }}
52 |
53 | # Enable GitHub auto merge
54 | - name: Auto merge
55 | uses: alexwilson/enable-github-automerge-action@56e3117d1ae1540309dc8f7a9f2825bc3c5f06ff # v2.0.0
56 | if: startsWith(steps.branchname.outputs.branch, 'automated/noid/') && endsWith(steps.branchname.outputs.branch, 'update-nextcloud-ocp')
57 | with:
58 | github-token: ${{ secrets.GITHUB_TOKEN }}
59 |
--------------------------------------------------------------------------------
/src/utils/splitter.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2025 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: LGPL-3.0-or-later
4 | */
5 |
6 | /**
7 | * Copied from Densaugeo/JSON-String-Splitter
8 | *
9 | * Split concatenated JSON strings
10 | * Accepts a string consisting of one or more valid JSON substrings and splits it. Any remaining string after the end of the last complete JSON substring is returned in the 'remainder' field.
11 | * Passing in invalid JSON can result in garbage output
12 | *
13 | * @param string The string to look for JSON in
14 | *
15 | * @example
16 | * const pieces = splitter('{"foo":"bar"}{"more":"json"}{"partial":"json"')
17 | *
18 | * console.log(pieces.jsons[0]); // '{"foo":"bar"}'
19 | * console.log(pieces.jsons[1]); // '{"more":"json"}'
20 | * console.log(pieces.remainder); // '{"partial":"json"'
21 | */
22 | export function splitter(string: string): { jsons: string[], remainder: string } {
23 | const START = 0, JSON = 1, STRING = 2, ESCAPE = 3
24 |
25 | let state = START
26 | let nestingLevel = 0
27 | let jsonStart = null
28 | const bounds = []
29 |
30 | for (let i = 0; i < string.length; ++i) {
31 | switch (state) {
32 | case START: {
33 | switch (string[i]) {
34 | case '{': {
35 | ++nestingLevel
36 | state = JSON
37 | jsonStart = i
38 | break
39 | }
40 | }
41 | break
42 | }
43 | case JSON: {
44 | switch (string[i]) {
45 | case '{': {
46 | ++nestingLevel
47 | break
48 | }
49 | case '}': {
50 | --nestingLevel
51 | if (nestingLevel === 0) {
52 | state = START
53 | bounds.push({ start: jsonStart, end: i + 1 })
54 | }
55 | break
56 | }
57 | case '"': {
58 | state = STRING
59 | break
60 | }
61 | }
62 | break
63 | }
64 | case STRING: {
65 | switch (string[i]) {
66 | case '"': {
67 | state = JSON
68 | break
69 | }
70 | case '\\': {
71 | state = ESCAPE
72 | break
73 | }
74 | }
75 | break
76 | }
77 | case ESCAPE: {
78 | state = STRING
79 | break
80 | }
81 | }
82 | }
83 |
84 | const result = {
85 | jsons: [],
86 | remainder: string.substring(bounds[bounds.length - 1].end),
87 | }
88 |
89 | bounds.forEach(function(v) {
90 | result.jsons.push(string.substring(v.start, v.end))
91 | })
92 |
93 | return result
94 | }
95 |
--------------------------------------------------------------------------------
/src/utils/exception.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import type { IException } from '../interfaces/ILogEntry'
7 |
8 | import { describe, expect, it } from 'vitest'
9 | import { parseException } from './exception'
10 |
11 | describe('utils:exception', () => {
12 | describe('parseException', () => {
13 | it('simply returns new style exceptions', () => {
14 | const exception = {
15 | Exception: 'GenericException',
16 | }
17 |
18 | expect(parseException(exception as IException)).toBe(exception)
19 | })
20 |
21 | it('parses nested json exception', () => {
22 | const exception = {
23 | Exception: 'GenericException',
24 | }
25 |
26 | expect(parseException(JSON.stringify(exception))).toEqual(exception)
27 | })
28 |
29 | it('parses old style exception', () => {
30 | const exception = {
31 | Exception: 'GenericException',
32 | }
33 |
34 | expect(parseException('Exception: ' + JSON.stringify(exception))).toEqual(exception)
35 | })
36 |
37 | it('parses old style exception with trace', () => {
38 | const exception = {
39 | Exception: 'GenericException',
40 | // string instead of array
41 | Trace: '1 strstr\n2 /srv/www/index.php(2):main\n3 [builtin]:helper\n4 :function',
42 | }
43 |
44 | const modernException = {
45 | Exception: 'GenericException',
46 | Trace: [
47 | {
48 | number: '1',
49 | function: 'strstr',
50 | file: false,
51 | },
52 | {
53 | number: '2',
54 | file: '/srv/www/index.php',
55 | line: 2,
56 | function: 'main',
57 | },
58 | {
59 | number: '3',
60 | file: '[builtin]',
61 | line: undefined,
62 | function: 'helper',
63 | },
64 | {
65 | number: '4',
66 | file: '',
67 | line: undefined,
68 | function: 'function',
69 | },
70 | ],
71 | }
72 |
73 | expect(parseException('Exception: ' + JSON.stringify(exception))).toEqual(modernException)
74 | })
75 |
76 | it('parses nested json with unescaped newline', () => {
77 | const exception = {
78 | Exception: 'GenericException',
79 | Message: 'With\nnewline',
80 | }
81 | const input = 'Exception: {"Exception":"GenericException", "Message": "With\nnewline"}'
82 |
83 | expect(parseException(input)).toEqual(exception)
84 | })
85 | })
86 | })
87 |
--------------------------------------------------------------------------------
/.github/workflows/lint-php.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Lint php
10 |
11 | on: pull_request
12 |
13 | permissions:
14 | contents: read
15 |
16 | concurrency:
17 | group: lint-php-${{ github.head_ref || github.run_id }}
18 | cancel-in-progress: true
19 |
20 | jobs:
21 | matrix:
22 | runs-on: ubuntu-latest-low
23 | outputs:
24 | php-versions: ${{ steps.versions.outputs.php-versions }}
25 | steps:
26 | - name: Checkout app
27 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
28 | with:
29 | persist-credentials: false
30 |
31 | - name: Get version matrix
32 | id: versions
33 | uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.0.0
34 |
35 | php-lint:
36 | runs-on: ubuntu-latest
37 | needs: matrix
38 | strategy:
39 | matrix:
40 | php-versions: ${{fromJson(needs.matrix.outputs.php-versions)}}
41 |
42 | name: php-lint
43 |
44 | steps:
45 | - name: Checkout
46 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
47 | with:
48 | persist-credentials: false
49 |
50 | - name: Set up php ${{ matrix.php-versions }}
51 | uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5
52 | with:
53 | php-version: ${{ matrix.php-versions }}
54 | extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
55 | coverage: none
56 | ini-file: development
57 | env:
58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59 |
60 | - name: Lint
61 | run: composer run lint
62 |
63 | summary:
64 | permissions:
65 | contents: none
66 | runs-on: ubuntu-latest-low
67 | needs: php-lint
68 |
69 | if: always()
70 |
71 | name: php-lint-summary
72 |
73 | steps:
74 | - name: Summary status
75 | run: if ${{ needs.php-lint.result != 'success' && needs.php-lint.result != 'skipped' }}; then exit 1; fi
76 |
--------------------------------------------------------------------------------
/src/utils/logfile.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import type { ILogEntry, IRawLogEntry } from '../interfaces'
7 |
8 | import { parseException } from './exception'
9 | import { logger } from './logger'
10 | import { splitter } from './splitter'
11 |
12 | /**
13 | * Parse a given log file
14 | *
15 | * @param file The log file
16 | */
17 | export async function parseLogFile(file: File): Promise {
18 | return parseLogString(await file.text())
19 | }
20 |
21 | /**
22 | * Parse a given log file as string
23 | *
24 | * @param raw The raw log file content
25 | */
26 | export async function parseLogString(raw: string): Promise {
27 | let entries: IRawLogEntry[]
28 | try {
29 | const lines = raw.split('\n')
30 | entries = lines.map(tryParseJSON)
31 | } catch (e) {
32 | logger.debug('falling back to json splitter')
33 |
34 | // the input might have had its data reformatted, breaking the original newline separated json
35 | const lines = splitter(raw).jsons
36 | entries = lines.map(tryParseJSON)
37 | }
38 | return entries.map(parseRawLogEntry)
39 | }
40 |
41 | /**
42 | * Parse a raw (unknown type of) log entry into a modern log entry
43 | *
44 | * @param entry The raw log entry
45 | */
46 | export function parseRawLogEntry(entry: IRawLogEntry): ILogEntry {
47 | return {
48 | ...entry,
49 | exception: parseException((entry as ILogEntry).exception || entry.message),
50 | } as ILogEntry
51 | }
52 |
53 | /**
54 | * Try to parse a single log entry
55 | *
56 | * @param json raw log entry
57 | */
58 | function tryParseJSON(json: string): IRawLogEntry {
59 | try {
60 | return JSON.parse(json)
61 | } catch (e) {
62 | logger.debug('Could not simply parse log entry', { error: e, json })
63 |
64 | // Handle quoted log entries
65 | if (json.startsWith('"') && json.endsWith('"')) {
66 | let inner = json.substring(1, json.length - 1)
67 |
68 | // csv escaped quotes
69 | if (inner.match(/^\{\s*""/)) {
70 | inner = inner.replace(/""/g, '"')
71 | }
72 | return JSON.parse(inner)
73 | }
74 |
75 | // fix unescaped message json
76 | const startPos = json.indexOf('"message":"') + 11
77 | const endPos = json.lastIndexOf('","level":')
78 | const start = json.substring(0, startPos)
79 | const end = json.substring(endPos)
80 | const message = json.slice(startPos, endPos)
81 |
82 | const escapedMessage = message.replace(/([^\\]|^)["]/g, '$1\\"')
83 | json = start + escapedMessage + end
84 |
85 | return JSON.parse(json)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 | # Log Reader
7 |
8 | [](https://api.reuse.software/info/github.com/nextcloud/logreader)
9 |
10 | 
11 |
12 | Log reader for Nextcloud with clean exception display, infinite scrolling and more.
13 |
14 | ## Install instructions
15 |
16 | ### Installed by default
17 |
18 | Log Reader is installed by default in recent versions of Nextcloud so you don't have to do anything else to use the app.
19 |
20 | ### Install the latest stable release manually
21 |
22 | - Download the last pre-build [release](https://github.com/nextcloud/logreader/releases)
23 | - Extract the `tar.gz` into the apps folder
24 |
25 | ### Install from source
26 |
27 | - clone the repo in the apps folder
28 | - Run `make` in the `logreader` folder
29 |
30 | ## Developing
31 |
32 | For building the app `node` and `npm` are required
33 |
34 | ### Building
35 |
36 | Building the app can be done using the `Makefile`
37 |
38 | ```
39 | make
40 | ```
41 |
42 | ### Automatic rebuilding during development
43 |
44 | During development the webpack dev server can be used to automatically build the code
45 | for every change.
46 |
47 | Since the compiled source from the webpack dev server need to be injected in the regular Nextcloud
48 | sources a proxy setup is needed to combine things.
49 |
50 | If your local Nextcloud setup runs at http://localcloud an nginx configuration for the proxy
51 | would look like the following:
52 |
53 | ```
54 | server {
55 | listen 81;
56 | server_name localcloud;
57 |
58 | location /apps/logreader/build/main.js {
59 | proxy_pass http://localhost:3000/build/main.js;
60 | }
61 | location /apps/logreader/build/main.css {
62 | return 404;
63 | }
64 |
65 | location /build {
66 | proxy_pass http://localhost:3000;
67 | }
68 | location /__webpack_hmr {
69 | proxy_set_header Host $host;
70 | proxy_pass http://localhost:3000;
71 | proxy_set_header Connection '';
72 | proxy_http_version 1.1;
73 | chunked_transfer_encoding off;
74 | proxy_buffering off;
75 | proxy_cache off;
76 | }
77 |
78 | location / {
79 | proxy_set_header Host $host;
80 | proxy_hide_header Content-Security-Policy;
81 | proxy_pass http://localcloud/;
82 | }
83 | }
84 |
85 | ```
86 |
87 | This will run the proxy at http://localcloud:81/
88 |
89 | With the proxy configured you can start the webpack dev server and specify where the
90 | Nextcloud proxy is.
91 |
92 | ```
93 | PROXY_URL="http://localcloud:81/ make watch
94 | ```
95 |
--------------------------------------------------------------------------------
/.github/workflows/pr-feedback.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 |
6 | # SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-FileCopyrightText: 2023 Marcel Klehr
8 | # SPDX-FileCopyrightText: 2023 Joas Schilling <213943+nickvergessen@users.noreply.github.com>
9 | # SPDX-FileCopyrightText: 2023 Daniel Kesselberg
10 | # SPDX-FileCopyrightText: 2023 Florian Steffens
11 | # SPDX-License-Identifier: MIT
12 |
13 | name: 'Ask for feedback on PRs'
14 | on:
15 | schedule:
16 | - cron: '30 1 * * *'
17 |
18 | permissions:
19 | contents: read
20 | pull-requests: write
21 |
22 | jobs:
23 | pr-feedback:
24 | if: ${{ github.repository_owner == 'nextcloud' }}
25 | runs-on: ubuntu-latest
26 | steps:
27 | - name: The get-github-handles-from-website action
28 | uses: marcelklehr/get-github-handles-from-website-action@06b2239db0a48fe1484ba0bfd966a3ab81a08308 # v1.0.1
29 | id: scrape
30 | with:
31 | website: 'https://nextcloud.com/team/'
32 |
33 | - name: Get blocklist
34 | id: blocklist
35 | run: |
36 | blocklist=$(curl https://raw.githubusercontent.com/nextcloud/.github/master/non-community-usernames.txt | paste -s -d, -)
37 | echo "blocklist=$blocklist" >> "$GITHUB_OUTPUT"
38 |
39 | - uses: nextcloud/pr-feedback-action@f0cab224dea8e1f282f9451de322f323c78fc7a5 # main
40 | with:
41 | feedback-message: |
42 | Hello there,
43 | Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.
44 |
45 | We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.
46 |
47 | Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6
48 |
49 | Thank you for contributing to Nextcloud and we hope to hear from you soon!
50 |
51 | (If you believe you should not receive this message, you can add yourself to the [blocklist](https://github.com/nextcloud/.github/blob/master/non-community-usernames.txt).)
52 | days-before-feedback: 14
53 | start-date: '2025-06-12'
54 | exempt-authors: '${{ steps.blocklist.outputs.blocklist }},${{ steps.scrape.outputs.users }}'
55 | exempt-bots: true
56 |
--------------------------------------------------------------------------------
/lib/SetupChecks/LogErrors.php:
--------------------------------------------------------------------------------
1 | l10n->t('Errors in the log');
34 | }
35 |
36 | public function getCategory(): string {
37 | return 'system';
38 | }
39 |
40 | public function run(): SetupResult {
41 | try {
42 | $logIterator = $this->logIteratorFactory->getLogIterator([self::LEVEL_WARNING,self::LEVEL_ERROR,self::LEVEL_FATAL]);
43 | } catch (\Exception $e) {
44 | if ($e instanceof UnsupportedLogTypeException) {
45 | return SetupResult::info($e->getMessage());
46 | }
47 |
48 | return SetupResult::error(
49 | $this->l10n->t('Failed to get an iterator for log entries: %s', [$e->getMessage()])
50 | );
51 | }
52 | $count = [
53 | self::LEVEL_WARNING => 0,
54 | self::LEVEL_ERROR => 0,
55 | self::LEVEL_FATAL => 0,
56 | ];
57 | $limit = new \DateTime('7 days ago');
58 | $startTime = microtime(true);
59 | foreach ($logIterator as $logItem) {
60 | if (!isset($logItem['time'])) {
61 | continue;
62 | }
63 | $time = \DateTime::createFromFormat(\DateTime::ATOM, $logItem['time']);
64 | if ($time < $limit) {
65 | break;
66 | }
67 | $count[$logItem['level']]++;
68 | if (microtime(true) > $startTime + 5) {
69 | $limit = $time;
70 | break;
71 | }
72 | }
73 | if (array_sum($count) === 0) {
74 | return SetupResult::success($this->l10n->t('No errors in the logs since %s', $this->dateFormatter->formatDateTime($limit)));
75 | } elseif ($count[self::LEVEL_ERROR] + $count[self::LEVEL_FATAL] > 0) {
76 | return SetupResult::warning(
77 | $this->l10n->n(
78 | '%n error in the logs since %s',
79 | '%n errors in the logs since %s',
80 | $count[self::LEVEL_ERROR] + $count[self::LEVEL_FATAL],
81 | [$this->dateFormatter->formatDateTime($limit)],
82 | )
83 | );
84 | } else {
85 | return SetupResult::info(
86 | $this->l10n->n(
87 | '%n warning in the logs since %s',
88 | '%n warnings in the logs since %s',
89 | $count[self::LEVEL_WARNING],
90 | [$this->dateFormatter->formatDateTime($limit)],
91 | )
92 | );
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/Command/Tail.php:
--------------------------------------------------------------------------------
1 | formatter = $formatter;
30 | $this->logIteratorFactory = $logIteratorFactory;
31 | }
32 |
33 | protected function configure() {
34 | $this
35 | ->setName('log:tail')
36 | ->setDescription('Tail the nextcloud logfile')
37 | ->addArgument('lines', InputArgument::OPTIONAL, 'The number of log entries to print', '10')
38 | ->addOption('follow', 'f', InputOption::VALUE_NONE, 'Output new log entries as they appear')
39 | ->addOption('raw', 'r', InputOption::VALUE_NONE, 'Output raw log json instead of formatted log item');
40 | parent::configure();
41 | }
42 |
43 | protected function execute(InputInterface $input, OutputInterface $output): int {
44 | $raw = $input->getOption('raw');
45 | $count = (int)$input->getArgument('lines');
46 | $io = new SymfonyStyle($input, $output);
47 | $logIterator = $this->logIteratorFactory->getLogIterator(Watch::ALL_LEVELS);
48 | $logIterator = new \LimitIterator($logIterator, 0, $count);
49 | $logItems = iterator_to_array($logIterator);
50 | $logItems = array_reverse($logItems);
51 |
52 | if ($raw) {
53 | foreach ($logItems as $logItem) {
54 | $output->writeln(json_encode($logItem));
55 | }
56 | } else {
57 | $terminal = new Terminal();
58 | $totalWidth = $terminal->getWidth();
59 | // 8 level, 18 for app, 26 for time, 6 for formatting
60 | $messageWidth = $totalWidth - 8 - 18 - 26 - 6;
61 |
62 | $tableItems = array_map(function (array $logItem) use ($messageWidth) {
63 | return [
64 | self::LEVELS[$logItem['level']],
65 | wordwrap($logItem['app'], 18),
66 | $this->formatter->formatMessage($logItem, $messageWidth) . "\n",
67 | $logItem['time'],
68 | ];
69 | }, $logItems);
70 | $io->table([
71 | 'Level',
72 | 'App',
73 | 'Message',
74 | 'Time',
75 | ], $tableItems);
76 | }
77 |
78 | if ($input->getOption('follow')) {
79 | $watch = new Watch($this->formatter, $this->logIteratorFactory);
80 | $watch->watch($raw, $output);
81 | }
82 |
83 | return 0;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/api.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import type { AxiosError, AxiosRequestConfig, AxiosResponse } from '@nextcloud/axios'
7 | import type { IAppSettings, INextcloud22LogEntry } from './interfaces'
8 |
9 | import axios from '@nextcloud/axios'
10 | import { generateUrl } from '@nextcloud/router'
11 |
12 | interface ApiGetLog {
13 | offset?: number
14 | count?: number
15 | query?: string
16 | }
17 |
18 | interface ApiPollLog {
19 | lastReqId: string
20 | }
21 |
22 | interface ApiLogResult {
23 | /** New entries */
24 | data: readonly INextcloud22LogEntry[]
25 | /** True if more entries are available */
26 | remain: boolean
27 | }
28 |
29 | type ApiPollLogResult = readonly INextcloud22LogEntry[]
30 |
31 | type IAppSettingsKey = keyof IAppSettings
32 |
33 | interface ApiSetAppSetting {
34 | settingsKey: I
35 | settingsValue: IAppSettings[I]
36 | }
37 |
38 | type ApiGetAppSettings = never
39 |
40 | /**
41 | * Fetch log entries from server
42 | *
43 | * @param data Parameters for request
44 | * @param config Axios config for setting data
45 | * @return Array of fetched log entries
46 | * @throws {AxiosError} with HTTP status 424 if log type is not set to `file`
47 | */
48 | export const getLog = (data: ApiGetLog, config: AxiosRequestConfig = {}) => axios.get>(generateUrl('apps/logreader/api/log'), { ...config, params: data }) as Promise>
49 |
50 | /**
51 | * Fetch log entries from server
52 | *
53 | * @param data Parameters for request
54 | * @param config Axios config for setting data
55 | * @return Array of fetched log entries
56 | * @throws {AxiosError} with HTTP status 424 if log type is not set to `file`
57 | */
58 | export const pollLog = (data: ApiPollLog, config: AxiosRequestConfig = {}) => axios.get>(generateUrl('apps/logreader/api/poll'), { ...config, params: data }) as Promise>
59 |
60 | /**
61 | * Change an app setting value
62 | *
63 | * @param data Parameters for request
64 | * @param config Axios config for setting data
65 | * @return
66 | */
67 | export const setAppSetting = (data: ApiSetAppSetting, config: AxiosRequestConfig> = {}) => axios.put>>(generateUrl('apps/logreader/api/settings'), data, config)
68 |
69 | /**
70 | * Get current app settings
71 | *
72 | * @param data Request parameters
73 | * @param config Optional Axios request config
74 | * @return The current app config
75 | */
76 | export const getAppSettings = (data?: ApiGetAppSettings, config: AxiosRequestConfig = {}) => axios.get>(generateUrl('apps/logreader/api/settings'), { ...config, params: data })
77 |
--------------------------------------------------------------------------------
/.github/workflows/npm-audit-fix.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Npm audit fix and compile
10 |
11 | on:
12 | workflow_dispatch:
13 | schedule:
14 | # At 2:30 on Sundays
15 | - cron: '30 2 * * 0'
16 |
17 | permissions:
18 | contents: read
19 |
20 | jobs:
21 | build:
22 | runs-on: ubuntu-latest
23 |
24 | strategy:
25 | fail-fast: false
26 | matrix:
27 | branches:
28 | - ${{ github.event.repository.default_branch }}
29 | - 'stable32'
30 | - 'stable31'
31 |
32 | name: npm-audit-fix-${{ matrix.branches }}
33 |
34 | steps:
35 | - name: Checkout
36 | id: checkout
37 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
38 | with:
39 | persist-credentials: false
40 | ref: ${{ matrix.branches }}
41 | continue-on-error: true
42 |
43 | - name: Read package.json node and npm engines version
44 | uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
45 | id: versions
46 | with:
47 | fallbackNode: '^24'
48 | fallbackNpm: '^11.3'
49 |
50 | - name: Set up node ${{ steps.versions.outputs.nodeVersion }}
51 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
52 | with:
53 | node-version: ${{ steps.versions.outputs.nodeVersion }}
54 |
55 | - name: Set up npm ${{ steps.versions.outputs.npmVersion }}
56 | run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'
57 |
58 | - name: Fix npm audit
59 | id: npm-audit
60 | uses: nextcloud-libraries/npm-audit-action@1b1728b2b4a7a78d69de65608efcf4db0e3e42d0 # v0.2.0
61 |
62 | - name: Run npm ci and npm run build
63 | if: steps.checkout.outcome == 'success'
64 | env:
65 | CYPRESS_INSTALL_BINARY: 0
66 | run: |
67 | npm ci
68 | npm run build --if-present
69 |
70 | - name: Create Pull Request
71 | if: steps.checkout.outcome == 'success'
72 | uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
73 | with:
74 | token: ${{ secrets.COMMAND_BOT_PAT }}
75 | commit-message: 'fix(deps): Fix npm audit'
76 | committer: GitHub
77 | author: nextcloud-command
78 | signoff: true
79 | branch: automated/noid/${{ matrix.branches }}-fix-npm-audit
80 | title: '[${{ matrix.branches }}] Fix npm audit'
81 | body: ${{ steps.npm-audit.outputs.markdown }}
82 | labels: |
83 | dependencies
84 | 3. to review
85 |
--------------------------------------------------------------------------------
/lib/Service/SettingsService.php:
--------------------------------------------------------------------------------
1 | config = $config;
18 | }
19 |
20 | /**
21 | * Load shown levels from app config
22 | */
23 | public function getShownLevels(): array {
24 | return json_decode($this->config->getAppValue('logreader', Constants::CONFIG_KEY_SHOWNLEVELS, '[0,1,2,3,4]'), flags: JSON_THROW_ON_ERROR);
25 | }
26 |
27 | /**
28 | * Load date time format to use for user from app config
29 | */
30 | public function getDateTimeFormat(): string {
31 | return json_decode($this->config->getAppValue('logreader', Constants::CONFIG_KEY_DATETIMEFORMAT, '"local"'), flags: JSON_THROW_ON_ERROR);
32 | }
33 |
34 | /**
35 | * Load app config if dates should be displayed as relative dates
36 | */
37 | public function getRelativeDates(): bool {
38 | return json_decode($this->config->getAppValue('logreader', Constants::CONFIG_KEY_RELATIVEDATES, 'false') ?: 'false', flags: JSON_THROW_ON_ERROR);
39 | }
40 |
41 | /**
42 | * Load app config if log should be updated automatically
43 | */
44 | public function getLiveLog(): bool {
45 | return json_decode($this->config->getAppValue('logreader', Constants::CONFIG_KEY_LIVELOG, 'true'), flags: JSON_THROW_ON_ERROR);
46 | }
47 |
48 | /**
49 | * Get all app settings for displaying the logfiles
50 | */
51 | public function getAppSettings(): array {
52 | return [
53 | Constants::CONFIG_KEY_SHOWNLEVELS => $this->getShownLevels(),
54 | Constants::CONFIG_KEY_LOGLEVEL => $this->config->getSystemValueInt('loglevel', 2),
55 | Constants::CONFIG_KEY_DATETIMEFORMAT => $this->getDateTimeFormat(),
56 | Constants::CONFIG_KEY_RELATIVEDATES => $this->getRelativeDates(),
57 | Constants::CONFIG_KEY_LIVELOG => $this->getLiveLog(),
58 | 'enabled' => $this->getLoggingType() === 'file',
59 | ];
60 | }
61 |
62 | /**
63 | * Get system setting of the logging type
64 | */
65 | public function getLoggingType(): string {
66 | return $this->config->getSystemValueString('log_type', 'file');
67 | }
68 |
69 | /**
70 | * Get system setting of the log file name
71 | */
72 | public function getLoggingFile(): string {
73 | return $this->config->getSystemValueString('logile', '');
74 | }
75 |
76 | /**
77 | * Get system setting for the log date format
78 | */
79 | public function getLoggingDateFormat(): string {
80 | // see default: https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html#file
81 | return $this->config->getSystemValueString('logdateformat', 'c');
82 | }
83 |
84 | /**
85 | * Get system setting for the log timezone
86 | */
87 | public function getLoggingTimezone(): string {
88 | return $this->config->getSystemValueString('logtimezone', 'UTC');
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/components/LogSearch.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
22 | {{ t('logreader', 'Search log entries') }}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
101 |
--------------------------------------------------------------------------------
/l10n/sl.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Logging" : "Dnevnik beleženja",
3 | "Errors in the log" : "Napake v dnevniških zapisih",
4 | "No errors in the logs since %s" : "V dnevniških zapisih ni napak od %s",
5 | "_%n error in the logs since %s_::_%n errors in the logs since %s_" : ["V dnevniških zapisih je zabeležena %n napaka (%s je datum prve)","V dnevniških zapisih sta zabeleženi %n napaki (%s je datum prve)","V dnevniških zapisih je zabeležene %n napake (%s je datum prve)","V dnevniških zapisih je zabeleženih %n napak (%s je datum prve)"],
6 | "_%n warning in the logs since %s_::_%n warnings in the logs since %s_" : ["V dnevniških zapisih je zabeleženo %n opozorilo (%s je datum prvega)","V dnevniških zapisih sta zabeleženi %n opozorili (%s je datum prvega)","V dnevniških zapisih so zabeležena %n opozorila (%s je datum prvega)","V dnevniških zapisih je zabeleženih %n opozoril (%s je datum prvega)"],
7 | "Log Reader" : "Bralnik dnevnikov",
8 | "A log reader for Nextcloud" : "Bralnik dnevnikov za Nextcloud",
9 | "Log reader for Nextcloud" : "Bralnik dnevnikov za Nextcloud",
10 | "Log reader" : "Bralnik dnevnikov",
11 | "Open log reader settings" : "Odpri nastavitve bralnika dnevnikov",
12 | "Log reader settings" : "Nastavitve bralnika dnevnikov",
13 | "Show server log" : "Pokaži dnevnik strežnika",
14 | "Live view is disabled" : "Pogled v živo je onemogočen",
15 | "No log file" : "Ni datoteke nevniškega zapisa",
16 | "Level" : "Raven",
17 | "App" : "Program",
18 | "Time" : "Čas",
19 | "Copy raw entry" : "Kopiraj neoblikovan vpis",
20 | "Copy formatted entry" : "Kopiraj oblikovan vpis",
21 | "Search log entries" : "Preišči dnevniške vnose",
22 | "Line {line}" : "Vrstica {line}",
23 | "Live view" : "Pogled v živo",
24 | "Ctrl" : "CTRL",
25 | "Download logs" : "Prejem dnevnikov",
26 | "Raw data" : "Surovi podatki",
27 | "Local time" : "Krajevni čas",
28 | "UTC time" : "Čas v sistemu UTC",
29 | "Relative" : "Relativno",
30 | "Filter logging levels" : "Filtriraj ravni beleženja",
31 | "Application" : "Program",
32 | "Message" : "Sporočilo",
33 | "Collapse row" : "Zloži vrstico",
34 | "Expand row" : "Razširi vrstico",
35 | "Show details" : "Pokaži podrobnosti",
36 | "Debug" : "Razhroščevanje",
37 | "Info" : "Podrobnosti",
38 | "Warning" : "Opozorilo",
39 | "Error" : "Napaka",
40 | "Fatal" : "Usodna napaka",
41 | "Could not load log entries" : "Ni mogoče naložiti vnosov dnevnika",
42 | "Clipboard" : "Odložišče",
43 | "Could not parse clipboard content" : "Ni mogoče razčleniti vsebine datoteke",
44 | "Could not fetch new log entries (server unavailable)" : "Ni mogoče pridobiti novih vnosov (strežnik ni na voljo)",
45 | "Could not fetch new entries" : "Ni mogoče pridobiti novih vnosov",
46 | "Could not copy to clipboard, please copy manually:" : "Kopiranje v odložišče ni mogoče, poskusite kopirati ročno:",
47 | "\tfrom {address} by {user} at {time}\n" : "\tz naslova {address}, uporabnik {user} ob {time}\n"
48 | },"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"
49 | }
--------------------------------------------------------------------------------
/l10n/sl.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "logreader",
3 | {
4 | "Logging" : "Dnevnik beleženja",
5 | "Errors in the log" : "Napake v dnevniških zapisih",
6 | "No errors in the logs since %s" : "V dnevniških zapisih ni napak od %s",
7 | "_%n error in the logs since %s_::_%n errors in the logs since %s_" : ["V dnevniških zapisih je zabeležena %n napaka (%s je datum prve)","V dnevniških zapisih sta zabeleženi %n napaki (%s je datum prve)","V dnevniških zapisih je zabeležene %n napake (%s je datum prve)","V dnevniških zapisih je zabeleženih %n napak (%s je datum prve)"],
8 | "_%n warning in the logs since %s_::_%n warnings in the logs since %s_" : ["V dnevniških zapisih je zabeleženo %n opozorilo (%s je datum prvega)","V dnevniških zapisih sta zabeleženi %n opozorili (%s je datum prvega)","V dnevniških zapisih so zabeležena %n opozorila (%s je datum prvega)","V dnevniških zapisih je zabeleženih %n opozoril (%s je datum prvega)"],
9 | "Log Reader" : "Bralnik dnevnikov",
10 | "A log reader for Nextcloud" : "Bralnik dnevnikov za Nextcloud",
11 | "Log reader for Nextcloud" : "Bralnik dnevnikov za Nextcloud",
12 | "Log reader" : "Bralnik dnevnikov",
13 | "Open log reader settings" : "Odpri nastavitve bralnika dnevnikov",
14 | "Log reader settings" : "Nastavitve bralnika dnevnikov",
15 | "Show server log" : "Pokaži dnevnik strežnika",
16 | "Live view is disabled" : "Pogled v živo je onemogočen",
17 | "No log file" : "Ni datoteke nevniškega zapisa",
18 | "Level" : "Raven",
19 | "App" : "Program",
20 | "Time" : "Čas",
21 | "Copy raw entry" : "Kopiraj neoblikovan vpis",
22 | "Copy formatted entry" : "Kopiraj oblikovan vpis",
23 | "Search log entries" : "Preišči dnevniške vnose",
24 | "Line {line}" : "Vrstica {line}",
25 | "Live view" : "Pogled v živo",
26 | "Ctrl" : "CTRL",
27 | "Download logs" : "Prejem dnevnikov",
28 | "Raw data" : "Surovi podatki",
29 | "Local time" : "Krajevni čas",
30 | "UTC time" : "Čas v sistemu UTC",
31 | "Relative" : "Relativno",
32 | "Filter logging levels" : "Filtriraj ravni beleženja",
33 | "Application" : "Program",
34 | "Message" : "Sporočilo",
35 | "Collapse row" : "Zloži vrstico",
36 | "Expand row" : "Razširi vrstico",
37 | "Show details" : "Pokaži podrobnosti",
38 | "Debug" : "Razhroščevanje",
39 | "Info" : "Podrobnosti",
40 | "Warning" : "Opozorilo",
41 | "Error" : "Napaka",
42 | "Fatal" : "Usodna napaka",
43 | "Could not load log entries" : "Ni mogoče naložiti vnosov dnevnika",
44 | "Clipboard" : "Odložišče",
45 | "Could not parse clipboard content" : "Ni mogoče razčleniti vsebine datoteke",
46 | "Could not fetch new log entries (server unavailable)" : "Ni mogoče pridobiti novih vnosov (strežnik ni na voljo)",
47 | "Could not fetch new entries" : "Ni mogoče pridobiti novih vnosov",
48 | "Could not copy to clipboard, please copy manually:" : "Kopiranje v odložišče ni mogoče, poskusite kopirati ročno:",
49 | "\tfrom {address} by {user} at {time}\n" : "\tz naslova {address}, uporabnik {user} ob {time}\n"
50 | },
51 | "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);");
52 |
--------------------------------------------------------------------------------
/src/components/settings/SettingsActions.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ t('logreader', 'Download logs') }}
17 |
18 |
19 |
20 |
21 |
22 | {{ t('logreader', 'Show local log file') }}
23 |
24 |
33 |
34 |
35 |
36 |
83 |
84 |
95 |
--------------------------------------------------------------------------------
/.github/workflows/lint-eslint.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Lint eslint
10 |
11 | on: pull_request
12 |
13 | permissions:
14 | contents: read
15 |
16 | concurrency:
17 | group: lint-eslint-${{ github.head_ref || github.run_id }}
18 | cancel-in-progress: true
19 |
20 | jobs:
21 | changes:
22 | runs-on: ubuntu-latest-low
23 | permissions:
24 | contents: read
25 | pull-requests: read
26 |
27 | outputs:
28 | src: ${{ steps.changes.outputs.src}}
29 |
30 | steps:
31 | - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
32 | id: changes
33 | continue-on-error: true
34 | with:
35 | filters: |
36 | src:
37 | - '.github/workflows/**'
38 | - 'src/**'
39 | - 'appinfo/info.xml'
40 | - 'package.json'
41 | - 'package-lock.json'
42 | - 'tsconfig.json'
43 | - '.eslintrc.*'
44 | - '.eslintignore'
45 | - '**.js'
46 | - '**.ts'
47 | - '**.vue'
48 |
49 | lint:
50 | runs-on: ubuntu-latest
51 |
52 | needs: changes
53 | if: needs.changes.outputs.src != 'false'
54 |
55 | name: NPM lint
56 |
57 | steps:
58 | - name: Checkout
59 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
60 | with:
61 | persist-credentials: false
62 |
63 | - name: Read package.json node and npm engines version
64 | uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
65 | id: versions
66 | with:
67 | fallbackNode: '^24'
68 | fallbackNpm: '^11.3'
69 |
70 | - name: Set up node ${{ steps.versions.outputs.nodeVersion }}
71 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
72 | with:
73 | node-version: ${{ steps.versions.outputs.nodeVersion }}
74 |
75 | - name: Set up npm ${{ steps.versions.outputs.npmVersion }}
76 | run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'
77 |
78 | - name: Install dependencies
79 | env:
80 | CYPRESS_INSTALL_BINARY: 0
81 | PUPPETEER_SKIP_DOWNLOAD: true
82 | run: npm ci
83 |
84 | - name: Lint
85 | run: npm run lint
86 |
87 | summary:
88 | permissions:
89 | contents: none
90 | runs-on: ubuntu-latest-low
91 | needs: [changes, lint]
92 |
93 | if: always()
94 |
95 | # This is the summary, we just avoid to rename it so that branch protection rules still match
96 | name: eslint
97 |
98 | steps:
99 | - name: Summary status
100 | run: if ${{ needs.changes.outputs.src != 'false' && needs.lint.result != 'success' }}; then exit 1; fi
101 |
--------------------------------------------------------------------------------
/src/components/settings/SettingsDatetimeFormat.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
45 |
46 |
47 |
94 |
95 |
100 |
--------------------------------------------------------------------------------
/src/components/table/LogTableHeader.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
27 | |
28 |
29 |
30 |
108 |
109 |
135 |
--------------------------------------------------------------------------------
/src/api.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | import { afterEach, describe, expect, it, vi } from 'vitest'
7 | import { getAppSettings, getLog, pollLog, setAppSetting } from './api'
8 |
9 | const mocks = vi.hoisted(() => {
10 | return {
11 | get: vi.fn(),
12 | put: vi.fn(),
13 | }
14 | })
15 |
16 | vi.mock('@nextcloud/axios', () => {
17 | return {
18 | default: {
19 | get: mocks.get,
20 | put: mocks.put,
21 | },
22 | }
23 | })
24 |
25 | vi.mock('@nextcloud/router', () => ({
26 | generateUrl: (path: string) => `/index.php/${path}`,
27 | }))
28 |
29 | describe('api', () => {
30 | afterEach(() => {
31 | vi.restoreAllMocks()
32 | })
33 |
34 | it('getLog', async () => {
35 | const data = { count: 1 }
36 |
37 | await getLog(data)
38 |
39 | expect(mocks.get).toBeCalled()
40 | expect(mocks.get).toBeCalledWith('/index.php/apps/logreader/api/log', { params: data })
41 | })
42 |
43 | it('getLog with config', async () => {
44 | const data = { count: 1 }
45 | const config = { headers: { Accept: 'application/json' } }
46 |
47 | await getLog(data, config)
48 |
49 | expect(mocks.get).toBeCalled()
50 | expect(mocks.get).toBeCalledWith('/index.php/apps/logreader/api/log', { ...config, params: data })
51 | })
52 |
53 | it('pollLog', async () => {
54 | const data = { lastReqId: '1234' }
55 |
56 | await pollLog(data)
57 |
58 | expect(mocks.get).toBeCalled()
59 | expect(mocks.get).toBeCalledWith('/index.php/apps/logreader/api/poll', { params: data })
60 | })
61 |
62 | it('pollLog with config', async () => {
63 | const data = { lastReqId: '1234' }
64 | const config = { headers: { Accept: 'application/json' } }
65 |
66 | await pollLog(data, config)
67 |
68 | expect(mocks.get).toBeCalled()
69 | expect(mocks.get).toBeCalledWith('/index.php/apps/logreader/api/poll', { ...config, params: data })
70 | })
71 |
72 | it('getAppSettings', async () => {
73 | await getAppSettings()
74 |
75 | expect(mocks.get).toBeCalled()
76 | expect(mocks.get).toBeCalledWith('/index.php/apps/logreader/api/settings', {})
77 | })
78 |
79 | it('getAppSettings with config', async () => {
80 | const config = { headers: { Accept: 'application/json' } }
81 |
82 | await getAppSettings(undefined, config)
83 |
84 | expect(mocks.get).toBeCalled()
85 | expect(mocks.get).toBeCalledWith('/index.php/apps/logreader/api/settings', { ...config, params: undefined })
86 | })
87 |
88 | it('setAppSetting', async () => {
89 | const data = { settingsKey: 'liveLog' as const, settingsValue: true }
90 |
91 | await setAppSetting<'liveLog'>(data)
92 |
93 | expect(mocks.put).toBeCalled()
94 | expect(mocks.put).toBeCalledWith('/index.php/apps/logreader/api/settings', data, {})
95 | })
96 |
97 | it('setAppSetting with config', async () => {
98 | const data = { settingsKey: 'liveLog' as const, settingsValue: true }
99 | const config = { headers: { Accept: 'application/json' } }
100 |
101 | await setAppSetting<'liveLog'>(data, config)
102 |
103 | expect(mocks.put).toBeCalled()
104 | expect(mocks.put).toBeCalledWith('/index.php/apps/logreader/api/settings', data, config)
105 | })
106 | })
107 |
--------------------------------------------------------------------------------
/src/interfaces/ILogEntry.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * SPDX-FileCopyrightText: 2023 Nextcloud Gmbh and Nextcloud contributors
3 | * SPDX-License-Identifier: AGPL-3.0-or-later
4 | */
5 |
6 | /**
7 | * Trace entry for exceptions
8 | */
9 | export interface ITraceLine {
10 | /**
11 | * The file in which the function was called
12 | */
13 | file: string
14 | /**
15 | * Line that was currently parsed when the exception occured
16 | */
17 | line?: number
18 | /**
19 | * Called function name
20 | */
21 | function: string
22 | /**
23 | * Class of the function that was called
24 | */
25 | class: string
26 | /**
27 | * Function arguments (might be a placeholder when sensitive value)
28 | */
29 | args?: readonly string[]
30 | /**
31 | * Type of function access, e.g. '->' or '::'
32 | */
33 | type: string
34 | }
35 |
36 | /**
37 | * Exception entry for log entries
38 | */
39 | export interface IException {
40 | Code: number
41 | CustomMessage: string
42 | Exception: string
43 | File: string
44 | Line: number
45 | Message: string
46 | Trace: readonly ITraceLine[]
47 | Previous?: this
48 | }
49 |
50 | /**
51 | * Common base format of a Nextcloud log entry
52 | */
53 | interface IBaseLogEntry {
54 | /** Request ID: any log lines related to a single request have the same value */
55 | reqId: string
56 | /** Logged incident’s level */
57 | level: number
58 | /** Affected app */
59 | app: string
60 | /** Date and time (format and timezone can be configured in config.php) */
61 | time: string
62 | /** Event information message */
63 | message: string
64 | /** Nextcloud version at the time of request */
65 | version: string
66 | /** The IP address of the user (if applicable) */
67 | remoteAddr?: string
68 | /** Acting user's id (if applicable) */
69 | user?: string
70 | /** HTTP method, for example GET, POST, PROPFIND, etc. – empty on occ calls */
71 | method?: string
72 | /** Request path (if applicable – empty on occ calls) */
73 | url?: string
74 | /** User agent (if applicable – empty on occ calls) */
75 | userAgent?: string
76 | /** Additional structured data (if applicable) */
77 | data?: string // if parsed: Record
78 | }
79 |
80 | /**
81 | * Nextcloud 14 version of a log entry, previously the exception was simply a string within the `message` property
82 | */
83 | export interface INextcloud14LogEntry extends Omit {
84 | /** Event information message or exception */
85 | message: string | IException
86 | }
87 |
88 | /**
89 | * Format of a log entry introduced with Nextcloud 22
90 | */
91 | export interface INextcloud22LogEntry extends IBaseLogEntry {
92 | /** Full exception with trace (if applicable) */
93 | exception?: string | IException
94 | }
95 |
96 | /**
97 | * Raw log entry from log source without fixing the exception
98 | */
99 | export type IRawLogEntry = INextcloud14LogEntry | INextcloud22LogEntry
100 |
101 | /**
102 | * Fixed version of the log entry where the exception has its own field of type IException
103 | */
104 | export interface ILogEntry extends Omit {
105 | /** Unique ID, appended to each iterator element (see LogController#poll) */
106 | id: string
107 | /** Full exception with trace (if applicable) */
108 | exception?: IException
109 | }
110 |
--------------------------------------------------------------------------------
/.github/workflows/psalm-matrix.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Static analysis
10 |
11 | on: pull_request
12 |
13 | concurrency:
14 | group: psalm-${{ github.head_ref || github.run_id }}
15 | cancel-in-progress: true
16 |
17 | permissions:
18 | contents: read
19 |
20 | jobs:
21 | matrix:
22 | runs-on: ubuntu-latest-low
23 | outputs:
24 | ocp-matrix: ${{ steps.versions.outputs.ocp-matrix }}
25 | steps:
26 | - name: Checkout app
27 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
28 | with:
29 | persist-credentials: false
30 |
31 | - name: Get version matrix
32 | id: versions
33 | uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
34 |
35 | - name: Check enforcement of minimum PHP version ${{ steps.versions.outputs.php-min }} in psalm.xml
36 | run: grep 'phpVersion="${{ steps.versions.outputs.php-min }}' psalm.xml
37 |
38 | static-analysis:
39 | runs-on: ubuntu-latest
40 | needs: matrix
41 | strategy:
42 | # do not stop on another job's failure
43 | fail-fast: false
44 | matrix: ${{ fromJson(needs.matrix.outputs.ocp-matrix) }}
45 |
46 | name: static-psalm-analysis ${{ matrix.ocp-version }}
47 | steps:
48 | - name: Checkout
49 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
50 | with:
51 | persist-credentials: false
52 |
53 | - name: Set up php${{ matrix.php-min }}
54 | uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5
55 | with:
56 | php-version: ${{ matrix.php-min }}
57 | extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
58 | coverage: none
59 | ini-file: development
60 | # Temporary workaround for missing pcntl_* in PHP 8.3
61 | ini-values: disable_functions=
62 | env:
63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
64 |
65 | - name: Install dependencies
66 | run: |
67 | composer remove nextcloud/ocp --dev --no-scripts
68 | composer i
69 |
70 | - name: Check for vulnerable PHP dependencies
71 | run: composer require --dev roave/security-advisories:dev-latest
72 |
73 | - name: Install dependencies # zizmor: ignore[template-injection]
74 | run: composer require --dev 'nextcloud/ocp:${{ matrix.ocp-version }}' --ignore-platform-reqs --with-dependencies
75 |
76 | - name: Run coding standards check
77 | run: composer run psalm -- --threads=1 --monochrome --no-progress --output-format=github
78 |
79 | summary:
80 | runs-on: ubuntu-latest-low
81 | needs: static-analysis
82 |
83 | if: always()
84 |
85 | name: static-psalm-analysis-summary
86 |
87 | steps:
88 | - name: Summary status
89 | run: if ${{ needs.static-analysis.result != 'success' }}; then exit 1; fi
90 |
--------------------------------------------------------------------------------
/lib/Controller/SettingsController.php:
--------------------------------------------------------------------------------
1 | settingsService->getAppSettings());
40 | }
41 |
42 | /**
43 | * Update values on the app config.
44 | *
45 | * @param string $settingsKey AppConfig Key to store
46 | * @param mixed $settingsValues Corresponding AppConfig Value
47 | *
48 | */
49 | #[AuthorizedAdminSetting(settings: Admin::class)]
50 | public function updateAppConfig(string $settingsKey, $settingsValue): JSONResponse {
51 | $this->logger->debug('Updating AppConfig: {settingsKey} => {settingsValue}', [
52 | 'settingsKey' => $settingsKey,
53 | 'settingsValue' => $settingsValue
54 | ]);
55 |
56 | // Check for allowed keys
57 | if (!in_array($settingsKey, Constants::CONFIG_KEYS)) {
58 | $this->logger->debug('Unknown appConfig key: ' . $settingsKey);
59 | return new JSONResponse([], Http::STATUS_BAD_REQUEST);
60 | }
61 |
62 | // Check type of value
63 | if (gettype($settingsValue) !== gettype($this->settingsService->getAppSettings()[$settingsKey])) {
64 | // Invalid type
65 | $this->logger->debug('Incorrect value type for appConfig key', ['settingsKey' => $settingsKey, 'valueType' => gettype($settingsValue)]);
66 | return new JSONResponse([], Http::STATUS_BAD_REQUEST);
67 | }
68 |
69 | if ($settingsKey === Constants::CONFIG_KEY_SHOWNLEVELS) {
70 | foreach ($settingsValue as $value) {
71 | if (!is_integer($value) || !in_array($value, Constants::LOGGING_LEVELS)) {
72 | $this->logger->debug('Invalid logging level given', ['value' => $value ]);
73 | return new JSONResponse([], Http::STATUS_BAD_REQUEST);
74 | }
75 | }
76 | }
77 |
78 | if ($settingsKey === Constants::CONFIG_KEY_LOGLEVEL) {
79 | // Validate loglevel value
80 | if (!is_int($settingsValue) || $settingsValue < 0 || $settingsValue > 4) {
81 | $this->logger->debug('Cannot set {settingsValue} as loglevel', ['settingsValue' => $settingsValue ]);
82 | return new JSONResponse([], Http::STATUS_BAD_REQUEST);
83 | }
84 | // Set backend loglevel directly via system value
85 | $this->config->setSystemValue('loglevel', $settingsValue);
86 | } else {
87 | // Set on DB
88 | $this->config->setAppValue($this->appName, $settingsKey, json_encode($settingsValue));
89 | }
90 |
91 | return new JSONResponse();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/lib/Log/LogIterator.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class LogIterator implements \Iterator {
14 | /**
15 | * @var resource
16 | */
17 | private $handle;
18 | private string $dateFormat;
19 | private \DateTimeZone $timezone;
20 |
21 | private string $buffer = '';
22 | private string $lastLine = '';
23 | private int $position = 0;
24 | private int $currentKey = -1;
25 |
26 | public const CHUNK_SIZE = 8192; // how many chars do we try at once to find a new line
27 |
28 | /**
29 | * @param resource $handle
30 | * @param string $dateFormat
31 | * @param string $timezone
32 | */
33 | public function __construct($handle, string $dateFormat, string $timezone) {
34 | $this->handle = $handle;
35 | $this->dateFormat = $dateFormat;
36 | $this->timezone = new \DateTimeZone($timezone);
37 | $this->rewind();
38 | }
39 |
40 | public function rewind(): void {
41 | fseek($this->handle, 0, SEEK_END);
42 | $this->position = ftell($this->handle);
43 | $this->lastLine = '';
44 | $this->buffer = '';
45 | $this->currentKey = -1;
46 | $this->next();
47 | }
48 |
49 | /**
50 | * @return array
51 | */
52 | #[\ReturnTypeWillChange]
53 | public function current() {
54 | $entry = json_decode($this->lastLine, true);
55 | if ($this->dateFormat !== \DateTime::ATOM) {
56 | if (isset($entry['time'])) {
57 | $time = \DateTime::createFromFormat($this->dateFormat, $entry['time'], $this->timezone);
58 | if ($time) {
59 | $entry['time'] = $time->format(\DateTime::ATOM);
60 | }
61 | }
62 | }
63 | return $entry;
64 | }
65 |
66 | public function key(): int {
67 | return $this->currentKey;
68 | }
69 |
70 | private function fillBuffer(): void {
71 | $chunkSize = min($this->position, self::CHUNK_SIZE);
72 | $this->position -= $chunkSize;
73 | fseek($this->handle, $this->position);
74 | $chunk = fread($this->handle, $chunkSize);
75 | $this->buffer = $chunk . $this->buffer;
76 | }
77 |
78 | public function next(): void {
79 | // Loop through each character of the file looking for new lines
80 | while ($this->position >= 0) {
81 | $newlinePos = strrpos($this->buffer, "\n");
82 | if ($newlinePos !== false) {
83 | if ($newlinePos + 1 === strlen($this->buffer)) {
84 | // try again with truncated buffer if it ends with newline, i.e. on first call
85 | $this->buffer = substr($this->buffer, 0, $newlinePos);
86 | continue;
87 | }
88 | $this->lastLine = substr($this->buffer, $newlinePos + 1);
89 | $this->buffer = substr($this->buffer, 0, $newlinePos);
90 | $this->currentKey++;
91 | return;
92 | } elseif ($this->position === 0) {
93 | $this->lastLine = $this->buffer;
94 | $this->buffer = '';
95 | $this->currentKey++;
96 | return;
97 | } else {
98 | $this->fillBuffer();
99 | }
100 | }
101 | }
102 |
103 | public function valid(): bool {
104 | if (!is_resource($this->handle)) {
105 | return false;
106 | }
107 |
108 | if ($this->lastLine === '' && $this->position === 0) {
109 | return false;
110 | }
111 |
112 | return true;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------