Bring up to date with cockpit starter kit

This encompasses a number of changes to the build process.
This commit is contained in:
Justin Stephenson 2023-04-13 11:28:00 -04:00
parent a0fffde59d
commit 235f110ec7
32 changed files with 533 additions and 1172 deletions

View file

@ -1,4 +0,0 @@
{
"presets": ["@babel/env",
"@babel/preset-react"]
}

View file

@ -1 +1,2 @@
node_modules/* node_modules/*
pkg/lib/*

View file

@ -4,8 +4,7 @@
"browser": true, "browser": true,
"es6": true "es6": true
}, },
"extends": ["eslint:recommended", "standard", "standard-react"], "extends": ["eslint:recommended", "standard", "standard-jsx", "react-app"],
"parser": "@babel/eslint-parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": "7", "ecmaVersion": "7",
"ecmaFeatures": { "ecmaFeatures": {
@ -15,22 +14,16 @@
}, },
"plugins": ["flowtype", "react", "react-hooks"], "plugins": ["flowtype", "react", "react-hooks"],
"rules": { "rules": {
"indent": [ "indent": ["error", 4,
"error",
4,
{ {
"ObjectExpression": "first", "ObjectExpression": "first",
"CallExpression": {"arguments": "first"}, "CallExpression": {"arguments": "first"},
"MemberExpression": 2, "MemberExpression": 2,
"ignoredNodes": [ "JSXAttribute" ] "ignoredNodes": [ "JSXAttribute" ]
} }],
],
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }], "newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }],
"lines-between-class-members": [ "no-var": "error",
"error", "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
"always",
{ "exceptAfterSingleLine": true }
],
"prefer-promise-reject-errors": ["error", { "allowEmptyReject": true }], "prefer-promise-reject-errors": ["error", { "allowEmptyReject": true }],
"react/jsx-indent": ["error", 4], "react/jsx-indent": ["error", 4],
"semi": ["error", "always", { "omitLastInOneLineBlock": true }], "semi": ["error", "always", { "omitLastInOneLineBlock": true }],
@ -43,22 +36,13 @@
"curly": "off", "curly": "off",
"jsx-quotes": "off", "jsx-quotes": "off",
"key-spacing": "off", "key-spacing": "off",
"new-cap": "off",
"no-console": "off", "no-console": "off",
"prefer-const": "off",
"quotes": "off", "quotes": "off",
"react/jsx-closing-bracket-location": "off",
"react/jsx-curly-spacing": "off", "react/jsx-curly-spacing": "off",
"react/jsx-indent-props": "off", "react/jsx-indent-props": "off",
"react/jsx-handler-names": "off",
"react/prop-types": "off", "react/prop-types": "off",
"space-before-function-paren": "off", "space-before-function-paren": "off",
"standard/no-callback-literal": "off", "standard/no-callback-literal": "off"
"eqeqeq": "off",
"import/no-webpack-loader-syntax": "off",
"object-property-newline": "off",
"react/jsx-no-bind": "off"
}, },
"globals": { "globals": {
"require": false, "require": false,

7
.gitignore vendored
View file

@ -4,15 +4,16 @@
*.rpm *.rpm
node_modules/ node_modules/
dist/ dist/
lib/
/*.spec /*.spec
/.vagrant /.vagrant
package-lock.json package-lock.json
Test*FAIL* Test*FAIL*
bots/ /bots
test/common/ test/common/
test/images/ test/images/
pkg
*.pot *.pot
POTFILES* POTFILES*
tmp/ tmp/
.mypy_cache /po/LINGUAS
/tools

38
.stylelintrc.json Normal file
View file

@ -0,0 +1,38 @@
{
"extends": "stylelint-config-standard-scss",
"rules": {
"declaration-colon-newline-after": null,
"selector-list-comma-newline-after": null,
"at-rule-empty-line-before": null,
"declaration-colon-space-before": null,
"declaration-empty-line-before": null,
"custom-property-empty-line-before": null,
"comment-empty-line-before": null,
"scss/double-slash-comment-empty-line-before": null,
"scss/dollar-variable-colon-space-after": null,
"custom-property-pattern": null,
"declaration-block-no-duplicate-properties": null,
"declaration-block-no-redundant-longhand-properties": null,
"declaration-block-no-shorthand-property-overrides": null,
"declaration-block-single-line-max-declarations": null,
"font-family-no-duplicate-names": null,
"function-url-quotes": null,
"indentation": null,
"keyframes-name-pattern": null,
"max-line-length": null,
"no-descending-specificity": null,
"no-duplicate-selectors": null,
"scss/at-extend-no-missing-placeholder": null,
"scss/at-import-partial-extension": null,
"scss/at-mixin-pattern": null,
"scss/comment-no-empty": null,
"scss/dollar-variable-pattern": null,
"scss/double-slash-comment-whitespace-inside": null,
"scss/no-global-function-names": null,
"scss/operator-no-unspaced": null,
"selector-class-pattern": null,
"selector-id-pattern": null
}
}

13
.tasks
View file

@ -1,13 +0,0 @@
#!/bin/sh
# When run automated, randomize to minimize stampeding herd
if [ -t 0 ]; then
chance=10
else
chance=$(shuf -i 0-10 -n 1)
fi
if [ $chance -gt 9 ]; then
# Open issues for things that need doing on a regular basis
bots/npm-trigger
fi

View file

@ -1,8 +0,0 @@
dist: trusty
sudo: false
language: node_js
node_js:
- "8"
script:
- npm install
- npm run build

View file

@ -1,17 +0,0 @@
FROM fedora:35
COPY . cockpit-session-recording
RUN sudo dnf -y install \
git \
gnupg \
intltool \
libappstream-glib \
make \
npm \
rpm-build \
rpmdevtools \
rsync \
tar
RUN cd cockpit-session-recording && make rpm

221
Makefile
View file

@ -3,24 +3,45 @@ PACKAGE_NAME := $(shell awk '/"name":/ {gsub(/[",]/, "", $$2); print $$2}' packa
RPM_NAME := cockpit-$(PACKAGE_NAME) RPM_NAME := cockpit-$(PACKAGE_NAME)
VERSION := $(shell T=$$(git describe 2>/dev/null) || T=1; echo $$T | tr '-' '.') VERSION := $(shell T=$$(git describe 2>/dev/null) || T=1; echo $$T | tr '-' '.')
ifeq ($(TEST_OS),) ifeq ($(TEST_OS),)
TEST_OS = rhel-x TEST_OS = centos-8-stream
endif endif
export TEST_OS export TEST_OS
TARFILE=cockpit-$(PACKAGE_NAME)-$(VERSION).tar.xz TARFILE=$(RPM_NAME)-$(VERSION).tar.xz
NODE_CACHE=$(RPM_NAME)-node-$(VERSION).tar.xz
SPEC=$(RPM_NAME).spec SPEC=$(RPM_NAME).spec
# rpmspec -q behaves differently in Fedora ≥ 37 PREFIX ?= /usr/local
RPMQUERY=$(shell rpmspec -D"VERSION $(VERSION)" -q --srpm $(SPEC).in).rpm APPSTREAMFILE=org.cockpit-project.$(PACKAGE_NAME).metainfo.xml
SRPMFILE=$(subst noarch,src,$(RPMQUERY))
RPMFILE=$(subst src,noarch,$(RPMQUERY))
VM_IMAGE=$(CURDIR)/test/images/$(TEST_OS) VM_IMAGE=$(CURDIR)/test/images/$(TEST_OS)
# stamp file to check if/when npm install ran # stamp file to check for node_modules/
NODE_MODULES_TEST=package-lock.json NODE_MODULES_TEST=package-lock.json
# one example file in dist/ from webpack to check if that already ran # one example file in dist/ from bundler to check if that already ran
WEBPACK_TEST=dist/manifest.json DIST_TEST=dist/manifest.json
# one example file in src/lib to check if it was already checked out # one example file in pkg/lib to check if it was already checked out
LIB_TEST=src/lib/cockpit-po-plugin.js COCKPIT_REPO_STAMP=pkg/lib/cockpit-po-plugin.js
# common arguments for tar, mostly to make the generated tarballs reproducible
TAR_ARGS = --sort=name --mtime "@$(shell git show --no-patch --format='%at')" --mode=go=rX,u+rw,a-s --numeric-owner --owner=0 --group=0
all: $(WEBPACK_TEST) all: $(DIST_TEST)
# checkout common files from Cockpit repository required to build this project;
# this has no API stability guarantee, so check out a stable tag when you start
# a new project, use the latest release, and update it from time to time
COCKPIT_REPO_FILES = \
pkg/lib \
test/common \
tools/git-utils.sh \
tools/make-bots \
$(NULL)
COCKPIT_REPO_URL = https://github.com/cockpit-project/cockpit.git
COCKPIT_REPO_COMMIT = 6073b2703acd68e216bd9dbc116c30d2d7a9701c # 288.1 + esbuild plugin updates
$(COCKPIT_REPO_FILES): $(COCKPIT_REPO_STAMP)
COCKPIT_REPO_TREE = '$(strip $(COCKPIT_REPO_COMMIT))^{tree}'
$(COCKPIT_REPO_STAMP): Makefile
@git rev-list --quiet --objects $(COCKPIT_REPO_TREE) -- 2>/dev/null || \
git fetch --no-tags --no-write-fetch-head --depth=1 $(COCKPIT_REPO_URL) $(COCKPIT_REPO_COMMIT)
git archive $(COCKPIT_REPO_TREE) -- $(COCKPIT_REPO_FILES) | tar x
# #
# i18n # i18n
@ -28,95 +49,98 @@ all: $(WEBPACK_TEST)
LINGUAS=$(basename $(notdir $(wildcard po/*.po))) LINGUAS=$(basename $(notdir $(wildcard po/*.po)))
po/POTFILES.js.in: po/$(PACKAGE_NAME).js.pot:
mkdir -p $(dir $@) xgettext --default-domain=$(PACKAGE_NAME) --output=$@ --language=C --keyword= \
find src/ -name '*.js' -o -name '*.jsx' > $@ --keyword=_:1,1t --keyword=_:1c,2,2t --keyword=C_:1c,2 \
po/$(PACKAGE_NAME).js.pot: po/POTFILES.js.in
xgettext --default-domain=cockpit --output=$@ --language=C --keyword= \
--keyword=_:1,1t --keyword=_:1c,2,1t --keyword=C_:1c,2 \
--keyword=N_ --keyword=NC_:1c,2 \ --keyword=N_ --keyword=NC_:1c,2 \
--keyword=gettext:1,1t --keyword=gettext:1c,2,2t \ --keyword=gettext:1,1t --keyword=gettext:1c,2,2t \
--keyword=ngettext:1,2,3t --keyword=ngettext:1c,2,3,4t \ --keyword=ngettext:1,2,3t --keyword=ngettext:1c,2,3,4t \
--keyword=gettextCatalog.getString:1,3c --keyword=gettextCatalog.getPlural:2,3,4c \ --keyword=gettextCatalog.getString:1,3c --keyword=gettextCatalog.getPlural:2,3,4c \
--from-code=UTF-8 --files-from=$^ --from-code=UTF-8 $$(find src/ -name '*.js' -o -name '*.jsx')
po/POTFILES.html.in: po/$(PACKAGE_NAME).html.pot: $(NODE_MODULES_TEST) $(COCKPIT_REPO_STAMP)
mkdir -p $(dir $@) pkg/lib/html2po.js -o $@ $$(find src -name '*.html')
find src -name '*.html' > $@
po/$(PACKAGE_NAME).html.pot: po/POTFILES.html.in po/$(PACKAGE_NAME).manifest.pot: $(NODE_MODULES_TEST) $(COCKPIT_REPO_STAMP)
po/html2po -f $^ -o $@ pkg/lib/manifest2po.js src/manifest.json -o $@
po/$(PACKAGE_NAME).manifest.pot: po/$(PACKAGE_NAME).metainfo.pot: $(APPSTREAMFILE)
po/manifest2po src/manifest.json -o $@ xgettext --default-domain=$(PACKAGE_NAME) --output=$@ $<
po/$(PACKAGE_NAME).pot: po/$(PACKAGE_NAME).html.pot po/$(PACKAGE_NAME).js.pot po/$(PACKAGE_NAME).manifest.pot po/$(PACKAGE_NAME).pot: po/$(PACKAGE_NAME).html.pot po/$(PACKAGE_NAME).js.pot po/$(PACKAGE_NAME).manifest.pot po/$(PACKAGE_NAME).metainfo.pot
msgcat --sort-output --output-file=$@ $^ msgcat --sort-output --output-file=$@ $^
# Update translations against current PO template po/LINGUAS:
update-po: po/$(PACKAGE_NAME).pot echo $(LINGUAS) | tr ' ' '\n' > $@
for lang in $(LINGUAS); do \
msgmerge --output-file=po/$$lang.po po/$$lang.po $<; \
done
dist/po.%.js: po/%.po $(NODE_MODULES_TEST)
mkdir -p $(dir $@)
po/po2json -m po/po.empty.js -o $@.js.tmp $<
mv $@.js.tmp $@
# #
# Build/Install/dist # Build/Install/dist
# #
%.spec: %.spec.in $(SPEC): packaging/$(SPEC).in $(NODE_MODULES_TEST)
sed -e 's/%{VERSION}/$(VERSION)/g' $< > $@ provides=$$(npm ls --omit dev --package-lock-only --depth=Infinity | grep -Eo '[^[:space:]]+@[^[:space:]]+' | sort -u | sed 's/^/Provides: bundled(npm(/; s/\(.*\)@/\1)) = /'); \
awk -v p="$$provides" '{gsub(/%{VERSION}/, "$(VERSION)"); gsub(/%{NPM_PROVIDES}/, p)}1' $< > $@
$(WEBPACK_TEST): $(NODE_MODULES_TEST) $(LIB_TEST) $(shell find src/ -type f) package.json webpack.config.js $(patsubst %,dist/po.%.js,$(LINGUAS)) $(DIST_TEST): $(NODE_MODULES_TEST) $(COCKPIT_REPO_STAMP) $(shell find src/ -type f) package.json build.js
NODE_ENV=$(NODE_ENV) node_modules/.bin/webpack NODE_ENV=$(NODE_ENV) ./build.js
watch: watch:
NODE_ENV=$(NODE_ENV) node_modules/.bin/webpack --watch NODE_ENV=$(NODE_ENV) npm run watch
clean: clean:
rm -rf dist/ rm -rf dist/
[ ! -e cockpit-$(PACKAGE_NAME).spec.in ] || rm -f cockpit-$(PACKAGE_NAME).spec rm -f $(SPEC)
rm -f po/LINGUAS
install: $(WEBPACK_TEST) install: $(DIST_TEST) po/LINGUAS
mkdir -p $(DESTDIR)/usr/share/cockpit/$(PACKAGE_NAME) mkdir -p $(DESTDIR)$(PREFIX)/share/cockpit/$(PACKAGE_NAME)
cp -r dist/* $(DESTDIR)/usr/share/cockpit/$(PACKAGE_NAME) cp -r dist/* $(DESTDIR)$(PREFIX)/share/cockpit/$(PACKAGE_NAME)
mkdir -p $(DESTDIR)/usr/share/metainfo/ mkdir -p $(DESTDIR)$(PREFIX)/share/metainfo/
cp org.cockpit-project.$(PACKAGE_NAME).metainfo.xml $(DESTDIR)/usr/share/metainfo/ msgfmt --xml -d po \
--template $(APPSTREAMFILE) \
-o $(DESTDIR)$(PREFIX)/share/metainfo/$(APPSTREAMFILE)
# this requires a built source tree and avoids having to install anything system-wide # this requires a built source tree and avoids having to install anything system-wide
devel-install: $(WEBPACK_TEST) devel-install: $(DIST_TEST)
mkdir -p ~/.local/share/cockpit mkdir -p ~/.local/share/cockpit
ln -s `pwd`/dist ~/.local/share/cockpit/$(PACKAGE_NAME) ln -s `pwd`/dist ~/.local/share/cockpit/$(PACKAGE_NAME)
dist: $(TARFILE) # assumes that there was symlink set up using the above devel-install target,
# and removes it
devel-uninstall:
rm -f ~/.local/share/cockpit/$(PACKAGE_NAME)
# when building a distribution tarball, call webpack with a 'production' environment print-version:
@echo "$(VERSION)"
dist: $(TARFILE)
@ls -1 $(TARFILE)
# when building a distribution tarball, call bundler with a 'production' environment
# we don't ship node_modules for license and compactness reasons; we ship a # we don't ship node_modules for license and compactness reasons; we ship a
# pre-built dist/ (so it's not necessary) and ship packge-lock.json (so that # pre-built dist/ (so it's not necessary) and ship package-lock.json (so that
# node_modules/ can be reconstructed if necessary) # node_modules/ can be reconstructed if necessary)
$(TARFILE): export NODE_ENV=production $(TARFILE): export NODE_ENV=production
$(TARFILE): $(WEBPACK_TEST) cockpit-$(PACKAGE_NAME).spec $(TARFILE): $(DIST_TEST) $(SPEC)
if type appstream-util >/dev/null 2>&1; then appstream-util validate-relax --nonet *.metainfo.xml; fi if type appstream-util >/dev/null 2>&1; then appstream-util validate-relax --nonet *.metainfo.xml; fi
touch -r package.json $(NODE_MODULES_TEST) tar --xz $(TAR_ARGS) -cf $(TARFILE) --transform 's,^,$(RPM_NAME)/,' \
touch dist/* --exclude packaging/$(SPEC).in --exclude node_modules \
tar --xz -cf cockpit-$(PACKAGE_NAME)-$(VERSION).tar.xz --transform 's,^,cockpit-$(PACKAGE_NAME)/,' \ $$(git ls-files) $(COCKPIT_REPO_FILES) $(NODE_MODULES_TEST) $(SPEC) dist/
--exclude cockpit-$(PACKAGE_NAME).spec.in --exclude node_modules \
$$(git ls-files) src/lib package-lock.json cockpit-$(PACKAGE_NAME).spec dist/
srpm: $(TARFILE) cockpit-$(PACKAGE_NAME).spec $(NODE_CACHE): $(NODE_MODULES_TEST)
tar --xz $(TAR_ARGS) -cf $@ node_modules
node-cache: $(NODE_CACHE)
# convenience target for developers
srpm: $(TARFILE) $(NODE_CACHE) $(SPEC)
rpmbuild -bs \ rpmbuild -bs \
--define "_sourcedir `pwd`" \ --define "_sourcedir `pwd`" \
--define "_srcrpmdir `pwd`" \ --define "_srcrpmdir `pwd`" \
cockpit-$(PACKAGE_NAME).spec $(SPEC)
rpm: $(RPMFILE) # convenience target for developers
rpm: $(TARFILE) $(NODE_CACHE) $(SPEC)
$(RPMFILE): $(TARFILE) cockpit-$(PACKAGE_NAME).spec
mkdir -p "`pwd`/output" mkdir -p "`pwd`/output"
mkdir -p "`pwd`/rpmbuild" mkdir -p "`pwd`/rpmbuild"
rpmbuild -bb \ rpmbuild -bb \
@ -126,55 +150,58 @@ $(RPMFILE): $(TARFILE) cockpit-$(PACKAGE_NAME).spec
--define "_srcrpmdir `pwd`" \ --define "_srcrpmdir `pwd`" \
--define "_rpmdir `pwd`/output" \ --define "_rpmdir `pwd`/output" \
--define "_buildrootdir `pwd`/build" \ --define "_buildrootdir `pwd`/build" \
cockpit-$(PACKAGE_NAME).spec $(SPEC)
find `pwd`/output -name '*.rpm' -printf '%f\n' -exec mv {} . \; find `pwd`/output -name '*.rpm' -printf '%f\n' -exec mv {} . \;
rm -r "`pwd`/rpmbuild" rm -r "`pwd`/rpmbuild"
rm -r "`pwd`/output" "`pwd`/build" rm -r "`pwd`/output" "`pwd`/build"
# sanity check
test -e "$(RPMFILE)"
# build a VM with locally built rpm installed ifeq ("$(TEST_SCENARIO)","pybridge")
$(VM_IMAGE): $(RPMFILE) bots COCKPIT_PYBRIDGE_REF = main
rm -f $(VM_IMAGE) $(VM_IMAGE).qcow2 COCKPIT_WHEEL = cockpit-0-py3-none-any.whl
bots/image-customize -v -i `pwd`/$(RPMFILE) -s $(CURDIR)/test/vm.install $(TEST_OS)
bots/image-customize -v -u ./test/files/1.journal:/var/log/journal/1.journal $(TEST_OS) $(COCKPIT_WHEEL):
bots/image-customize -v -u ./test/files/binary-rec.journal:/var/log/journal/binary-rec.journal $(TEST_OS) pip wheel git+https://github.com/cockpit-project/cockpit.git@${COCKPIT_PYBRIDGE_REF}
VM_DEPENDS = $(COCKPIT_WHEEL)
VM_CUSTOMIZE_FLAGS = --install $(COCKPIT_WHEEL)
endif
# build a VM with locally built distro pkgs installed
# disable networking, VM images have mock/pbuilder with the common build dependencies pre-installed
$(VM_IMAGE): $(TARFILE) $(NODE_CACHE) bots test/vm.install $(VM_DEPENDS)
bots/image-customize --fresh \
$(VM_CUSTOMIZE_FLAGS) \
--upload $(NODE_CACHE):/var/tmp/ --build $(TARFILE) \
--upload ./test/files/1.journal:/var/log/journal/1.journal \
--upload ./test/files/binary-rec.journal:/var/log/journal/binary-rec.journal \
--script $(CURDIR)/test/vm.install $(TEST_OS)
# convenience target for the above # convenience target for the above
vm: $(VM_IMAGE) vm: $(VM_IMAGE)
echo $(VM_IMAGE) @echo $(VM_IMAGE)
# convenience target to print the filename of the test image
print-vm:
@echo $(VM_IMAGE)
# convenience target to setup all the bits needed for the integration tests
# without actually running them
prepare-check: $(NODE_MODULES_TEST) $(VM_IMAGE) test/common
# run the browser integration tests; skip check for SELinux denials # run the browser integration tests; skip check for SELinux denials
# this will run all tests/check-* and format them as TAP # this will run all tests/check-* and format them as TAP
check: $(NODE_MODULES_TEST) $(VM_IMAGE) test/common check: prepare-check
TEST_AUDIT_NO_SELINUX=1 test/common/run-tests TEST_AUDIT_NO_SELINUX=1 test/common/run-tests ${RUN_TESTS_OPTIONS}
# checkout Cockpit's bots for standard test VM images and API to launch them # checkout Cockpit's bots for standard test VM images and API to launch them
# must be from master, as only that has current and existing images; but testvm.py API is stable bots: tools/make-bots
# support CI testing against a bots change tools/make-bots
bots:
git clone --quiet --reference-if-able $${XDG_CACHE_HOME:-$$HOME/.cache}/cockpit-project/bots https://github.com/cockpit-project/bots.git
if [ -n "$$COCKPIT_BOTS_REF" ]; then git -C bots fetch --quiet --depth=1 origin "$$COCKPIT_BOTS_REF"; git -C bots checkout --quiet FETCH_HEAD; fi
@echo "checked out bots/ ref $$(git -C bots rev-parse HEAD)"
# checkout Cockpit's test API; this has no API stability guarantee, so check out a stable tag
# when you start a new project, use the latest release, and update it from time to time
test/common:
git fetch --depth=1 https://github.com/cockpit-project/cockpit.git 267
git checkout --force FETCH_HEAD -- test/common
git reset test/common
# checkout Cockpit's PF/React/build library; again this has no API stability guarantee, so check out a stable tag
$(LIB_TEST):
git clone -b 256 --depth=1 https://github.com/cockpit-project/cockpit.git tmp/cockpit
mv tmp/cockpit/pkg/lib src/
rm -rf tmp/cockpit
$(NODE_MODULES_TEST): package.json $(NODE_MODULES_TEST): package.json
# if it exists already, npm install won't update it; force that so that we always get up-to-date packages # if it exists already, npm install won't update it; force that so that we always get up-to-date packages
rm -f package-lock.json rm -f package-lock.json
# unset NODE_ENV, skips devDependencies otherwise # unset NODE_ENV, skips devDependencies otherwise
env -u NODE_ENV npm install env -u NODE_ENV npm install --ignore-scripts
env -u NODE_ENV npm prune env -u NODE_ENV npm prune
.PHONY: all clean install devel-install dist srpm rpm check vm update-po .PHONY: all clean install devel-install devel-uninstall print-version dist node-cache rpm prepare-check check vm print-vm

View file

@ -17,6 +17,9 @@ GitHub Organization:
* [scribery.github.io](http://scribery.github.io/) * [scribery.github.io](http://scribery.github.io/)
* [Scribery](https://github.com/Scribery) * [Scribery](https://github.com/Scribery)
This project is based on the [Cockpit Starter Kit](https://github.com/cockpit-project/starter-kit).
See [Starter Kit Intro](http://cockpit-project.org/blog/cockpit-starter-kit.html) for details.
# Getting and building the source # Getting and building the source
Make sure you have `npm` available (usually from your distribution package). Make sure you have `npm` available (usually from your distribution package).
@ -30,15 +33,17 @@ make
# Installing # Installing
`make install` compiles and installs the package in `/usr/share/cockpit/`. The `make install` compiles and installs the package in `/usr/local/share/cockpit/`. The
convenience targets `srpm` and `rpm` build the source and binary rpms, convenience targets `srpm` and `rpm` build the source and binary rpms,
respectively. Both of these make use of the `dist-gzip` target, which is used respectively. Both of these make use of the `dist` target, which is used
to generate the distribution tarball. In `production` mode, source files are to generate the distribution tarball. In `production` mode, source files are
automatically minified and compressed. Set `NODE_ENV=production` if you want to automatically minified and compressed. Set `NODE_ENV=production` if you want to
duplicate this behavior. duplicate this behavior.
For development, you usually want to run your module straight out of the git For development, you usually want to run your module straight out of the git
tree. To do that, link that to the location were `cockpit-bridge` looks for packages: tree. To do that, run `make devel-install`, which links your checkout to the
location were cockpit-bridge looks for packages. If you prefer to do
this manually:
``` ```
mkdir -p ~/.local/share/cockpit mkdir -p ~/.local/share/cockpit
@ -48,12 +53,39 @@ ln -s `pwd`/dist ~/.local/share/cockpit/session-recording
After changing the code and running `make` again, reload the Cockpit page in After changing the code and running `make` again, reload the Cockpit page in
your browser. your browser.
You can also use
[watch mode](https://esbuild.github.io/api/#watch) to
automatically update the bundle on every code change with
$ npm run watch
or
$ make watch
When developing against a virtual machine, watch mode can also automatically upload
the code changes by setting the `RSYNC` environment variable to
the remote hostname.
$ RSYNC=c make watch
When developing against a remote host as a normal user, `RSYNC_DEVEL` can be
set to upload code changes to `~/.local/share/cockpit/` instead of
`/usr/local`.
$ RSYNC_DEVEL=example.com make watch
To "uninstall" the locally installed version, run `make devel-uninstall`, or
remove manually the symlink:
rm ~/.local/share/cockpit/starter-kit
# Running eslint # Running eslint
Cockpit Starter Kit uses [ESLint](https://eslint.org/) to automatically check Cockpit Starter Kit uses [ESLint](https://eslint.org/) to automatically check
JavaScript code style in `.jsx` and `.es6` files. JavaScript code style in `.js` and `.jsx` files.
The linter is executed within every build as a webpack preloader. eslint is executed within every build.
For developer convenience, the ESLint can be started explicitly by: For developer convenience, the ESLint can be started explicitly by:
@ -65,6 +97,49 @@ Violations of some rules can be fixed automatically by:
Rules configuration can be found in the `.eslintrc.json` file. Rules configuration can be found in the `.eslintrc.json` file.
# Credits ## Running stylelint
Cockpit-session-recording is based on [starter-kit](http://cockpit-project.org/blog/cockpit-starter-kit.html). Cockpit uses [Stylelint](https://stylelint.io/) to automatically check CSS code
style in `.css` and `scss` files.
styleint is executed within every build.
For developer convenience, the Stylelint can be started explicitly by:
$ npm run stylelint
Violations of some rules can be fixed automatically by:
$ npm run stylelint:fix
Rules configuration can be found in the `.stylelintrc.json` file.
During fast iterative development, you can also choose to not run eslint/stylelint.
This speeds up the build and avoids build failures due to e. g. ill-formatted
css or other issues:
$ make LINT=0
# Running tests locally
Run `make check` to build an RPM, install it into a standard Cockpit test VM
(centos-8-stream by default), and run the test/check-application integration test on
it. This uses Cockpit's Chrome DevTools Protocol based browser tests, through a
Python API abstraction. Note that this API is not guaranteed to be stable, so
if you run into failures and don't want to adjust tests, consider checking out
Cockpit's test/common from a tag instead of main (see the `test/common`
target in `Makefile`).
After the test VM is prepared, you can manually run the test without rebuilding
the VM, possibly with extra options for tracing and halting on test failures
(for interactive debugging):
TEST_OS=centos-8-stream test/check-application -tvs
It is possible to setup the test environment without running the tests:
TEST_OS=centos-8-stream make prepare-check
You can also run the test against a different Cockpit image, for example:
TEST_OS=fedora-34 make check

30
Vagrantfile vendored
View file

@ -1,30 +0,0 @@
Vagrant.configure(2) do |config|
config.vm.box = "fedora/28-cloud-base"
config.vm.network "forwarded_port", guest: 9090, host: 9090
if Dir.glob("dist/*").length == 0
config.vm.post_up_message = "NOTE: Distribution directory is empty. Run `make` to see your module show up in cockpit"
end
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.synced_folder "dist/", "/usr/local/share/cockpit/" + File.basename(Dir.pwd), type: "rsync", create: true
config.vm.provider "libvirt" do |libvirt|
libvirt.memory = 1024
end
config.vm.provider "virtualbox" do |virtualbox|
virtualbox.memory = 1024
end
config.vm.provision "shell", inline: <<-EOF
set -eu
sudo dnf install -y cockpit
printf "[WebService]\nAllowUnencrypted=true\n" > /etc/cockpit/cockpit.conf
systemctl enable cockpit.socket
systemctl start cockpit.socket
EOF
end

136
build.js Executable file
View file

@ -0,0 +1,136 @@
#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import copy from 'esbuild-plugin-copy';
import { cleanPlugin } from './pkg/lib/esbuild-cleanup-plugin.js';
import { cockpitCompressPlugin } from './pkg/lib/esbuild-compress-plugin.js';
import { cockpitPoEsbuildPlugin } from './pkg/lib/cockpit-po-plugin.js';
import { cockpitRsyncEsbuildPlugin } from './pkg/lib/cockpit-rsync-plugin.js';
import { esbuildStylesPlugins } from './pkg/lib/esbuild-common.js';
import { eslintPlugin } from './pkg/lib/esbuild-eslint-plugin.js';
import { stylelintPlugin } from './pkg/lib/esbuild-stylelint-plugin.js';
const useWasm = os.arch() !== 'x64';
const esbuild = (await import(useWasm ? 'esbuild-wasm' : 'esbuild')).default;
const production = process.env.NODE_ENV === 'production';
const watchMode = process.env.ESBUILD_WATCH === "true";
// linters dominate the build time, so disable them for production builds by default, but enable in watch mode
const lint = process.env.LINT ? (process.env.LINT !== 0) : (watchMode || !production);
// List of directories to use when using import statements
const nodePaths = ['pkg/lib'];
const outdir = 'dist';
// Obtain package name from package.json
const packageJson = JSON.parse(fs.readFileSync('package.json'));
function notifyEndPlugin() {
return {
name: 'notify-end',
setup(build) {
let startTime;
build.onStart(() => {
startTime = new Date();
});
build.onEnd(() => {
const endTime = new Date();
const timeStamp = endTime.toTimeString().split(' ')[0];
console.log(`${timeStamp}: Build finished in ${endTime - startTime} ms`);
});
}
};
}
const cwd = process.cwd();
// similar to fs.watch(), but recursively watches all subdirectories
function watch_dirs(dir, on_change) {
const callback = (ev, dir, fname) => {
// only listen for "change" events, as renames are noisy
// ignore hidden files
const isHidden = /^\./.test(fname);
if (ev !== "change" || isHidden) {
return;
}
on_change(path.join(dir, fname));
};
fs.watch(dir, {}, (ev, path) => callback(ev, dir, path));
// watch all subdirectories in dir
const d = fs.opendirSync(dir);
let dirent;
while ((dirent = d.readSync()) !== null) {
if (dirent.isDirectory())
watch_dirs(path.join(dir, dirent.name), on_change);
}
d.closeSync();
}
const context = await esbuild.context({
...!production ? { sourcemap: "linked" } : {},
bundle: true,
entryPoints: ['./src/index.js'],
external: ['*.woff', '*.woff2', '*.jpg', '*.svg', '../../assets*'], // Allow external font files which live in ../../static/fonts
legalComments: 'external', // Move all legal comments to a .LEGAL.txt file
loader: { ".js": "jsx" },
minify: production,
nodePaths,
outdir,
target: ['es2020'],
plugins: [
cleanPlugin(),
...lint
? [
stylelintPlugin({ filter: new RegExp(cwd + '\/src\/.*\.(css?|scss?)$') }),
eslintPlugin({ filter: new RegExp(cwd + '\/src\/.*\.(jsx?|js?)$') })
]
: [],
// Esbuild will only copy assets that are explicitly imported and used
// in the code. This is a problem for index.html and manifest.json which are not imported
copy({
assets: [
{ from: ['./src/manifest.json'], to: ['./manifest.json'] },
{ from: ['./src/index.html'], to: ['./index.html'] },
]
}),
...esbuildStylesPlugins,
cockpitPoEsbuildPlugin(),
...production ? [cockpitCompressPlugin()] : [],
cockpitRsyncEsbuildPlugin({ dest: packageJson.name }),
notifyEndPlugin(),
]
});
try {
await context.rebuild();
} catch (e) {
if (!watchMode)
process.exit(1);
// ignore errors in watch mode
}
if (watchMode) {
const on_change = async path => {
console.log("change detected:", path);
await context.cancel();
try {
await context.rebuild();
} catch (e) {} // ignore in watch mode
};
watch_dirs('src', on_change);
// wait forever until Control-C
await new Promise(() => {});
}
context.dispose();

View file

@ -1,31 +0,0 @@
# This is a script run to release welder-web through Cockpituous:
# https://github.com/cockpit-project/cockpituous/tree/master/release
# Anything that start with 'job' may run in a way that it SIGSTOP's
# itself when preliminary preparition and then gets a SIGCONT in
# order to complete its work.
#
# Check cockpituous documentation for available release targets.
RELEASE_SOURCE="_release/source"
RELEASE_SPEC="cockpit-session-recording.spec"
RELEASE_SRPM="_release/srpm"
job release-source
job release-srpm -V
# Once you have a Fedora package and add the https://pagure.io/user/cockpit
# user to your project's maintainers, you can also upload to Fedora automatically:
## Authenticate for pushing into Fedora dist-git (works in Cockpituous release container)
# cat ~/.fedora-password | kinit cockpit@FEDORAPROJECT.ORG
## Do fedora builds for the tag, using tarball
# job release-koji -k master
# job release-koji f29
# job release-bodhi F29
# These are likely the first of your release targets; but run them after Fedora uploads,
# so that failures there will fail the release early, before publishing on GitHub
# job release-github
# job release-copr @myorg/myrepo

View file

@ -1,17 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<component type="addon"> <component type="addon">
<id>org.cockpit-project.session-recording</id> <id>org.cockpit_project.session-recording</id>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<name>Session Recording</name> <name>Session Recording</name>
<summary> <summary>Session Recording module for Cockpit</summary>
Session Recording module for Cockpit
</summary>
<description> <description>
<p> <p>
Provides Session Recording module for Cockpit. Provides list of recorded by tlog terminal sessions from Journal. Provides Session Recoridng moduel for Cockpit. Provides list of recorded by tlog terminal sessions from Journal.
Allows to play them in a player with various controls. Shows correlated logs which happened during session. Allows to play them in a player with various controls. Shows correlated logs which happened during session.
</p> </p>
</description> </description>
<extends>org.cockpit_project.cockpit</extends> <extends>org.cockpit_project.cockpit</extends>
<launchable type="cockpit-manifest">session-recording</launchable> <launchable type="cockpit-manifest">session-recording</launchable>
<url type="homepage">https://github.com/Scribery/cockpit-session-recording</url>
<url type="bugtracker">https://github.com/Scribery/cockpit-session-recording/issues</url>
<update_contact>cockpit-devel_AT_lists.fedorahosted.org</update_contact>
</component> </component>

View file

@ -1,65 +1,51 @@
{ {
"name": "session-recording", "name": "session-recording",
"version": "0.1.0",
"description": "Module for Cockpit which provides session recording configuration and playback", "description": "Module for Cockpit which provides session recording configuration and playback",
"type": "module",
"main": "index.js", "main": "index.js",
"repository": "git@github.com:Scribery/cockpit-session-recording.git", "repository": "git@github.com:Scribery/cockpit-session-recording.git",
"author": "", "author": "",
"license": "LGPL-2.1", "license": "LGPL-2.1",
"scripts": { "scripts": {
"watch": "webpack --watch", "watch": "ESBUILD_WATCH='true' ./build.js",
"build": "webpack", "build": "./build.js",
"eslint": "eslint --ext .js --ext .jsx src/", "eslint": "eslint --ext .js --ext .jsx src/",
"eslint:fix": "eslint --fix --ext .js --ext .jsx src/" "eslint:fix": "eslint --fix --ext .js --ext .jsx src/",
"stylelint": "stylelint src/*{.css,scss}",
"stylelint:fix": "stylelint --fix src/*{.css,scss}"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.12", "argparse": "^2.0.1",
"@babel/eslint-parser": "^7.19.1", "chrome-remote-interface": "^0.32.1",
"@babel/preset-env": "^7.20.2", "esbuild": "^0.17.15",
"@babel/preset-react": "^7.0.0", "esbuild-plugin-copy": "^2.1.1",
"babel-loader": "^9.1.2", "esbuild-plugin-replace": "^1.3.0",
"chrome-remote-interface": "^0.32.0", "esbuild-sass-plugin": "^2.8.0",
"compression-webpack-plugin": "^10.0.0", "esbuild-wasm": "^0.17.16",
"copy-webpack-plugin": "^11.0.0", "eslint": "^8.13.0",
"css-loader": "^6.7.3", "eslint-config-react-app": "^7.0.0",
"eslint": "^8.31.0", "eslint-config-standard": "^17.0.0-1",
"eslint-config-standard": "^17.0.0", "eslint-config-standard-jsx": "^11.0.0-1",
"eslint-config-standard-react": "^9.2.0",
"eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.27.4", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.14.3", "eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-hooks": "^4.4.0",
"eslint-plugin-standard": "^4.1.0",
"eslint-webpack-plugin": "^3.2.0",
"htmlparser": "^1.7.7", "htmlparser": "^1.7.7",
"jed": "^1.1.1", "jed": "^1.1.1",
"mini-css-extract-plugin": "^2.7.2",
"po2json": "^1.0.0-alpha", "po2json": "^1.0.0-alpha",
"sass": "^1.57.1", "qunit": "^2.9.3",
"sass-loader": "^13.2.0", "sass": "^1.61.0",
"sizzle": "^2.3.9", "sizzle": "^2.3.3",
"stdio": "^2.1.1", "stylelint": "^14.9.1",
"string-replace-loader": "^3.1.0", "stylelint-config-standard-scss": "^5.0.0",
"webpack": "^5.75.0", "stylelint-formatter-pretty": "^3.2.0"
"webpack-cli": "^5.0.1"
}, },
"dependencies": { "dependencies": {
"@patternfly/patternfly": "4.132.2", "@patternfly/patternfly": "4.224.4",
"@patternfly/react-core": "4.152.4", "@patternfly/react-core": "4.276.9",
"@patternfly/react-icons": "^4.19.8", "react": "17.0.2",
"@patternfly/react-table": "4.29.58", "react-dom": "17.0.2"
"buffer": "^6.0.3",
"comment-json": "^4.2.3",
"core-js": "3.27.1",
"date-fns": "2.29.3",
"ini": "^3.0.1",
"jquery": "3.6.3",
"raw-loader": "^4.0.2",
"react": "16.13.1",
"react-dom": "16.13.1",
"throttle-debounce": "5.0.0",
"xterm": "^3.14.5"
} }
} }

View file

@ -7,26 +7,37 @@ URL: https://github.com/Scribery/%{name}
Source: https://github.com/Scribery/%{name}/releases/download/%{version}/%{name}-%{version}.tar.xz Source: https://github.com/Scribery/%{name}/releases/download/%{version}/%{name}-%{version}.tar.xz
BuildArch: noarch BuildArch: noarch
BuildRequires: libappstream-glib BuildRequires: nodejs
BuildRequires: make BuildRequires: make
BuildRequires: libappstream-glib
BuildRequires: gettext
%if 0%{?rhel} && 0%{?rhel} <= 8
BuildRequires: libappstream-glib-devel
%endif
Requires: cockpit-system Requires: cockpit-system
Requires: tlog Requires: tlog
%{NPM_PROVIDES}
%description %description
Cockpit module providing session recording configuration and playback. Cockpit module providing session recording configuration and playback.
This module allows viewing and playback of journal-stored terminal session This module allows viewing and playback of journal-stored terminal session
recordings generated by the tlog component. recordings generated by the tlog component.
%prep %prep
%setup -qn cockpit-session-recording %setup -q -n %{name}
%build
# Nothing to build
%install %install
%make_install %make_install PREFIX=/usr
appstream-util validate-relax --nonet %{buildroot}/%{_datadir}/metainfo/* appstream-util validate-relax --nonet %{buildroot}/%{_datadir}/metainfo/*
%files %files
%{_datadir}/cockpit/session-recording %{_datadir}/cockpit/*
%{_datadir}/metainfo/org.cockpit-project.session-recording.metainfo.xml %{_datadir}/metainfo/*
%changelog %changelog
* Wed Jan 13 2021 Justin Stephenson <jstephen@redhat.com> - 7-1 * Wed Jan 13 2021 Justin Stephenson <jstephen@redhat.com> - 7-1

View file

@ -4,27 +4,20 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: starter-kit 1.0\n" "Project-Id-Version: starter-kit 1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-08-29 00:14+0200\n" "POT-Creation-Date: 2022-03-09 16:09+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1\n"
#: src/index.html:20 #: src/app.jsx:43
msgid "Cockpit Starter Kit"
msgstr "Cockpit Bausatz"
#: src/app.jsx:42
msgid "Running on $0" msgid "Running on $0"
msgstr "Läuft auf $0" msgstr "Läuft auf $0"
#: src/manifest.json
msgid "Starter Kit"
msgstr "Bausatz"
#: src/app.jsx:29 #: src/app.jsx:29
msgid "Unknown" msgid "Unknown"
msgstr "Unbekannt" msgstr "Unbekannt"

View file

@ -1,264 +0,0 @@
#!/usr/bin/env node
/*
* Extracts translatable strings from HTML files in the following forms:
*
* <tag translate>String</tag>
* <tag translate context="value">String</tag>
* <tag translate="...">String</tag>
* <tag translate-attr attr="String"></tag>
*
* Supports the following Glade compatible forms:
*
* <tag translatable="yes">String</tag>
* <tag translatable="yes" context="value">String</tag>
*
* Supports the following angular-gettext compatible forms:
*
* <translate>String</translate>
* <tag translate-plural="Plural">Singular</tag>
*
* Note that some of the use of the translated may not support all the strings
* depending on the code actually using these strings to translate the HTML.
*/
function fatal(message, code) {
console.log((filename || "html2po") + ": " + message);
process.exit(code || 1);
}
function usage() {
console.log("usage: html2po input output");
process.exit(2);
}
var fs, htmlparser, path, stdio;
try {
fs = require('fs');
path = require('path');
htmlparser = require('htmlparser');
stdio = require('stdio');
} catch (ex) {
fatal(ex.message, 127); /* missing looks for this */
}
var opts = stdio.getopt({
directory: { key: "d", args: 1, description: "Base directory for input files" },
output: { key: "o", args: 1, description: "Output file" },
from: { key: "f", args: 1, description: "File containing list of input files" },
});
if (!opts.from && opts.args.length < 1) {
usage();
}
var input = opts.args;
var entries = { };
/* Filename being parsed and offset of line number */
var filename = null;
var offsets = 0;
/* The HTML parser we're using */
var handler = new htmlparser.DefaultHandler(function(error, dom) {
if (error)
fatal(error);
else
walk(dom);
});
prepare();
/* Decide what input files to process */
function prepare() {
if (opts.from) {
fs.readFile(opts.from, { encoding: "utf-8"}, function(err, data) {
if (err)
fatal(err.message);
input = data.split("\n").filter(function(value) {
return !!value;
}).concat(input);
step();
});
} else {
step();
}
}
/* Now process each file in turn */
function step() {
filename = input.shift();
if (filename === undefined) {
finish();
return;
}
/* Qualify the filename if necessary */
var full = filename;
if (opts.directory)
full = path.join(opts.directory, filename);
fs.readFile(full, { encoding: "utf-8"}, function(err, data) {
if (err)
fatal(err.message);
var parser = new htmlparser.Parser(handler, { includeLocation: true });
parser.parseComplete(data);
step();
});
}
/* Process an array of nodes */
function walk(children) {
if (!children)
return;
children.forEach(function(child) {
var line = (child.location || { }).line || 0;
var offset = line - 1;
/* Scripts get their text processed as HTML */
if (child.type == 'script' && child.children) {
var parser = new htmlparser.Parser(handler, { includeLocation: true });
/* Make note of how far into the outer HTML file we are */
offsets += offset;
child.children.forEach(function(node) {
parser.parseChunk(node.raw);
});
parser.done();
offsets -= offset;
/* Tags get extracted as usual */
} else if (child.type == 'tag') {
tag(child);
}
});
}
/* Process a single loaded tag */
function tag(node) {
var tasks, line, entry;
var attrs = node.attribs || { };
var nest = true;
/* Extract translate strings */
if ("translate" in attrs || "translatable" in attrs) {
tasks = (attrs["translate"] || attrs["translatable"] || "yes").split(" ");
/* Calculate the line location taking into account nested parsing */
line = (node.location || { })["line"] || 0;
line += offsets;
entry = {
msgctxt: attrs['translate-context'] || attrs['context'],
msgid_plural: attrs['translate-plural'],
locations: [ filename + ":" + line ]
};
/* For each thing listed */
tasks.forEach(function(task) {
var copy = Object.assign({}, entry);
/* The element text itself */
if (task == "yes" || task == "translate") {
copy.msgid = extract(node.children);
nest = false;
/* An attribute */
} else if (task) {
copy.msgid = attrs[task];
}
if (copy.msgid)
push(copy);
});
}
/* Walk through all the children */
if (nest)
walk(node.children);
}
/* Push an entry onto the list */
function push(entry) {
var key = entry.msgid + "\0" + entry.msgid_plural + "\0" + entry.msgctxt;
var prev = entries[key];
if (prev) {
prev.locations = prev.locations.concat(entry.locations);
} else {
entries[key] = entry;
}
}
/* Extract the given text */
function extract(children) {
if (!children)
return null;
var i, len, node, str = [];
children.forEach(function(node) {
if (node.type == 'tag' && node.children)
str.push(extract(node.children))
else if (node.type == 'text' && node.data)
str.push(node.data);
});
return str.join("");
}
/* Escape a string for inclusion in po file */
function escape(string) {
var bs = string.split('\\').join('\\\\').split('"').join('\\"');
return bs.split("\n").map(function(line) {
return '"' + line + '"';
}).join("\n");
}
/* Finish by writing out the strings */
function finish() {
var result = [
'msgid ""',
'msgstr ""',
'"Project-Id-Version: PACKAGE_VERSION\\n"',
'"MIME-Version: 1.0\\n"',
'"Content-Type: text/plain; charset=UTF-8\\n"',
'"Content-Transfer-Encoding: 8bit\\n"',
'"X-Generator: Cockpit html2po\\n"',
'',
];
var msgid, entry;
for (msgid in entries) {
entry = entries[msgid];
result.push('#: ' + entry.locations.join(" "));
if (entry.msgctxt)
result.push('msgctxt ' + escape(entry.msgctxt));
result.push('msgid ' + escape(entry.msgid));
if (entry.msgid_plural) {
result.push('msgid_plural ' + escape(entry.msgid_plural));
result.push('msgstr[0] ""');
result.push('msgstr[1] ""');
} else {
result.push('msgstr ""');
}
result.push('');
}
var data = result.join('\n');
if (!opts.output) {
process.stdout.write(data);
process.exit(0);
} else {
fs.writeFile(opts.output, data, function(err) {
if (err)
fatal(err.message);
process.exit(0);
});
}
}

View file

@ -1,185 +0,0 @@
#!/usr/bin/env node
/*
* Extracts translatable strings from manifest.json files.
*
*/
function fatal(message, code) {
console.log((filename || "manifest2po") + ": " + message);
process.exit(code || 1);
}
function usage() {
console.log("usage: manifest2po [-o output] input...");
process.exit(2);
}
var fs, path, stdio;
try {
fs = require('fs');
path = require('path');
stdio = require('stdio');
} catch (ex) {
fatal(ex.message, 127); /* missing looks for this */
}
var opts = stdio.getopt({
directory: { key: "d", args: 1, description: "Base directory for input files" },
output: { key: "o", args: 1, description: "Output file" },
from: { key: "f", args: 1, description: "File containing list of input files" },
});
if (!opts.from && opts.args.length < 1) {
usage();
}
var input = opts.args;
var entries = { };
/* Filename being parsed */
var filename = null;
prepare();
/* Decide what input files to process */
function prepare() {
if (opts.from) {
fs.readFile(opts.from, { encoding: "utf-8"}, function(err, data) {
if (err)
fatal(err.message);
input = data.split("\n").filter(function(value) {
return !!value;
}).concat(input);
step();
});
} else {
step();
}
}
/* Now process each file in turn */
function step() {
filename = input.shift();
if (filename === undefined) {
finish();
return;
}
if (path.basename(filename) != "manifest.json")
return step();
fs.readFile(filename, { encoding: "utf-8"}, function(err, data) {
if (err)
fatal(err.message);
process_manifest(JSON.parse(data));
return step();
});
}
function process_manifest(manifest) {
if (manifest.menu)
process_menu(manifest.menu);
if (manifest.tools)
process_menu(manifest.tools);
}
function process_keywords(keywords) {
keywords.forEach(v => {
v.matches.forEach(keyword =>
push({
msgid: keyword,
locations: [ filename ]
})
);
});
}
function process_docs(docs) {
docs.forEach(doc => {
push({
msgid: doc.label,
locations: [ filename ]
})
});
}
function process_menu(menu) {
for (var m in menu) {
if (menu[m].label) {
push({
msgid: menu[m].label,
locations: [ filename ]
});
}
if (menu[m].keywords)
process_keywords(menu[m].keywords);
if (menu[m].docs)
process_docs(menu[m].docs);
}
}
/* Push an entry onto the list */
function push(entry) {
var key = entry.msgid + "\0" + entry.msgid_plural + "\0" + entry.msgctxt;
var prev = entries[key];
if (prev) {
prev.locations = prev.locations.concat(entry.locations);
} else {
entries[key] = entry;
}
}
/* Escape a string for inclusion in po file */
function escape(string) {
var bs = string.split('\\').join('\\\\').split('"').join('\\"');
return bs.split("\n").map(function(line) {
return '"' + line + '"';
}).join("\n");
}
/* Finish by writing out the strings */
function finish() {
var result = [
'msgid ""',
'msgstr ""',
'"Project-Id-Version: PACKAGE_VERSION\\n"',
'"MIME-Version: 1.0\\n"',
'"Content-Type: text/plain; charset=UTF-8\\n"',
'"Content-Transfer-Encoding: 8bit\\n"',
'"X-Generator: Cockpit manifest2po\\n"',
'',
];
var msgid, entry;
for (msgid in entries) {
entry = entries[msgid];
result.push('#: ' + entry.locations.join(" "));
if (entry.msgctxt)
result.push('msgctxt ' + escape(entry.msgctxt));
result.push('msgid ' + escape(entry.msgid));
if (entry.msgid_plural) {
result.push('msgid_plural ' + escape(entry.msgid_plural));
result.push('msgstr[0] ""');
result.push('msgstr[1] ""');
} else {
result.push('msgstr ""');
}
result.push('');
}
var data = result.join('\n');
if (!opts.output) {
process.stdout.write(data);
process.exit(0);
} else {
fs.writeFile(opts.output, data, function(err) {
if (err)
fatal(err.message);
process.exit(0);
});
}
}

View file

@ -1,14 +0,0 @@
(function (root, data) {
var loaded, module;
/* Load into Cockpit locale */
if (typeof cockpit === 'object') {
cockpit.locale(data)
loaded = true;
}
if (!loaded)
root.po = data;
/* The syntax of this line is important by po2json */
}(this, {"":{"language":"en"}}));

View file

@ -1,127 +0,0 @@
#!/usr/bin/env node
function fatal(message, code) {
console.log((filename || "html2po") + ": " + message);
process.exit(code || 1);
}
function usage() {
console.log("usage: po2json [--module=template.js] input output");
process.exit(2);
}
var fs, po2json, Jed, stdio;
try {
fs = require('fs');
po2json = require('po2json');
Jed = require('jed');
stdio = require('stdio');
} catch(ex) {
fatal(ex.message, 127); /* missing looks for this */
}
var argi = 2;
var filename = null;
var opts = stdio.getopt({
module: { key: "m", args: 1, description: "Module template to include" },
output: { key: "o", args: 1, description: "Output file" },
});
if (opts.args.length != 1) {
usage();
}
parse();
function prepareHeader(header) {
var body, statement, plurals = header["plural-forms"], ret = null;
if (plurals) {
try {
/* Check that the plural forms isn't being sneaky since we build a function here */
Jed.PF.parse(plurals);
} catch(ex) {
fatal("bad plural forms: " + ex.message, 1);
}
/* A function for the front end */
statement = header["plural-forms"];
if (statement[statement.length - 1] != ';')
statement += ';';
ret = 'function(n) {\nvar nplurals, plural;\n' + statement + '\nreturn plural;\n}';
/* Added back in later */
delete header["plural-forms"];
}
/* We don't need to be transferring this */
delete header["project-id-version"];
delete header["report-msgid-bugs-to"];
delete header["pot-creation-date"];
delete header["po-revision-date"];
delete header["last-translator"];
delete header["language-team"];
delete header["mime-version"];
delete header["content-type"];
delete header["content-transfer-encoding"];
return ret;
}
/* Parse and process the po data */
function parse() {
filename = opts.args[0];
po2json.parseFile(opts.args[0], { "fuzzy": true }, function(err, jsonData) {
var plurals, pos;
if (err)
fatal(err.message);
var header = jsonData[""];
if (header)
plurals = prepareHeader(header);
var data = JSON.stringify(jsonData, null, 1);
/* We know the brace in is the location to insert our function */
if (plurals) {
pos = data.indexOf('{', 1);
data = data.substr(0, pos + 1) + "'plural-forms':" + String(plurals) + "," + data.substr(pos + 1);
}
if (data == JSON.stringify({}))
finish("");
else
wrap(data);
});
}
/* Wrap the data if desired */
function wrap(data) {
if (opts.module) {
filename = opts.module;
fs.readFile(opts.module, { encoding: "utf-8" }, function(err, template) {
if (err)
fatal(err.message);
data = template.replace('{"":{"language":"en"}}', data);
finish(data);
});
} else {
finish(data);
}
}
/* Write it out */
function finish(data) {
if (opts.output) {
fs.writeFile(opts.output, data, function(err) {
if (err)
fatal(err.message);
process.exit(0);
});
} else {
process.stdout.write(data);
process.exit(0);
}
}

View file

@ -19,7 +19,6 @@
import cockpit from 'cockpit'; import cockpit from 'cockpit';
import React from 'react'; import React from 'react';
import './app.scss';
import View from "./recordings.jsx"; import View from "./recordings.jsx";
const _ = cockpit.gettext; const _ = cockpit.gettext;
@ -29,8 +28,7 @@ export class Application extends React.Component {
super(); super();
this.state = { hostname: _("Unknown") }; this.state = { hostname: _("Unknown") };
cockpit.file('/etc/hostname').read() cockpit.file('/etc/hostname').watch(content => {
.done((content) => {
this.setState({ hostname: content.trim() }); this.setState({ hostname: content.trim() });
}); });
} }

View file

@ -1,7 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- <!--
This file is part of Cockpit.
Copyright (C) 2017 Red Hat, Inc. Copyright (C) 2017 Red Hat, Inc.
Cockpit is free software; you can redistribute it and/or modify it Cockpit is free software; you can redistribute it and/or modify it
@ -15,25 +13,22 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License You should have received a copy of the GNU Lesser General Public License
along with Cockpit; If not, see <http://www.gnu.org/licenses/>. along with this package; If not, see <http://www.gnu.org/licenses/>.
--> -->
<html> <html lang="en">
<head> <head>
<title translate>Session Recording</title> <title translate>Cockpit Session Recording</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="index.css"> <link rel="stylesheet" href="index.css">
<script type="text/javascript" src="../base1/cockpit.js"></script>
<script type="text/javascript" src="po.js"></script>
<script type="text/javascript" src="index.js"></script> <script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="po.js"></script>
</head> </head>
<body class="pf-m-redhat-form"> <body class="pf-m-redhat-font">
<div class="ct-page-fill" id="app"></div> <div id="app"></div>
</body> </body>
</html> </html>

View file

@ -17,12 +17,11 @@
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>. * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/ */
import "./lib/patternfly/patternfly-4-cockpit.scss"; import "cockpit-dark-theme";
import "patternfly/patternfly-4-cockpit.scss";
import "core-js/stable";
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import { Application } from './app.jsx'; import { Application } from './app.jsx';
/* /*
* PF4 overrides need to come after the JSX components imports because * PF4 overrides need to come after the JSX components imports because
@ -31,8 +30,10 @@ import { Application } from './app.jsx';
* out of the dist/index.js and since it will maintain the order of the imported CSS, * out of the dist/index.js and since it will maintain the order of the imported CSS,
* the overrides will be correctly in the end of our stylesheet. * the overrides will be correctly in the end of our stylesheet.
*/ */
import "./lib/patternfly/patternfly-4-overrides.scss"; import "patternfly/patternfly-4-overrides.scss";
import './app.scss';
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
ReactDOM.render(React.createElement(Application, {}), document.getElementById('app')); const root = createRoot(document.getElementById('app'));
root.render(<Application />);
}); });

View file

@ -3,7 +3,7 @@
"name": "session-recording", "name": "session-recording",
"requires": { "requires": {
"cockpit": "122" "cockpit": "137"
}, },
"menu": { "menu": {

View file

@ -75,7 +75,7 @@
margin-right: 5px; margin-right: 5px;
} }
.pf-c-progress__indicator:after { .pf-c-progress__indicator::after {
content: ""; content: "";
position: relative; position: relative;
width: 20px; width: 20px;

View file

@ -623,7 +623,7 @@ const PacketBuffer = class {
function SearchEntry(props) { function SearchEntry(props) {
return ( return (
<span className="search-result"><a onClick={(e) => props.fastForwardToTS(props.pos, e)}>{formatDuration(props.pos)}</a></span> <span className="search-result"><a href="#search-result" onClick={(e) => props.fastForwardToTS(props.pos, e)}>{formatDuration(props.pos)}</a></span>
); );
} }

View file

@ -13,10 +13,12 @@ import configparser
TEST_DIR = os.path.dirname(__file__) TEST_DIR = os.path.dirname(__file__)
sys.path.append(os.path.join(TEST_DIR, "common")) sys.path.append(os.path.join(TEST_DIR, "common"))
sys.path.append(os.path.join(os.path.dirname(TEST_DIR), "bots/machine")) sys.path.append(os.path.join(os.path.dirname(TEST_DIR), "bots/machine"))
from testlib import * import testlib
# Test with pre-recorded journal files # Nondestructive tests all run in the same running VM. This allows them to run in Packit, Fedora, and RHEL dist-git gating
class TestApplication(MachineCase): # They must not permanently change any file or configuration on the system in a way that influences other tests.
@testlib.nondestructive
class TestApplication(testlib.MachineCase):
def _login(self, loc="/session-recording", wait="#app"): def _login(self, loc="/session-recording", wait="#app"):
self.login_and_go(loc) self.login_and_go(loc)
b = self.browser b = self.browser
@ -408,4 +410,4 @@ class TestApplication(MachineCase):
b.wait_present("#app") b.wait_present("#app")
if __name__ == "__main__": if __name__ == "__main__":
test_main() testlib.test_main()

1
test/reference-image Normal file
View file

@ -0,0 +1 @@
fedora-36

View file

@ -4,5 +4,11 @@ set -eu
# This is the expected entry point for Cockpit CI; will be called without # This is the expected entry point for Cockpit CI; will be called without
# arguments but with an appropriate $TEST_OS, and optionally $TEST_SCENARIO # arguments but with an appropriate $TEST_OS, and optionally $TEST_SCENARIO
[ -z "${TEST_SCENARIO:-}" ] || export TEST_BROWSER="$TEST_SCENARIO" TEST_SCENARIO="${TEST_SCENARIO:-}"
[ "${TEST_SCENARIO}" = "${TEST_SCENARIO##firefox}" ] || export TEST_BROWSER=firefox
export RUN_TESTS_OPTIONS=--track-naughties
# linters are off by default for production builds, but we want to run them in CI
export LINT=1
make check make check

View file

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# image-customize script to enable cockpit in test VMs # image-customize script to prepare a bots VM for testing this application
# The application RPM will be installed separately # The application package will be installed separately
set -eu set -eux
# don't force https:// (self-signed cert) # don't force https:// (self-signed cert)
printf "[WebService]\\nAllowUnencrypted=true\\n" > /etc/cockpit/cockpit.conf printf "[WebService]\\nAllowUnencrypted=true\\n" > /etc/cockpit/cockpit.conf
@ -11,5 +11,5 @@ if type firewall-cmd >/dev/null 2>&1; then
fi fi
systemctl enable cockpit.socket systemctl enable cockpit.socket
# HACK: See https://github.com/cockpit-project/cockpit/issues/14133 # needed for testAppMenu
mkdir -p /usr/share/cockpit/packagekit dnf install -y cockpit-packagekit

View file

@ -1,202 +0,0 @@
const path = require("path");
const copy = require("copy-webpack-plugin");
const extract = require("mini-css-extract-plugin");
const fs = require("fs");
const webpack = require("webpack");
const CompressionPlugin = require("compression-webpack-plugin");
const ESLintPlugin = require('eslint-webpack-plugin');
var externals = {
cockpit: "cockpit",
};
/* These can be overridden, typically from the Makefile.am */
const srcdir = (process.env.SRCDIR || __dirname) + path.sep + "src";
const builddir = process.env.SRCDIR || __dirname;
const distdir = builddir + path.sep + "dist";
const section = process.env.ONLYDIR || null;
const libdir = path.resolve(srcdir, "lib")
// absolute path disables recursive module resolution, so build a relative one
const nodedir = path.relative(process.cwd(), path.resolve((process.env.SRCDIR || __dirname), "node_modules"));
/* A standard nodejs and webpack pattern */
var production = process.env.NODE_ENV === "production";
var info = {
entries: {
index: [
"./index.js",
],
},
files: [
"index.html",
"manifest.json"
],
};
var output = {
path: distdir,
filename: "[name].js",
sourceMapFilename: "[file].map",
};
// Non-JS files which are copied verbatim to dist/
const copy_files = [
"./src/index.html",
"./src/manifest.json",
];
/*
* Note that we're avoiding the use of path.join as webpack and nodejs
* want relative paths that start with ./ explicitly.
*
* In addition we mimic the VPATH style functionality of GNU Makefile
* where we first check builddir, and then srcdir.
*/
function vpath(/* ... */) {
var filename = Array.prototype.join.call(arguments, path.sep);
var expanded = builddir + path.sep + filename;
if (fs.existsSync(expanded)) return expanded;
expanded = srcdir + path.sep + filename;
return expanded;
}
/* Qualify all the paths in entries */
Object.keys(info.entries).forEach(function (key) {
if (section && key.indexOf(section) !== 0) {
delete info.entries[key];
return;
}
info.entries[key] = info.entries[key].map(function (value) {
if (value.indexOf("/") === -1) return value;
else return vpath(value);
});
});
/* Qualify all the paths in files listed */
var files = [];
info.files.forEach(function (value) {
if (!section || value.indexOf(section) === 0)
files.push({ from: vpath("src", value), to: value });
});
info.files = files;
const plugins = [
new copy({ patterns: copy_files }),
new extract({filename: "[name].css"}),
new ESLintPlugin({ extensions: ["js", "jsx"], exclude: ["spec", "node_modules", "src/lib"] }),
];
/* Only minimize when in production mode */
if (production) {
plugins.unshift(
new CompressionPlugin({
test: /\.(css|js|html)$/,
deleteOriginalAssets: true,
}));
}
var babel_loader = {
loader: "babel-loader",
options: {
presets: [
[
"@babel/env",
{
targets: {
chrome: "57",
firefox: "52",
safari: "10.3",
edge: "16",
opera: "44",
},
},
],
"@babel/preset-react",
],
},
};
module.exports = {
mode: production ? "production" : "development",
resolve: {
modules: [libdir, nodedir],
},
entry: info.entries,
externals: externals,
output: output,
devtool: "source-map",
module: {
rules: [
{
exclude: /node_modules/,
use: babel_loader,
test: /\.(js|jsx)$/,
},
/* HACK: remove unwanted fonts from PatternFly's css */
{
test: /patternfly-4-cockpit.scss$/,
use: [
extract.loader,
{
loader: "css-loader",
options: {
sourceMap: true,
url: false,
},
},
{
loader: "string-replace-loader",
options: {
multiple: [
{
search: /src:url\("patternfly-icons-fake-path\/pficon[^}]*/g,
replace: "src:url('fonts/patternfly.woff')format('woff');",
},
{
search: /@font-face[^}]*patternfly-fonts-fake-path[^}]*}/g,
replace: "",
},
],
},
},
{
loader: 'sass-loader',
options: {
sourceMap: !production,
sassOptions: {
outputStyle: production ? 'compressed' : undefined,
},
},
},
],
},
{
test: /\.s?css$/,
exclude: /patternfly-4-cockpit.scss/,
use: [
extract.loader,
{
loader: "css-loader",
options: {
sourceMap: true,
url: false,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: !production,
sassOptions: {
outputStyle: production ? 'compressed' : undefined,
},
},
},
],
},
],
},
plugins: plugins,
};