diff --git a/.github/assetatlas/docker-compose-ghcr-tailscale.yml b/.github/assetatlas/docker-compose-ghcr-tailscale.yml index 293e8d79..5d90205e 100644 --- a/.github/assetatlas/docker-compose-ghcr-tailscale.yml +++ b/.github/assetatlas/docker-compose-ghcr-tailscale.yml @@ -7,6 +7,7 @@ services: volumes: - assetatlas-config:/usr/src/app/config - assetatlas-tsbuild-cache:/usr/src/app/.tsbuildcache + - assetatlas-image-uploads:/usr/src/app/uploads env_file: secrets.env environment: NODE_ENV: production @@ -53,6 +54,19 @@ services: restart: unless-stopped network_mode: host + tusd: + image: tusproject/tusd:latest + command: + - "-upload-dir=/data" + - "-port=1080" + - "-verbose" + ports: + - "1080:1080" + volumes: + - assetatlas-image-uploads:/data + networks: + - default + networks: assetatlas-network: driver: bridge @@ -67,3 +81,5 @@ volumes: name: assetatlas-config assetatlas-tailscale-state: name: assetatlas-tailscale-state + assetatlas-image-uploads: + name: assetatlas-image-uploads diff --git a/.github/assetatlas/docker-compose-ghcr.yml b/.github/assetatlas/docker-compose-ghcr.yml index 25086490..dedfbe78 100644 --- a/.github/assetatlas/docker-compose-ghcr.yml +++ b/.github/assetatlas/docker-compose-ghcr.yml @@ -5,8 +5,8 @@ services: image: ${DOCKER_IMAGE:-ghcr.io/assetatlastracker/assetatlas:latest} pull_policy: always volumes: - - assetatlas-config:/usr/src/app/config - assetatlas-tsbuild-cache:/usr/src/app/.tsbuildcache + - assetatlas-image-uploads:/usr/src/app/uploads env_file: secrets.env environment: NODE_ENV: production @@ -37,6 +37,18 @@ services: environment: MONGO_URI: mongodb://mongo:27017/assetatlas_db + tusd: + image: tusproject/tusd:latest + command: + - "-upload-dir=/data" + - "-port=1080" + - "-verbose" + ports: + - "1080:1080" + volumes: + - assetatlas-image-uploads:/data + networks: + - default networks: assetatlas-network: @@ -50,3 +62,5 @@ volumes: name: assetatlas-tsbuild-cache assetatlas-config: name: assetatlas-config + assetatlas-image-uploads: + name: assetatlas-image-uploads diff --git a/.github/workflows/release-ghcr-files.yml b/.github/workflows/release-ghcr-files.yml index 50f66342..ccce3a74 100644 --- a/.github/workflows/release-ghcr-files.yml +++ b/.github/workflows/release-ghcr-files.yml @@ -54,7 +54,7 @@ jobs: - README.md - Instructions for running the containers - ConfiguringOauthProviders.md - Guide for setting up OAuth providers - - docker-compose-ghcr.yml - For runing locally + - docker-compose-ghcr.yml - For running locally - docker-compose-ghcr-tailscale.yml - For running with Tailscale networking You can download either the individual files or the complete ZIP archive. diff --git a/NEWUSERREADME.md b/NEWUSERREADME.md new file mode 100644 index 00000000..1b662a70 --- /dev/null +++ b/NEWUSERREADME.md @@ -0,0 +1,38 @@ +# AssetAtlas + +AssetAtlas is a self hosted inventory and asset management app that helps you organize, track, and manage everything you own. AssetAtlas gives you a clear, centralized view so you always know what you have and where it is, great for keeping track of items around the house or managing tools in a maker space. If this appeals to you, you can download the project [here](https://github.com/AssetAtlasTracker/AssetAtlas/releases/tag/ghcr-files-latest) or get started with development [here](https://github.com/AssetAtlasTracker/AssetAtlas/wiki/Getting-Started). + +

+ Overview Screen +

+ +## Simple to Use + +AssetAtlas is designed with simplicity in mind, offering an intuitive interface where every screen has a clear purpose. Creating and managing items is quick and easy, so you can stay organized without the learning curve. +

+ Blank Overview Screen +

+ +## Customizable to Your Needs + +As mentioned above, AssetAtlas is designed to be easy from the start, with a simple setup that lets you begin tracking items as soon as you download and run the application. As your needs grow, AssetAtlas also offers customization features like templates and custom tags to help you tailor the experience and streamline your workflow. +

+ Creating a New Item +

+ +## Alternative Views and Location Tracking + +AssetAtlas also offers an intuitive Tree View where you can visualize where your items are located as well as providing a structured, hierarchal view of your entire inventory. Since every item can be given a home location, you'll always be able to know where things belong and where they are now. +

+ Tree View and Home Location +

+ + +## Secure and Password Free + +AssetAtlas uses secure, password-free sign-in powered by trusted providers like Google and GitHub. That means no passwords to remember—and no sensitive credentials stored on our servers. + +For added security and offline access, you can also enable authentication through a separate verification app. +

+ Login Screen +

diff --git a/README.md b/README.md index f24a7621..0b353dbf 100644 --- a/README.md +++ b/README.md @@ -71,4 +71,4 @@ docker compose -f docker-compose-ghcr-tailscale.yml down ## Also -See [ConfiguringOauthProviders.md](./ConfiguringOauthProviders.md) for instructions on how to set up OAuth providers to allow users to log in and log out of their accounts +See [ConfiguringOauthProviders.md](./docs/ConfiguringOauthProviders.md) for instructions on how to set up OAuth providers to allow users to log in and log out of their accounts diff --git a/cspell.json b/cspell.json index 9bebe098..3cbe5cb4 100644 --- a/cspell.json +++ b/cspell.json @@ -6,13 +6,34 @@ "language": "en", // words - list of words to be always considered correct "words": [ + "assetatlas", "buildx", + "columnspan", + "customfields", "dbmdl", + "deleter", + "dropshadow", "gridfs", + "hoverable", + "hovertiptext", + "keyuri", + "noscroll", "oklch", + "otpauth", + "otplib", + "padx", + "pady", "pycache", "pyenv", + "qrcode", + "Recents", "rollup", + "skeletonlabs", + "softprops", + "subcard", + "tailscalecompose", + "topbar", + "totp", "vite" ], // flagWords - list of words to be always considered incorrect diff --git a/docker/Dockerfile b/docker/Dockerfile index 4206b937..f46c1078 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -9,42 +9,47 @@ RUN apk add --no-cache python3 make g++ iputils # Set working directory WORKDIR /usr/src/app -# Copy package files -COPY package.json package-lock.json* pnpm-lock.yaml* ./ +# Copy lockfiles first for better cache utilization +COPY package*.json ./ -# Install dependencies +# Install all dependencies (dev+prod) for build RUN npm ci --silent || npm install --silent -# Copy source files +# Copy source and build COPY . . - -# Build the app RUN npm run build FROM node:22-alpine AS production -# Install OS dependencies for Alpine +# Install runtime OS dependencies RUN apk add --no-cache python3 make g++ iputils WORKDIR /usr/src/app ENV NODE_ENV=production -# Copy package files +# Install only production deps COPY package.json package-lock.json* pnpm-lock.yaml* ./ RUN npm ci --omit=dev --silent || npm install --omit=dev --silent +RUN npm install migrate-mongo --no-audit --no-fund --silent || true -# Copy built output from builder stage (SvelteKit outputs to 'build' not 'dist') +# Copy built artifacts from build stage COPY --from=build /usr/src/app/build ./build COPY --from=build /usr/src/app/static ./static COPY --from=build /usr/src/app/package.json ./package.json COPY --from=build /usr/src/app/docker/migrate-mongo-docker-config.cjs ./ COPY --from=build /usr/src/app/docker/migrations ./migrations -# Create uploads directory with proper permissions -RUN mkdir -p /usr/src/app/uploads && chown -R node:node /usr/src/app/uploads +# Copy entrypoint (keeps existing script name) +COPY docker/entrypoint-ghcr.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Ensure node user has ownership of the app directory and uploads +RUN mkdir -p /usr/src/app/uploads && chown -R node:node /usr/src/app +# Run as non-root USER node EXPOSE 3000 +ENTRYPOINT ["/entrypoint.sh"] CMD ["node", "build/index.js"] \ No newline at end of file diff --git a/docker/Dockerfile.ghcr b/docker/Dockerfile.ghcr deleted file mode 100644 index 28ba5d8d..00000000 --- a/docker/Dockerfile.ghcr +++ /dev/null @@ -1,50 +0,0 @@ -FROM node:22-alpine AS build - -ARG APP_VERSION=dev -ENV APP_VERSION=$APP_VERSION - -# Install OS dependencies -RUN apk add --no-cache python3 make g++ iputils - -# Set working directory -WORKDIR /usr/src/app - -# Copy only package.json + package-lock.json to install dependencies first -COPY package*.json ./ - -# Install all (dev+prod) dependencies -RUN npm ci --silent || npm install --silent - -COPY . . - -# Build -RUN npm run build - -FROM node:22-alpine AS production - -# Install OS dependencies - no Tailscale here anymore -RUN apk add --no-cache python3 make g++ iputils - -WORKDIR /usr/src/app -ENV NODE_ENV=production - -# Copy package.json + lock to install only prod dependencies -COPY package.json package-lock.json* pnpm-lock.yaml* ./ -RUN npm ci --omit=dev --silent || npm install --omit=dev --silent -RUN npm install migrate-mongo - -# Copy over the dist folder from the build stage -COPY --from=build /usr/src/app/build ./build -COPY --from=build /usr/src/app/static ./static -COPY --from=build /usr/src/app/package.json ./package.json -COPY --from=build /usr/src/app/docker/migrate-mongo-docker-config.cjs ./ -COPY --from=build /usr/src/app/docker/migrations ./migrations - -# Copy entrypoint script -COPY docker/entrypoint-ghcr.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -EXPOSE 3000 - -ENTRYPOINT ["/entrypoint.sh"] -CMD ["node", "build/index.js"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 60441497..8f94722a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -13,8 +13,6 @@ services: args: APP_VERSION: ${APP_VERSION:-dev} BUILDKIT_INLINE_CACHE: "1" - cache_from: - - assetatlas:latest env_file: secrets.env environment: NODE_ENV: production diff --git a/docker/entrypoint-ghcr.sh b/docker/entrypoint-ghcr.sh index 45a2b99f..110f1036 100644 --- a/docker/entrypoint-ghcr.sh +++ b/docker/entrypoint-ghcr.sh @@ -1,19 +1,20 @@ -#!/bin/bash +#!/bin/sh set -e echo "Starting AssetAtlas..." +echo "Current working directory: $(pwd)" +echo "Running as user: $(id)" + +# Set working directory +cd /usr/src/app +echo "Changed to: $(pwd)" +echo "Files in current dir: $(ls -la)" # Create the .env file mkdir -p /usr/src/app/docker echo "IP=${IP:-localhost:3000}" > /usr/src/app/docker/.env echo "Created .env file at /usr/src/app/docker/.env" -# Ensure proper ownership of files -if [ "$(id -u)" = "0" ]; then - chown -R node:node /usr/src/app - # If running as root, switch to node user to run the application - echo "Switching to node user..." - exec su -c '"$0" "$@"' node -- "$@" -else - exec "$@" -fi +# Run the command passed in +echo "Executing: $@" +exec "$@" diff --git a/docker/migrations/20260407143553-add_pinned_field.cjs b/docker/migrations/20260407143553-add_pinned_field.cjs new file mode 100644 index 00000000..86ac4c84 --- /dev/null +++ b/docker/migrations/20260407143553-add_pinned_field.cjs @@ -0,0 +1,31 @@ +module.exports = { + /** + * @param db {import('mongodb').Db} + * @param client {import('mongodb').MongoClient} + * @returns {Promise} + */ + async up(db, client) { + // TODO write your migration here. + // See https://github.com/seppevs/migrate-mongo/#creating-a-new-migration-script + // Example: + // await db.collection('albums').updateOne({artist: 'The Beatles'}, {$set: {blacklisted: true}}); + const basicItemsCollection = db.collection('basicitems'); + await basicItemsCollection.updateMany( + { pinned: { $exists: false } }, + { $set: { pinned: false } } + ); + }, + + /** + * @param db {import('mongodb').Db} + * @param client {import('mongodb').MongoClient} + * @returns {Promise} + */ + async down(db, client) { + const basicItemsCollection = db.collection('basicitems'); + await basicItemsCollection.updateMany( + { pinned: { $exists: true } }, + { $unset: { pinned: "" } } + ); + } +}; diff --git a/docs/25-26-retrospective b/docs/25-26-retrospective new file mode 100644 index 00000000..9b202ebd --- /dev/null +++ b/docs/25-26-retrospective @@ -0,0 +1,76 @@ +# AssetAtlas 2025-2026 Project Team Retrospective + +## Things that worked well + +When starting out, working full-stack and focusing on learning: + +- the project itself + - folder structure + - tests + - CI/CD workflows + - development process + - branches, PRs/reviews + - tasks board +- tools/languages + - svelte + - typescript + - docker + +Keeping documentation updated + +- writing good documentation forces you to understand what you're writing about well enough to explain it concisely +- documentation will have to be updated eventually, so you might as well do it when the code/design changes you made are still fresh in your mind + +Keeping the codebase clean + +- auto-formatting, linting, spellcheck +- running tests +- adding tests as features are developed +- keeping old tests updated +- refactoring to reduce duplication + +Keeping Rob in the loop + +- getting feedback during live demos of our progress +- asking for clarification +- having him open bug reports as he found problems + +## Things we could have done better + +Breaking tasks into smaller pieces + +- our svelte 4 -> svelte 5 upgrade took longer than it should have and was more painful because it was done as one huge task by one team member + +Making releases more frequently + +- we rarely pushed releases and as a result we didn't become familiar with the process or how the workflows worked, and we're ending the year with a release system that needs improvement + +Reviewing PRs sooner + +- so that they don't pile up +- to avoid nasty merge conflicts caused by older code needing to be merged + +## Things we would have done if we had more time + +Better CI/CD workflows + +- figure out and improve release system +- block PRs from being merged if the `test` workflow fails or if the PR has not yet been reviewed + +Features that we originally had planned at the beginning of the year (our priority shifted from new features to refining what we had) + +- QR codes +- import/export + +Working with Rob to do more planning with the task board + +- breaking large tasks into sub-tasks +- refining the 10+ drafts in the 'unrefined' category +- getting more users and having them submit new bug reports and feature requests + +## Resources and other links + +- [svelte documentation](https://svelte.dev/docs/svelte/overview) +- [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) +- [competitors / existing solutions](https://docs.google.com/document/d/1svFfeDEdXfsJAAsR4LF6bOa_de3sMw0UCRunMBLZEUA/edit?tab=t.0) +- [lucide icons](https://lucide.dev/icons/) diff --git a/.github/assetatlas/ConfiguringOauthProviders.md b/docs/ConfiguringOauthProviders.md similarity index 100% rename from .github/assetatlas/ConfiguringOauthProviders.md rename to docs/ConfiguringOauthProviders.md diff --git a/docs/actionsWorkflows.md b/docs/actionsWorkflows.md new file mode 100644 index 00000000..80e7fa5b --- /dev/null +++ b/docs/actionsWorkflows.md @@ -0,0 +1,32 @@ +# Github Actions Workflows + +This project uses GitHub Actions continuous integration and continuous deployment (CI/CD) workflows. The goal of this document is to provide an overview of these workflows. + +As of May 2026 there are three workflows, located in `.github/workflows/`. Workflow 1 is considered mostly finalized, while workflows 2 and 3 are likely to change soon. + +Results from previous runs of the workflows can be found in the [Actions](https://github.com/AssetAtlasTracker/AssetAtlas/actions) tab of the project repo. + +--- + +## 1. Run Lint and Tests with Coverage (`test.yml`) + +This is the most frequently run workflow by far. + +- runs every time a PR to [main](https://github.com/AssetAtlasTracker/AssetAtlas) is opened or merged +- ensures there are no linting errors/warnings +- ensures all tests pass +- uploads a coverage report generated by the test runs + +--- + +## 2. Build and Publish Docker Images (`docker-publish.yml`) + +This workflow runs whenever the [production](https://github.com/AssetAtlasTracker/AssetAtlas/tree/production) branch is updated and creates [these release packages](https://github.com/AssetAtlasTracker/AssetAtlas/pkgs/container/assetatlas). It takes a while (5-10 minutes) to run. + +**Important detail:** when a pull request to [production](https://github.com/AssetAtlasTracker/AssetAtlas/tree/production) is opened, it will trigger the build (but not publish). The image will only be published when the PR is merged. This helps us avoid publishing broken builds. + +--- + +## 3. Create Release with Docker Files (`release-ghcr-files.yml`) + +This workflow also runs whenever the [production](https://github.com/AssetAtlasTracker/AssetAtlas/tree/production) branch is updated, and creates [these GHCR files](https://github.com/AssetAtlasTracker/AssetAtlas/releases/tag/ghcr-files-latest). diff --git a/docs/gitWorkflow.md b/docs/gitWorkflow.md deleted file mode 100644 index 9dd9e238..00000000 --- a/docs/gitWorkflow.md +++ /dev/null @@ -1,14 +0,0 @@ -# Git Workflow - -This project runs with a GitHub Actions continuous integration and continuous deployment systems and this document seeks to document the workings of said workflow. There are currently 2 main workflows used with integration and deployment, those workflows are documented in test.yml and docker-publish.yml - -## Test - -The most commonly used workflow is test.yml by far. It is run on every single commit and ensures that all tests still pass to prevent faulty code from being committed. The main events that occur in test are that it first checks out the repository before setting up Node and installing the necessary dependencies with `rm package-lock.json && npm install` then finally running the tests with `npm run test`. Additionally, it now also runs `npm run lint` for code coverage. It notably does not ensure that code has been auto formatted when committed as has been recently discovered. - -## Build and Publish - -This is the workflow triggered when a new build is being pushed to production. It takes a much longer time to run when compared to Test and has very different steps. This workflow also begins with -checking out the repository following which it sets up QEMU (which enables cross-platform builds) with `uses: docker/setup-qemu-action@v2`. It then Logs in to GitHub’s container registry using `uses: docker/login-action@v2` and the -automatically provided token. Following that it extracts the Docker metadata with `uses: docker/metadata-action@v4` and automatically generates image tags and labels. It then sets up Docker Buildx with `uses: docker/setup-buildx-action@v2` -before finally building and pushing the Docker image using `uses: docker/build-push-action@v4` and pushes it to GitHub. Note that pull requests targeting production will run the build but not push the image. diff --git a/images/AssetAtlasBlank.png b/images/AssetAtlasBlank.png new file mode 100644 index 00000000..6e4ef632 Binary files /dev/null and b/images/AssetAtlasBlank.png differ diff --git a/images/AssetAtlasBottleMoved.png b/images/AssetAtlasBottleMoved.png new file mode 100644 index 00000000..aa0bc183 Binary files /dev/null and b/images/AssetAtlasBottleMoved.png differ diff --git a/images/AssetAtlasLogin.png b/images/AssetAtlasLogin.png new file mode 100644 index 00000000..8e2a80e2 Binary files /dev/null and b/images/AssetAtlasLogin.png differ diff --git a/images/AssetAtlasNewItem.png b/images/AssetAtlasNewItem.png new file mode 100644 index 00000000..10e8bf25 Binary files /dev/null and b/images/AssetAtlasNewItem.png differ diff --git a/images/AssetAtlasOverview.png b/images/AssetAtlasOverview.png new file mode 100644 index 00000000..f5f44cab Binary files /dev/null and b/images/AssetAtlasOverview.png differ diff --git a/package-lock.json b/package-lock.json index 6d4caba9..3215b2ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8925,16 +8925,7 @@ } } }, - "node_modules/svelte/node_modules/aria-query": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/svelte-writable-derived": { + "node_modules/svelte-writable-derived": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/svelte-writable-derived/-/svelte-writable-derived-3.1.1.tgz", "integrity": "sha512-w4LR6/bYZEuCs7SGr+M54oipk/UQKtiMadyOhW0PTwAtJ/Ai12QS77sLngEcfBx2q4H8ZBQucc9ktSA5sUGZWw==", @@ -8945,7 +8936,16 @@ "peerDependencies": { "svelte": "^3.2.1 || ^4.0.0-next.1 || ^5.0.0-next.94" } - }, + }, + "node_modules/svelte/node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/svelte/node_modules/is-reference": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", diff --git a/package.json b/package.json index ebd6d20e..27de359d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "type": "module", "scripts": { - "build": "vite build", + "sync": "svelte-kit sync", + "build": "npm run sync && vite build", "start": "node build/index.js", "dev": "npm run dev:services && npx vite dev", "dev:services": "cd docker && docker-compose -f docker-compose.dev.yml up -d", diff --git a/src/lib/components/CreateItemDesktop.svelte b/src/lib/components/CreateItemDesktop.svelte index 08b2ba05..7532d8b6 100644 --- a/src/lib/components/CreateItemDesktop.svelte +++ b/src/lib/components/CreateItemDesktop.svelte @@ -1,9 +1,10 @@ - {}} close={resetAllFields}> + {}} + close={() => { + formContainer?.scrollTo(0,0); + resetAllFields(); + }} + requireCloseConfirmation={true} +> {#if originalItem}

Duplicate & Edit Item @@ -103,10 +113,11 @@ Create New Item

{/if} -
+
{ event.preventDefault(); + formContainer?.scrollTo(0,0); submitAndCloseItem(dialog, imageSelector); } }> @@ -318,7 +329,7 @@ if (field.dataType === 'item') { createItemState.fieldItemSuggestions = []; - checkIfItemExists(field.displayValue || '').then((itemId) => { + getItemIdGivenName(field.displayValue || '').then((itemId) => { if (itemId) { createItemState.customFields[index].value = itemId; return true; @@ -416,7 +427,8 @@ create={() => {}} close={() => { showTemplateSelectionDialog = false; - }}> + }} + >

Add Template

diff --git a/src/lib/components/CreateItemMobile.svelte b/src/lib/components/CreateItemMobile.svelte index c1b85ec3..fd432052 100644 --- a/src/lib/components/CreateItemMobile.svelte +++ b/src/lib/components/CreateItemMobile.svelte @@ -1,10 +1,11 @@ { + if (event.key === "Escape") { + handleClose(event); + } + }} class="glass dialog-component self-center {isLarge ? 'large-dialog-noscroll' : ''}" @@ -37,4 +71,21 @@ bind:this={dialog}> {@render children?.()} + {#if requireCloseConfirmation && isConfirmationOpen} +
+
+
+
Are you sure?
+
+ + +
+
+
+
+ {/if}
diff --git a/src/lib/components/EditItem.svelte b/src/lib/components/EditItem.svelte index 2062aa6a..b7f424d1 100644 --- a/src/lib/components/EditItem.svelte +++ b/src/lib/components/EditItem.svelte @@ -1,6 +1,7 @@
- {#if !getEditOnLogin() || ($login?.isLoggedIn && $login?.permissionLevel > 1)} + {#if permissionsAllowEdit(2)}
+ + {#if viewMode === "list"} {#if itemCount > 0} + {#if lastUpdatedItem} +
+ Last Edited Item: + + + + +
+ {/if} handleSearch(searchQuery)} @@ -319,6 +339,16 @@
{/if} {:else if showItemTree} + {#if lastUpdatedItem} + + + + + {/if} handleCloseWindow(window.id)} on:openNewTab={() => openInNewTab(window.id)}> @@ -368,7 +399,7 @@ {/each} {/if} - {#if !getEditOnLogin() || (currentLogin?.isLoggedIn && currentLogin?.permissionLevel > 1)} + {#if permissionsAllowEdit(2)}