npm package: @lamtp3-jobs/agendash-i18n
Description: A modern dashboard for Agenda.js with Pagination and Search capabilities
Latest version: 1.0.3-alpha.1

# Agendash

A Dashboard for [Agenda](https://github.com/lamtp3/lamtp3-agenda-i18n).

---

### Features

- Job status auto-refreshes: 60-second polling by default.
- Schedule a new job from the UI.
- Dive in to see more details about the job, like the json data.
- Requeue a job. Clone the data and run immediately.
- Delete jobs. Useful for cleaning up old completed jobs.
- Search jobs by name and metadata. Supports querying by Mongo Object Id.
- Pagination
- Responsive UI

---

### Screenshots

#### Dashboard

![Auto-refresh list of jobs](all-jobs.png)

---

#### Create jobs

![See job details, requeue or delete jobs](create-job.png)

---

#### Search by name, metadata, job status

![Search for a job by name or metadata ](search.png)

---

#### Responsive UI

![Mobile UI small devices](mobile-ui-sm.png)

![Mobile UI extra small devices](mobile-ui-xs.png)

---

# Troubleshooting

### Index for sorting

It may be required to create the following index for faster sorting (see [#24](https://github.com/agenda/agendash/issues/24))

```
db.agendaJobs.ensureIndex({
    "nextRunAt" : -1,
    "lastRunAt" : -1,
    "lastFinishedAt" : -1
}, "agendash")
```

---

# Roadmap

- [ ] Get more test coverage
- [ ] Add middlewares for fastify, and other express-like libraries
- [ ] You decide! Submit a feature request

### Install

```
npm install --save @lamtp3-jobs/agendash-i18n
```

_Note_: `Agendash` requires mongodb version >2.6.0 to perform the needed aggregate queries. This is your mongo database version, not your node package version! To check your database version, connect to mongo and run `db.version()`.

### Middleware usage

#### Express

Agendash provides Express middleware you can use at a specified path, for example this will
make Agendash available on your site at the `/dash` path. Note: Do not try to mount Agendash
at the root level like `app.use('/', Agendash(agenda))`.

```js
var express = require("express");
var app = express();

// ... your other express middleware like body-parser

var Agenda = require("@lamtp3-jobs/agenda-i18n");
var Agendash = require("@lamtp3-jobs/agendash-i18n");

var agenda = new Agenda({ db: { address: "mongodb://127.0.0.1/agendaDb" } });
// or provide your own mongo client:
// var agenda = new Agenda({mongo: myMongoClient})

app.use(
  "/dash",
  Agendash(agenda, {
    // 1. Set default language
    language: "en",

    // 2. Define available languages in dropdown
    localeSettings: {
      en: { flag: "us.svg", name: "lang_en" },
      vi: { flag: "vn.svg", name: "lang_vi" },
      fr: {
        flag: "https://flagcdn.com/w40/fr.png",
        name: "lang_fr",
      },
    },

    // 3. Optional: Add CSP domains for external flag images
    cspDomains: ["https://flagcdn.com"],

    // 4. Customize translations PER LANGUAGE
    i18n: {
      // English overrides
      en: {
        agendash: "My Custom Dashboard",
        overview: "Overview",
        // ... see below for all available keys
      },
      // Vietnamese overrides
      vi: {
        agendash: "Bảng điều khiển của tôi",
        overview: "Tổng quan",
        // ... see below for all available keys
      },
      // French (new language - must provide all keys)
      fr: {
        lang_fr: "Français", // Language name in dropdown
        agendash: "Mon tableau de bord",
        overview: "Aperçu",
        // ... see below for all available keys
      },
    },
  })
);

// ... your other routes

app.listen(3000);
```

### Available i18n Keys

Each language object can contain the following keys. You only need to override the keys you want to customize:

```javascript
{
  // Language selector
  lang_vi: "Vietnamese",
  lang_en: "English (US)",

  // Main interface
  agendash: "Agendash",
  name: "Name",
  property: "Property",
  value: "Value",
  isObjectId: "Is ObjectId?",
  refreshInterval: "Refresh Interval",
  pageSize: "Page Size",
  state: "State",
  apply: "Apply",

  // Sidebar
  overview: "Overview",
  newJob: "New Job",

  // Job states
  total: "Total",
  scheduled: "Scheduled",
  queued: "Queued",
  running: "Running",
  completed: "Completed",
  failed: "Failed",
  repeating: "Repeating",

  // Job list
  jobsSelected: "jobs selected",
  multipleRequeue: "Multiple Requeue",
  multipleDelete: "Multiple Delete",
  status: "Status",
  name_col: "Name",
  lastRunStarted: "Last run started",
  nextRunStarts: "Next run starts",
  lastFinished: "Last finished",
  locked: "Locked",
  actions: "Actions",
  loadingJobs: "Loading Jobs...",

  // Pagination
  previous: "Previous",
  next: "Next",
  page: "Page",

  // Job actions
  requeue: "Requeue",
  jobData: "Job Data",
  deletePermanently: "Delete permanently",

  // Search
  jobName: "job name",
  dataColor: "data.color",
  green: "green",
  all: "All",
  multi: "Multi",

  // Job details
  locked_at: "Locked At",
  failCount: "Fail Count",
  failedAt: "Failed At",
  reason: "Reason",
  metadata: "Metadata",
  close: "Close",

  // Confirmation dialogs
  confirmDeleteTitle: "Confirm Delete Permanently",
  delete: "Delete",
  cancel: "Cancel",
  confirmRequeueTitle: "Confirm Requeue",
  confirmMultiDeleteTitle: "Confirm Multiple Delete",
  confirmMultiRequeueTitle: "Confirm Multiple Requeue",

  // New job form
  jobNameLabel: "Job Name",
  jobScheduleLabel: "Job Schedule",
  jobRepeatLabel: "Job Repeat Every",
  jobMetadataLabel: "Job Metadata",
  createJob: "Create Job",

  // Confirmation messages
  requeueSure: "Are you sure you want to requeue this job?",
  deleteSure: "Are you sure you want to delete this job?",
  humanIntervalHelper: 'Number/Every Unit i.e: "1 seconds" or "3 days"',

  // Success messages
  jobDeletedSuccess: "Job Deleted successfully",
  jobRequeueSuccess: "Job Requeue successfully",
  jobCreatedSuccess: "Job Created successfully"
}
```

**Example: Adding French language support**

```javascript
i18n: {
  fr: {
    lang_fr: "Français",
    agendash: "Tableau de bord",
    overview: "Aperçu",
    newJob: "Nouveau Job",
    total: "Total",
    scheduled: "Programmé",
    queued: "En file d'attente",
    running: "En cours",
    completed: "Terminé",
    failed: "Échoué",
    repeating: "Répétitif",
    jobsSelected: "jobs sélectionnés",
    multipleRequeue: "Relancer plusieurs",
    multipleDelete: "Supprimer plusieurs",
    // ... add other keys as needed
  }
}
```

By mounting Agendash as middleware on a specific path, you may provide your
own authentication for that path. For example if you have an authenticated
session using passport, you can protect the dashboard path like this:

```js
app.use(
  "/dash",
  function (req, res, next) {
    if (!req.user || !req.user.is_admin) {
      res.send(401);
    } else {
      next();
    }
  },
  Agendash(agenda)
);
```

Other middlewares will come soon in the folder `/lib/middlewares/`.
You'll just have to update the last line to require the middleware you need:

```js
app.use(
  "/agendash",
  Agendash(agenda, {
    middleware: "connect",
  })
);
```

Note that if you use a CSRF protection middleware like [`csurf`](https://www.npmjs.com/package/csurf), you might need to [configure it off](https://github.com/agenda/agendash/issues/23#issuecomment-270917949) for Agendash-routes.

#### Hapi

A minimum Node.js version 12 is required for `@hapi/hapi` dependency.

```shell
npm i @hapi/inert @hapi/hapi
```

```js
const Hapi = require("@hapi/hapi");
const Agendash = require("@lamtp3-jobs/agendash-i18n");

const server = Hapi.server({
  port: 3000,
  host: "localhost",
});

const init = async () => {
  await server.register({
    plugin: Agendash(agenda),
    options: {
      path: "/dash",
    },
  });

  await server.start();
  console.log("Server running at:", server.info.uri);
};

init();
```

Then browse to `http://localhost:3002/`.

#### Koa

```shell
npm i koa koa-bodyparser koa-router koa-static
```

```js
const Agenda = require("@lamtp3-jobs/agenda-i18n");
const agenda = new Agenda().database(
  "mongodb://127.0.0.1/agendaDb",
  "agendaJobs"
);

const Koa = require("koa");
const app = new Koa();
const Agendash = require("@lamtp3-jobs/agendash-i18n");
const middlewares = Agendash(agenda, {
  middleware: "koa",
});
for (const middleware of middlewares) {
  app.use(middleware);
}

app.listen(3002);
```

Then browse to `http://localhost:3002/`.

#### Fastify

```shell
npm i fastify
```

```js
const Agenda = require("@lamtp3-jobs/agenda-i18n");
const agenda = new Agenda().database(
  "mongodb://127.0.0.1/agendaDb",
  "agendaJobs"
);

const Fastify = require("fastify");
const fastify = new Fastify();
const Agendash = require("@lamtp3-jobs/agendash-i18n");

fastify.register(Agendash(agenda, { middleware: "fastify" }));

fastify.listen(3002);
```

Then browse to `http://localhost:3002/`.

### Standalone usage

Agendash comes with a standalone Express app which you can use like this:

```bash
./node_modules/.bin/agendash --db=mongodb://localhost/agendaDb --collection=agendaCollection --port=3002
```

or like this, for default collection `agendaJobs` and default port `3000`:

```bash
./node_modules/.bin/agendash --db=mongodb://localhost/agendaDb
```

If you are using npm >= 5.2, then you can use [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b):

```bash
npx @lamtp3-jobs/agendash-i18n --db=mongodb://localhost/agendaDb --collection=agendaCollection --port=3002
```

Then browse to `http://localhost:3002/`.

### Docker usage

Agendash can also be run within a Docker container like this:

```bash
docker run -p 3000:3000 \
  --env MONGODB_URI=mongo://myUser:myPass@myHost/myDb \
  --env COLLECTION=myAgendaCollection agenda/agendash
```

Then browse to `http://localhost:3000/`.