mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) updates from grist-core
This commit is contained in:
commit
cea0404a22
107
README.md
107
README.md
@ -1,23 +1,12 @@
|
|||||||
# Grist
|
# Grist
|
||||||
|
|
||||||
Grist is a modern relational spreadsheet. It combines the flexibility of a spreadsheet with the
|
Grist is a modern relational spreadsheet. It combines the flexibility of a spreadsheet with the robustness of a database to organize your data and make you more productive.
|
||||||
robustness of a database to organize your data and make you more productive.
|
|
||||||
|
|
||||||
This repository, `grist-core`, is the heart of Grist, and has what you
|
This repository, `grist-core`, is the heart of Grist, and has what you need to run a powerful spreadsheet hosting server. If you wish to view and edit spreadsheets stored locally, another option is to use the [`grist-electron`](https://github.com/gristlabs/grist-electron) desktop app for Linux, Mac, and Windows. And to show Grist spreadsheets on a website without any special back-end support, you can use [`grist-static`](https://github.com/gristlabs/grist-static), a fully in-browser build of Grist.
|
||||||
need to run a powerful spreadsheet hosting server. If you wish to view and edit
|
|
||||||
spreadsheets stored locally, another option is to use the
|
The `grist-core` repository is the basis for all these options, and for the hosted spreadsheet services offered by [Grist Labs](https://getgrist.com), an NYC-based company 🇺🇸 that is the main developer of Grist, and by [ANCT Données et Territoires](https://donnees.incubateur.anct.gouv.fr/toolbox/grist), a French government agency 🇫🇷 whose developers have made many contributions to the codebase.
|
||||||
[`grist-electron`](https://github.com/gristlabs/grist-electron) desktop app for Linux, Mac, and Windows. And to show Grist spreadsheets on a website
|
|
||||||
without any special back-end support, your options include
|
The `grist-core`, `grist-electron`, and `grist-static` repositories are all open source (Apache License, Version 2.0).
|
||||||
[`grist-static`](https://github.com/gristlabs/grist-static),
|
|
||||||
a fully in-browser build of Grist.
|
|
||||||
The `grist-core` repository is the basis for all these options, and
|
|
||||||
for the hosted spreadsheet services offered by
|
|
||||||
[`Grist Labs`](https://getgrist.com), an NYC-based company 🇺🇸 that is the main developer of Grist, and by
|
|
||||||
[`ANCT Données et Territoires`](https://donnees.incubateur.anct.gouv.fr/toolbox/grist),
|
|
||||||
a French government agency 🇫🇷 whose developers have made many
|
|
||||||
contributions to the code-base.
|
|
||||||
The `grist-core`, `grist-electron`, and `grist-static` repositories
|
|
||||||
are all open-source (Apache License, Version 2.0).
|
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/118367/151245587-892e50a6-41f5-4b74-9786-fe3566f6b1fb.mp4
|
https://user-images.githubusercontent.com/118367/151245587-892e50a6-41f5-4b74-9786-fe3566f6b1fb.mp4
|
||||||
|
|
||||||
@ -25,81 +14,69 @@ https://user-images.githubusercontent.com/118367/151245587-892e50a6-41f5-4b74-97
|
|||||||
|
|
||||||
Grist is a hybrid database/spreadsheet, meaning that:
|
Grist is a hybrid database/spreadsheet, meaning that:
|
||||||
|
|
||||||
- Columns work like they do in databases. They are named, and hold one kind of data.
|
- Columns work like they do in databases: they are named, and they hold one kind of data.
|
||||||
- Columns can be filled by formula, spreadsheet-style, with automatic updates when referenced cells change.
|
- Columns can be filled by formula, spreadsheet-style, with automatic updates when referenced cells change.
|
||||||
|
|
||||||
This difference can confuse people coming directly from Excel or Google Sheets. Give it a chance!
|
This difference can confuse people coming directly from Excel or Google Sheets. Give it a chance! If you are coming from Airtable, you'll find the model familiar though (and there's a [Grist vs Airtable](https://www.getgrist.com/blog/grist-v-airtable/) article that might interest you).
|
||||||
If you are coming from Airtable, you'll find the model familiar though (and there's a
|
|
||||||
[Grist vs Airtable](https://www.getgrist.com/blog/grist-v-airtable/) article that might interest you).
|
|
||||||
|
|
||||||
|
|
||||||
Here are some specific feature highlights of Grist:
|
Here are some specific feature highlights of Grist:
|
||||||
|
|
||||||
* Python formulas.
|
* Python formulas.
|
||||||
- Full [Python syntax is supported](https://support.getgrist.com/formulas/#python), and the standard library.
|
- Full [Python syntax is supported](https://support.getgrist.com/formulas/#python), including the standard library.
|
||||||
- Many [Excel functions](https://support.getgrist.com/functions/) also available.
|
- Many [Excel functions](https://support.getgrist.com/functions/) also available.
|
||||||
|
- An [AI Assistant](https://www.getgrist.com/ai-formula-assistant/) specifically tuned for formula generation (using OpenAI gpt-3.5-turbo or [Llama](https://ai.meta.com/llama/) via <a href="https://github.com/abetlen/llama-cpp-python">llama-cpp-python</a>).
|
||||||
* A portable, self-contained format.
|
* A portable, self-contained format.
|
||||||
- Based on SQLite, the most widely deployed database engine.
|
- Based on SQLite, the most widely deployed database engine.
|
||||||
- Any tool that can read SQLite can read numeric and text data from a Grist file.
|
- Any tool that can read SQLite can read numeric and text data from a Grist file.
|
||||||
- Great format for [backups](https://support.getgrist.com/exports/#backing-up-an-entire-document) that you can be confident you can restore in full.
|
- Enables [backups](https://support.getgrist.com/exports/#backing-up-an-entire-document) that you can confidently restore in full.
|
||||||
- Great format for moving between different hosts.
|
- Great for moving between different hosts.
|
||||||
- Can be displayed on a static website with [grist-static](https://github.com/gristlabs/grist-static), no special server needed.
|
* Can be displayed on a static website with [`grist-static`](https://github.com/gristlabs/grist-static) – no special server needed.
|
||||||
- There's a self-contained desktop app available for viewing and editing: [grist-electron](https://github.com/gristlabs/grist-electron).
|
* A self-contained desktop app for viewing and editing locally: [`grist-electron`](https://github.com/gristlabs/grist-electron).
|
||||||
* Convenient editing and formatting features.
|
* Convenient editing and formatting features.
|
||||||
- Choices and [choice lists](https://support.getgrist.com/col-types/#choice-list-columns), for adding colorful tags to records without fuss.
|
- Choices and [choice lists](https://support.getgrist.com/col-types/#choice-list-columns), for adding colorful tags to records.
|
||||||
- [References](https://support.getgrist.com/col-refs/#creating-a-new-reference-list-column) and reference lists, for cross-referencing records in other tables.
|
- [References](https://support.getgrist.com/col-refs/#creating-a-new-reference-list-column) and reference lists, for cross-referencing records in other tables.
|
||||||
- [Attachments](https://support.getgrist.com/col-types/#attachment-columns), to include media or document files in records.
|
- [Attachments](https://support.getgrist.com/col-types/#attachment-columns), to include media or document files in records.
|
||||||
- Dates and times, toggles, and special numerics such as currency all have specialized editors and formatting options.
|
- Dates and times, toggles, and special numerics such as currency all have specialized editors and formatting options.
|
||||||
- [Conditional Formatting](https://support.getgrist.com/conditional-formatting/), letting you control the style of cells with formulas, to draw attention to important information.
|
- [Conditional Formatting](https://support.getgrist.com/conditional-formatting/), letting you control the style of cells with formulas to draw attention to important information.
|
||||||
* Great for dashboards, visualizations, and data entry.
|
* Drag-and-drop dashboards.
|
||||||
- [Charts](https://support.getgrist.com/widget-chart/) for visualization.
|
- [Charts](https://support.getgrist.com/widget-chart/) for visualization.
|
||||||
- [Summary tables](https://support.getgrist.com/summary-tables/) for summing and counting across groups.
|
- [Summary tables](https://support.getgrist.com/summary-tables/) for summing and counting across groups.
|
||||||
- [Widget linking](https://support.getgrist.com/linking-widgets/) streamlines filtering and editing data.
|
- [Widget linking](https://support.getgrist.com/linking-widgets/) streamlines filtering and editing data.
|
||||||
Grist has a unique approach to visualization, where you can lay out and link distinct widgets to show together,
|
Grist has a unique approach to visualization, where you can lay out and link distinct widgets to show together,
|
||||||
without cramming mixed material into a table.
|
without cramming mixed material into a table.
|
||||||
- The [Filter bar](https://support.getgrist.com/search-sort-filter/#filter-buttons) is great for quick slicing and dicing.
|
- [Filter bar](https://support.getgrist.com/search-sort-filter/#filter-buttons) for quick slicing and dicing.
|
||||||
* [Incremental imports](https://support.getgrist.com/imports/#updating-existing-records).
|
* [Incremental imports](https://support.getgrist.com/imports/#updating-existing-records).
|
||||||
- So you can import a CSV of the last three months activity from your bank...
|
- Import a CSV of the last three months activity from your bank...
|
||||||
- ... and import new activity a month later without fuss or duplicates.
|
- ...and import new activity a month later without fuss or duplication.
|
||||||
* Integrations.
|
* Integrations.
|
||||||
- A [REST API](https://support.getgrist.com/api/), [Zapier actions/triggers](https://support.getgrist.com/integrators/#integrations-via-zapier), and support from similar [integrators](https://support.getgrist.com/integrators/).
|
- A [REST API](https://support.getgrist.com/api/), [Zapier actions/triggers](https://support.getgrist.com/integrators/#integrations-via-zapier), and support from similar [integrators](https://support.getgrist.com/integrators/).
|
||||||
- Import/export to Google drive, Excel format, CSV.
|
- Import/export to Google drive, Excel format, CSV.
|
||||||
- Can link data with custom widgets hosted externally.
|
- Link data with [custom widgets](https://support.getgrist.com/widget-custom/#_top), hosted externally.
|
||||||
- You can set up outgoing webhooks.
|
- Configurable outgoing webhooks.
|
||||||
* [Many templates](https://templates.getgrist.com/) to get you started, from investment research to organizing treasure hunts.
|
* [Many templates](https://templates.getgrist.com/) to get you started, from investment research to organizing treasure hunts.
|
||||||
* Access control options.
|
* Access control options.
|
||||||
- (You'll need SSO logins set up to make use of these options; [grist-omnibus](https://github.com/gristlabs/grist-omnibus) has a prepackaged solution if configuring this feels daunting)
|
- (You'll need SSO logins set up to make use of these options; [`grist-omnibus`](https://github.com/gristlabs/grist-omnibus) has a prepackaged solution if configuring this feels daunting)
|
||||||
- Share [individual documents](https://support.getgrist.com/sharing/), or workspaces, or [team sites](https://support.getgrist.com/team-sharing/).
|
- Share [individual documents](https://support.getgrist.com/sharing/), workspaces, or [team sites](https://support.getgrist.com/team-sharing/).
|
||||||
- Control access to [individual rows, columns, and tables](https://support.getgrist.com/access-rules/).
|
- Control access to [individual rows, columns, and tables](https://support.getgrist.com/access-rules/).
|
||||||
- Control access based on cell values and user attributes.
|
- Control access based on cell values and user attributes.
|
||||||
* Can be self-maintained.
|
* Self-maintainable.
|
||||||
- Useful for intranet operation and specific compliance requirements.
|
- Useful for intranet operation and specific compliance requirements.
|
||||||
* Sandboxing options for untrusted documents.
|
* Sandboxing options for untrusted documents.
|
||||||
- On Linux or with docker, you can enable
|
- On Linux or with Docker, you can enable [gVisor](https://github.com/google/gvisor) sandboxing at the individual document level.
|
||||||
[gVisor](https://github.com/google/gvisor) sandboxing at the individual
|
- On macOS, you can use native sandboxing.
|
||||||
document level.
|
|
||||||
- On OSX, you can use native sandboxing.
|
|
||||||
- On any OS, including Windows, you can use a wasm-based sandbox.
|
- On any OS, including Windows, you can use a wasm-based sandbox.
|
||||||
* Translated to many languages.
|
* Translated to many languages.
|
||||||
* Support for an AI Formula Assistant (using OpenAI gpt-3.5-turbo or comparable models).
|
* `F1` key brings up some quick help. This used to go without saying, but in general Grist has good keyboard support.
|
||||||
* `F1` key brings up some quick help. This used to go without saying. In general Grist has good keyboard support.
|
* We post progress on [𝕏 or Twitter or whatever](https://twitter.com/getgrist) and publish [monthly newsletters](https://support.getgrist.com/newsletters/).
|
||||||
* We post progress on [𝕏 or Twitter or whatever](https://twitter.com/getgrist).
|
|
||||||
|
|
||||||
If you are curious about where Grist is going heading,
|
|
||||||
see [our roadmap](https://github.com/gristlabs/grist-core/projects/1), drop a
|
|
||||||
question in [our forum](https://community.getgrist.com),
|
|
||||||
or browse [our extensive documentation](https://support.getgrist.com).
|
|
||||||
|
|
||||||
|
If you are curious about where Grist is heading, see [our roadmap](https://github.com/gristlabs/grist-core/projects/1), drop a question in [our forum](https://community.getgrist.com), or browse [our extensive documentation](https://support.getgrist.com).
|
||||||
|
|
||||||
## Using Grist
|
## Using Grist
|
||||||
|
|
||||||
If you just want a quick demo of Grist:
|
If you just want a quick demo of Grist:
|
||||||
|
|
||||||
* You can try Grist out at the hosted service run
|
* You can try Grist out at the hosted service run by Grist Labs at [docs.getgrist.com](https://docs.getgrist.com) (no registration needed).
|
||||||
by Grist Labs at [docs.getgrist.com](https://docs.getgrist.com)
|
* Or you can see a fully in-browser build of Grist at [gristlabs.github.io/grist-static](https://gristlabs.github.io/grist-static/).
|
||||||
(no registration needed).
|
|
||||||
* Or you can see an experimental fully in-browser build of Grist
|
|
||||||
at [gristlabs.github.io/grist-static](https://gristlabs.github.io/grist-static/).
|
|
||||||
* Or you can download Grist as a desktop app from [github.com/gristlabs/grist-electron](https://github.com/gristlabs/grist-electron).
|
* Or you can download Grist as a desktop app from [github.com/gristlabs/grist-electron](https://github.com/gristlabs/grist-electron).
|
||||||
|
|
||||||
To get `grist-core` running on your computer with [Docker](https://www.docker.com/get-started), do:
|
To get `grist-core` running on your computer with [Docker](https://www.docker.com/get-started), do:
|
||||||
@ -150,7 +127,7 @@ Grist formulas in documents will be run using Python executed directly on your
|
|||||||
machine. You can configure sandboxing using a `GRIST_SANDBOX_FLAVOR`
|
machine. You can configure sandboxing using a `GRIST_SANDBOX_FLAVOR`
|
||||||
environment variable.
|
environment variable.
|
||||||
|
|
||||||
* On OSX, `export GRIST_SANDBOX_FLAVOR=macSandboxExec`
|
* On macOS, `export GRIST_SANDBOX_FLAVOR=macSandboxExec`
|
||||||
uses the native `sandbox-exec` command for sandboxing.
|
uses the native `sandbox-exec` command for sandboxing.
|
||||||
* On Linux with [gVisor's runsc](https://github.com/google/gvisor)
|
* On Linux with [gVisor's runsc](https://github.com/google/gvisor)
|
||||||
installed, `export GRIST_SANDBOX_FLAVOR=gvisor` is an option.
|
installed, `export GRIST_SANDBOX_FLAVOR=gvisor` is an option.
|
||||||
@ -195,18 +172,15 @@ did the hard work of making a good chunk of the application localizable. Merci b
|
|||||||
|
|
||||||
This repository, [grist-core](https://github.com/gristlabs/grist-core), is maintained by Grist
|
This repository, [grist-core](https://github.com/gristlabs/grist-core), is maintained by Grist
|
||||||
Labs. Our flagship product available at [getgrist.com](https://www.getgrist.com) is built from the code you see
|
Labs. Our flagship product available at [getgrist.com](https://www.getgrist.com) is built from the code you see
|
||||||
here, combined with business-specific software designed to scale it to many users, handle billing,
|
here, combined with business-specific software designed to scale to many users, handle billing,
|
||||||
etc.
|
etc.
|
||||||
|
|
||||||
Grist Labs is an open-core company. We offer Grist hosting as a
|
Grist Labs is an open-core company. We offer Grist hosting as a
|
||||||
service, with free and paid plans. We also develop and sell
|
service, with free and paid plans. We also develop and sell
|
||||||
features related to Grist using a proprietary license, targeted at the
|
features related to Grist using a proprietary license, targeted at the
|
||||||
needs of enterprises with large self-managed installations. We see
|
needs of enterprises with large self-managed installations.
|
||||||
data portability and autonomy as a key value Grist can bring to our
|
|
||||||
users, and `grist-core` as an essential means to deliver that. We are
|
We see data portability and autonomy as a key value, and `grist-core` is an essential part of that. We are committed to maintaining and improving the `grist-core` codebase, and to be thoughtful about how proprietary offerings impact data portability and autonomy.
|
||||||
committed to maintaining and improving the `grist-core` codebase, and
|
|
||||||
to be thoughtful about how proprietary offerings impact data portability
|
|
||||||
and autonomy.
|
|
||||||
|
|
||||||
By opening its source code and offering an [OSI](https://opensource.org/)-approved free license,
|
By opening its source code and offering an [OSI](https://opensource.org/)-approved free license,
|
||||||
Grist benefits its users:
|
Grist benefits its users:
|
||||||
@ -223,10 +197,9 @@ Grist benefits its users:
|
|||||||
- **Price flexibility.** If you are low on funds but have time to invest, self-hosting is a great
|
- **Price flexibility.** If you are low on funds but have time to invest, self-hosting is a great
|
||||||
option to have. And DIY users may have the technical savvy and motivation to delve in and make improvements,
|
option to have. And DIY users may have the technical savvy and motivation to delve in and make improvements,
|
||||||
which can benefit all users of Grist.
|
which can benefit all users of Grist.
|
||||||
- **Extensibility.** For developers, having the source open makes it easier to build extensions (such as the
|
- **Extensibility.** For developers, having the source open makes it easier to build extensions (such as [Custom Widgets](https://support.getgrist.com/widget-custom/)). You can more easily include Grist in your pipeline. And if a feature is missing, you can just take the source code and build on top of it.
|
||||||
experimental [Custom Widget](https://support.getgrist.com/widget-custom/)). You can more easily
|
|
||||||
include Grist in your pipeline. And if a feature is missing, you can just take the source code and
|
For more on Grist Labs' history and principles, see our [About Us](https://www.getgrist.com/about/) page.
|
||||||
build on top of it.
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
|
@ -72,6 +72,8 @@ export interface SessionObj {
|
|||||||
oidc?: {
|
oidc?: {
|
||||||
// codeVerifier is used during OIDC authentication, to protect against attacks like CSRF.
|
// codeVerifier is used during OIDC authentication, to protect against attacks like CSRF.
|
||||||
codeVerifier?: string;
|
codeVerifier?: string;
|
||||||
|
state?: string;
|
||||||
|
targetUrl?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export async function collectTableSchemaInFrictionlessFormat(
|
|||||||
req: express.Request,
|
req: express.Request,
|
||||||
options: DownloadOptions
|
options: DownloadOptions
|
||||||
): Promise<FrictionlessFormat> {
|
): Promise<FrictionlessFormat> {
|
||||||
const {tableId} = options;
|
const {tableId, header} = options;
|
||||||
if (!activeDoc.docData) {
|
if (!activeDoc.docData) {
|
||||||
throw new Error('No docData in active document');
|
throw new Error('No docData in active document');
|
||||||
}
|
}
|
||||||
@ -50,24 +50,15 @@ export async function collectTableSchemaInFrictionlessFormat(
|
|||||||
throw new ApiError(`Table ${tableId} not found.`, 404);
|
throw new ApiError(`Table ${tableId} not found.`, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await exportTable(activeDoc, tableRef, req);
|
const {tableName, columns} = await exportTable(activeDoc, tableRef, req);
|
||||||
const tableSchema = columnsToTableSchema(tableId, data, settings.locale);
|
|
||||||
return tableSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
function columnsToTableSchema(
|
|
||||||
tableId: string,
|
|
||||||
{tableName, columns}: {tableName: string, columns: ExportColumn[]},
|
|
||||||
locale: string,
|
|
||||||
): FrictionlessFormat {
|
|
||||||
return {
|
return {
|
||||||
name: tableId.toLowerCase().replace(/_/g, '-'),
|
name: tableId.toLowerCase().replace(/_/g, '-'),
|
||||||
title: tableName,
|
title: tableName,
|
||||||
schema: {
|
schema: {
|
||||||
fields: columns.map(col => ({
|
fields: columns.map(col => ({
|
||||||
name: col.label,
|
name: col[header || "label"],
|
||||||
...(col.description ? {description: col.description} : {}),
|
...(col.description ? {description: col.description} : {}),
|
||||||
...buildTypeField(col, locale),
|
...buildTypeField(col, settings.locale),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -78,6 +78,7 @@ export class OIDCConfig {
|
|||||||
redirect_uris: [ this._redirectUrl ],
|
redirect_uris: [ this._redirectUrl ],
|
||||||
response_types: [ 'code' ],
|
response_types: [ 'code' ],
|
||||||
});
|
});
|
||||||
|
log.info(`OIDCConfig: initialized with issuer ${issuerUrl}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addEndpoints(app: express.Application, sessions: Sessions): void {
|
public addEndpoints(app: express.Application, sessions: Sessions): void {
|
||||||
@ -85,15 +86,18 @@ export class OIDCConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async handleCallback(sessions: Sessions, req: express.Request, res: express.Response): Promise<void> {
|
public async handleCallback(sessions: Sessions, req: express.Request, res: express.Response): Promise<void> {
|
||||||
|
const mreq = req as RequestWithLogin;
|
||||||
try {
|
try {
|
||||||
const params = this._client.callbackParams(req);
|
const params = this._client.callbackParams(req);
|
||||||
const { state } = params;
|
const { state, targetUrl } = mreq.session?.oidc ?? {};
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw new Error('Login or logout failed to complete');
|
throw new Error('Login or logout failed to complete');
|
||||||
}
|
}
|
||||||
|
|
||||||
const codeVerifier = await this._retrieveCodeVerifierFromSession(req);
|
const codeVerifier = await this._retrieveCodeVerifierFromSession(req);
|
||||||
|
|
||||||
|
// The callback function will compare the state present in the params and the one we retrieved from the session.
|
||||||
|
// If they don't match, it will throw an error.
|
||||||
const tokenSet = await this._client.callback(
|
const tokenSet = await this._client.callback(
|
||||||
this._redirectUrl,
|
this._redirectUrl,
|
||||||
params,
|
params,
|
||||||
@ -102,23 +106,29 @@ export class OIDCConfig {
|
|||||||
|
|
||||||
const userInfo = await this._client.userinfo(tokenSet);
|
const userInfo = await this._client.userinfo(tokenSet);
|
||||||
const profile = this._makeUserProfileFromUserInfo(userInfo);
|
const profile = this._makeUserProfileFromUserInfo(userInfo);
|
||||||
|
log.info(`OIDCConfig: got OIDC response for ${profile.email} (${profile.name}) redirecting to ${targetUrl}`);
|
||||||
|
|
||||||
const scopedSession = sessions.getOrCreateSessionFromRequest(req);
|
const scopedSession = sessions.getOrCreateSessionFromRequest(req);
|
||||||
await scopedSession.operateOnScopedSession(req, async (user) => Object.assign(user, {
|
await scopedSession.operateOnScopedSession(req, async (user) => Object.assign(user, {
|
||||||
profile,
|
profile,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
res.redirect('/');
|
delete mreq.session.oidc;
|
||||||
|
res.redirect(targetUrl ?? '/');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(`OIDC callback failed: ${err.message}`);
|
log.error(`OIDC callback failed: ${err.stack}`);
|
||||||
res.status(500).send(`OIDC callback failed: ${err.message}`);
|
// Delete the session data even if the login failed.
|
||||||
|
// This way, we prevent several login attempts.
|
||||||
|
//
|
||||||
|
// Also session deletion must be done before sending the response.
|
||||||
|
delete mreq.session.oidc;
|
||||||
|
res.status(500).send(`OIDC callback failed.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getLoginRedirectUrl(req: express.Request): Promise<string> {
|
public async getLoginRedirectUrl(req: express.Request, targetUrl: URL): Promise<string> {
|
||||||
const codeVerifier = await this._generateAndStoreCodeVerifier(req);
|
const { codeVerifier, state } = await this._generateAndStoreConnectionInfo(req, targetUrl.href);
|
||||||
const codeChallenge = generators.codeChallenge(codeVerifier);
|
const codeChallenge = generators.codeChallenge(codeVerifier);
|
||||||
const state = generators.state();
|
|
||||||
|
|
||||||
const authUrl = this._client.authorizationUrl({
|
const authUrl = this._client.authorizationUrl({
|
||||||
scope: process.env.GRIST_OIDC_IDP_SCOPES || 'openid email profile',
|
scope: process.env.GRIST_OIDC_IDP_SCOPES || 'openid email profile',
|
||||||
@ -135,15 +145,18 @@ export class OIDCConfig {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _generateAndStoreCodeVerifier(req: express.Request) {
|
private async _generateAndStoreConnectionInfo(req: express.Request, targetUrl: string) {
|
||||||
const mreq = req as RequestWithLogin;
|
const mreq = req as RequestWithLogin;
|
||||||
if (!mreq.session) { throw new Error('no session available'); }
|
if (!mreq.session) { throw new Error('no session available'); }
|
||||||
const codeVerifier = generators.codeVerifier();
|
const codeVerifier = generators.codeVerifier();
|
||||||
|
const state = generators.state();
|
||||||
mreq.session.oidc = {
|
mreq.session.oidc = {
|
||||||
codeVerifier,
|
codeVerifier,
|
||||||
|
state,
|
||||||
|
targetUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
return codeVerifier;
|
return { codeVerifier, state };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _retrieveCodeVerifierFromSession(req: express.Request) {
|
private async _retrieveCodeVerifierFromSession(req: express.Request) {
|
||||||
@ -151,7 +164,6 @@ export class OIDCConfig {
|
|||||||
if (!mreq.session) { throw new Error('no session available'); }
|
if (!mreq.session) { throw new Error('no session available'); }
|
||||||
const codeVerifier = mreq.session.oidc?.codeVerifier;
|
const codeVerifier = mreq.session.oidc?.codeVerifier;
|
||||||
if (!codeVerifier) { throw new Error('Login is stale'); }
|
if (!codeVerifier) { throw new Error('Login is stale'); }
|
||||||
delete mreq.session.oidc?.codeVerifier;
|
|
||||||
return codeVerifier;
|
return codeVerifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "grist-core",
|
"name": "grist-core",
|
||||||
"version": "1.1.7",
|
"version": "1.1.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Grist is the evolution of spreadsheets",
|
"description": "Grist is the evolution of spreadsheets",
|
||||||
"homepage": "https://github.com/gristlabs/grist-core",
|
"homepage": "https://github.com/gristlabs/grist-core",
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
"Everyone": "Všichni",
|
"Everyone": "Všichni",
|
||||||
"Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Umožni všem kopírovat celý dokument, nebo zobrazit plně v \"fiddle\" režimu.\nUžiitečné pro ukázky a šablony, ale ne pro citlivá data.",
|
"Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Umožni všem kopírovat celý dokument, nebo zobrazit plně v \"fiddle\" režimu.\nUžiitečné pro ukázky a šablony, ale ne pro citlivá data.",
|
||||||
"Add Default Rule": "Přidej Základní Pravidlo",
|
"Add Default Rule": "Přidej Základní Pravidlo",
|
||||||
"Checking...": "Kontroluji…"
|
"Checking...": "Kontroluji…",
|
||||||
|
"Permissions": "Povolení",
|
||||||
|
"Permission to view Access Rules": "Povolení na zobrazení Přistupových Pravidel"
|
||||||
},
|
},
|
||||||
"ACUserManager": {
|
"ACUserManager": {
|
||||||
"Invite new member": "Pozvi nového uživatele",
|
"Invite new member": "Pozvi nového uživatele",
|
||||||
|
@ -1055,7 +1055,8 @@
|
|||||||
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Не можете найти нужные столбцы? Нажмите «Изменить виджет», чтобы выбрать таблицу с данными о событиях.",
|
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Не можете найти нужные столбцы? Нажмите «Изменить виджет», чтобы выбрать таблицу с данными о событиях.",
|
||||||
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "UUID - это случайно сгенерированная строка, которая полезна для уникальных идентификаторов и ключевых ссылок.",
|
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "UUID - это случайно сгенерированная строка, которая полезна для уникальных идентификаторов и ключевых ссылок.",
|
||||||
"Lookups return data from related tables.": "Lookups возвращают данные из связанных таблиц.",
|
"Lookups return data from related tables.": "Lookups возвращают данные из связанных таблиц.",
|
||||||
"Use reference columns to relate data in different tables.": "Используйте ссылочные столбцы для сопоставления данных в разных таблицах."
|
"Use reference columns to relate data in different tables.": "Используйте ссылочные столбцы для сопоставления данных в разных таблицах.",
|
||||||
|
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "Вы можете выбрать виджеты, доступные вам в раскрывающемся списке, или встроить свой собственный, указав его полный URL-адрес."
|
||||||
},
|
},
|
||||||
"DescriptionConfig": {
|
"DescriptionConfig": {
|
||||||
"DESCRIPTION": "ОПИСАНИЕ"
|
"DESCRIPTION": "ОПИСАНИЕ"
|
||||||
|
@ -19,19 +19,19 @@
|
|||||||
"Type a message...": "Vnesite sporočilo…",
|
"Type a message...": "Vnesite sporočilo…",
|
||||||
"User Attributes": "Atributi uporabnika",
|
"User Attributes": "Atributi uporabnika",
|
||||||
"View As": "Poglej kot",
|
"View As": "Poglej kot",
|
||||||
"When adding table rules, automatically add a rule to grant OWNER full access.": "Pri dodajanju pravil za tabele samodejno dodajte pravilo, ki lastniku omogoča popoln dostop.",
|
"When adding table rules, automatically add a rule to grant OWNER full access.": "Pri dodajanju pravil za tabele samodejno dodaj pravilo, ki lastniku omogoča popoln dostop.",
|
||||||
"Permission to edit document structure": "Dovoljenje za urejanje strukture dokumenta",
|
"Permission to edit document structure": "Dovoljenje za urejanje strukture dokumenta",
|
||||||
"Everyone": "Vsi",
|
"Everyone": "Vsi",
|
||||||
"Everyone Else": "Vsi ostali",
|
"Everyone Else": "Vsi ostali",
|
||||||
"Checking...": "Preverjanje…",
|
"Checking...": "Preverjanje…",
|
||||||
"Condition": "Stanje",
|
"Condition": "Stanje",
|
||||||
"Enter Condition": "Vnesite pogoj",
|
"Enter Condition": "Vnesite pogoj",
|
||||||
"Add Column Rule": "Dodajanje pravila za stolpce",
|
"Add Column Rule": "Dodaj pravila za stolpec",
|
||||||
"Add Default Rule": "Dodaj privzeto pravilo",
|
"Add Default Rule": "Dodaj privzeto pravilo",
|
||||||
"Add Table Rules": "Dodajanje pravil tabele",
|
"Add Table Rules": "Dodaj pravila za tabelo",
|
||||||
"Add User Attributes": "Dodajanje atributov uporabnika",
|
"Add User Attributes": "Dodaj atribute za uporabnika",
|
||||||
"Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Vsakomur omogočite kopiranje celotnega dokumenta ali pa si ga oglejte v celoti v načinu fiddle.\nUporabno za primere in predloge, ne pa za občutljive podatke.",
|
"Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Vsakomur omogočite kopiranje celotnega dokumenta ali pa si ga oglejte v celoti v načinu fiddle.\nUporabno za primere in predloge, ne pa za občutljive podatke.",
|
||||||
"Allow everyone to view Access Rules.": "Vsakomur omogočite ogled pravil za dostop.",
|
"Allow everyone to view Access Rules.": "Omogoči vsakomur ogled pravil za dostop.",
|
||||||
"Attribute name": "Ime atributa",
|
"Attribute name": "Ime atributa",
|
||||||
"Attribute to Look Up": "Atribut za iskanje",
|
"Attribute to Look Up": "Atribut za iskanje",
|
||||||
"Lookup Table": "Preglednica za iskanje",
|
"Lookup Table": "Preglednica za iskanje",
|
||||||
@ -40,8 +40,8 @@
|
|||||||
},
|
},
|
||||||
"ACUserManager": {
|
"ACUserManager": {
|
||||||
"We'll email an invite to {{email}}": "Vabilo bomo poslali po e-pošti {{email}}",
|
"We'll email an invite to {{email}}": "Vabilo bomo poslali po e-pošti {{email}}",
|
||||||
"Enter email address": "Vnesite e-poštni naslov",
|
"Enter email address": "Vnesi e-poštni naslov",
|
||||||
"Invite new member": "Povabite novega člana"
|
"Invite new member": "Povabi novega člana"
|
||||||
},
|
},
|
||||||
"AccountPage": {
|
"AccountPage": {
|
||||||
"API": "API",
|
"API": "API",
|
||||||
@ -110,7 +110,7 @@
|
|||||||
"Translators: please translate this only when your language is ready to be offered to users": "Prevajalci: prosimo, prevedite to šele, ko bo vaš jezik pripravljen, da se ponudi uporabnikom"
|
"Translators: please translate this only when your language is ready to be offered to users": "Prevajalci: prosimo, prevedite to šele, ko bo vaš jezik pripravljen, da se ponudi uporabnikom"
|
||||||
},
|
},
|
||||||
"CellContextMenu": {
|
"CellContextMenu": {
|
||||||
"Delete {{count}} columns_one": "Brisanje stolpca",
|
"Delete {{count}} columns_one": "Briši stolpec",
|
||||||
"Delete {{count}} columns_other": "Brisanje stolpcev {{count}}",
|
"Delete {{count}} columns_other": "Brisanje stolpcev {{count}}",
|
||||||
"Delete {{count}} rows_one": "Brisanje vrstice",
|
"Delete {{count}} rows_one": "Brisanje vrstice",
|
||||||
"Delete {{count}} rows_other": "Brisanje vrstic {{count}}",
|
"Delete {{count}} rows_other": "Brisanje vrstic {{count}}",
|
||||||
@ -118,12 +118,12 @@
|
|||||||
"Copy anchor link": "Kopiranje sidrne povezave",
|
"Copy anchor link": "Kopiranje sidrne povezave",
|
||||||
"Duplicate rows_one": "Podvoji vrstico",
|
"Duplicate rows_one": "Podvoji vrstico",
|
||||||
"Duplicate rows_other": "Podvoji vrstice",
|
"Duplicate rows_other": "Podvoji vrstice",
|
||||||
"Insert column to the right": "Vstavi stolpec na desno",
|
"Insert column to the right": "Vstavi stolpec na desno stran",
|
||||||
"Insert column to the left": "Vstavi stolpec na levo",
|
"Insert column to the left": "Vstavi stolpec na levo stran",
|
||||||
"Insert row": "Vstavljanje vrstice",
|
"Insert row": "Vstavljanje vrstice",
|
||||||
"Insert row above": "Vstavite vrstico zgoraj",
|
"Insert row above": "Vstavite vrstico zgoraj",
|
||||||
"Insert row below": "Vstavi vrstico spodaj",
|
"Insert row below": "Vstavi vrstico spodaj",
|
||||||
"Reset {{count}} columns_one": "Ponastavitev stolpca",
|
"Reset {{count}} columns_one": "Ponastavi stolpec",
|
||||||
"Reset {{count}} columns_other": "Ponastavit {{count}} stolpcev",
|
"Reset {{count}} columns_other": "Ponastavit {{count}} stolpcev",
|
||||||
"Reset {{count}} entire columns_one": "Ponastavi celote stolpec",
|
"Reset {{count}} entire columns_one": "Ponastavi celote stolpec",
|
||||||
"Reset {{count}} entire columns_other": "Ponastavi {{count}} celotnih stolpcev",
|
"Reset {{count}} entire columns_other": "Ponastavi {{count}} celotnih stolpcev",
|
||||||
@ -177,7 +177,7 @@
|
|||||||
},
|
},
|
||||||
"GridViewMenus": {
|
"GridViewMenus": {
|
||||||
"Rename column": "Preimenuj stolpec",
|
"Rename column": "Preimenuj stolpec",
|
||||||
"Delete {{count}} columns_one": "Brisanje stolpca",
|
"Delete {{count}} columns_one": "Briši stolpec",
|
||||||
"Delete {{count}} columns_other": "Brisanje stolpcev {{count}}",
|
"Delete {{count}} columns_other": "Brisanje stolpcev {{count}}",
|
||||||
"Unfreeze {{count}} columns_one": "Odmrzni ta stolpec",
|
"Unfreeze {{count}} columns_one": "Odmrzni ta stolpec",
|
||||||
"Sorted (#{{count}})_one": "Razvrščeno (#{{count}})",
|
"Sorted (#{{count}})_one": "Razvrščeno (#{{count}})",
|
||||||
@ -189,7 +189,7 @@
|
|||||||
"Filter Data": "Filtriranje podatkov",
|
"Filter Data": "Filtriranje podatkov",
|
||||||
"Hide {{count}} columns_other": "Skrij {{count}} stolpcev",
|
"Hide {{count}} columns_other": "Skrij {{count}} stolpcev",
|
||||||
"Add Column": "Dodaj stolpec",
|
"Add Column": "Dodaj stolpec",
|
||||||
"Reset {{count}} columns_one": "Ponastavitev stolpca",
|
"Reset {{count}} columns_one": "Ponastavi stolpec",
|
||||||
"Freeze {{count}} columns_one": "Zamrzni stolpec",
|
"Freeze {{count}} columns_one": "Zamrzni stolpec",
|
||||||
"More sort options ...": "Več možnosti razvrščanja…",
|
"More sort options ...": "Več možnosti razvrščanja…",
|
||||||
"Freeze {{count}} more columns_one": "Zamrznite še en stolpec",
|
"Freeze {{count}} more columns_one": "Zamrznite še en stolpec",
|
||||||
@ -200,10 +200,10 @@
|
|||||||
"Freeze {{count}} more columns_other": "Zamrznite še {{count}} stolpcev",
|
"Freeze {{count}} more columns_other": "Zamrznite še {{count}} stolpcev",
|
||||||
"Hide {{count}} columns_one": "Skrij stolpec",
|
"Hide {{count}} columns_one": "Skrij stolpec",
|
||||||
"Sorted (#{{count}})_other": "Razvrščeno (#{{count}})",
|
"Sorted (#{{count}})_other": "Razvrščeno (#{{count}})",
|
||||||
"Insert column to the {{to}}": "Vstavi stolpec v {{to}}",
|
"Insert column to the {{to}}": "Vstavi stolpec na {{to}}",
|
||||||
"Reset {{count}} entire columns_other": "Ponastavi {{count}} stolpcev",
|
"Reset {{count}} entire columns_other": "Ponastavi {{count}} stolpcev",
|
||||||
"Unfreeze {{count}} columns_other": "Odmrznite {{count}} stolpcev",
|
"Unfreeze {{count}} columns_other": "Odmrznite {{count}} stolpcev",
|
||||||
"Insert column to the right": "Vstavi stolpec na desno",
|
"Insert column to the right": "Vstavi stolpec na desno stran",
|
||||||
"Reset {{count}} entire columns_one": "Ponastavi celoten stolpec",
|
"Reset {{count}} entire columns_one": "Ponastavi celoten stolpec",
|
||||||
"Insert column to the left": "Vstavi stolpec na levo",
|
"Insert column to the left": "Vstavi stolpec na levo",
|
||||||
"Shortcuts": "Bližnjice",
|
"Shortcuts": "Bližnjice",
|
||||||
@ -270,7 +270,7 @@
|
|||||||
"Access Rules": "Pravila dostopa",
|
"Access Rules": "Pravila dostopa",
|
||||||
"Code View": "Pogled kode",
|
"Code View": "Pogled kode",
|
||||||
"Raw Data": "Neobdelani podatki",
|
"Raw Data": "Neobdelani podatki",
|
||||||
"Document History": "Zgodovina dokumentov",
|
"Document History": "Zgodovina Dokumentov",
|
||||||
"Validate Data": "Potrdi podatke",
|
"Validate Data": "Potrdi podatke",
|
||||||
"How-to Tutorial": "Vadnica kako narediti",
|
"How-to Tutorial": "Vadnica kako narediti",
|
||||||
"Tour of this Document": "Ogled tega dokumenta",
|
"Tour of this Document": "Ogled tega dokumenta",
|
||||||
@ -278,7 +278,7 @@
|
|||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
"Rename": "Preimenuj",
|
"Rename": "Preimenuj",
|
||||||
"Duplicate Page": "Podvojena stran",
|
"Duplicate Page": "Podvoji stran",
|
||||||
"You do not have edit access to this document": "Nimate dovoljenja za urejanje tega dokumenta",
|
"You do not have edit access to this document": "Nimate dovoljenja za urejanje tega dokumenta",
|
||||||
"Remove": "Odstrani"
|
"Remove": "Odstrani"
|
||||||
},
|
},
|
||||||
@ -293,7 +293,7 @@
|
|||||||
"Add New": "Dodaj"
|
"Add New": "Dodaj"
|
||||||
},
|
},
|
||||||
"DataTables": {
|
"DataTables": {
|
||||||
"Delete {{formattedTableName}} data, and remove it from all pages?": "Izbrišite podatke {{formattedTableName}} in jih odstranite z vseh strani?",
|
"Delete {{formattedTableName}} data, and remove it from all pages?": "Izbriši podatke {{formattedTableName}} in jih odstrani z vseh strani?",
|
||||||
"Click to copy": "Kliknite za kopiranje",
|
"Click to copy": "Kliknite za kopiranje",
|
||||||
"Duplicate Table": "Podvojena tabela",
|
"Duplicate Table": "Podvojena tabela",
|
||||||
"Table ID copied to clipboard": "ID tabele kopiran v odložišče",
|
"Table ID copied to clipboard": "ID tabele kopiran v odložišče",
|
||||||
@ -305,8 +305,8 @@
|
|||||||
"Delete widget": "Izbriši gradnik",
|
"Delete widget": "Izbriši gradnik",
|
||||||
"Advanced Sort & Filter": "Napredno razvrščanje in filtriranje",
|
"Advanced Sort & Filter": "Napredno razvrščanje in filtriranje",
|
||||||
"Data selection": "Izbira podatkov",
|
"Data selection": "Izbira podatkov",
|
||||||
"Download as XLSX": "Prenesite kot XLSX",
|
"Download as XLSX": "Prenesi kot XLSX",
|
||||||
"Download as CSV": "Prenesite kot CSV",
|
"Download as CSV": "Prenesi kot CSV",
|
||||||
"Widget options": "Možnosti gradnika",
|
"Widget options": "Možnosti gradnika",
|
||||||
"Print widget": "Gradnik za tiskanje",
|
"Print widget": "Gradnik za tiskanje",
|
||||||
"Open configuration": "Odpri konfiguracijo",
|
"Open configuration": "Odpri konfiguracijo",
|
||||||
@ -423,13 +423,13 @@
|
|||||||
"Formula Columns_other": "Stolpci formule",
|
"Formula Columns_other": "Stolpci formule",
|
||||||
"Formula Columns_one": "Stolpec formule",
|
"Formula Columns_one": "Stolpec formule",
|
||||||
"Enter formula": "Vnesite formulo",
|
"Enter formula": "Vnesite formulo",
|
||||||
"Clear and make into formula": "Brišite in pretvorite v formulo",
|
"Clear and make into formula": "Briši in pretvori v formulo",
|
||||||
"Mixed Behavior": "Mešano vedenje",
|
"Mixed Behavior": "Mešano vedenje",
|
||||||
"Convert to trigger formula": "Pretvori v sprožitveno formulo",
|
"Convert to trigger formula": "Pretvori v sprožitveno formulo",
|
||||||
"Data Columns_one": "Stolpec podatkov",
|
"Data Columns_one": "Stolpec podatkov",
|
||||||
"TRIGGER FORMULA": "SPROŽILNA FORMULA",
|
"TRIGGER FORMULA": "SPROŽILNA FORMULA",
|
||||||
"Set trigger formula": "Nastavite sprožitveno formulo",
|
"Set trigger formula": "Nastavi sprožitveno formulo",
|
||||||
"Make into data column": "Spremenite v podatkovni stolpec",
|
"Make into data column": "Spremeni v podatkovni stolpec",
|
||||||
"COLUMN BEHAVIOR": "OBNAŠANJE STOLPCA"
|
"COLUMN BEHAVIOR": "OBNAŠANJE STOLPCA"
|
||||||
},
|
},
|
||||||
"DuplicateTable": {
|
"DuplicateTable": {
|
||||||
@ -440,7 +440,7 @@
|
|||||||
},
|
},
|
||||||
"DocPageModel": {
|
"DocPageModel": {
|
||||||
"Sorry, access to this document has been denied. [{{error}}]": "Žal je bil dostop do tega dokumenta zavrnjen. [{{error}}]",
|
"Sorry, access to this document has been denied. [{{error}}]": "Žal je bil dostop do tega dokumenta zavrnjen. [{{error}}]",
|
||||||
"Add Empty Table": "Dodajte prazno tabelo",
|
"Add Empty Table": "Dodaj prazno tabelo",
|
||||||
"You do not have edit access to this document": "Nimate dostopa za urejanje tega dokumenta",
|
"You do not have edit access to this document": "Nimate dostopa za urejanje tega dokumenta",
|
||||||
"Add Widget to Page": "Dodaj widget na stran",
|
"Add Widget to Page": "Dodaj widget na stran",
|
||||||
"Add Page": "Dodaj stran",
|
"Add Page": "Dodaj stran",
|
||||||
@ -552,7 +552,7 @@
|
|||||||
"SOURCE DATA": "IZVORNI PODATKI",
|
"SOURCE DATA": "IZVORNI PODATKI",
|
||||||
"CHART TYPE": "VRSTA DIAGRAMA",
|
"CHART TYPE": "VRSTA DIAGRAMA",
|
||||||
"Detach": "Odklopi",
|
"Detach": "Odklopi",
|
||||||
"Change Widget": "Spremite widget",
|
"Change Widget": "Spremeni Pripomoček",
|
||||||
"Columns_one": "Stolpec",
|
"Columns_one": "Stolpec",
|
||||||
"Series_other": "Serija",
|
"Series_other": "Serija",
|
||||||
"Fields_other": "Polja",
|
"Fields_other": "Polja",
|
||||||
@ -661,8 +661,8 @@
|
|||||||
"Apply conditional formatting to cells in this column when formula conditions are met.": "Uporabi pogojno oblikovanje za celice v tem stolpcu, ko so izpolnjeni pogoji formule.",
|
"Apply conditional formatting to cells in this column when formula conditions are met.": "Uporabi pogojno oblikovanje za celice v tem stolpcu, ko so izpolnjeni pogoji formule.",
|
||||||
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Če želite narediti sidrno povezavo, ki uporabnika pripelje do določene celice, kliknite vrstico in pritisnite {{shortcut}}.",
|
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Če želite narediti sidrno povezavo, ki uporabnika pripelje do določene celice, kliknite vrstico in pritisnite {{shortcut}}.",
|
||||||
"Unpin to hide the the button while keeping the filter.": "Odpnite, da skrijete gumb in obdržite filter.",
|
"Unpin to hide the the button while keeping the filter.": "Odpnite, da skrijete gumb in obdržite filter.",
|
||||||
"Apply conditional formatting to rows based on formulas.": "Uporabite pogojno oblikovanje za vrstice na podlagi formul.",
|
"Apply conditional formatting to rows based on formulas.": "Uporabi pogojno oblikovanje za vrstice na podlagi formul.",
|
||||||
"Click on “Open row styles” to apply conditional formatting to rows.": "Kliknite »Odpri sloge vrstic«, da za vrstice uporabite pogojno oblikovanje.",
|
"Click on “Open row styles” to apply conditional formatting to rows.": "Klikni »Odpri sloge vrstic«, da za vrstice uporabiš pogojno oblikovanje.",
|
||||||
"Pinned filters are displayed as buttons above the widget.": "Pripeti filtri so prikazani kot gumbi nad pripomočkom.",
|
"Pinned filters are displayed as buttons above the widget.": "Pripeti filtri so prikazani kot gumbi nad pripomočkom.",
|
||||||
"Link your new widget to an existing widget on this page.": "Povežite svoj novi pripomoček z obstoječim pripomočkom na tej strani.",
|
"Link your new widget to an existing widget on this page.": "Povežite svoj novi pripomoček z obstoječim pripomočkom na tej strani.",
|
||||||
"Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.": "Uporabite ikono \\u{1D6BA} za ustvarjanje povzetkov (ali vrtilnih) tabel za vsote ali delne vsote.",
|
"Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.": "Uporabite ikono \\u{1D6BA} za ustvarjanje povzetkov (ali vrtilnih) tabel za vsote ali delne vsote.",
|
||||||
@ -688,7 +688,7 @@
|
|||||||
"User has view access to {{resource}} resulting from manually-set access to resources inside. If removed here, this user will lose access to resources inside.": "Uporabnik ima vpogled v {{resource}}, ki je posledica ročno nastavljenega dostopa do virov v njem. Če ga tukaj odstranite, bo ta uporabnik izgubil dostop do notranjih virov.",
|
"User has view access to {{resource}} resulting from manually-set access to resources inside. If removed here, this user will lose access to resources inside.": "Uporabnik ima vpogled v {{resource}}, ki je posledica ročno nastavljenega dostopa do virov v njem. Če ga tukaj odstranite, bo ta uporabnik izgubil dostop do notranjih virov.",
|
||||||
"User may not modify their own access.": "Uporabnik ne more spreminjati lastnega dostopa.",
|
"User may not modify their own access.": "Uporabnik ne more spreminjati lastnega dostopa.",
|
||||||
"member": "član",
|
"member": "član",
|
||||||
"Add {{member}} to your team": "Dodajte {{member}} v svojo ekipo",
|
"Add {{member}} to your team": "Dodaj {{member}} v svojo ekipo",
|
||||||
"Collaborator": "Sodelavec",
|
"Collaborator": "Sodelavec",
|
||||||
"Link copied to clipboard": "Povezava kopirana v odložišče",
|
"Link copied to clipboard": "Povezava kopirana v odložišče",
|
||||||
"team site": "spletno mesto ekipe",
|
"team site": "spletno mesto ekipe",
|
||||||
@ -758,7 +758,7 @@
|
|||||||
},
|
},
|
||||||
"ColumnTitle": {
|
"ColumnTitle": {
|
||||||
"Column ID copied to clipboard": "ID stolpca kopiran v odložišče",
|
"Column ID copied to clipboard": "ID stolpca kopiran v odložišče",
|
||||||
"Add description": "Dodajte opis",
|
"Add description": "Dodaj opis",
|
||||||
"Column description": "Opis stolpca",
|
"Column description": "Opis stolpca",
|
||||||
"Provide a column label": "Navedite oznako stolpca",
|
"Provide a column label": "Navedite oznako stolpca",
|
||||||
"Close": "Zapri",
|
"Close": "Zapri",
|
||||||
@ -870,7 +870,7 @@
|
|||||||
},
|
},
|
||||||
"RecordLayoutEditor": {
|
"RecordLayoutEditor": {
|
||||||
"Show field {{- label}}": "Prikaži polje {{- label}}",
|
"Show field {{- label}}": "Prikaži polje {{- label}}",
|
||||||
"Add Field": "Dodajte polje",
|
"Add Field": "Dodaj polje",
|
||||||
"Save Layout": "Shrani postavitev",
|
"Save Layout": "Shrani postavitev",
|
||||||
"Cancel": "Prekliči",
|
"Cancel": "Prekliči",
|
||||||
"Create New Field": "Ustvari novo polje"
|
"Create New Field": "Ustvari novo polje"
|
||||||
@ -926,7 +926,7 @@
|
|||||||
"Switch appearance automatically to match system": "Samodejno preklopite videz, da se ujema s sistemom"
|
"Switch appearance automatically to match system": "Samodejno preklopite videz, da se ujema s sistemom"
|
||||||
},
|
},
|
||||||
"SiteSwitcher": {
|
"SiteSwitcher": {
|
||||||
"Switch Sites": "Preklopite mesta",
|
"Switch Sites": "Preklopi mesta",
|
||||||
"Create new team site": "Ustvarite novo spletno mesto ekipe"
|
"Create new team site": "Ustvarite novo spletno mesto ekipe"
|
||||||
},
|
},
|
||||||
"WebhookPage": {
|
"WebhookPage": {
|
||||||
@ -1016,7 +1016,7 @@
|
|||||||
"Reference List": "Referenčni seznam",
|
"Reference List": "Referenčni seznam",
|
||||||
"* Workspaces are available on team plans. ": "* Delovni prostori so na voljo v skupinskih načrtih. ",
|
"* Workspaces are available on team plans. ": "* Delovni prostori so na voljo v skupinskih načrtih. ",
|
||||||
"Upgrade now": "Nadgradi zdaj",
|
"Upgrade now": "Nadgradi zdaj",
|
||||||
"Toggle": "Preklopi",
|
"Toggle": "Preklop",
|
||||||
"Choice List": "Izbirni seznam",
|
"Choice List": "Izbirni seznam",
|
||||||
"Any": "Katerikoli",
|
"Any": "Katerikoli",
|
||||||
"DateTime": "Datum čas",
|
"DateTime": "Datum čas",
|
||||||
@ -1170,8 +1170,8 @@
|
|||||||
"ConditionalStyle": {
|
"ConditionalStyle": {
|
||||||
"Rule must return True or False": "Pravilo mora vrniti True ali False",
|
"Rule must return True or False": "Pravilo mora vrniti True ali False",
|
||||||
"Row Style": "Slog vrstice",
|
"Row Style": "Slog vrstice",
|
||||||
"Add another rule": "Dodajte še eno pravilo",
|
"Add another rule": "Dodaj dodatno pravilo",
|
||||||
"Add conditional style": "Dodajte pogojni slog",
|
"Add conditional style": "Dodaj pogojni slog",
|
||||||
"Error in style rule": "Napaka v slogovnem pravilu"
|
"Error in style rule": "Napaka v slogovnem pravilu"
|
||||||
},
|
},
|
||||||
"EditorTooltip": {
|
"EditorTooltip": {
|
||||||
@ -1219,7 +1219,7 @@
|
|||||||
},
|
},
|
||||||
"duplicatePage": {
|
"duplicatePage": {
|
||||||
"Note that this does not copy data, but creates another view of the same data.": "Upoštevajte, da s tem ne kopirate podatkov, ampak ustvarite drug pogled istih podatkov.",
|
"Note that this does not copy data, but creates another view of the same data.": "Upoštevajte, da s tem ne kopirate podatkov, ampak ustvarite drug pogled istih podatkov.",
|
||||||
"Duplicate page {{pageName}}": "Podvojena stran {{pageName}}"
|
"Duplicate page {{pageName}}": "Podvoji stran {{pageName}}"
|
||||||
},
|
},
|
||||||
"CurrencyPicker": {
|
"CurrencyPicker": {
|
||||||
"Invalid currency": "Neveljavna valuta"
|
"Invalid currency": "Neveljavna valuta"
|
||||||
|
@ -151,7 +151,27 @@
|
|||||||
"Unfreeze {{count}} columns_one": "Відкріпити цей стовпець",
|
"Unfreeze {{count}} columns_one": "Відкріпити цей стовпець",
|
||||||
"Unfreeze {{count}} columns_other": "Відкріпити {{count}} стовпців",
|
"Unfreeze {{count}} columns_other": "Відкріпити {{count}} стовпців",
|
||||||
"Insert column to the right": "Вставити стовпець праворуч",
|
"Insert column to the right": "Вставити стовпець праворуч",
|
||||||
"Insert column to the left": "Вставити стовпець ліворуч"
|
"Insert column to the left": "Вставити стовпець ліворуч",
|
||||||
|
"Detect Duplicates in...": "Виявлення дублікатів у...",
|
||||||
|
"UUID": "UUID",
|
||||||
|
"Shortcuts": "Ярлики",
|
||||||
|
"Show hidden columns": "Показати приховані стовпці",
|
||||||
|
"Created At": "Дата створення",
|
||||||
|
"Authorship": "Авторство",
|
||||||
|
"Last Updated By": "Автор останньої зміни",
|
||||||
|
"Hidden Columns": "Приховані стовпці",
|
||||||
|
"Lookups": "Lookups",
|
||||||
|
"No reference columns.": "Немає довідкових стовпців.",
|
||||||
|
"Apply on record changes": "Застосовувати при зміні записів",
|
||||||
|
"Duplicate in {{- label}}": "Дублікат у {{- label}}\"",
|
||||||
|
"Created By": "Автор створення",
|
||||||
|
"Last Updated At": "Дата останньої зміни",
|
||||||
|
"Apply to new records": "Застосувати до нових записів",
|
||||||
|
"Search columns": "Шукати стовпці",
|
||||||
|
"Timestamp": "Часова мітка",
|
||||||
|
"no reference column": "немає довідкових стовпців",
|
||||||
|
"Adding UUID column": "Adding UUID column",
|
||||||
|
"Adding duplicates column": "Adding duplicates column"
|
||||||
},
|
},
|
||||||
"ThemeConfig": {
|
"ThemeConfig": {
|
||||||
"Switch appearance automatically to match system": "Автоматично змінювати оформлення відповідно до системи",
|
"Switch appearance automatically to match system": "Автоматично змінювати оформлення відповідно до системи",
|
||||||
@ -820,7 +840,8 @@
|
|||||||
"Reference": "Посилання",
|
"Reference": "Посилання",
|
||||||
"DateTime": "Дата і час",
|
"DateTime": "Дата і час",
|
||||||
"Reference List": "Список посилань",
|
"Reference List": "Список посилань",
|
||||||
"Attachment": "Вкладення"
|
"Attachment": "Вкладення",
|
||||||
|
"Search columns": "Шукати стовпці"
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
"Cancel": "Відмінити",
|
"Cancel": "Відмінити",
|
||||||
@ -913,14 +934,15 @@
|
|||||||
},
|
},
|
||||||
"FieldBuilder": {
|
"FieldBuilder": {
|
||||||
"CELL FORMAT": "ФОРМАТ КЛІТИНКИ",
|
"CELL FORMAT": "ФОРМАТ КЛІТИНКИ",
|
||||||
"Changing multiple column types": "Зміна кількох типів стовпців",
|
"Changing multiple column types": "Changing multiple column types",
|
||||||
"DATA FROM TABLE": "ДАНІ З ТАБЛИЦІ",
|
"DATA FROM TABLE": "ДАНІ З ТАБЛИЦІ",
|
||||||
"Mixed format": "Змішаний формат",
|
"Mixed format": "Змішаний формат",
|
||||||
"Mixed types": "Змішані типи",
|
"Mixed types": "Змішані типи",
|
||||||
"Use separate field settings for {{colId}}": "Використати окремі налаштування поля для {{colId}}",
|
"Use separate field settings for {{colId}}": "Використати окремі налаштування поля для {{colId}}",
|
||||||
"Apply Formula to Data": "Застосувати формулу до даних",
|
"Apply Formula to Data": "Застосувати формулу до даних",
|
||||||
"Revert field settings for {{colId}} to common": "Змінити налаштування поля для {{colId}} на загальні",
|
"Revert field settings for {{colId}} to common": "Змінити налаштування поля для {{colId}} на загальні",
|
||||||
"Save field settings for {{colId}} as common": "Зберегти налаштування поля для {{colId}} як загальні"
|
"Save field settings for {{colId}} as common": "Зберегти налаштування поля для {{colId}} як загальні",
|
||||||
|
"Changing column type": "Changing column type"
|
||||||
},
|
},
|
||||||
"FieldEditor": {
|
"FieldEditor": {
|
||||||
"Unable to finish saving edited cell": "Неможливо зберегти відредаговану клітинку",
|
"Unable to finish saving edited cell": "Неможливо зберегти відредаговану клітинку",
|
||||||
@ -931,7 +953,11 @@
|
|||||||
"Error in the cell": "Помилка в клітинці",
|
"Error in the cell": "Помилка в клітинці",
|
||||||
"Errors in all {{numErrors}} cells": "Помилки в усіх клітинках {{numErrors}}",
|
"Errors in all {{numErrors}} cells": "Помилки в усіх клітинках {{numErrors}}",
|
||||||
"editingFormula is required": "editingFormula обов'язкове",
|
"editingFormula is required": "editingFormula обов'язкове",
|
||||||
"Errors in {{numErrors}} of {{numCells}} cells": "Помилки в {{numErrors}} в {{numCells}} клітинках"
|
"Errors in {{numErrors}} of {{numCells}} cells": "Помилки в {{numErrors}} в {{numCells}} клітинках",
|
||||||
|
"Enter formula or {{button}}.": "Введіть формулу або {{button}}.",
|
||||||
|
"Enter formula.": "Введіть формулу.",
|
||||||
|
"Expand Editor": "Розгорнути редактор",
|
||||||
|
"use AI Assistant": "Використати AI Помічника"
|
||||||
},
|
},
|
||||||
"HyperLinkEditor": {
|
"HyperLinkEditor": {
|
||||||
"[link label] url": "[мітка посилання] URL"
|
"[link label] url": "[мітка посилання] URL"
|
||||||
@ -1017,9 +1043,14 @@
|
|||||||
"Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Корисно для збереження позначки часу або автора нового запису, очищення даних тощо.",
|
"Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Корисно для збереження позначки часу або автора нового запису, очищення даних тощо.",
|
||||||
"You can filter by more than one column.": "Ви можете фільтрувати за кількома стовпцями.",
|
"You can filter by more than one column.": "Ви можете фільтрувати за кількома стовпцями.",
|
||||||
"Try out changes in a copy, then decide whether to replace the original with your edits.": "Спробуйте змінити копію, а потім вирішіть, чи слід замінювати оригінал своїми правками.",
|
"Try out changes in a copy, then decide whether to replace the original with your edits.": "Спробуйте змінити копію, а потім вирішіть, чи слід замінювати оригінал своїми правками.",
|
||||||
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Правила доступу дають вам можливість створювати детальні правила, щоб визначити, хто може бачити або редагувати (та які) частини вашого документа."
|
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Правила доступу дають вам можливість створювати детальні правила, щоб визначити, хто може бачити або редагувати (та які) частини вашого документа.",
|
||||||
|
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Щоб створити якірне посилання, яке перенаправляє користувача до певної комірки, клацніть на рядку та натисніть {{shortcut}}.",
|
||||||
|
"Anchor Links": "Якірні посилання"
|
||||||
},
|
},
|
||||||
"DescriptionConfig": {
|
"DescriptionConfig": {
|
||||||
"DESCRIPTION": "ОПИС"
|
"DESCRIPTION": "ОПИС"
|
||||||
|
},
|
||||||
|
"FieldContextMenu": {
|
||||||
|
"Copy anchor link": "Скопіювати якірне посилання"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2700,6 +2700,43 @@ function testDocApi() {
|
|||||||
assert.equal(resp2.data, 'A,B\nSanta,1\nBob,11\nAlice,2\nFelix,22\n');
|
assert.equal(resp2.data, 'A,B\nSanta,1\nBob,11\nAlice,2\nFelix,22\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("GET /docs/{did}/download/table-schema serves table-schema-encoded document with header=colId", async function () {
|
||||||
|
const { docUrl, tableUrl } = await generateDocAndUrl('tableSchemaWithColIdAsHeader');
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
id: 'Some_ID',
|
||||||
|
fields: {
|
||||||
|
label: 'Some Label',
|
||||||
|
type: 'Text',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const setupColResp = await axios.put(`${tableUrl}/columns`, { columns }, {...chimpy, params: { replaceall: true }});
|
||||||
|
assert.equal(setupColResp.status, 200);
|
||||||
|
|
||||||
|
const resp = await axios.get(`${docUrl}/download/table-schema?tableId=Table1&header=colId`, chimpy);
|
||||||
|
assert.equal(resp.status, 200);
|
||||||
|
const expected = {
|
||||||
|
format: "csv",
|
||||||
|
mediatype: "text/csv",
|
||||||
|
encoding: "utf-8",
|
||||||
|
dialect: {
|
||||||
|
delimiter: ",",
|
||||||
|
doubleQuote: true,
|
||||||
|
},
|
||||||
|
name: 'table1',
|
||||||
|
title: 'Table1',
|
||||||
|
schema: {
|
||||||
|
fields: [{
|
||||||
|
name: 'Some_ID',
|
||||||
|
type: 'string',
|
||||||
|
format: 'default',
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert.deepInclude(resp.data, expected);
|
||||||
|
});
|
||||||
|
|
||||||
it("GET /docs/{did}/download/table-schema respects permissions", async function () {
|
it("GET /docs/{did}/download/table-schema respects permissions", async function () {
|
||||||
// kiwi has no access to TestDoc
|
// kiwi has no access to TestDoc
|
||||||
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema?tableId=Table1`, kiwi);
|
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema?tableId=Table1`, kiwi);
|
||||||
|
Loading…
Reference in New Issue
Block a user