├── .babelrc ├── .dockerignore ├── .editorconfig ├── .env ├── .erdconfig ├── .eslintignore ├── .eslintrc ├── .github ├── issue_template.md ├── pull_request_template.md └── stale.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── Vagrantfile ├── app ├── assets │ ├── images │ │ ├── layout │ │ │ ├── ._portus-logo-dashboard.png │ │ │ ├── bg.png │ │ │ ├── company-panel-bg.jpg │ │ │ ├── portus-error.png │ │ │ ├── portus-logo-dashboard.png │ │ │ └── portus-logo-login-page.png │ │ └── user.svg │ ├── javascripts │ │ ├── base │ │ │ └── component.js │ │ ├── bootstrap.js │ │ ├── config.js │ │ ├── globals.js │ │ ├── main.js │ │ ├── modules │ │ │ ├── admin │ │ │ │ └── registries │ │ │ │ │ ├── components │ │ │ │ │ └── form.vue │ │ │ │ │ ├── index.js │ │ │ │ │ ├── pages │ │ │ │ │ ├── edit.vue │ │ │ │ │ └── new.vue │ │ │ │ │ └── service.js │ │ │ ├── dashboard │ │ │ │ ├── components │ │ │ │ │ ├── search.js │ │ │ │ │ └── tabbed_widget.js │ │ │ │ ├── index.js │ │ │ │ └── pages │ │ │ │ │ └── dashboard.js │ │ │ ├── explore │ │ │ │ ├── components │ │ │ │ │ ├── result-item.vue │ │ │ │ │ └── result.vue │ │ │ │ ├── index.js │ │ │ │ └── pages │ │ │ │ │ └── index.js │ │ │ ├── namespaces │ │ │ │ ├── components │ │ │ │ │ ├── delete-btn.vue │ │ │ │ │ ├── details.vue │ │ │ │ │ ├── edit-form.vue │ │ │ │ │ ├── info.vue │ │ │ │ │ ├── new-form.vue │ │ │ │ │ ├── panel.vue │ │ │ │ │ ├── table-row.vue │ │ │ │ │ ├── table.vue │ │ │ │ │ ├── transfer-modal.vue │ │ │ │ │ ├── visibility-chooser.vue │ │ │ │ │ └── visibility.vue │ │ │ │ ├── index.js │ │ │ │ ├── mixins │ │ │ │ │ ├── form.js │ │ │ │ │ └── visibility.js │ │ │ │ ├── pages │ │ │ │ │ ├── index.vue │ │ │ │ │ └── show.vue │ │ │ │ ├── services │ │ │ │ │ └── namespaces.js │ │ │ │ └── store.js │ │ │ ├── repositories │ │ │ │ ├── components │ │ │ │ │ ├── comments │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── list-item.vue │ │ │ │ │ │ ├── list.vue │ │ │ │ │ │ └── wrapper.vue │ │ │ │ │ ├── overview.vue │ │ │ │ │ ├── panel.vue │ │ │ │ │ ├── remote-panel.vue │ │ │ │ │ ├── remote-table.vue │ │ │ │ │ ├── star.vue │ │ │ │ │ ├── table-row.vue │ │ │ │ │ ├── table.vue │ │ │ │ │ └── tags │ │ │ │ │ │ ├── delete-tag-action.vue │ │ │ │ │ │ ├── tag.vue │ │ │ │ │ │ ├── tags-not-loaded.vue │ │ │ │ │ │ ├── tags-table-row.vue │ │ │ │ │ │ ├── tags-table.vue │ │ │ │ │ │ └── wrapper.vue │ │ │ │ ├── index.js │ │ │ │ ├── pages │ │ │ │ │ ├── index.vue │ │ │ │ │ └── show.vue │ │ │ │ ├── services │ │ │ │ │ ├── comments.js │ │ │ │ │ ├── repositories.js │ │ │ │ │ ├── tags.js │ │ │ │ │ └── vulnerabilities-parser.js │ │ │ │ └── store.js │ │ │ ├── tags │ │ │ │ ├── index.js │ │ │ │ └── pages │ │ │ │ │ └── show.vue │ │ │ ├── teams │ │ │ │ ├── components │ │ │ │ │ ├── delete-modal.vue │ │ │ │ │ ├── details.vue │ │ │ │ │ ├── edit-form.vue │ │ │ │ │ ├── info.vue │ │ │ │ │ ├── members │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── panel.vue │ │ │ │ │ │ ├── table-row.vue │ │ │ │ │ │ └── table.vue │ │ │ │ │ ├── new-form.vue │ │ │ │ │ ├── panel.vue │ │ │ │ │ ├── table-row.vue │ │ │ │ │ └── table.vue │ │ │ │ ├── index.js │ │ │ │ ├── pages │ │ │ │ │ ├── index.vue │ │ │ │ │ └── show.vue │ │ │ │ ├── service.js │ │ │ │ └── store.js │ │ │ ├── users │ │ │ │ ├── components │ │ │ │ │ ├── application-tokens │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── panel.vue │ │ │ │ │ │ ├── table-row.vue │ │ │ │ │ │ ├── table.vue │ │ │ │ │ │ └── wrapper.vue │ │ │ │ │ ├── deletion-modal.vue │ │ │ │ │ ├── deletion-panel.vue │ │ │ │ │ ├── disable-account-panel.vue │ │ │ │ │ ├── edit-panel.vue │ │ │ │ │ ├── new-form.vue │ │ │ │ │ ├── panel.vue │ │ │ │ │ ├── password-form.js │ │ │ │ │ ├── profile-form.js │ │ │ │ │ ├── table-row.vue │ │ │ │ │ └── table.vue │ │ │ │ ├── index.js │ │ │ │ ├── pages │ │ │ │ │ ├── edit.vue │ │ │ │ │ ├── index.vue │ │ │ │ │ ├── legacy │ │ │ │ │ │ └── edit.js │ │ │ │ │ ├── sign-in.js │ │ │ │ │ └── sign-up.js │ │ │ │ ├── service.js │ │ │ │ ├── store.js │ │ │ │ └── unauthenticated.js │ │ │ ├── vulnerabilities │ │ │ │ └── components │ │ │ │ │ ├── panel.vue │ │ │ │ │ ├── preview.vue │ │ │ │ │ ├── summary.vue │ │ │ │ │ ├── table-row.vue │ │ │ │ │ └── table.vue │ │ │ └── webhooks │ │ │ │ ├── components │ │ │ │ ├── deliveries │ │ │ │ │ ├── panel.vue │ │ │ │ │ ├── table-row.vue │ │ │ │ │ └── table.vue │ │ │ │ ├── details.vue │ │ │ │ ├── edit-form.vue │ │ │ │ ├── headers │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── panel.vue │ │ │ │ │ ├── table-row.vue │ │ │ │ │ └── table.vue │ │ │ │ ├── info.vue │ │ │ │ ├── new-form.vue │ │ │ │ ├── panel.vue │ │ │ │ ├── table-row.vue │ │ │ │ └── table.vue │ │ │ │ ├── index.js │ │ │ │ ├── pages │ │ │ │ ├── index.vue │ │ │ │ └── show.vue │ │ │ │ ├── services │ │ │ │ ├── deliveries.js │ │ │ │ ├── headers.js │ │ │ │ └── webhooks.js │ │ │ │ └── store.js │ │ ├── plugins │ │ │ ├── alert.js │ │ │ ├── config.js │ │ │ ├── eventbus.js │ │ │ ├── index.js │ │ │ └── unauthenticated.js │ │ ├── polyfill.js │ │ ├── shared │ │ │ ├── components │ │ │ │ ├── checkbox.vue │ │ │ │ ├── loading-icon.vue │ │ │ │ ├── modal.vue │ │ │ │ ├── panel.vue │ │ │ │ ├── sign-out-btn.vue │ │ │ │ ├── star.vue │ │ │ │ ├── table-pagination.vue │ │ │ │ └── toggle-link.vue │ │ │ └── mixins │ │ │ │ ├── form.js │ │ │ │ ├── table-paginated.js │ │ │ │ └── table-sortable.js │ │ ├── unauthenticated.js │ │ └── utils │ │ │ ├── alert.js │ │ │ ├── comparator.js │ │ │ ├── csrf.js │ │ │ ├── date.js │ │ │ ├── effects.js │ │ │ ├── http.js │ │ │ └── range.js │ └── stylesheets │ │ ├── application.scss │ │ ├── components │ │ ├── activities.scss │ │ ├── buttons.scss │ │ ├── errors.scss │ │ ├── images.scss │ │ ├── layout.scss │ │ ├── namespaces │ │ │ └── info.scss │ │ ├── pagination.scss │ │ ├── repositories │ │ │ └── overview.scss │ │ ├── responsive-utilities.scss │ │ ├── tables.scss │ │ ├── vue-multiselect.scss │ │ ├── vulnerabilities │ │ │ ├── severities.scss │ │ │ ├── summary.scss │ │ │ └── table.scss │ │ └── webhooks │ │ │ └── edit-form.scss │ │ ├── core │ │ ├── mixins.scss │ │ └── variables.scss │ │ ├── includes │ │ ├── alignment.scss │ │ ├── forms.scss │ │ ├── labels.scss │ │ ├── modals.scss │ │ ├── panels.scss │ │ └── types.scss │ │ └── pages │ │ ├── comments.scss │ │ ├── dashboard.scss │ │ ├── explore.scss │ │ ├── registries.scss │ │ ├── repositories.scss │ │ └── search.scss ├── controllers │ ├── admin │ │ ├── activities_controller.rb │ │ ├── base_controller.rb │ │ ├── registries_controller.rb │ │ └── users_controller.rb │ ├── api │ │ ├── base_controller.rb │ │ └── v2 │ │ │ ├── events_controller.rb │ │ │ ├── ping_controller.rb │ │ │ └── tokens_controller.rb │ ├── application_controller.rb │ ├── auth │ │ ├── omniauth_callbacks_controller.rb │ │ ├── omniauth_registrations_controller.rb │ │ ├── registrations_controller.rb │ │ └── sessions_controller.rb │ ├── comments_controller.rb │ ├── concerns │ │ ├── check_ldap.rb │ │ ├── headers.rb │ │ ├── session_flash.rb │ │ ├── with_ordering.rb │ │ └── with_pagination.rb │ ├── dashboard_controller.rb │ ├── errors_controller.rb │ ├── explore_controller.rb │ ├── help_controller.rb │ ├── namespaces_controller.rb │ ├── passwords_controller.rb │ ├── repositories_controller.rb │ ├── search_controller.rb │ ├── tags_controller.rb │ ├── teams_controller.rb │ ├── webhook_deliveries_controller.rb │ ├── webhook_headers_controller.rb │ └── webhooks_controller.rb ├── helpers │ ├── activities_helper.rb │ ├── application_helper.rb │ ├── namespaces_helper.rb │ ├── registries_helper.rb │ ├── repositories_helper.rb │ ├── search_helper.rb │ ├── sessions_helper.rb │ ├── teams_helper.rb │ └── webhooks_helper.rb ├── mailers │ └── devise_mailer.rb ├── models │ ├── activity │ │ └── fallback.rb │ ├── application_record.rb │ ├── application_token.rb │ ├── comment.rb │ ├── namespace.rb │ ├── namespace │ │ └── auth_scope.rb │ ├── registry.rb │ ├── registry │ │ └── auth_scope.rb │ ├── registry_event.rb │ ├── repository.rb │ ├── scan_result.rb │ ├── star.rb │ ├── tag.rb │ ├── team.rb │ ├── team_user.rb │ ├── user.rb │ ├── vulnerability.rb │ ├── webhook.rb │ ├── webhook_delivery.rb │ └── webhook_header.rb ├── policies │ ├── application_token_policy.rb │ ├── comment_policy.rb │ ├── namespace_policy.rb │ ├── public_activity │ │ └── activity_policy.rb │ ├── registry_policy.rb │ ├── repository_policy.rb │ ├── tag_policy.rb │ ├── team_policy.rb │ ├── team_user_policy.rb │ ├── webhook_delivery_policy.rb │ ├── webhook_header_policy.rb │ └── webhook_policy.rb ├── services │ ├── README.md │ ├── base_service.rb │ ├── base_validate_service.rb │ ├── helpers │ │ └── change_name_description.rb │ ├── namespaces │ │ ├── build_service.rb │ │ ├── create_service.rb │ │ ├── destroy_service.rb │ │ └── update_service.rb │ ├── registries │ │ ├── base_service.rb │ │ ├── create_service.rb │ │ ├── update_service.rb │ │ └── validate_service.rb │ ├── repositories │ │ ├── destroy_service.rb │ │ └── update_service.rb │ ├── tags │ │ └── destroy_service.rb │ ├── team_users │ │ ├── base_service.rb │ │ ├── build_service.rb │ │ ├── create_service.rb │ │ ├── destroy_service.rb │ │ └── update_service.rb │ └── teams │ │ ├── create_service.rb │ │ ├── destroy_service.rb │ │ ├── migration_service.rb │ │ └── update_service.rb ├── validators │ ├── http_validator.rb │ ├── namespace_validator.rb │ └── unique_tag_validator.rb └── views │ ├── admin │ ├── activities │ │ ├── index.csv.slim │ │ └── index.html.slim │ ├── dashboard │ │ └── index.html.slim │ ├── registries │ │ ├── edit.html.slim │ │ ├── index.html.slim │ │ └── new.html.slim │ └── users │ │ ├── edit.html.slim │ │ ├── index.html.slim │ │ └── new.html.slim │ ├── auth │ └── omniauth_registrations │ │ └── new.html.slim │ ├── dashboard │ ├── _repository.html.slim │ ├── _star.html.slim │ ├── index.html.slim │ └── partials │ │ ├── _portus_user_alert.html.slim │ │ ├── _recent_activities.html.slim │ │ ├── _repositories.html.slim │ │ └── _stats.html.slim │ ├── devise │ ├── passwords │ │ ├── edit.html.slim │ │ └── new.html.slim │ ├── registrations │ │ ├── edit.html.slim │ │ └── new.html.slim │ └── sessions │ │ ├── _social_login.html.slim │ │ └── new.html.slim │ ├── errors │ ├── 401.html.slim │ ├── 404.html.slim │ ├── 422.html.slim │ ├── 500.csv.slim │ ├── 500.html.erb │ ├── _status_title.html.slim │ └── show.html.erb │ ├── explore │ ├── index.slim │ └── partials │ │ ├── _form.html.slim │ │ └── _top.html.slim │ ├── help │ └── index.slim │ ├── kaminari │ ├── _first_page.html.slim │ ├── _gap.html.slim │ ├── _last_page.html.slim │ ├── _next_page.html.slim │ ├── _page.html.slim │ ├── _paginator.html.slim │ └── _prev_page.html.slim │ ├── layouts │ ├── application.html.slim │ ├── authentication.html.slim │ └── errors.html.slim │ ├── namespaces │ ├── index.html.erb │ └── show.html.slim │ ├── public_activity │ ├── application_token │ │ ├── _create.csv.slim │ │ ├── _create.html.slim │ │ ├── _destroy.csv.slim │ │ └── _destroy.html.slim │ ├── namespace │ │ ├── _change_namespace_description.csv.slim │ │ ├── _change_namespace_description.html.slim │ │ ├── _change_team.csv.slim │ │ ├── _change_team.html.slim │ │ ├── _change_visibility.csv.slim │ │ ├── _change_visibility.html.slim │ │ ├── _create.csv.slim │ │ ├── _create.html.slim │ │ ├── _delete.csv.slim │ │ ├── _delete.html.slim │ │ ├── _private.csv.slim │ │ ├── _private.html.slim │ │ ├── _public.csv.slim │ │ └── _public.html.slim │ ├── registry │ │ ├── _delete.csv.slim │ │ ├── _delete.html.slim │ │ ├── _remove_team.csv.slim │ │ └── _remove_team.html.slim │ ├── repository │ │ ├── _delete.csv.slim │ │ ├── _delete.html.slim │ │ ├── _push.csv.slim │ │ └── _push.html.slim │ ├── team │ │ ├── _add_member.csv.slim │ │ ├── _add_member.html.slim │ │ ├── _change_member_role.csv.slim │ │ ├── _change_member_role.html.slim │ │ ├── _change_team_description.csv.slim │ │ ├── _change_team_description.html.slim │ │ ├── _change_team_name.csv.slim │ │ ├── _change_team_name.html.slim │ │ ├── _create.csv.slim │ │ ├── _create.html.slim │ │ ├── _migration.html.slim │ │ ├── _remove_member.csv.slim │ │ └── _remove_member.html.slim │ ├── user │ │ ├── _delete.csv.slim │ │ └── _delete.html.slim │ └── webhook │ │ ├── _create.csv.slim │ │ ├── _create.html.slim │ │ ├── _destroy.csv.slim │ │ ├── _destroy.html.slim │ │ ├── _disabled.csv.slim │ │ ├── _disabled.html.slim │ │ ├── _enabled.csv.slim │ │ ├── _enabled.html.slim │ │ ├── _update.csv.slim │ │ └── _update.html.slim │ ├── repositories │ ├── index.html.slim │ └── show.html.slim │ ├── search │ ├── _desktop_categories.html.slim │ ├── _mobile_categories.html.slim │ ├── _namespaces.html.slim │ ├── _repositories.html.slim │ ├── _teams.html.slim │ └── index.html.slim │ ├── shared │ ├── _aside.html.slim │ ├── _config.html.slim │ ├── _header.html.slim │ ├── _notification.html.slim │ ├── _notifications.html.slim │ └── _search.html.slim │ ├── tags │ └── show.html.slim │ ├── teams │ ├── index.html.slim │ └── show.html.slim │ └── webhooks │ ├── index.html.slim │ └── show.html.slim ├── bin ├── background.rb ├── bundle ├── check_db.rb ├── ci │ ├── after_script.sh │ ├── before_script.sh │ ├── install.sh │ └── run.sh ├── client.rb ├── generate_test_data.rb ├── health.rb ├── integration │ └── integration.rb ├── rails ├── rake ├── schema_check.rb ├── security.rb ├── setup ├── spring └── test-integration.sh ├── config.ru ├── config ├── application.rb ├── boot.rb ├── brakeman.ignore ├── brakeman.yml ├── config.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ ├── staging.rb │ └── test.rb ├── initializers │ ├── 01_database.rb │ ├── activity.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── checks.rb │ ├── cookies_serializer.rb │ ├── devise.rb │ ├── devise │ │ └── oauth.rb │ ├── draw_def.rb │ ├── environment_check.rb │ ├── filter_parameter_logging.rb │ ├── gravatar_image_tag.rb │ ├── inflections.rb │ ├── ldap_authenticatable.rb │ ├── mail.rb │ ├── mailer_url_options.rb │ ├── mime_types.rb │ ├── portus_user.rb │ ├── rails.rb │ ├── registry.rb │ ├── ruby.rb │ ├── session_store.rb │ ├── swagger.rb │ ├── version.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ ├── en.bootstrap.yml │ └── en.yml ├── puma.rb ├── routes.rb ├── routes │ ├── admin.rb │ ├── namespaces.rb │ ├── registry_api.rb │ ├── repositories.rb │ ├── root.rb │ ├── teams.rb │ └── users.rb ├── secrets.yml └── webpack.js ├── db ├── migrate │ ├── 20150414104850_devise_create_users.rb │ ├── 20150416121417_create_namespaces.rb │ ├── 20150416121842_create_repositories.rb │ ├── 20150416122030_create_tags.rb │ ├── 20150417095841_create_teams.rb │ ├── 20150417100647_add_team_reference_to_namespace.rb │ ├── 20150426115313_create_team_users.rb │ ├── 20150427092952_add_admin_to_users.rb │ ├── 20150428081205_add_owner_to_team_users.rb │ ├── 20150428081530_remove_owner_id_from_teams.rb │ ├── 20150429102443_add_role_to_team_user.rb │ ├── 20150429185051_add_public_to_namespace.rb │ ├── 20150507155138_create_registries.rb │ ├── 20150507155425_add_registry_id_to_namespaces.rb │ ├── 20150512105052_create_activities.rb │ ├── 20150513133049_add_index_to_activities_key.rb │ ├── 20150515111638_add_author_to_tag.rb │ ├── 20150515114145_add_global_flag_to_namespaces.rb │ ├── 20150518142223_add_hidden_to_teams.rb │ ├── 20150521145620_add_fulltext_indexes_on_repositories_namespaces.rb │ ├── 20150522140822_add_index_name_on_teams.rb │ ├── 20150522141052_fix_index_name_on_repositories.rb │ ├── 20150522141547_fix_index_name_on_namespaces.rb │ ├── 20150522144027_add_index_on_tag_name_repository_id.rb │ ├── 20150629080516_change_registry_id_from_namespace.rb │ ├── 20150722194840_create_stars.rb │ ├── 20150729153854_add_enabled_to_users.rb │ ├── 20150805130722_create_crono_jobs.rb │ ├── 20150831131727_add_ldap_name_to_users.rb │ ├── 20150915130327_add_lockable_to_users.rb │ ├── 20150923091830_add_use_ssl_to_registries.rb │ ├── 20150924084635_add_attribute_to_teams.rb │ ├── 20150928112551_add_description_to_namespace.rb │ ├── 20151029145958_remove_not_null_constraint_on_users_email.rb │ ├── 20151112003047_add_digest_to_tags.rb │ ├── 20151113162311_create_comments.rb │ ├── 20151113162513_add_author_to_comment.rb │ ├── 20151117181723_create_application_tokens.rb │ ├── 20151124150353_removetitle.rb │ ├── 20151215152138_remove_log_from_crono_job.rb │ ├── 20160411150441_create_webhooks.rb │ ├── 20160411150458_create_webhook_headers.rb │ ├── 20160411150745_create_webhook_deliveries.rb │ ├── 20160422075603_add_image_id_to_tag.rb │ ├── 20160502140301_add_marked_to_repositories_and_tags.rb │ ├── 20160510153011_add_namespace_id_to_users.rb │ ├── 20160519110301_add_external_hostname_to_registries.rb │ ├── 20160526105216_remove_index_on_ldap_users.rb │ ├── 20160531151718_add_display_name_to_users.rb │ ├── 20160614114318_add_visibility_to_namespace.rb │ ├── 20160614121943_move_public_to_visibility_on_namespace.rb │ ├── 20160614122012_remove_public_from_namespace.rb │ ├── 20160825105515_add_username_to_tags.rb │ ├── 20160927141850_create_registry_events.rb │ ├── 20170630194953_add_omniauth_to_users.rb │ ├── 20171011150408_remove_fulltext_index_repositories_on_name_index.rb │ ├── 20171014192702_add_indexes_to_activities.rb │ ├── 20171031112721_remove_index_on_tag_name_repository_id.rb │ ├── 20171130095821_add_scanned_and_vulns_to_tag.rb │ ├── 20171213093923_add_status_and_data_to_registry_event.rb │ ├── 20171213094038_remove_repository_and_tag_from_registry_event.rb │ ├── 20171220095321_drop_crono_jobs_table.rb │ ├── 20180109114124_add_name_to_webhooks.rb │ ├── 20180207145522_change_vulnerabilities_to_medium_text.rb │ ├── 20180409142550_create_vulnerabilities.rb │ ├── 20180409143945_create_scan_results.rb │ ├── 20180411102022_remove_vulnerabilities_from_tag.rb │ ├── 20180412140442_add_description_to_vulnerabilities.rb │ ├── 20180612145708_add_bot_to_user.rb │ ├── 20181213101302_add_index_scan_results_vulnerability_tag.rb │ ├── 20181224123453_add_ldap_group_checked_to_teams.rb │ ├── 20190102143451_add_description_to_repositories.rb │ ├── 20190103113934_add_checked_at_to_team.rb │ ├── 20190103124548_add_ldap_group_checked_to_user.rb │ ├── 20190109112643_add_size_to_tags.rb │ ├── 20190115133935_add_pulled_at_to_tags.rb │ └── 20190314173309_change_tag_size.rb ├── schema.mysql.rb ├── schema.postgresql.rb └── seeds.rb ├── doc ├── README.md └── database.png ├── docker-compose.yml ├── docker ├── Dockerfile ├── README.md └── init ├── examples ├── README.md ├── compose │ ├── .env │ ├── README.md │ ├── clair │ │ └── clair.yml │ ├── docker-compose.clair-ssl.yml │ ├── docker-compose.clair.yml │ ├── docker-compose.insecure.yml │ ├── docker-compose.ldap.yml │ ├── docker-compose.yml │ ├── nginx │ │ └── nginx.conf │ ├── registry │ │ ├── config.yml │ │ └── init │ └── secrets │ │ └── .gitignore ├── development │ ├── compose │ │ ├── bootstrap-webpack │ │ ├── config.yml │ │ ├── init.rb │ │ └── portus.crt │ ├── postgresql │ │ ├── README.md │ │ └── docker-compose.yml │ └── vagrant │ │ ├── conf │ │ ├── ca_bundle │ │ │ ├── README.md │ │ │ ├── ca.crt │ │ │ ├── ca.key │ │ │ ├── server.crt │ │ │ ├── server.csr │ │ │ ├── server.key │ │ │ ├── server.key.secure │ │ │ └── server.pub │ │ ├── portus │ │ │ ├── httpd.conf.local │ │ │ ├── portus.test.lan.conf │ │ │ └── sysconfig_apache2 │ │ └── registry-config.yml │ │ ├── portus_required_packages.yml │ │ ├── provision_client │ │ ├── provision_portus_by_rpms │ │ ├── provision_registry │ │ └── setup_private_network └── kubernetes │ └── README.md ├── lib ├── api │ ├── entities.rb │ ├── helpers.rb │ ├── helpers │ │ ├── application_tokens.rb │ │ ├── comments.rb │ │ ├── errors.rb │ │ ├── namespaces.rb │ │ ├── ordering.rb │ │ ├── pagination.rb │ │ ├── repositories.rb │ │ ├── teams.rb │ │ └── webhooks.rb │ ├── root_api.rb │ ├── v1 │ │ ├── application_tokens.rb │ │ ├── health.rb │ │ ├── namespaces.rb │ │ ├── ordering_params.rb │ │ ├── pagination_params.rb │ │ ├── registries.rb │ │ ├── repositories.rb │ │ ├── tags.rb │ │ ├── teams.rb │ │ ├── users.rb │ │ └── vulnerabilities.rb │ └── version.rb ├── omni_auth │ └── strategies │ │ └── bitbucket.rb ├── portus │ ├── auth_from_token.rb │ ├── auth_scope.rb │ ├── background │ │ ├── README.md │ │ ├── garbage_collector.rb │ │ ├── ldap.rb │ │ ├── registry.rb │ │ ├── security_scanning.rb │ │ └── sync.rb │ ├── checks.rb │ ├── cmd.rb │ ├── db.rb │ ├── deprecation_error.rb │ ├── errors.rb │ ├── health.rb │ ├── health_checks │ │ ├── clair.rb │ │ ├── db.rb │ │ ├── ldap.rb │ │ └── registry.rb │ ├── http_helpers.rb │ ├── jwt_token.rb │ ├── ldap │ │ ├── adapter.rb │ │ ├── authenticatable.rb │ │ ├── configuration.rb │ │ ├── connection.rb │ │ ├── errors.rb │ │ ├── login.rb │ │ └── search.rb │ ├── mail.rb │ ├── migrate.rb │ ├── registry_client.rb │ ├── registry_notification.rb │ ├── request_error.rb │ ├── security.rb │ ├── security_backends │ │ ├── base.rb │ │ ├── clair.rb │ │ ├── dummy.rb │ │ ├── fixtures │ │ │ ├── dummy.json │ │ │ └── empty.json │ │ └── zypper.rb │ └── test.rb └── tasks │ ├── annotate.rake │ ├── assets.rake │ ├── brakeman.rake │ ├── helpers.rb │ ├── info.rake │ ├── portus │ ├── admin.rake │ ├── api.rake │ ├── db.rake │ ├── registry.rake │ ├── tags.rake │ └── user.rake │ ├── release │ ├── bump.rake │ └── prepare.rake │ ├── test │ ├── git_validate.rake │ └── integration.rake │ └── yarn.rake ├── package.json ├── packaging └── suse │ ├── .gitignore │ ├── README.md │ ├── conf │ └── registry.config.yml.in │ ├── make_spec.sh │ ├── package_and_push_to_obs.sh │ ├── patches │ └── README │ ├── portus.spec.in │ ├── release │ ├── .gitignore │ ├── README │ ├── project.xml.template │ ├── projectconfig.txt │ └── release.sh │ └── rubygem_check.rb ├── public ├── favicon.ico ├── favicon │ ├── android-chrome-144x144.png │ ├── android-chrome-192x192.png │ ├── android-chrome-36x36.png │ ├── android-chrome-48x48.png │ ├── android-chrome-72x72.png │ ├── android-chrome-96x96.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── manifest.json │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ └── safari-pinned-tab.svg ├── landing.html.in └── robots.txt ├── spec ├── api │ ├── grape_api │ │ └── v1 │ │ │ ├── application_tokens_spec.rb │ │ │ ├── health_spec.rb │ │ │ ├── namespaces_spec.rb │ │ │ ├── registries_spec.rb │ │ │ ├── repositories_spec.rb │ │ │ ├── root_spec.rb │ │ │ ├── tags_spec.rb │ │ │ ├── teams_spec.rb │ │ │ ├── users_spec.rb │ │ │ ├── version_spec.rb │ │ │ └── vulnerabilities_spec.rb │ ├── helpers │ │ └── namespaces_spec.rb │ └── v2 │ │ ├── events_spec.rb │ │ ├── ping_spec.rb │ │ └── token_spec.rb ├── factories │ ├── activities.rb │ ├── application_token.rb │ ├── comments.rb │ ├── namespaces.rb │ ├── registries.rb │ ├── registry_raw_event.rb │ ├── repositories.rb │ ├── scan_result.rb │ ├── star.rb │ ├── tag.rb │ ├── teams_factory.rb │ ├── users_factory.rb │ ├── vulnerability.rb │ ├── webhook_deliveries.rb │ ├── webhook_headers.rb │ └── webhooks.rb ├── helpers │ ├── activities_helper_spec.rb │ ├── application_helper_spec.rb │ ├── namespaces_helper_spec.rb │ ├── registries_helper_spec.rb │ ├── repositories_helper_spec.rb │ ├── search_helper_spec.rb │ ├── sessions_helper_spec.rb │ └── teams_helper_spec.rb ├── integration │ ├── README.md │ ├── events.bats │ ├── health.bats │ ├── helpers.bash │ ├── helpers │ │ ├── curl.rb │ │ ├── delete.rb │ │ ├── eval.rb │ │ ├── ldap.rb │ │ ├── wait_event_done.rb │ │ ├── wait_ldap_check.rb │ │ └── waiter.rb │ ├── ldap │ │ ├── health.bats │ │ ├── login.bats │ │ ├── team.bats │ │ └── user.bats │ ├── login.bats │ ├── profiles │ │ ├── full.rb │ │ ├── ldap.rb │ │ ├── minimal.rb │ │ └── shared.rb │ └── push.bats ├── javascripts │ ├── .eslintrc │ ├── modules │ │ ├── repositories │ │ │ ├── delete-tag-action.spec.js │ │ │ ├── tag.spec.js │ │ │ ├── tags-table-row.spec.js │ │ │ ├── tags-table.spec.js │ │ │ ├── vulnerabilities-parser.spec.js │ │ │ └── vulnerabilities-preview.spec.js │ │ └── teams │ │ │ └── form.spec.js │ ├── setup.js │ ├── shared │ │ ├── loading-icon.spec.js │ │ ├── table-pagination.spec.js │ │ └── toggle-link.spec.js │ └── utils │ │ ├── comparator.spec.js │ │ ├── date.spec.js │ │ └── range.spec.js ├── lib │ ├── omni_auth │ │ └── strategies │ │ │ └── bitbucket_spec.rb │ └── portus │ │ ├── background │ │ ├── garbage_collector_spec.rb │ │ ├── ldap_spec.rb │ │ ├── registry_spec.rb │ │ ├── security_scanning_spec.rb │ │ └── sync_spec.rb │ │ ├── db_spec.rb │ │ ├── deprecation_error_spec.rb │ │ ├── jwt_token_spec.rb │ │ ├── ldap │ │ ├── authenticatable_spec.rb │ │ ├── configuration_spec.rb │ │ └── search_spec.rb │ │ ├── mail_spec.rb │ │ ├── migrate_spec.rb │ │ ├── registry_client_spec.rb │ │ ├── registry_notification_spec.rb │ │ ├── request_error_spec.rb │ │ ├── security │ │ └── clair_spec.rb │ │ └── security_spec.rb ├── models │ ├── application_token_spec.rb │ ├── comment_spec.rb │ ├── namespace │ │ └── auth_scope_spec.rb │ ├── namespace_spec.rb │ ├── registry │ │ └── auth_scope_spec.rb │ ├── registry_spec.rb │ ├── repository_spec.rb │ ├── star_spec.rb │ ├── tag_spec.rb │ ├── team_spec.rb │ ├── team_user_spec.rb │ ├── user_spec.rb │ ├── webhook_delivery_spec.rb │ ├── webhook_header_spec.rb │ └── webhook_spec.rb ├── policies │ ├── namespace_policy_spec.rb │ ├── public_activity │ │ └── activity_policy_spec.rb │ ├── registry_policy_spec.rb │ ├── repository_policy_spec.rb │ ├── team_policy_spec.rb │ ├── team_user_policy_spec.rb │ └── webhook_policy_spec.rb ├── rails_helper.rb ├── requests │ ├── admin │ │ ├── activities_controller_spec.rb │ │ ├── registries_controller_spec.rb │ │ └── users_controller_spec.rb │ ├── api │ │ └── v2 │ │ │ └── ping_controller_spec.rb │ ├── auth │ │ └── registrations_controller_spec.rb │ ├── comments_controller_spec.rb │ ├── dashboard_controller_spec.rb │ ├── errors_controller_spec.rb │ ├── explore_controller_spec.rb │ ├── help_controller_spec.rb │ ├── namespaces_controller_spec.rb │ ├── passwords_controller_spec.rb │ ├── repositories_controller_spec.rb │ ├── search_controller_spec.rb │ ├── tags_controller_spec.rb │ ├── teams_controller_spec.rb │ ├── webhook_deliveries_controller_spec.rb │ ├── webhook_headers_controller_spec.rb │ └── webhooks_controller_spec.rb ├── routing │ ├── namespaces_routing_spec.rb │ └── repositories_routing_spec.rb ├── services │ ├── namespaces │ │ ├── build_service_spec.rb │ │ ├── create_service_spec.rb │ │ └── destroy_service_spec.rb │ ├── registries │ │ ├── create_service_spec.rb │ │ └── validate_service_spec.rb │ ├── repositories │ │ ├── destroy_service_spec.rb │ │ └── update_service_spec.rb │ ├── tags │ │ └── destroy_service_spec.rb │ ├── team_users │ │ ├── build_service_spec.rb │ │ ├── create_service_spec.rb │ │ ├── destroy_service_spec.rb │ │ └── update_service_spec.rb │ └── teams │ │ ├── create_service_spec.rb │ │ ├── destroy_service_spec.rb │ │ └── migration_service_spec.rb ├── spec_helper.rb ├── support │ ├── connection.rb │ ├── containers.rb │ ├── database_cleaner.rb │ ├── devise.rb │ ├── helpers.rb │ ├── models │ │ └── registry_raw_event.rb │ ├── registry_events.rb │ └── wait_for_events.rb ├── system │ ├── admin │ │ ├── registries_spec.rb │ │ └── users_spec.rb │ ├── application_spec.rb │ ├── application_tokens_spec.rb │ ├── auth │ │ ├── login_feature_spec.rb │ │ ├── logout_feature_spec.rb │ │ ├── omniauth_registrations_spec.rb │ │ ├── profile_feature_spec.rb │ │ └── signup_feature_spec.rb │ ├── dashboard_spec.rb │ ├── errors_spec.rb │ ├── explore_spec.rb │ ├── forgotten_password_spec.rb │ ├── gravatar_spec.rb │ ├── help_spec.rb │ ├── namespaces_spec.rb │ ├── repositories_comments_spec.rb │ ├── repositories_spec.rb │ ├── tags_spec.rb │ ├── teams_spec.rb │ └── webhooks_spec.rb ├── vcr_cassettes │ ├── api_github_orgs.yml │ ├── api_github_teams.yml │ ├── api_gitlab_groups.yml │ ├── background │ │ ├── clair.yml │ │ └── node.yml │ ├── health │ │ ├── clair-bad.yml │ │ ├── clair-ok.yml │ │ └── ok.yml │ ├── registry │ │ ├── catalog_lots_of_repos.yml │ │ ├── catalog_lots_of_tags.yml │ │ ├── delete_blob.yml │ │ ├── delete_disabled.yml │ │ ├── delete_missing_blob.yml │ │ ├── get_image_manifest.yml │ │ ├── get_image_manifest_another_webhook.yml │ │ ├── get_image_manifest_namespaced_webhook.yml │ │ ├── get_image_manifest_namespaced_webhook_v2.yml │ │ ├── get_image_manifest_tags.yml │ │ ├── get_image_manifest_webhook.yml │ │ ├── get_missing_catalog_endpoint.yml │ │ ├── get_missing_image_manifest.yml │ │ ├── get_registry_catalog.yml │ │ ├── get_registry_catalog_namespace_missing.yml │ │ ├── get_registry_one_fails.yml │ │ ├── get_tags_list_webhook.yml │ │ ├── invalid_delete_blob.yml │ │ ├── missing_credentials.yml │ │ ├── successful_authentication.yml │ │ └── wrong_authentication.yml │ └── security │ │ ├── clair-is-not-there.yml │ │ ├── clair-is-unknown.yml │ │ ├── clair-wrong-get.yml │ │ ├── clair-wrong-post.yml │ │ ├── clair.yml │ │ └── clair_features_nil.yml └── views │ ├── explore │ └── index.html.slim_spec.rb │ ├── help │ └── index.html.slim_spec.rb │ ├── public_activity │ ├── registry │ │ ├── _delete.csv.slim_spec.rb │ │ └── _delete.html.slim_spec.rb │ ├── team │ │ ├── _remove_member.csv.slim_spec.rb │ │ └── _remove_member.html.slim_spec.rb │ └── webhook │ │ ├── _create.csv.slim_spec.rb │ │ ├── _create.html.slim_spec.rb │ │ ├── _enabled.csv.slim_spec.rb │ │ └── _enabled.html.slim_spec.rb │ └── search │ └── index.html.slim_spec.rb ├── vendor └── assets │ ├── fonts │ ├── pacifico.woff2 │ ├── raleway-bold.woff2 │ ├── raleway-heavy.woff2 │ ├── raleway-light.woff2 │ └── raleway.woff2 │ ├── javascripts │ └── lifeitup_layout.js │ └── stylesheets │ ├── bootstrap-typeahead.scss │ ├── lifeitup │ ├── alerts.scss │ ├── buttons.scss │ ├── images.scss │ ├── labels.scss │ ├── layout.scss │ ├── lifeitup.scss │ ├── links.scss │ ├── lists.scss │ ├── mixings.scss │ ├── panels.scss │ ├── responsive_tools.scss │ ├── table.scss │ └── variables.scss │ ├── pacifico.css.erb │ ├── raleway.css.erb │ └── vue-multiselect.css └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }] 4 | ], 5 | "plugins": [ 6 | "lodash", 7 | "transform-object-rest-spread" 8 | ], 9 | "env": { 10 | "test": { 11 | "plugins": ["istanbul"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git* 2 | tmp 3 | vagrant 4 | packaging 5 | log/* 6 | .DS_Store 7 | .bundle/config 8 | config/config-local.yml -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MACHINE_FQDN=172.17.0.1 2 | REGISTRY_PORT=5000 -------------------------------------------------------------------------------- /.erdconfig: -------------------------------------------------------------------------------- 1 | attributes: 2 | - content 3 | - primary_keys 4 | - foreign_key 5 | - inheritance 6 | - timestamps 7 | disconnected: true 8 | filename: doc/database 9 | filetype: png 10 | indirect: true 11 | inheritance: true 12 | markup: true 13 | notation: simple 14 | orientation: vertical 15 | polymorphism: false 16 | sort: false 17 | warn: true 18 | title: Portus 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /builds/ 2 | /coverage/ 3 | /coverage-javascript/ 4 | /node_modules/ 5 | /public/ 6 | /tmp/ 7 | /vendor/ -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | Provide a general description of the changes in your pull request. If this pull 4 | request fixes a known issue, please tag it as well (e.g.: `Fixes #1`). Check the 5 | `CONTRIBUTING.md` file for more information. 6 | 7 | Thanks for contributing to Portus! 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle 2 | /bundle 3 | /**/*.swp 4 | /log/* 5 | /*.log 6 | /tmp 7 | /.nyc_output 8 | /.vagrant 9 | /coverage 10 | /public/assets 11 | /config/config-local.yml 12 | /bin/portus 13 | /bin/integration/init 14 | /docker-compose.overlay.yml 15 | /spec/integration/fixtures/config.yml 16 | /spec/integration/fixtures/data 17 | /node_modules 18 | /webpack-report 19 | /db/schema.rb 20 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.2 2 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require File.expand_path("config/application", __dir__) 7 | 8 | Rails.application.load_tasks 9 | 10 | require "grape-swagger/rake/oapi_tasks" 11 | require "api/root_api" 12 | GrapeSwagger::Rake::OapiTasks.new(API::RootAPI) 13 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.5.0-dev 2 | -------------------------------------------------------------------------------- /app/assets/images/layout/._portus-logo-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/app/assets/images/layout/._portus-logo-dashboard.png -------------------------------------------------------------------------------- /app/assets/images/layout/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/app/assets/images/layout/bg.png -------------------------------------------------------------------------------- /app/assets/images/layout/company-panel-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/app/assets/images/layout/company-panel-bg.jpg -------------------------------------------------------------------------------- /app/assets/images/layout/portus-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/app/assets/images/layout/portus-error.png -------------------------------------------------------------------------------- /app/assets/images/layout/portus-logo-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/app/assets/images/layout/portus-logo-dashboard.png -------------------------------------------------------------------------------- /app/assets/images/layout/portus-logo-login-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/app/assets/images/layout/portus-logo-login-page.png -------------------------------------------------------------------------------- /app/assets/images/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/javascripts/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | pagination: window.PAGINATION, 3 | apiUrl: window.API_URL, 4 | }; 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/main.js: -------------------------------------------------------------------------------- 1 | // Bootstrap 2 | import 'bootstrap/js/transition'; 3 | import 'bootstrap/js/tab'; 4 | import 'bootstrap/js/tooltip'; 5 | import 'bootstrap/js/popover'; 6 | import 'bootstrap/js/dropdown'; 7 | import 'bootstrap/js/button'; 8 | import 'bootstrap/js/collapse'; 9 | 10 | // Life it up 11 | import 'vendor/lifeitup_layout'; 12 | 13 | // misc 14 | import './plugins'; 15 | import './polyfill'; 16 | 17 | // modules 18 | import './modules/admin/registries'; 19 | import './modules/users'; 20 | import './modules/dashboard'; 21 | import './modules/repositories'; 22 | import './modules/namespaces'; 23 | import './modules/tags'; 24 | import './modules/teams'; 25 | import './modules/webhooks'; 26 | 27 | import './bootstrap'; 28 | import './globals'; 29 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/admin/registries/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import AdminRegistriesNewPage from './pages/new'; 4 | import AdminRegistriesEditPage from './pages/edit'; 5 | 6 | $(() => { 7 | if (!$('body[data-controller="admin/registries"]').length) { 8 | return; 9 | } 10 | 11 | // eslint-disable-next-line no-new 12 | new Vue({ 13 | el: '.vue-root', 14 | 15 | components: { 16 | AdminRegistriesNewPage, 17 | AdminRegistriesEditPage, 18 | }, 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/admin/registries/pages/edit.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Registry: {{ registry.name }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 27 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/admin/registries/service.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueResource from 'vue-resource'; 3 | 4 | Vue.use(VueResource); 5 | 6 | const customActions = { 7 | validate: { 8 | method: 'GET', 9 | url: 'api/v1/registries/validate', 10 | }, 11 | }; 12 | 13 | const resource = Vue.resource('api/v1/registries/{/id}.json', {}, customActions); 14 | 15 | function validate(registry, field = null) { 16 | const data = registry; 17 | 18 | if (field) { 19 | data['only[]'] = field; 20 | } 21 | 22 | return resource.validate(data) 23 | .then(response => response.data) 24 | .catch(() => null); 25 | } 26 | 27 | export default { 28 | validate, 29 | }; 30 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/dashboard/components/tabbed_widget.js: -------------------------------------------------------------------------------- 1 | import BaseComponent from '~/base/component'; 2 | 3 | class TabbedWidget extends BaseComponent { 4 | elements() { 5 | this.$links = this.$el.find('a'); 6 | } 7 | 8 | events() { 9 | this.$links.on('click', (e) => { 10 | e.preventDefault(); 11 | $(this).tab('show'); 12 | }); 13 | } 14 | } 15 | 16 | export default TabbedWidget; 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import SearchComponent from './components/search'; 2 | import DashboardPage from './pages/dashboard'; 3 | 4 | const DASHBOARD_INDEX = 'dashboard/index'; 5 | 6 | $(() => { 7 | const $body = $('body'); 8 | const route = $body.data('route'); 9 | 10 | // Enable the search component globally if the HTML code is there. 11 | if ($('#search').length > 0) { 12 | // eslint-disable-next-line 13 | new SearchComponent($body); 14 | } 15 | 16 | if (route === DASHBOARD_INDEX) { 17 | // eslint-disable-next-line 18 | new DashboardPage($body); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/dashboard/pages/dashboard.js: -------------------------------------------------------------------------------- 1 | import BaseComponent from '~/base/component'; 2 | import TabbedWidget from '../components/tabbed_widget'; 3 | 4 | const TAB_WIDGET = '#sidebar-tabs'; 5 | 6 | class DashboardPage extends BaseComponent { 7 | elements() { 8 | this.$widget = this.$el.find(TAB_WIDGET); 9 | } 10 | 11 | mount() { 12 | this.tabbedWidget = new TabbedWidget(this.$widget); 13 | } 14 | } 15 | 16 | export default DashboardPage; 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/explore/index.js: -------------------------------------------------------------------------------- 1 | import './pages/index'; 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/explore/pages/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import Result from '../components/result'; 4 | 5 | $(() => { 6 | if (!$('body[data-route="explore/index"]').length) { 7 | return; 8 | } 9 | 10 | // eslint-disable-next-line no-new 11 | new Vue({ 12 | el: 'body[data-route="explore/index"] .vue-root', 13 | 14 | components: { 15 | Result, 16 | }, 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/namespaces/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import NamespacesIndexPage from './pages/index'; 4 | import NamespacesShowPage from './pages/show'; 5 | 6 | $(() => { 7 | if (!$('body[data-controller="namespaces"]').length) { 8 | return; 9 | } 10 | 11 | // eslint-disable-next-line no-new 12 | new Vue({ 13 | el: '.vue-root', 14 | 15 | components: { 16 | NamespacesIndexPage, 17 | NamespacesShowPage, 18 | }, 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/namespaces/store.js: -------------------------------------------------------------------------------- 1 | class RepositoriesStore { 2 | constructor() { 3 | this.state = { 4 | newFormVisible: false, 5 | editFormVisible: false, 6 | isDeleting: false, 7 | isLoading: false, 8 | notLoaded: false, 9 | onGoingVisibilityRequest: false, 10 | }; 11 | } 12 | } 13 | 14 | export default new RepositoriesStore(); 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/repositories/components/comments/list.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Nobody has left a comment yet 9 | 10 | 11 | 12 | 13 | 24 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/repositories/components/tags/tags-not-loaded.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Unable to fetch tags data. 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/repositories/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import RepositoriesIndexPage from './pages/index'; 4 | import RepositoriesShowPage from './pages/show'; 5 | 6 | $(() => { 7 | if (!$('body[data-controller="repositories"]').length) { 8 | return; 9 | } 10 | 11 | // eslint-disable-next-line no-new 12 | new Vue({ 13 | el: '.vue-root', 14 | 15 | components: { 16 | RepositoriesIndexPage, 17 | RepositoriesShowPage, 18 | }, 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/repositories/services/comments.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueResource from 'vue-resource'; 3 | 4 | Vue.use(VueResource); 5 | 6 | const oldResource = Vue.resource('repositories/{repositoryId}/comments{/id}.json'); 7 | 8 | function save(repositoryId, comment) { 9 | return oldResource.save({ repositoryId }, { comment }); 10 | } 11 | 12 | function remove(repositoryId, id) { 13 | return oldResource.delete({ repositoryId, id }); 14 | } 15 | 16 | export default { 17 | save, 18 | remove, 19 | }; 20 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/repositories/services/tags.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueResource from 'vue-resource'; 3 | 4 | Vue.use(VueResource); 5 | 6 | const resource = Vue.resource('api/v1/tags{/id}'); 7 | 8 | function remove(id) { 9 | return resource.delete({ id }); 10 | } 11 | 12 | export default { 13 | remove, 14 | }; 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/repositories/services/vulnerabilities-parser.js: -------------------------------------------------------------------------------- 1 | const countBySeverities = function (vulnerabilities) { 2 | const severities = { 3 | Defcon1: 0, 4 | Critical: 0, 5 | High: 0, 6 | Medium: 0, 7 | Low: 0, 8 | Unknown: 0, 9 | Negligible: 0, 10 | }; 11 | 12 | if (vulnerabilities) { 13 | vulnerabilities.forEach((vul) => { 14 | severities[vul.severity] += 1; 15 | }); 16 | } 17 | 18 | return severities; 19 | }; 20 | 21 | export default { 22 | countBySeverities, 23 | }; 24 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/repositories/store.js: -------------------------------------------------------------------------------- 1 | export default class RepositoriesStore { 2 | constructor() { 3 | this.state = { 4 | commentFormVisible: false, 5 | descriptionFormVisible: false, 6 | isDeleting: false, 7 | isLoading: true, 8 | notLoaded: false, 9 | selectedTags: [], 10 | currentTab: 'tags', 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/tags/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import TagsShowPage from './pages/show'; 4 | 5 | $(() => { 6 | if (!$('body[data-controller="tags"]').length) { 7 | return; 8 | } 9 | 10 | // eslint-disable-next-line no-new 11 | new Vue({ 12 | el: '.vue-root', 13 | 14 | components: { 15 | TagsShowPage, 16 | }, 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/teams/components/info.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/teams/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import TeamsIndexPage from './pages/index'; 4 | import TeamsShowPage from './pages/show'; 5 | 6 | $(() => { 7 | if (!$('body[data-controller="teams"]').length) { 8 | return; 9 | } 10 | 11 | // eslint-disable-next-line no-new 12 | new Vue({ 13 | el: '.vue-root', 14 | 15 | components: { 16 | TeamsIndexPage, 17 | TeamsShowPage, 18 | }, 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/teams/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const { set } = Vue; 4 | 5 | class TeamsStore { 6 | constructor() { 7 | this.state = { 8 | membersFormVisible: false, 9 | newFormVisible: false, 10 | editFormVisible: false, 11 | currentMember: {}, 12 | availableRoles: [], 13 | }; 14 | } 15 | 16 | setState(key, value) { 17 | set(this.state, key, value); 18 | } 19 | } 20 | 21 | export default new TeamsStore(); 22 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/users/components/panel.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Users 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/users/pages/sign-in.js: -------------------------------------------------------------------------------- 1 | import BaseComponent from '~/base/component'; 2 | 3 | import { fadeIn } from '~/utils/effects'; 4 | 5 | // UsersSignInPage component responsible to instantiate 6 | // the user's sign in page components and handle interactions. 7 | class UsersSignInPage extends BaseComponent { 8 | mount() { 9 | fadeIn(this.$el.find('> .container-fluid')); 10 | } 11 | } 12 | 13 | export default UsersSignInPage; 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/users/pages/sign-up.js: -------------------------------------------------------------------------------- 1 | import BaseComponent from '~/base/component'; 2 | 3 | import { fadeIn } from '~/utils/effects'; 4 | 5 | // UsersSignUpPage component responsible to instantiate 6 | // the user's sign up page components and handle interactions. 7 | class UsersSignUpPage extends BaseComponent { 8 | mount() { 9 | fadeIn(this.$el.find('> .container-fluid')); 10 | } 11 | } 12 | 13 | export default UsersSignUpPage; 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/users/store.js: -------------------------------------------------------------------------------- 1 | class UsersStore { 2 | constructor() { 3 | this.state = { 4 | newFormVisible: false, 5 | }; 6 | } 7 | } 8 | 9 | export default new UsersStore(); 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/users/unauthenticated.js: -------------------------------------------------------------------------------- 1 | import UsersSignUpPage from './pages/sign-up'; 2 | import UsersSignInPage from './pages/sign-in'; 3 | 4 | const USERS_SIGN_IN_ROUTE = 'auth/sessions/new'; 5 | const USERS_SIGN_UP_ROUTE = 'auth/registrations/new'; 6 | 7 | $(() => { 8 | const $body = $('body'); 9 | const route = $body.data('route'); 10 | 11 | if (route === USERS_SIGN_UP_ROUTE) { 12 | // eslint-disable-next-line 13 | new UsersSignUpPage($body); 14 | } 15 | 16 | if (route === USERS_SIGN_IN_ROUTE) { 17 | // eslint-disable-next-line 18 | new UsersSignInPage($body); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/webhooks/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import WebhooksIndexPage from './pages/index'; 4 | import WebhooksShowPage from './pages/show'; 5 | 6 | $(() => { 7 | if (!$('body[data-controller="webhooks"]').length) { 8 | return; 9 | } 10 | 11 | // eslint-disable-next-line no-new 12 | new Vue({ 13 | el: '.vue-root', 14 | 15 | components: { 16 | WebhooksIndexPage, 17 | WebhooksShowPage, 18 | }, 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/webhooks/services/deliveries.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueResource from 'vue-resource'; 3 | 4 | Vue.use(VueResource); 5 | 6 | const oldResource = Vue.resource('namespaces/{namespaceId}/webhooks/{webhookId}/deliveries{/id}.json'); 7 | 8 | function retrigger(namespaceId, webhookId, id) { 9 | return oldResource.update({ namespaceId, webhookId, id }, {}); 10 | } 11 | 12 | export default { 13 | retrigger, 14 | }; 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/webhooks/services/headers.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueResource from 'vue-resource'; 3 | 4 | Vue.use(VueResource); 5 | 6 | const oldResource = Vue.resource('namespaces/{namespaceId}/webhooks/{webhookId}/headers{/id}.json'); 7 | 8 | function destroy(namespaceId, webhookId, id) { 9 | return oldResource.delete({ namespaceId, webhookId, id }); 10 | } 11 | 12 | function save(namespaceId, webhookId, webhook_header) { 13 | return oldResource.save({ namespaceId, webhookId }, { webhook_header }); 14 | } 15 | 16 | export default { 17 | destroy, 18 | save, 19 | }; 20 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules/webhooks/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const { set } = Vue; 4 | 5 | class WebhooksStore { 6 | constructor() { 7 | this.state = { 8 | newFormVisible: false, 9 | editFormVisible: false, 10 | newHeaderFormVisible: false, 11 | }; 12 | } 13 | 14 | set(key, value) { 15 | set(this.state, key, value); 16 | } 17 | } 18 | 19 | export default new WebhooksStore(); 20 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/alert.js: -------------------------------------------------------------------------------- 1 | import Alert from '~/utils/alert'; 2 | 3 | export default function install(Vue) { 4 | Object.defineProperties(Vue.prototype, { 5 | $alert: { 6 | get() { 7 | return Alert; 8 | }, 9 | }, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/config.js: -------------------------------------------------------------------------------- 1 | import config from '~/config'; 2 | 3 | export default function install(Vue) { 4 | Object.defineProperties(Vue.prototype, { 5 | $config: { 6 | get() { 7 | return config; 8 | }, 9 | }, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/eventbus.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-shadow */ 2 | import Vue from 'vue'; 3 | 4 | const bus = new Vue(); 5 | 6 | export default function install(Vue) { 7 | Object.defineProperties(Vue.prototype, { 8 | $bus: { 9 | get() { 10 | return bus; 11 | }, 12 | }, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/unauthenticated.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import Alert from '~/plugins/alert'; 4 | 5 | Vue.use(Alert); 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/polyfill.js: -------------------------------------------------------------------------------- 1 | import 'core-js/fn/array/some'; 2 | import 'core-js/fn/array/from'; 3 | import 'core-js/fn/array/find'; 4 | import 'core-js/fn/array/find-index'; 5 | import 'core-js/fn/object/assign'; 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/shared/components/loading-icon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /app/assets/javascripts/shared/components/star.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/app/assets/javascripts/shared/components/star.vue -------------------------------------------------------------------------------- /app/assets/javascripts/unauthenticated.js: -------------------------------------------------------------------------------- 1 | // Bootstrap 2 | import 'bootstrap/js/tooltip'; 3 | 4 | // misc 5 | import './plugins/unauthenticated'; 6 | import './polyfill'; 7 | 8 | // modules 9 | import './modules/explore'; 10 | import './modules/users/unauthenticated'; 11 | 12 | import './bootstrap'; 13 | -------------------------------------------------------------------------------- /app/assets/javascripts/utils/comparator.js: -------------------------------------------------------------------------------- 1 | import dateutil from '~/utils/date'; 2 | 3 | const stringComparator = (a, b) => a.localeCompare(b); 4 | 5 | const numberComparator = (a, b) => a - b; 6 | 7 | const dateComparator = (a, b) => new Date(a) - new Date(b); 8 | 9 | function of(value) { 10 | let type = typeof value; 11 | 12 | if (dateutil.isISO8601(value)) { 13 | type = 'date'; 14 | } 15 | 16 | switch (type) { 17 | case 'boolean': 18 | case 'number': 19 | return numberComparator; 20 | case 'date': 21 | return dateComparator; 22 | default: 23 | return stringComparator; 24 | } 25 | } 26 | 27 | export default { 28 | of, 29 | }; 30 | -------------------------------------------------------------------------------- /app/assets/javascripts/utils/csrf.js: -------------------------------------------------------------------------------- 1 | const token = () => { 2 | const tokenEl = document.querySelector('meta[name=csrf-token]'); 3 | 4 | if (tokenEl !== null) { 5 | return tokenEl.getAttribute('content'); 6 | } 7 | 8 | return null; 9 | }; 10 | 11 | export default { 12 | token, 13 | }; 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/utils/date.js: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | const isISO8601 = (date) => { 4 | const type = typeof date; 5 | const regex = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/; 6 | 7 | if (type === 'object') { 8 | return dayjs(date).isValid(); 9 | } 10 | 11 | if (type !== 'string' 12 | || !regex.test(date)) { 13 | return false; 14 | } 15 | 16 | return dayjs(date).isValid(); 17 | }; 18 | 19 | export default { 20 | isISO8601, 21 | }; 22 | -------------------------------------------------------------------------------- /app/assets/javascripts/utils/http.js: -------------------------------------------------------------------------------- 1 | import Alert from '~/utils/alert'; 2 | 3 | export function handleHttpResponseError(response) { 4 | const errors = response.data.message || response.data; 5 | let messages = []; 6 | 7 | if (typeof errors === 'string') { 8 | messages = [errors]; 9 | } else if (Array.isArray(errors)) { 10 | messages = errors; 11 | } else if (Object.prototype.toString.call(errors) === '[object Object]') { 12 | Object.keys(errors).forEach((k) => { 13 | const keyCapitalized = k.charAt(0).toUpperCase() + k.substr(1); 14 | messages = messages.concat(errors[k].map(m => `${keyCapitalized} ${m}`)); 15 | }); 16 | } 17 | 18 | Alert.$show(messages.join('')); 19 | } 20 | 21 | export default { 22 | handleHttpResponseError, 23 | }; 24 | -------------------------------------------------------------------------------- /app/assets/javascripts/utils/range.js: -------------------------------------------------------------------------------- 1 | // generates a range of integers 2 | export default (start, end) => { 3 | if (start > end) { 4 | throw new Error('Range: "start" cannot be greater than "end"'); 5 | } 6 | 7 | return Array.from({ length: (end - start) + 1 }, (_, i) => i + start); 8 | }; 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | // Vendored fonts and styles. 2 | @import "font-awesome"; 3 | @import "raleway"; 4 | @import "pacifico"; 5 | @import "bootstrap-typeahead"; 6 | @import "vue-multiselect"; 7 | 8 | // Core libraries. 9 | @import "bootstrap-sprockets"; 10 | @import 'core/**/*'; 11 | @import 'bootstrap'; 12 | @import 'lifeitup/lifeitup'; 13 | 14 | // Portus 15 | @import "includes/**/*"; 16 | @import "pages/**/*"; 17 | @import "components/**/*"; 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/buttons.scss: -------------------------------------------------------------------------------- 1 | header .btn-default { 2 | @include button-variant($white, transparent, $white); 3 | } 4 | 5 | header .topbar.btn.btn-default { 6 | background: rgba(255,255,255,0.2); 7 | border: 1px solid transparent; 8 | padding-top: 1rem; 9 | &:hover { 10 | color: $second-colour; 11 | background: rgba(255,255,255,0.7); 12 | } 13 | } 14 | .btn-toolbar { 15 | margin-bottom: 1em; 16 | .pull-right { 17 | margin-left: 1em; 18 | } 19 | } 20 | 21 | .align-top-inline { 22 | display: inline-block; 23 | vertical-align: top; 24 | } 25 | 26 | i.btn.btn-options { 27 | padding: .4em 1em 0 0; 28 | } 29 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/errors.scss: -------------------------------------------------------------------------------- 1 | 2 | .oops { 3 | padding-top: 2em; 4 | text-align: center; 5 | .broken-lighthouse { 6 | margin: 2em 0; 7 | } 8 | h1, h3 { 9 | color: $third-colour; 10 | line-height: 0; 11 | color: $logo-text-colour; 12 | } 13 | h1 { 14 | font-size: 4em; 15 | font-family: 'Pacifico', cursive; 16 | text-shadow: 2px 2px 3px rgba(0, 0, 0, 0.3); 17 | } 18 | h3 { 19 | padding-top: 2em; 20 | font-size: 1.5em; 21 | } 22 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/components/images.scss: -------------------------------------------------------------------------------- 1 | .login-picture { 2 | margin: 2em 0; 3 | } 4 | 5 | i.user-picture { 6 | padding-top: 5px; 7 | } 8 | 9 | .activitie-container .user-picture { 10 | margin-right: 0; 11 | } 12 | 13 | .user-picture { 14 | background-color: #fff; 15 | display: inline-block; 16 | font-size: 20px; 17 | height: 40px; 18 | margin-right: 0.7em; 19 | text-align: center; 20 | width: 40px; 21 | /* Same as in: /vendor/assets/stylesheets/lifeitup/layout.scss LINE: 44 */ 22 | border: 4px solid darken($header-user-bg, 15%); 23 | border-radius: 100px; 24 | } 25 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/namespaces/info.scss: -------------------------------------------------------------------------------- 1 | .visibility-info { 2 | cursor: default; 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/pagination.scss: -------------------------------------------------------------------------------- 1 | .pagination-wrapper { 2 | .pagination { 3 | margin: 0; 4 | } 5 | 6 | .text-left { 7 | padding-left: 0; 8 | line-height: 34px; 9 | } 10 | 11 | .text-right { 12 | padding-right: 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/repositories/overview.scss: -------------------------------------------------------------------------------- 1 | .repository-overview { 2 | .title { 3 | font-weight: 600; 4 | } 5 | 6 | .click-description { 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | cursor: pointer; 12 | } 13 | 14 | .more-info { 15 | ul { 16 | padding-left: 20px; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/vulnerabilities/severities.scss: -------------------------------------------------------------------------------- 1 | .severity { 2 | &-critical, 3 | &-defcon1 { 4 | color: #B9121B; 5 | } 6 | 7 | &-high { 8 | color: #FF5E38; 9 | } 10 | 11 | &-medium { 12 | color: #f28c33; 13 | } 14 | 15 | &-low { 16 | color: #f8ca1c; 17 | } 18 | 19 | &-unknown { 20 | color: #5b5b5b; 21 | } 22 | 23 | &-passed { 24 | color: #5cb85c; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/vulnerabilities/summary.scss: -------------------------------------------------------------------------------- 1 | .vulnerabilities-summary { 2 | .highlight { 3 | font-size: 16px; 4 | } 5 | 6 | .list { 7 | list-style: none; 8 | padding-left: 0; 9 | font-size: 14px; 10 | 11 | li { 12 | margin-bottom: 3px; 13 | } 14 | 15 | strong { 16 | width: 25px; 17 | text-align: right; 18 | display: inline-block; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/vulnerabilities/table.scss: -------------------------------------------------------------------------------- 1 | .vulnerabilities-table { 2 | .fa-link { 3 | margin-left: 5px; 4 | } 5 | 6 | .col-caret { 7 | padding-right: 1px; 8 | 9 | .fa { 10 | padding: 0 3px; 11 | cursor: pointer; 12 | } 13 | } 14 | 15 | .vulnerability-details { 16 | td { 17 | padding: 17px 10px; 18 | } 19 | 20 | .title { 21 | margin-top: 0; 22 | margin-bottom: 8px; 23 | font-weight: 600; 24 | font-size: 13px; 25 | } 26 | 27 | .description { 28 | margin-bottom: 0; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/webhooks/edit-form.scss: -------------------------------------------------------------------------------- 1 | #edit-webhook-form { 2 | .toggle { 3 | line-height: 34px; 4 | cursor: pointer; 5 | } 6 | 7 | .fa-toggle-on { 8 | color: $first-colour; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/assets/stylesheets/core/mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixin for overriding input placeholder. 2 | @mixin input-placeholder($color) { 3 | &::-webkit-input-placeholder { 4 | color: $color; 5 | } 6 | &:-moz-placeholder { /* Firefox 18- */ 7 | color: $color; 8 | } 9 | &::-moz-placeholder { /* Firefox 19+ */ 10 | color: $color; 11 | } 12 | &:-ms-input-placeholder { 13 | color: $color; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/includes/alignment.scss: -------------------------------------------------------------------------------- 1 | .v-align-top { 2 | vertical-align: top !important; 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/includes/modals.scss: -------------------------------------------------------------------------------- 1 | .modal-content { 2 | margin: 10vh auto; 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/includes/types.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | small { 3 | font-size: floor($font-size-base - 1); 4 | margin-left: 0.5em; 5 | font-family: $font-family-sans-serif; 6 | } 7 | } 8 | 9 | .space-xs-sides { 10 | margin-left: .6em; 11 | margin-right: .6em; 12 | } 13 | 14 | .text-monospace { 15 | font-family: monospace; 16 | } 17 | -------------------------------------------------------------------------------- /app/assets/stylesheets/pages/dashboard.scss: -------------------------------------------------------------------------------- 1 | .is-admin { 2 | .recent-activities-panel { 3 | .panel-heading { 4 | padding-bottom: 0; 5 | } 6 | } 7 | 8 | #activities-tabs, 9 | .view-all-activities { 10 | display: block; 11 | } 12 | } 13 | 14 | #activities-tabs, 15 | .view-all-activities { 16 | display: none; 17 | } 18 | 19 | .starred { 20 | .fa { 21 | color: $first-colour !important; 22 | } 23 | } 24 | 25 | #repositories_sidebar { 26 | .panel-heading { 27 | padding-bottom: 0px; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/assets/stylesheets/pages/explore.scss: -------------------------------------------------------------------------------- 1 | .explore { 2 | h2 { 3 | color: $white; 4 | margin-bottom: 15px; 5 | } 6 | 7 | .result-list { 8 | list-style-type: none; 9 | padding: 0; 10 | } 11 | 12 | .result-item { 13 | background: rgba(255, 255, 255, 0.85); 14 | border-radius: 5px; 15 | margin-bottom: 10px; 16 | padding: 15px; 17 | } 18 | 19 | .result-item-name { 20 | margin-top: 0; 21 | } 22 | 23 | .result-item-stars { 24 | line-height: 23px; // 18 height + 5 diff between fonts 25 | 26 | .fa-star { 27 | color: $primary-colour; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/assets/stylesheets/pages/registries.scss: -------------------------------------------------------------------------------- 1 | .registry-status { 2 | .fa-chain { 3 | color: #449d44; 4 | } 5 | 6 | .fa-chain-broken { 7 | color: #c9302c; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/pages/repositories.scss: -------------------------------------------------------------------------------- 1 | @import "comments"; 2 | 3 | .add-comment { 4 | padding-top: 0px; 5 | padding-bottom: 0px; 6 | border: 0px; 7 | } 8 | 9 | .tags .label.label-success { 10 | margin: 0px 2px; 11 | } 12 | 13 | .repository-information-icon ~ .popover ul { 14 | margin: 0; 15 | padding-left: 15px; 16 | } 17 | 18 | .repositories-show-page { 19 | .panel-heading { 20 | padding-bottom: 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/controllers/admin/activities_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "csv" 4 | 5 | class Admin::ActivitiesController < Admin::BaseController 6 | respond_to :html, :csv 7 | 8 | def index 9 | respond_to do |format| 10 | format.html do 11 | @activities = PublicActivity::Activity.order(created_at: :desc).page(params[:page]) 12 | end 13 | format.csv do 14 | @activities = PublicActivity::Activity.order(created_at: :desc) 15 | headers["Content-Disposition"] = 'attachment; filename="activities.csv"' 16 | headers["Content-Type"] = "text/csv" 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/admin/base_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Admin::BaseController < ApplicationController 4 | before_action :ensure_admin! 5 | 6 | protected 7 | 8 | def ensure_admin! 9 | deny_access unless current_user.admin? 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/api/base_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Api::BaseController < ActionController::Base 4 | class ScopeNotHandled < StandardError; end 5 | class RegistryNotHandled < StandardError; end 6 | 7 | include Pundit 8 | 9 | respond_to :json 10 | 11 | rescue_from Pundit::NotAuthorizedError, with: :deny_access 12 | rescue_from ScopeNotHandled, with: :deny_access 13 | rescue_from RegistryNotHandled, with: :deny_access 14 | rescue_from Portus::AuthScope::ResourceNotFound, with: :deny_access 15 | 16 | protected 17 | 18 | # It logs the exception message and sends a 401. 19 | def deny_access(exception) 20 | logger.info "Denied access on these grounds: #{exception.message}" 21 | head :unauthorized 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/controllers/api/v2/events_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This is the endpoint being used to handle notifications from the Registry. 4 | class Api::V2::EventsController < Api::BaseController 5 | # A new notification is coming, register it if valid. 6 | def create 7 | body = JSON.parse(request.body.read) 8 | Portus::RegistryNotification.process!(body) 9 | head :accepted 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/api/v2/ping_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Api::V2::PingController < Api::BaseController 4 | def ping 5 | authenticate_user! 6 | head :ok 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/controllers/concerns/check_ldap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # CheckLDAP redirects the user to the new_user_session_path if LDAP support is 4 | # enabled. A `before_action` will be created for the :new and the :create 5 | # methods. 6 | module CheckLDAP 7 | extend ActiveSupport::Concern 8 | 9 | included do 10 | # rubocop:disable Rails/LexicallyScopedActionFilter 11 | before_action :check_ldap, only: %i[new create] 12 | # rubocop:enable Rails/LexicallyScopedActionFilter 13 | end 14 | 15 | # Redirect to the login page if LDAP is enabled. 16 | def check_ldap 17 | redirect_to new_user_session_path if APP_CONFIG.enabled?("ldap") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/concerns/headers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Concern which has methods dealing with headers that might be interesting for 4 | # controllers deriving directly from ActionController::Base. 5 | module Headers 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | after_action :default_headers 10 | end 11 | 12 | # Adds some default headers. 13 | def default_headers 14 | headers["X-UA-Compatible"] = "IE=edge" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/controllers/concerns/with_ordering.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Concern for handling of ordering parameters. 4 | module WithOrdering 5 | extend ActiveSupport::Concern 6 | 7 | include ::API::Helpers::Ordering 8 | 9 | included do 10 | before_action :default_ordering_params 11 | end 12 | 13 | # Adds some default ordering parameters. 14 | def default_ordering_params 15 | params[:sort_attr] ||= :id 16 | params[:sort_order] ||= :asc 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/concerns/with_pagination.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Concern for handling of pagination parameters. 4 | module WithPagination 5 | extend ActiveSupport::Concern 6 | 7 | include ::API::Helpers::Pagination 8 | 9 | included do 10 | before_action :default_pagination_params 11 | end 12 | 13 | # Adds some default pagination parameters. 14 | def default_pagination_params 15 | params[:page] ||= 1 16 | params[:per_page] = APP_CONFIG["pagination"]["per_page"] 17 | end 18 | 19 | def header(header, value) 20 | response.headers[header] = value 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/controllers/help_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # HelpController holds the methods for rendering the help pages inside of Portus. 4 | class HelpController < ApplicationController 5 | # Main help page. 6 | def index; end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/search_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SearchController < ApplicationController 4 | def index 5 | search = params[:search].split(":").first 6 | @repositories = policy_scope(Repository).search(search) 7 | @teams = policy_scope(Team).search(search) 8 | @namespaces = policy_scope(Namespace).search(search) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/tags_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TagsController < ApplicationController 4 | def show 5 | @tag = Tag.includes(:repository, :namespace).find(params[:id]) 6 | @repository = @tag.repository 7 | @namespace = @tag.repository.namespace 8 | authorize @tag 9 | 10 | @tag_serialized = API::Entities::Tags.represent( 11 | @tag, 12 | current_user: current_user, 13 | type: :internal 14 | ).to_json 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/helpers/registries_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RegistriesHelper 4 | # Render registry status icon 5 | def registry_status_icon(registry) 6 | error = registry.reachable? 7 | msg = error.empty? ? "Reachable" : error 8 | time = Time.now.getlocal.to_s(:rfc822) 9 | icon = "chain" 10 | icon += "-broken" unless error.empty? 11 | 12 | title = "#{msg} - Checked at #{time}" 13 | 14 | content_tag :i, "", class: "fa fa-lg fa-#{icon}", title: title 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/helpers/search_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SearchHelper 4 | def build_search_category_url(params, category) 5 | "#{search_index_path}?utf8=#{params[:utf8]}&search=#{params[:search]}&type=#{category}" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/helpers/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Renders the social login buttons if one of the `oauth` configuration has been enabled. 5 | # 6 | module SessionsHelper 7 | def social_login 8 | return unless APP_CONFIG["oauth"] 9 | 10 | enabled_providers = APP_CONFIG["oauth"].find_all { |k, v| k != "local_login" && v["enabled"] } 11 | render partial: "devise/sessions/social_login" unless enabled_providers.empty? 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/helpers/teams_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TeamsHelper 4 | include ::API::Helpers::Teams 5 | 6 | def manage_teams_enabled? 7 | APP_CONFIG.enabled?("user_permission.manage_team") 8 | end 9 | 10 | def can_create_team? 11 | TeamPolicy.new(current_user, nil).create? 12 | end 13 | 14 | # Render the namespace scope icon. 15 | def team_scope_icon(team) 16 | if team.team_users.enabled.count > 1 17 | icon = "fa-users" 18 | title = "Team" 19 | else 20 | icon = "fa-user" 21 | title = "Personal" 22 | end 23 | 24 | content_tag :i, "", class: "fa #{icon} fa-lg", title: title 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/helpers/webhooks_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebhooksHelper 4 | def can_create_webhook?(namespace) 5 | WebhookPolicy.new(current_user, namespace.webhooks.build).create? 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/mailers/devise_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # DeviseMailer is the mailer being used by Devise. 4 | class DeviseMailer < Devise::Mailer 5 | default from: "#{APP_CONFIG["email"]["name"]} <#{APP_CONFIG["email"]["from"]}>" 6 | default reply_to: APP_CONFIG["email"]["reply_to"] if APP_CONFIG["email"]["reply_to"].present? 7 | end 8 | -------------------------------------------------------------------------------- /app/models/activity/fallback.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Activity holds utility methods to update activity records depending on the 4 | # model including this module. 5 | module Activity 6 | # Fallback has a set of methods that manage trackable types for existing 7 | # activities. 8 | module Fallback 9 | # fallback_activity updates the model including this method by setting a new 10 | # type an id for all the rows. 11 | def fallback_activity(type, id) 12 | PublicActivity::Activity.where(trackable: self).update_all( 13 | trackable_type: type, 14 | trackable_id: id, 15 | recipient_type: nil 16 | ) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | end 6 | -------------------------------------------------------------------------------- /app/models/registry/auth_scope.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Registry::AuthScope parses the scope string so it can be used afterwards for 4 | # the "registry" type. 5 | class Registry::AuthScope < Portus::AuthScope 6 | def resource 7 | reg = Registry.by_hostname_or_external(@registry.hostname) 8 | raise ResourceNotFound, "Could not find registry #{@registry.hostname}" if reg.nil? 9 | 10 | reg 11 | end 12 | 13 | def scopes 14 | catalog? ? ["all"] : [] 15 | end 16 | 17 | private 18 | 19 | # Returns true if the given scope string corresponds to the /v2/_catalog 20 | # endpoint. 21 | def catalog? 22 | @resource_name == "catalog" && @actions[0] == "*" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/star.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: stars 6 | # 7 | # id :integer not null, primary key 8 | # user_id :integer 9 | # repository_id :integer 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # 13 | # Indexes 14 | # 15 | # index_stars_on_repository_id (repository_id) 16 | # index_stars_on_user_id (user_id) 17 | # 18 | 19 | class Star < ApplicationRecord 20 | belongs_to :repository 21 | belongs_to :user 22 | 23 | validates :repository, presence: true 24 | validates :user, presence: true, uniqueness: { scope: :repository } 25 | end 26 | -------------------------------------------------------------------------------- /app/policies/application_token_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationTokenPolicy 4 | attr_reader :user, :application_token 5 | 6 | def initialize(user, application_token) 7 | raise Pundit::NotAuthorizedError, "must be logged in" unless user 8 | 9 | @user = user 10 | @application_token = application_token 11 | end 12 | 13 | def destroy? 14 | user.id == application_token.user_id || 15 | user.admin? && application_token.user.bot 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/policies/registry_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # RegistryPolicy implements the authorization policy for methods inside the 4 | # "registry" type. 5 | class RegistryPolicy 6 | attr_reader :user 7 | 8 | def initialize(user, _) 9 | raise Pundit::NotAuthorizedError, "must be logged in" unless user 10 | 11 | @user = user 12 | end 13 | 14 | # This method defines the permissions for the following scope string 15 | # "registry:catalog:*". Only the admin users are allowed to perform 16 | # this call. 17 | def all? 18 | @user.admin? 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/policies/tag_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Defines the policy for the tag object. 4 | class TagPolicy 5 | attr_reader :user, :tag 6 | 7 | def initialize(user, tag) 8 | @user = user 9 | @tag = tag 10 | end 11 | 12 | def show? 13 | @user.admin? || 14 | @tag.repository.namespace.visibility_public? || 15 | @tag.repository.namespace.visibility_protected? || 16 | @tag.repository.namespace.team.users.exists?(user.id) 17 | end 18 | 19 | # Returns true if the tag can be destroyed. 20 | def destroy? 21 | RepositoryPolicy.new(user, tag.repository).destroy? 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/policies/team_user_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Policy for team members. It does not authorize anything unless the author of 4 | # the action is an owner. An owner is either an admin of Portus or an owner of 5 | # the team itself. 6 | class TeamUserPolicy 7 | attr_reader :user, :team_user 8 | 9 | def initialize(user, team_user) 10 | raise Pundit::NotAuthorizedError, "must be logged in" unless user 11 | 12 | @user = user 13 | @team_user = team_user 14 | end 15 | 16 | def owner? 17 | user.admin? || @team_user.team.owners.exists?(user.id) 18 | end 19 | 20 | alias destroy? owner? 21 | alias update? owner? 22 | alias create? owner? 23 | end 24 | -------------------------------------------------------------------------------- /app/policies/webhook_delivery_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WebhookDeliveryPolicy < WebhookPolicy 4 | attr_reader :webhook_delivery 5 | 6 | def initialize(user, webhook_delivery) 7 | raise Pundit::NotAuthorizedError, "must be logged in" unless user 8 | 9 | @user = user 10 | @webhook_delivery = webhook_delivery 11 | 12 | @webhook = webhook_delivery.webhook 13 | @namespace = @webhook.namespace 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/policies/webhook_header_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WebhookHeaderPolicy < WebhookPolicy 4 | attr_reader :webhook_header 5 | 6 | def initialize(user, webhook_header) 7 | raise Pundit::NotAuthorizedError, "must be logged in" unless user 8 | 9 | @user = user 10 | @webhook_header = webhook_header 11 | 12 | @webhook = webhook_header.webhook 13 | @namespace = @webhook.namespace 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/services/README.md: -------------------------------------------------------------------------------- 1 | ## Services 2 | 3 | This directory contains services. A service is simply a piece of code that is 4 | shared between the Grape API and the controllers. Each action has been 5 | implemented as a different service, which can then be executed independently of 6 | the context. 7 | -------------------------------------------------------------------------------- /app/services/base_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BaseService 4 | attr_accessor :current_user, :params 5 | 6 | def initialize(user, params = {}) 7 | @current_user = user 8 | @params = params.dup 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/services/base_validate_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BaseValidateService 4 | attr_accessor :params 5 | 6 | def initialize(params = {}) 7 | @params = params.dup 8 | @messages = {} 9 | end 10 | 11 | private 12 | 13 | def only_params 14 | params[:only] 15 | end 16 | 17 | def messages 18 | return @messages if only_params.nil? 19 | 20 | @messages.keep_if { |key, v| v.present? && only_params.include?(key.to_s) } 21 | end 22 | 23 | def valid? 24 | return @valid if only_params.nil? 25 | 26 | only_params.all? { |field| @messages[field.to_sym].blank? } 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/services/namespaces/build_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Namespaces 4 | class BuildService < ::BaseService 5 | def execute 6 | build_namespace unless params.empty? 7 | end 8 | 9 | private 10 | 11 | def build_namespace 12 | team = fetch_team 13 | namespace_params = params.merge( 14 | visibility: Namespace.visibilities[:visibility_private], 15 | registry: Registry.get 16 | ) 17 | team.namespaces.build(namespace_params) 18 | end 19 | 20 | def fetch_team 21 | team = Team.find_by(name: params[:team], hidden: false) 22 | params.delete(:team) 23 | raise ActiveRecord::RecordNotFound if team.nil? 24 | 25 | team 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/services/namespaces/create_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Namespaces 4 | class CreateService < ::BaseService 5 | attr_accessor :namespace 6 | 7 | def initialize(current_user, namespace = nil) 8 | @current_user = current_user 9 | @namespace = namespace 10 | end 11 | 12 | def execute 13 | return if namespace.nil? 14 | 15 | create_activity! if namespace.save 16 | 17 | namespace 18 | end 19 | 20 | private 21 | 22 | def create_activity! 23 | namespace.create_activity :create, 24 | owner: current_user, 25 | parameters: { team: @namespace.team.name } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/services/registries/base_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Registries 4 | class BaseService < ::BaseService 5 | attr_reader :messages 6 | attr_accessor :force 7 | 8 | def valid? 9 | @valid 10 | end 11 | 12 | def reachable? 13 | @reachable 14 | end 15 | 16 | protected 17 | 18 | def check_reachability! 19 | msg = @registry.reachable? 20 | return if msg.blank? 21 | 22 | Rails.logger.info "\nRegistry not reachable:\n#{@registry.inspect}\n#{msg}\n" 23 | @valid = false 24 | @reachable = false 25 | @messages[:hostname] = (@messages[:hostname] || []).push(msg) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/services/repositories/update_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Repositories 4 | class UpdateService < ::BaseService 5 | def build 6 | return if params[:id].blank? 7 | 8 | @repository = Repository.find(params[:id]) 9 | end 10 | 11 | def execute 12 | return if @repository.blank? 13 | 14 | @repository.update(params[:repository]) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/services/tags/destroy_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tags 4 | class DestroyService < ::BaseService 5 | attr_accessor :error 6 | 7 | def execute(tag) 8 | raise ActiveRecord::RecordNotFound if tag.nil? 9 | 10 | repository = tag.repository 11 | tag_destroyed = tag.delete_by_digest!(current_user) 12 | 13 | if tag_destroyed && repository.tags.empty? 14 | ::Repositories::DestroyService.new(current_user).execute(repository) 15 | else 16 | full_messages = !tag.errors.empty? && tag.errors.full_messages 17 | @error = full_messages || "Could not remove #{tag.name} tag" 18 | end 19 | 20 | tag_destroyed 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/services/team_users/base_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TeamUsers 4 | class BaseService < ::BaseService 5 | attr_reader :message 6 | 7 | protected 8 | 9 | # Responds with an error if the client is trying to remove the only owner of 10 | # the team through either the update or the destroy methods. 11 | def owners_remaining?(team_user) 12 | return true unless team_user.only_owner? 13 | return true unless params[:role].nil? || params[:role] != "owner" 14 | 15 | @message = "Cannot remove the only owner of the team" 16 | false 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/services/team_users/create_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TeamUsers 4 | class CreateService < ::BaseService 5 | attr_accessor :team_user 6 | 7 | def initialize(current_user, team_user = nil) 8 | @current_user = current_user 9 | @team_user = team_user 10 | end 11 | 12 | def execute 13 | return if team_user.nil? 14 | 15 | create_activity! if team_user.save 16 | 17 | team_user 18 | end 19 | 20 | private 21 | 22 | def create_activity! 23 | team_user.create_activity!(:add_member, current_user, 24 | team_user: team_user.user.username, 25 | team: team_user.team.name) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/services/team_users/destroy_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TeamUsers 4 | class DestroyService < ::TeamUsers::BaseService 5 | def execute(team_user) 6 | return false if team_user.nil? 7 | return false unless owners_remaining?(team_user) 8 | 9 | destroyed = team_user.destroy 10 | create_activity!(team_user) if destroyed 11 | destroyed 12 | end 13 | 14 | private 15 | 16 | def create_activity!(team_user) 17 | team_user.create_activity!(:remove_member, current_user, 18 | team_user: team_user.user.username, 19 | team: team_user.team.name) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/services/teams/update_service.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Teams 4 | class UpdateService < ::BaseService 5 | include ::Helpers::ChangeNameDescription 6 | 7 | def build 8 | @team = Team.find(params[:id]) 9 | end 10 | 11 | def execute 12 | change_name_description(@team, :team, params[:team], team: @team.name) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/validators/namespace_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Validates the name of the namespace as specified by Docker Distribution. 4 | class NamespaceValidator < ActiveModel::EachValidator 5 | NAME_REGEXP = /\A[a-z0-9]+(?:[._\\-][a-z0-9]+)*\Z/.freeze 6 | 7 | # Validator for the name. 8 | def validate_each(record, attribute, value) 9 | # Already validated by the presence validator. 10 | return if value.nil? 11 | return if value.match?(NAME_REGEXP) 12 | 13 | record.errors[attribute] << "can only contain lower case alphanumeric "\ 14 | "characters, with optional underscores and dashes in the middle." 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/admin/activities/index.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line ['Tracked item', 'Item', 'Event', 'Recipient', 'Triggered by', 'Time', 'Notes'] 2 | - @activities.each do |activity| 3 | = render_activity activity 4 | -------------------------------------------------------------------------------- /app/views/admin/activities/index.html.slim: -------------------------------------------------------------------------------- 1 | .panel.panel-default 2 | .panel-heading 3 | .row 4 | .col-sm-6 5 | h5 6 | | All activities 7 | .col-sm-6.text-right 8 | a.btn.btn-link.btn-xs[role="button" href="#{url_for(admin_activities_path(format: :csv))}" title="Download in CSV format"] 9 | i.fa.fa-download 10 | | Download CSV 11 | .panel-body 12 | ul.activity 13 | - @activities.each do |activity| 14 | = render_activity(activity) 15 | = paginate(@activities) 16 | -------------------------------------------------------------------------------- /app/views/admin/registries/edit.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/admin/registries/new.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/admin/users/edit.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/admin/users/index.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/dashboard/_repository.html.slim: -------------------------------------------------------------------------------- 1 | tr 2 | td 3 | = link_to repository.namespace.clean_name, repository.namespace 4 | | / 5 | = link_to repository.name, repository 6 | -------------------------------------------------------------------------------- /app/views/dashboard/_star.html.slim: -------------------------------------------------------------------------------- 1 | tr 2 | td 3 | = link_to star.repository.namespace.clean_name, star.repository.namespace 4 | | / 5 | = link_to star.repository.name, star.repository 6 | td= star.repository.stars.count 7 | td 8 | i.fa.fa-star 9 | -------------------------------------------------------------------------------- /app/views/dashboard/index.html.slim: -------------------------------------------------------------------------------- 1 | = render "dashboard/partials/portus_user_alert" 2 | = render "dashboard/partials/stats" 3 | 4 | .row 5 | .col-md-8 6 | = render "dashboard/partials/recent_activities" 7 | 8 | .col-md-4 9 | = render "dashboard/partials/repositories" 10 | -------------------------------------------------------------------------------- /app/views/dashboard/partials/_portus_user_alert.html.slim: -------------------------------------------------------------------------------- 1 | - unless @portus_exists 2 | .alert.alert-danger.fade.in.text-left.alert-dismissible 3 | button.close data-dismiss="alert" type="button" 4 | span aria-hidden="true" × 5 | span.sr-only Close 6 | .row 7 | .alert-icon.pull-left 8 | i.fa.fa-exclamation-circle.fa-3x 9 | .pull-left 10 | p 11 | | The Portus user does not exist! 12 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.slim: -------------------------------------------------------------------------------- 1 | section.row-0 2 | .center-panel 3 | .col-md-4.col-sm-2.col-xs-1 4 | .col-md-4.col-sm-8.col-xs-10.text-center 5 | = render 'shared/notifications' 6 | = image_tag 'layout/portus-logo-login-page.png', class: 'login-picture' 7 | = form_for(resource, as: resource_name, url: password_path(resource_name)) do |f| 8 | = f.email_field :email, class: 'input form-control input-lg', placeholder: 'Email', autofocus: true, required: true 9 | = f.button class: 'classbutton btn btn-primary btn-block btn-lg' do 10 | i.fa.fa-check 11 | ' Reset password 12 | 13 | .text-center = link_to 'Go back to the login page', new_user_session_path, class: 'btn btn-link' 14 | -------------------------------------------------------------------------------- /app/views/errors/401.html.slim: -------------------------------------------------------------------------------- 1 | | You are not authorized to access this page. If you are sure that you should 2 | have access to this page, contact the administrator of this Portus instance. 3 | -------------------------------------------------------------------------------- /app/views/errors/404.html.slim: -------------------------------------------------------------------------------- 1 | | The page you are looking for does not exist. 2 | -------------------------------------------------------------------------------- /app/views/errors/422.html.slim: -------------------------------------------------------------------------------- 1 | | The given resource was well-formatted but it was semantically incorrect. Maybe 2 | you tried to change something you did not have access to. 3 | -------------------------------------------------------------------------------- /app/views/errors/500.csv.slim: -------------------------------------------------------------------------------- 1 | Internal Server Error -------------------------------------------------------------------------------- /app/views/errors/_status_title.html.slim: -------------------------------------------------------------------------------- 1 | - if @status == 401 2 | | Unauthorized! 3 | - elsif @status == 404 4 | | Page not found! 5 | - elsif @status == 422 6 | | Unprocessable Entity 7 | - else 8 | | Something's wrong with our lighthouse. 9 | -------------------------------------------------------------------------------- /app/views/explore/index.slim: -------------------------------------------------------------------------------- 1 | section.row-0 2 | .center-panel.explore 3 | .col-md-4.col-sm-3.col-xs-1 4 | .col-md-4.col-sm-6.col-xs-10.text-center 5 | = render 'explore/partials/top' 6 | 7 | -------------------------------------------------------------------------------- /app/views/explore/partials/_form.html.slim: -------------------------------------------------------------------------------- 1 | = form_for(:explore, method: "GET") do |f| 2 | = f.text_field :search, class: 'input form-control input-lg first', placeholder: "Repository", autofocus: true, required: true, value: @current 3 | 4 | = f.button "Search", type: "submit", id: "login-btn", class: 'classbutton btn btn-primary btn-block btn-lg' 5 | -------------------------------------------------------------------------------- /app/views/explore/partials/_top.html.slim: -------------------------------------------------------------------------------- 1 | = render 'shared/notifications' 2 | = image_tag 'layout/portus-logo-login-page.png', class: 'login-picture' 3 | 4 | .text-center = link_to 'Go back to the login page', new_user_session_path, class: 'btn btn-link' 5 | 6 | = render 'explore/partials/form' 7 | -------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.first?, raw(t 'views.pagination.first'), 3 | url, remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.slim: -------------------------------------------------------------------------------- 1 | li.disabled 2 | = link_to raw(t 'views.pagination.truncate'), '#' 3 | -------------------------------------------------------------------------------- /app/views/kaminari/_last_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.last?, raw(t 'views.pagination.last'), 3 | url, remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.last?, raw(t 'views.pagination.next'), 3 | url, rel: 'next', remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.slim: -------------------------------------------------------------------------------- 1 | li class="#{'active' if page.current?}" 2 | = link_to page, page.current? ? '#' : url, 3 | remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.slim: -------------------------------------------------------------------------------- 1 | = paginator.render do 2 | ul.pagination 3 | == first_page_tag unless current_page.first? 4 | == prev_page_tag unless current_page.first? 5 | 6 | - each_page do |page| 7 | - if page.left_outer? || page.right_outer? || page.inside_window? 8 | == page_tag page 9 | - elsif !page.was_truncated? 10 | == gap_tag 11 | 12 | == next_page_tag unless current_page.last? 13 | == last_page_tag unless current_page.last? 14 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), 3 | url, rel: 'prev', remote: remote 4 | -------------------------------------------------------------------------------- /app/views/namespaces/index.html.erb: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /app/views/namespaces/show.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/public_activity/application_token/_create.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(["application token", "#{activity.parameters[:application]}", "create", "-", activity_owner(activity), activity.created_at, "-"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/application_token/_create.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.application-token-created 4 | i.fa.fa-key 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} created a token" 11 | = " for the application " 12 | strong 13 | = activity.parameters[:application] 14 | small 15 | i.fa.fa-clock-o 16 | = activity_time_tag activity.created_at 17 | -------------------------------------------------------------------------------- /app/views/public_activity/application_token/_destroy.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(["application token", "#{activity.parameters[:application]}", "destroy", "-", activity_owner(activity), activity.created_at, "-"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/application_token/_destroy.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.application-token-destroyed 4 | i.fa.fa-key 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} removed the token" 11 | = " for the application " 12 | strong 13 | = activity.parameters[:application] 14 | small 15 | i.fa.fa-clock-o 16 | = activity_time_tag activity.created_at 17 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_change_namespace_description.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['namespace', 2 | activity.trackable.name, 3 | 'change the description of the namespace', 4 | activity.recipient.name, 5 | activity_owner(activity), 6 | activity.created_at, 7 | "from #{activity.parameters[:old_description]} to \ 8 | #{activity.parameters[:new_description]}"]) 9 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_change_namespace_description.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-description 4 | i.fa.fa-list-alt 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | - if activity.parameters[:new].blank? 10 | strong 11 | = "#{activity_owner(activity)} deleted the description of the namespace " 12 | - else 13 | strong 14 | = "#{activity_owner(activity)} edited the description of the namespace " 15 | = link_to activity.trackable.name, activity.trackable 16 | small 17 | i.fa.fa-clock-o 18 | = activity_time_tag activity.created_at 19 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_change_team.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['namespace', activity.trackable.name, 'change team', '-', activity.owner.username, activity.created_at, "owned by team #{activity_team(activity, true)}"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_change_team.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-team 4 | i.fa.fa-arrow-right 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity.owner.username} moved the " 11 | = link_to activity.trackable.name , activity.trackable 12 | = " namespace to the " 13 | = link_to activity_team(activity, true), activity.trackable 14 | = " team" 15 | small 16 | i.fa.fa-clock-o 17 | = activity_time_tag activity.created_at 18 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_change_visibility.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['namespace', activity.trackable.name, 'change visibility', '-', activity_owner(activity), activity.created_at, "is #{activity.parameters[:visibility].sub('visibility_', '')}"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_change_visibility.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-namespace-visibility 4 | i.fa.fa-unlock 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} set " 11 | | the 12 | = render_namespace_name(activity) 13 | = " namespace as #{activity.parameters[:visibility].sub('visibility_', '')}" 14 | small 15 | i.fa.fa-clock-o 16 | = activity_time_tag activity.created_at 17 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_create.csv.slim: -------------------------------------------------------------------------------- 1 | - name = activity.parameters[:namespace_name].presence || activity.trackable.name 2 | 3 | = CSV.generate_line(['namespace', name, 'create', '-', activity_owner(activity), activity.created_at, "owned by team #{activity_team(activity, true)}"]) 4 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_create.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.create-namespace 4 | i.fa.fa-ship 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} created " 11 | - if activity.trackable.respond_to?(:orphan?) && activity.trackable.orphan? 12 | = render_namespace_name(activity, true) 13 | | namespace automatically (orphan) 14 | - else 15 | = render_namespace_name(activity, true) 16 | = " namespace under " 17 | = render_namespace_team(activity) 18 | = " team" 19 | small 20 | i.fa.fa-clock-o 21 | = activity_time_tag activity.created_at 22 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_delete.csv.slim: -------------------------------------------------------------------------------- 1 | - if activity.parameters[:repository_name] 2 | = CSV.generate_line(['repository', "#{activity.trackable.clean_name}", 'delete repository', "#{activity.parameters[:repository_name]}", activity_owner(activity), activity.created_at, "-"]) 3 | - else 4 | = CSV.generate_line(['repository', "#{activity.trackable.clean_name}", 'delete repository', "-", activity_owner(activity), activity.created_at, "-"]) 5 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_delete.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.repo-deleted 4 | i.fa.fa-ship 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = " #{activity_owner(activity)} deleted " 11 | - if activity.parameters[:repository_name] 12 | = render_namespace_name(activity) 13 | = "/#{activity.parameters[:repository_name]}" 14 | - else 15 | = "a repository under " 16 | = render_namespace_name(activity) 17 | = " namespace" 18 | small 19 | i.fa.fa-clock-o 20 | = activity_time_tag activity.created_at 21 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_private.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['namespace', activity.trackable.name, 'make private', '-', activity_owner(activity), activity.created_at, '-']) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_private.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-namespace-visibility 4 | i.fa.fa-lock 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} set the " 11 | = link_to activity.trackable.name, activity.trackable 12 | = " namespace as private" 13 | small 14 | i.fa.fa-clock-o 15 | = activity_time_tag activity.created_at 16 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_public.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['namespace', activity.trackable.name, 'make public', '-', activity_owner(activity), activity.created_at, '-']) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/namespace/_public.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-namespace-visibility 4 | i.fa.fa-unlock 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} set the " 11 | = link_to activity.trackable.name, activity.trackable 12 | = " namespace as public" 13 | small 14 | i.fa.fa-clock-o 15 | = activity_time_tag activity.created_at 16 | -------------------------------------------------------------------------------- /app/views/public_activity/registry/_delete.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['user', "-", 'removed namespace', activity.parameters[:namespace_name], activity_owner(activity), activity.created_at, "-"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/registry/_delete.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.repo-deleted 4 | i.fa.fa-ship 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = " #{activity_owner(activity)} removed namespace " 11 | = "'#{activity.parameters[:namespace_name]}'" 12 | small 13 | i.fa.fa-clock-o 14 | = activity_time_tag activity.created_at 15 | -------------------------------------------------------------------------------- /app/views/public_activity/registry/_remove_team.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['team', activity_team(activity), 'remove', '-', activity_owner(activity), activity.created_at, '-']) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/registry/_remove_team.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.team-removed 4 | i.fa.fa-users 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} removed team '#{activity_team(activity)}'" 11 | small 12 | i.fa.fa-clock-o 13 | = activity_time_tag activity.created_at 14 | -------------------------------------------------------------------------------- /app/views/public_activity/repository/_delete.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.repo-deleted 4 | i.fa.fa-forward.fa-fw 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | = render_delete_activity(activity) 10 | small 11 | i.fa.fa-clock-o 12 | = activity_time_tag activity.created_at 13 | -------------------------------------------------------------------------------- /app/views/public_activity/repository/_push.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.repo-pushed 4 | i.fa.fa-forward.fa-fw 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | = render_push_activity(activity) 10 | small 11 | i.fa.fa-clock-o 12 | = activity_time_tag activity.created_at 13 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_add_member.csv.slim: -------------------------------------------------------------------------------- 1 | - owner = activity_owner(activity) 2 | - action = "#{activity_action(owner, "add")} member" 3 | - created_at = activity.created_at 4 | - role = "role #{activity.parameters[:role]}" 5 | - team = activity_team(activity) 6 | 7 | - if activity.recipient 8 | = CSV.generate_line(['team', team, action, activity.recipient.display_username, owner, created_at, role]) 9 | - elsif activity.parameters[:team_user] 10 | = CSV.generate_line(['team', team, action, activity.parameters[:team_user], owner, created_at, role]) 11 | - else 12 | = CSV.generate_line(['team', team, action, "a user", owner, created_at, role]) 13 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_change_member_role.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['team', activity_team(activity), 'change member role', activity_user_recipient(activity, :team_user), activity_owner(activity), activity.created_at, "from #{activity.parameters[:old_role]} to #{activity.parameters[:new_role]}"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_change_team_description.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['team', activity_team(activity), 'change the description of the team', activity.recipient.name, activity_owner(activity), activity.created_at, "from #{activity.parameters[:old_description]} to #{activity.parameters[:new_description]}"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_change_team_description.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-description 4 | i.fa.fa-list-alt 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | - if activity.parameters[:new].blank? 10 | strong 11 | ="#{activity_owner(activity)} deleted the description of the team " 12 | - else 13 | strong 14 | = "#{activity_owner(activity)} edited the description of the team " 15 | = link_to activity_team(activity), activity.trackable 16 | small 17 | i.fa.fa-clock-o 18 | = activity_time_tag activity.created_at 19 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_change_team_name.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['team', activity_team(activity), 'change the name of the team', activity.recipient.name, activity_owner(activity), activity.created_at, "from #{activity.parameters[:old_name]} to #{activity.parameters[:new_name]}"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_change_team_name.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-name 4 | i.fa.fa-list-alt 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} renamed the team #{activity.parameters[:old]} to " 11 | = link_to activity.parameters[:new], activity.trackable 12 | small 13 | i.fa.fa-clock-o 14 | = activity_time_tag activity.created_at 15 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_create.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['team', activity_team(activity), 'create', '-', activity_owner(activity), activity.created_at, '-']) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_create.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.team-created 4 | i.fa.fa-users 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} created team " 11 | - if activity.trackable_type == "Team" 12 | = link_to activity_team(activity), activity.trackable 13 | - else 14 | strong 15 | = "'#{activity_team(activity)}'" 16 | small 17 | i.fa.fa-clock-o 18 | = activity_time_tag activity.created_at 19 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_migration.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-team 4 | i.fa.fa-arrow-right 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} migrated all namespaces" 11 | | from 12 | strong 13 | = activity.parameters[:old_team] 14 | | team to 15 | = link_to activity_team(activity), activity.trackable 16 | | team 17 | small 18 | i.fa.fa-clock-o 19 | = activity_time_tag activity.created_at 20 | -------------------------------------------------------------------------------- /app/views/public_activity/team/_remove_member.csv.slim: -------------------------------------------------------------------------------- 1 | - owner = activity_owner(activity) 2 | - action = "#{activity_action(owner, "remove")} member" 3 | - role = "role #{activity.parameters[:role]}" 4 | - team = activity_team(activity) 5 | - created_at = activity.created_at 6 | - removed_user = activity.recipient&.display_username || activity.parameters[:team_user] 7 | 8 | = CSV.generate_line(['team', team, action, removed_user, owner, created_at, role]) 9 | -------------------------------------------------------------------------------- /app/views/public_activity/user/_delete.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['user', "-", 'removed user', activity.parameters[:username], activity_owner(activity), activity.created_at, "role #{activity.parameters[:role]}"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/user/_delete.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-namespace-visibility 4 | i.fa.fa-unlock 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} removed the user #{activity.parameters[:username]}" 11 | small 12 | i.fa.fa-clock-o 13 | = activity_time_tag activity.created_at 14 | -------------------------------------------------------------------------------- /app/views/public_activity/webhook/_create.csv.slim: -------------------------------------------------------------------------------- 1 | - if activity.trackable 2 | = CSV.generate_line(['webhook', activity.trackable.url, 'create', '-', activity_owner(activity), activity.created_at, "owned by namespace #{activity.trackable.namespace.name}"]) 3 | - else 4 | = CSV.generate_line(['webhook', activity.parameters[:webhook_host], 'create', '-', activity_owner(activity), activity.created_at, "owned by namespace #{activity.parameters[:namespace_name]}"]) 5 | -------------------------------------------------------------------------------- /app/views/public_activity/webhook/_destroy.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(["webhook", activity.parameters[:webhook_host], "destroy", "-", activity_owner(activity), activity.created_at, "owned by namespace #{activity.parameters[:namespace_name]}"]) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/webhook/_destroy.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.application-webhook-destroyed 4 | i.fa.fa-trash 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity.owner.username} removed webhook " 11 | = activity.parameters[:webhook_host] 12 | = " from the " 13 | = link_to activity.parameters[:namespace_name], controller: "namespaces", id: activity.parameters[:namespace_id] 14 | = " namespace" 15 | small 16 | i.fa.fa-clock-o 17 | = activity_time_tag activity.created_at 18 | -------------------------------------------------------------------------------- /app/views/public_activity/webhook/_disabled.csv.slim: -------------------------------------------------------------------------------- 1 | = CSV.generate_line(['webhook', activity.trackable.url, 'make disabled', '-', activity.owner.username, activity.created_at, '-']) 2 | -------------------------------------------------------------------------------- /app/views/public_activity/webhook/_disabled.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-webhook-visibility 4 | i.fa.fa-lock 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity.owner.username} set the " 11 | - if activity.trackable.nil? 12 | = activity.parameters[:webhook_host] 13 | - else 14 | = link_to activity.trackable.host, [activity.trackable.namespace, activity.trackable] 15 | = " webhook as disabled" 16 | small 17 | i.fa.fa-clock-o 18 | = activity_time_tag activity.created_at 19 | -------------------------------------------------------------------------------- /app/views/public_activity/webhook/_enabled.csv.slim: -------------------------------------------------------------------------------- 1 | - if activity.trackable 2 | = CSV.generate_line(['webhook', activity.trackable.url, 'make enabled', '-', activity_owner(activity), activity.created_at, '-']) 3 | - else 4 | = CSV.generate_line(['webhook', activity.parameters[:webhook_host] , 'make enabled', '-', activity_owner(activity), activity.created_at, '-']) 5 | -------------------------------------------------------------------------------- /app/views/public_activity/webhook/_enabled.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | .activitie-container 3 | .activity-type.change-webhook-visibility 4 | i.fa.fa-unlock 5 | .user-image 6 | = user_image_tag(activity.owner) 7 | .description 8 | h6 9 | strong 10 | = "#{activity_owner(activity)} set the " 11 | - if activity.trackable.nil? 12 | = activity.parameters[:webhook_host] 13 | - else 14 | = link_to activity.trackable.host, [activity.trackable.namespace, activity.trackable] 15 | | webhook as enabled 16 | small 17 | i.fa.fa-clock-o 18 | = activity_time_tag activity.created_at 19 | -------------------------------------------------------------------------------- /app/views/public_activity/webhook/_update.csv.slim: -------------------------------------------------------------------------------- 1 | - if activity.trackable 2 | = CSV.generate_line(['webhook', activity.trackable.url, 'update', '-', activity_owner(activity), activity.created_at, "owned by namespace #{activity.trackable.namespace.name}"]) 3 | - else 4 | = CSV.generate_line(['webhook', activity.parameters[:webhook_host], 'update', '-', activity_owner(activity), activity.created_at, "owned by namespace #{activity.parameters[:namespace_name]}"]) 5 | -------------------------------------------------------------------------------- /app/views/repositories/index.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/repositories/show.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/search/_desktop_categories.html.slim: -------------------------------------------------------------------------------- 1 | ul.nav.nav-pills.nav-stacked 2 | li[class="category #{params[:type] == "repositories" ? 'active-cetegory' : ''}"] 3 | a href="#{build_search_category_url params, 'repositories'}" 4 | | Repositories 5 | span.result-counter = @repositories.count 6 | li[class="category #{params[:type] == "namespaces" ? 'active-cetegory' : ''}"] 7 | a href="#{build_search_category_url params, 'namespaces'}" 8 | | Namespaces 9 | span.result-counter = @namespaces.count 10 | li[class="category #{params[:type] == "teams" ? 'active-cetegory' : ''}"] 11 | a href="#{build_search_category_url params, 'teams'}" 12 | | Teams 13 | span.result-counter = @teams.count 14 | -------------------------------------------------------------------------------- /app/views/search/_mobile_categories.html.slim: -------------------------------------------------------------------------------- 1 | ul.nav.nav-tabs 2 | li[class="category #{params[:type] == "repositories" ? 'active-cetegory' : ''}"] 3 | a href="#{build_search_category_url params, 'repositories'}" 4 | | Repositories 5 | span.result-counter = @repositories.count 6 | li[class="category #{params[:type] == "namespaces" ? 'active-cetegory' : ''}"] 7 | a href="#{build_search_category_url params, 'namespaces'}" 8 | | Namespaces 9 | span.result-counter = @namespaces.count 10 | li[class="category #{params[:type] == "teams" ? 'active-cetegory' : ''}"] 11 | a href="#{build_search_category_url params, 'teams'}" 12 | | Teams 13 | span.result-counter = @teams.count 14 | -------------------------------------------------------------------------------- /app/views/search/_repositories.html.slim: -------------------------------------------------------------------------------- 1 | - if @repositories.empty? 2 | p No match found. 3 | - else 4 | .table-responsive 5 | table.table.table-striped.table-hover 6 | thead 7 | tr 8 | th Namespace 9 | th Repository 10 | th Updated at 11 | tbody 12 | - @repositories.each do |repository| 13 | tr 14 | td= link_to repository.namespace.clean_name, repository.namespace 15 | td= link_to repository.name, repository 16 | td= time_tag repository.updated_at 17 | -------------------------------------------------------------------------------- /app/views/search/_teams.html.slim: -------------------------------------------------------------------------------- 1 | - if @teams.empty? 2 | p No match found. 3 | - else 4 | .table-responsive 5 | table.table.table-striped.table-hover 6 | thead 7 | tr 8 | th 9 | th Team 10 | th Number of members 11 | th Number of namespaces 12 | tbody#teams 13 | - @teams.each do |team| 14 | tr 15 | td.table-icon= team_scope_icon(team) 16 | td= link_to team.name, team 17 | td= team.users.enabled.count 18 | td= team.namespaces.count 19 | -------------------------------------------------------------------------------- /app/views/search/index.html.slim: -------------------------------------------------------------------------------- 1 | .panel.panel-default 2 | .panel-heading 3 | h5 Search results 4 | .panel-body.search 5 | .row 6 | .col-xs-12.col-md-4.col-lg-3.category-panel 7 | .hidden-xs 8 | = render template: "search/_desktop_categories.html.slim" 9 | .visible-xs 10 | = render template: "search/_mobile_categories.html.slim" 11 | .col-xs-12.col-md-8.col-lg-9.search.search-result 12 | - case params[:type] 13 | - when 'repositories' 14 | = render template: "search/_repositories.html.slim" 15 | - when 'namespaces' 16 | = render template: "search/_namespaces.html.slim" 17 | - when 'teams' 18 | = render template: "search/_teams.html.slim" 19 | -------------------------------------------------------------------------------- /app/views/shared/_config.html.slim: -------------------------------------------------------------------------------- 1 | javascript: 2 | window.API_URL = '//#{app_path}'; 3 | window.PAGINATION = { 4 | perPage: #{pagination_per_page}, 5 | beforeAfter: #{pagination_before_after} 6 | }; 7 | -------------------------------------------------------------------------------- /app/views/shared/_search.html.slim: -------------------------------------------------------------------------------- 1 | .row 2 | .col-xs-12.visible-xs 3 | = form_tag search_index_path, method: 'get', class: 'input-group shared-search' do 4 | .input-group.shared-search 5 | = search_field_tag 'search', params[:search], class: 'form-control', placeholder: 'Search', id: 'other-search' 6 | = hidden_field_tag 'type', 'repositories' 7 | span.input-group-btn 8 | button.btn.btn-default 9 | i.fa.fa-search 10 | -------------------------------------------------------------------------------- /app/views/tags/show.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/teams/index.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/teams/show.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/webhooks/index.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/views/webhooks/show.html.slim: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 5 | load Gem.bin_path("bundler", "bundle") 6 | -------------------------------------------------------------------------------- /bin/check_db.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This is a rails runner that will print the status of Portus' database. 4 | # Possible outcomes: 5 | # 6 | # * `DB_READY`: the database has been created and initialized. 7 | # * `DB_EMPTY`: the database has been created but has not been initialized. 8 | # * `DB_MISSING`: the database has not been created. 9 | # * `DB_DOWN`: cannot connect to the database. 10 | # * `DB_UNKNOWN`: unknown error. 11 | 12 | require "portus/db" 13 | 14 | puts case Portus::DB.ping 15 | when :ready 16 | "DB_READY" 17 | when :empty 18 | "DB_EMPTY" 19 | when :missing 20 | "DB_MISSING" 21 | when :down 22 | "DB_DOWN" 23 | else 24 | "DB_UNKNOWN" 25 | end 26 | -------------------------------------------------------------------------------- /bin/ci/after_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | if [ "$PORTUS_CI" = "unit" ] || [ "$PORTUS_CI" = "all" ]; then 6 | ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 7 | fi 8 | -------------------------------------------------------------------------------- /bin/ci/before_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | if [ "$PORTUS_CI" = "unit" ] || [ "$PORTUS_CI" = "all" ]; then 6 | mysql -e 'create database portus_test;' 7 | psql -c 'create database portus_test' -U postgres 8 | 9 | # Fix for chrome headless 10 | export DISPLAY=:99.0 && sh -e /etc/init.d/xvfb start && sleep 3 11 | 12 | # Code climate, 13 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 14 | chmod +x ./cc-test-reporter 15 | ./cc-test-reporter before-build 16 | fi 17 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | begin 5 | load File.expand_path("spring", __dir__) 6 | rescue LoadError => e 7 | raise unless e.message.include?("spring") 8 | end 9 | 10 | APP_PATH = File.expand_path("../config/application", __dir__) 11 | require_relative "../config/boot" 12 | require "rails/commands" 13 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | begin 5 | load File.expand_path("spring", __dir__) 6 | rescue LoadError => e 7 | raise unless e.message.include?("spring") 8 | end 9 | 10 | require_relative "../config/boot" 11 | require "rake" 12 | Rake.application.run 13 | -------------------------------------------------------------------------------- /bin/schema_check.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | db = /version: ([\d\_]+)/ 4 | 5 | mysql = IO.read(Rails.root.join("db", "schema.mysql.rb")).scan(db).first.first 6 | postgresql = IO.read(Rails.root.join("db", "schema.postgresql.rb")).scan(db).first.first 7 | 8 | if mysql == postgresql 9 | Rails.logger.tagged(:schema_check) { Rails.logger.info "All fine" } 10 | exit 0 11 | else 12 | Rails.logger.tagged(:schema_check) do 13 | Rails.logger.info "You are not using the same schema version for MySQL and PostgreSQL!" 14 | end 15 | exit 1 16 | end 17 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # This file loads spring without using Bundler, in order to be fast. 5 | # It gets overwritten when you run the `spring binstub` command. 6 | 7 | unless defined?(Spring) 8 | require "rubygems" 9 | require "bundler" 10 | 11 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 12 | Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } 13 | gem "spring", match[1] 14 | require "spring/binstub" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require ::File.expand_path("../config/environment", __FILE__) 6 | map Rails.application.config.relative_url_root || "/" do 7 | run Rails.application 8 | end 9 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 4 | 5 | require "bundler/setup" # Set up gems listed in the Gemfile. 6 | -------------------------------------------------------------------------------- /config/brakeman.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :exit_on_warn: true 3 | :run_all_checks: true 4 | :ensure_latest: true 5 | :summary_only: :no_summary 6 | :min_confidence: 0 7 | :report_progress: false 8 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative "application" 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /config/initializers/01_database.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "fileutils" 4 | require "portus/db" 5 | 6 | def link!(name) 7 | link = Rails.root.join("db", "schema.rb") 8 | target = Rails.root.join("db", "schema.#{name}.rb") 9 | FileUtils.ln_s(target, link, force: true) 10 | 11 | Rails.logger.tagged(:schema) { Rails.logger.info "Selected the schema for #{name}" } 12 | end 13 | 14 | ::Portus::DB.mysql? ? link!("mysql") : link!("postgresql") 15 | -------------------------------------------------------------------------------- /config/initializers/activity.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Re-opening from the `public_activity` gem due to a regression when upgrading 4 | # to Rails 5.x. See: https://github.com/chaps-io/public_activity/issues/321. 5 | module ::PublicActivity 6 | class Activity 7 | serialize :parameters, Hash 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.config.assets.version = "1.0" 4 | Rails.application.config.assets.precompile += %w[*.woff2] 5 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # You can add backtrace silencers for libraries that you're using but don't wish 5 | # to see in your backtraces. 6 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 7 | 8 | # You can also remove all the silencers if you're trying to debug a problem that 9 | # might stem from framework code. 10 | # Rails.backtrace_cleaner.remove_silencers! 11 | -------------------------------------------------------------------------------- /config/initializers/checks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Clear the cache 4 | Rails.cache.write("portus-checks", nil) 5 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/draw_def.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Code taken from Gitlab's routing_draw.rb file. 4 | def draw(routes_module) 5 | instance_eval(File.read(Rails.root.join("config/routes/#{routes_module}.rb"))) 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure sensitive parameters which will be filtered from the log file. 6 | Rails.application.config.filter_parameters += %i[password reset_password_token authenticity_token] 7 | -------------------------------------------------------------------------------- /config/initializers/gravatar_image_tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | GravatarImageTag.configure do |config| 4 | config.default_image = :identicon 5 | config.include_size_attributes = true 6 | config.rating = "G" 7 | config.size = 40 8 | config.secure = true 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new inflection rules using the following format. Inflections 5 | # are locale specific, and you may define rules for as many different 6 | # locales as you wish. All of these examples are active by default: 7 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 8 | # inflect.plural /^(ox)$/i, '\1en' 9 | # inflect.singular /^(ox)en/i, '\1' 10 | # inflect.irregular 'person', 'people' 11 | # inflect.uncountable %w( fish sheep ) 12 | # end 13 | 14 | # These inflection rules are supported but not enabled by default: 15 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 16 | # inflect.acronym 'RESTful' 17 | # end 18 | -------------------------------------------------------------------------------- /config/initializers/ldap_authenticatable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "portus/ldap/authenticatable" 4 | Warden::Strategies.add(:ldap_authenticatable, ::Portus::LDAP::Authenticatable) 5 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new mime types for use in respond_to blocks: 5 | # Mime::Type.register "text/richtext", :rtf 6 | -------------------------------------------------------------------------------- /config/initializers/portus_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file updates the password of the portus hidden user if this 4 | # exists and the secret is given. 5 | 6 | portus_exists = false 7 | begin 8 | portus_exists = User.exists?(username: "portus") 9 | rescue StandardError 10 | # We will ignore any error and skip this initializer. This is done this way 11 | # because it can get really tricky to catch all the myriad of exceptions that 12 | # might be raised on database errors. 13 | portus_exists = false 14 | end 15 | 16 | password = Rails.application.secrets.portus_password 17 | if portus_exists && password.present? 18 | portus = User.portus 19 | portus&.update_attribute("password", Rails.application.secrets.portus_password) 20 | end 21 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | Rails.application.config.session_store :cookie_store, key: "_portus_session" 6 | -------------------------------------------------------------------------------- /config/initializers/swagger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | unless Rails.env.production? 4 | protocol = ::APP_CONFIG.enabled?("check_ssl_usage") ? "https://" : "http://" 5 | port = if ENV["PORTUS_PUMA_HOST"] 6 | val = ENV["PORTUS_PUMA_HOST"].split(":") 7 | ":#{val.last}" if val.size == 2 8 | end 9 | 10 | GrapeSwaggerRails.options.url = "/api/openapi-spec" 11 | GrapeSwaggerRails.options.app_url = "#{protocol}#{APP_CONFIG["machine_fqdn"]["value"]}#{port}" 12 | end 13 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /config/locales/en.bootstrap.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | breadcrumbs: 6 | application: 7 | root: "Index" 8 | pages: 9 | pages: "Pages" 10 | helpers: 11 | actions: "Actions" 12 | links: 13 | back: "Back" 14 | cancel: "Cancel" 15 | confirm: "Are you sure?" 16 | destroy: "Delete" 17 | new: "New" 18 | edit: "Edit" 19 | titles: 20 | edit: "Edit %{model}" 21 | save: "Save %{model}" 22 | new: "New %{model}" 23 | delete: "Delete %{model}" 24 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | %i[root teams namespaces admin registry_api repositories users].each { |f| draw f } 5 | 6 | mount API::RootAPI => "/" 7 | mount GrapeSwaggerRails::Engine, at: "/documentation" unless Rails.env.production? 8 | 9 | # Error pages. 10 | %w[401 404 422 500].each do |code| 11 | get "/#{code}", to: "errors#show", status: code 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /config/routes/admin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :admin do 4 | resources :activities, only: [:index] 5 | resources :registries, except: %i[show destroy] 6 | resources :users do 7 | put "toggle_admin", on: :member 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/routes/namespaces.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | resources :namespaces, only: %i[index show] do 4 | resources :webhooks do 5 | resources :headers, only: %i[create destroy], controller: :webhook_headers 6 | resources :deliveries, only: [:update], controller: :webhook_deliveries 7 | member do 8 | put "toggle_enabled" 9 | end 10 | end 11 | end 12 | 13 | get "namespaces/typeahead/:query" => "namespaces#typeahead", 14 | as: "namespaces_typeahead", :defaults => { format: "json" } 15 | -------------------------------------------------------------------------------- /config/routes/registry_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :v2, module: "api/v2", defaults: { format: :json } do 4 | root to: "ping#ping", as: :ping 5 | resource :token, only: [:show] 6 | resource :webhooks, only: [] do 7 | resources :events, only: [:create] 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/routes/repositories.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | resources :repositories, only: %i[index show destroy] do 4 | get :team, action: :team_repositories, on: :collection 5 | get :other, action: :other_repositories, on: :collection 6 | post :toggle_star, on: :member 7 | resources :comments, only: %i[create destroy] 8 | end 9 | 10 | resources :tags, only: %i[show destroy] 11 | -------------------------------------------------------------------------------- /config/routes/root.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | resources :errors, only: [:show] 4 | resource :dashboard, only: [:index] 5 | resources :search, only: [:index] 6 | resources :explore, only: [:index] 7 | resources :help, only: [:index] 8 | -------------------------------------------------------------------------------- /config/routes/teams.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | resources :teams, only: %i[index show] do 4 | member do 5 | get "typeahead/:query" => "teams#typeahead", as: "typeahead", 6 | :defaults => { format: "json" } 7 | end 8 | end 9 | get "/teams/typeahead/:query" => "teams#all_with_query", 10 | as: "teams_typeahead", :defaults => { format: "json" } 11 | 12 | resources :team_users, only: %i[create destroy update] 13 | -------------------------------------------------------------------------------- /db/migrate/20150416121417_create_namespaces.rb: -------------------------------------------------------------------------------- 1 | class CreateNamespaces < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :namespaces do |t| 4 | t.string :name, default: nil 5 | 6 | t.timestamps null: false 7 | end 8 | add_index :namespaces, :name, unique: true 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20150416121842_create_repositories.rb: -------------------------------------------------------------------------------- 1 | class CreateRepositories < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :repositories do |t| 4 | t.string :name, null: false, default: '' 5 | t.integer :namespace_id, default: nil 6 | 7 | t.timestamps null: false 8 | end 9 | add_index :repositories, :name, unique: true 10 | add_index :repositories, :namespace_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20150416122030_create_tags.rb: -------------------------------------------------------------------------------- 1 | class CreateTags < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :tags do |t| 4 | t.string :name, null: false, default: 'latest' 5 | t.integer :repository_id, null: false 6 | 7 | t.timestamps null: false 8 | end 9 | add_index :tags, :repository_id 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20150417095841_create_teams.rb: -------------------------------------------------------------------------------- 1 | class CreateTeams < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :teams do |t| 4 | t.string :name 5 | t.references :owner, index: true 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20150417100647_add_team_reference_to_namespace.rb: -------------------------------------------------------------------------------- 1 | class AddTeamReferenceToNamespace < ActiveRecord::Migration[4.2] 2 | def change 3 | add_belongs_to :namespaces, :team, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150426115313_create_team_users.rb: -------------------------------------------------------------------------------- 1 | class CreateTeamUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :team_users do |t| 4 | t.belongs_to :user, index: true 5 | t.belongs_to :team, index: true 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20150427092952_add_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAdminToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :admin, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150428081205_add_owner_to_team_users.rb: -------------------------------------------------------------------------------- 1 | class AddOwnerToTeamUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :team_users, :owner, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150428081530_remove_owner_id_from_teams.rb: -------------------------------------------------------------------------------- 1 | class RemoveOwnerIdFromTeams < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_column :teams, :owner_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150429102443_add_role_to_team_user.rb: -------------------------------------------------------------------------------- 1 | class AddRoleToTeamUser < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :team_users, :role, :integer, default: TeamUser.roles['viewer'] 4 | remove_column :team_users, :owner 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150429185051_add_public_to_namespace.rb: -------------------------------------------------------------------------------- 1 | class AddPublicToNamespace < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :namespaces, :public, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150507155138_create_registries.rb: -------------------------------------------------------------------------------- 1 | class CreateRegistries < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :registries do |t| 4 | t.string :name, null: false 5 | t.string :hostname, null: false 6 | 7 | t.timestamps null: false 8 | end 9 | add_index :registries, :name, unique: true 10 | add_index :registries, :hostname, unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20150507155425_add_registry_id_to_namespaces.rb: -------------------------------------------------------------------------------- 1 | class AddRegistryIdToNamespaces < ActiveRecord::Migration[4.2] 2 | def change 3 | add_belongs_to :namespaces, :registry, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150512105052_create_activities.rb: -------------------------------------------------------------------------------- 1 | # Migration responsible for creating a table with activities 2 | class CreateActivities < ActiveRecord::Migration[4.2] 3 | # Create table 4 | def self.up 5 | create_table :activities do |t| 6 | t.belongs_to :trackable, polymorphic: true 7 | t.belongs_to :owner, polymorphic: true 8 | t.string :key 9 | t.text :parameters 10 | t.belongs_to :recipient, polymorphic: true 11 | 12 | t.timestamps 13 | end 14 | 15 | add_index :activities, [:trackable_id, :trackable_type] 16 | add_index :activities, [:owner_id, :owner_type] 17 | add_index :activities, [:recipient_id, :recipient_type] 18 | end 19 | # Drop table 20 | def self.down 21 | drop_table :activities 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20150513133049_add_index_to_activities_key.rb: -------------------------------------------------------------------------------- 1 | class AddIndexToActivitiesKey < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :activities, :key 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150515111638_add_author_to_tag.rb: -------------------------------------------------------------------------------- 1 | class AddAuthorToTag < ActiveRecord::Migration[4.2] 2 | def change 3 | add_belongs_to :tags, :user, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150515114145_add_global_flag_to_namespaces.rb: -------------------------------------------------------------------------------- 1 | class AddGlobalFlagToNamespaces < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :namespaces, :global, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150518142223_add_hidden_to_teams.rb: -------------------------------------------------------------------------------- 1 | class AddHiddenToTeams < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :teams, :hidden, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150521145620_add_fulltext_indexes_on_repositories_namespaces.rb: -------------------------------------------------------------------------------- 1 | require "portus/db" 2 | 3 | class AddFulltextIndexesOnRepositoriesNamespaces < ActiveRecord::Migration[4.2] 4 | def change 5 | # This `if` statement has been added after this migration was 6 | # created. Modifying migrations is generally a *bad* idea but this will only 7 | # apply to PostgreSQL deployments that start from scratch, which haven't 8 | # been supported until now. 9 | if ::Portus::DB.mysql? 10 | add_index :namespaces, :name, type: :fulltext, name: 'fulltext_index_namespaces_on_name' 11 | add_index :repositories, :name, type: :fulltext, name: 'fulltext_index_repositories_on_name' 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20150522140822_add_index_name_on_teams.rb: -------------------------------------------------------------------------------- 1 | class AddIndexNameOnTeams < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :teams, :name, unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150522141052_fix_index_name_on_repositories.rb: -------------------------------------------------------------------------------- 1 | class FixIndexNameOnRepositories < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_index :repositories, column: :name 4 | add_index :repositories, [:name, :namespace_id], unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150522141547_fix_index_name_on_namespaces.rb: -------------------------------------------------------------------------------- 1 | class FixIndexNameOnNamespaces < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_index :namespaces, column: :name 4 | add_index :namespaces, [:name, :registry_id], unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150522144027_add_index_on_tag_name_repository_id.rb: -------------------------------------------------------------------------------- 1 | class AddIndexOnTagNameRepositoryId < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :tags, [:name, :repository_id], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150722194840_create_stars.rb: -------------------------------------------------------------------------------- 1 | class CreateStars < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :stars do |t| 4 | t.references :user, index: true, foreign_key: true 5 | t.references :repository, index: true, foreign_key: true 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20150729153854_add_enabled_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddEnabledToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :enabled, :bool, default: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150805130722_create_crono_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateCronoJobs < ActiveRecord::Migration[4.2] 2 | def self.up 3 | create_table :crono_jobs do |t| 4 | t.string :job_id, null: false 5 | t.text :log 6 | t.datetime :last_performed_at 7 | t.boolean :healthy 8 | t.timestamps null: false 9 | end 10 | add_index :crono_jobs, [:job_id], unique: true 11 | end 12 | 13 | def self.down 14 | drop_table :crono_jobs 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20150831131727_add_ldap_name_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddLdapNameToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :ldap_name, :string, default: nil 4 | add_index :users, :ldap_name, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150915130327_add_lockable_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddLockableToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :failed_attempts, :integer, default: 0 4 | add_column :users, :locked_at, :datetime 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150923091830_add_use_ssl_to_registries.rb: -------------------------------------------------------------------------------- 1 | class AddUseSslToRegistries < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :registries, :use_ssl, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150924084635_add_attribute_to_teams.rb: -------------------------------------------------------------------------------- 1 | class AddAttributeToTeams < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :teams, :description, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150928112551_add_description_to_namespace.rb: -------------------------------------------------------------------------------- 1 | class AddDescriptionToNamespace < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :namespaces, :description, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20151029145958_remove_not_null_constraint_on_users_email.rb: -------------------------------------------------------------------------------- 1 | class RemoveNotNullConstraintOnUsersEmail < ActiveRecord::Migration[4.2] 2 | def change 3 | change_column :users, :email, :string, :null => true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20151112003047_add_digest_to_tags.rb: -------------------------------------------------------------------------------- 1 | class AddDigestToTags < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :tags, :digest, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20151113162311_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :comments do |t| 4 | t.string :title 5 | t.text :body 6 | t.references :repository, index: true, foreign_key: true 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20151113162513_add_author_to_comment.rb: -------------------------------------------------------------------------------- 1 | class AddAuthorToComment < ActiveRecord::Migration[4.2] 2 | def change 3 | add_belongs_to :comments, :user, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20151117181723_create_application_tokens.rb: -------------------------------------------------------------------------------- 1 | class CreateApplicationTokens < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :application_tokens do |t| 4 | t.string :application, null: false 5 | t.string :token_hash, null:false 6 | t.string :token_salt, null:false 7 | t.integer :user_id, null:false 8 | end 9 | 10 | add_index :application_tokens, :user_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20151124150353_removetitle.rb: -------------------------------------------------------------------------------- 1 | class Removetitle < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_column :comments, :title 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20151215152138_remove_log_from_crono_job.rb: -------------------------------------------------------------------------------- 1 | class RemoveLogFromCronoJob < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_column :crono_jobs, :log, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160411150441_create_webhooks.rb: -------------------------------------------------------------------------------- 1 | class CreateWebhooks < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :webhooks do |t| 4 | t.references :namespace, index: true, foreign_key: true 5 | t.string :url 6 | t.string :username 7 | t.string :password 8 | t.integer :request_method 9 | t.integer :content_type 10 | t.boolean :enabled, default: false 11 | 12 | t.timestamps null: false 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20160411150458_create_webhook_headers.rb: -------------------------------------------------------------------------------- 1 | class CreateWebhookHeaders < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :webhook_headers do |t| 4 | t.references :webhook, index: true, foreign_key: true 5 | t.string :name 6 | t.string :value 7 | 8 | t.timestamps null: false 9 | end 10 | 11 | add_index :webhook_headers, [:webhook_id, :name], unique: true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20160411150745_create_webhook_deliveries.rb: -------------------------------------------------------------------------------- 1 | class CreateWebhookDeliveries < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :webhook_deliveries do |t| 4 | t.references :webhook, index: true, foreign_key: true 5 | t.string :uuid 6 | t.integer :status 7 | t.text :request_header 8 | t.text :request_body 9 | t.text :response_header 10 | t.text :response_body 11 | 12 | t.timestamps null: false 13 | end 14 | 15 | add_index :webhook_deliveries, [:webhook_id, :uuid], unique: true 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20160422075603_add_image_id_to_tag.rb: -------------------------------------------------------------------------------- 1 | class AddImageIdToTag < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :tags, :image_id, :string, default: "" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160502140301_add_marked_to_repositories_and_tags.rb: -------------------------------------------------------------------------------- 1 | class AddMarkedToRepositoriesAndTags < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :repositories, :marked, :boolean, default: false 4 | add_column :tags, :marked, :boolean, default: false 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160510153011_add_namespace_id_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddNamespaceIdToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_reference :users, :namespace, index: true, foreign_key: true, default: nil 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160519110301_add_external_hostname_to_registries.rb: -------------------------------------------------------------------------------- 1 | class AddExternalHostnameToRegistries < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :registries, :external_hostname, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160526105216_remove_index_on_ldap_users.rb: -------------------------------------------------------------------------------- 1 | class RemoveIndexOnLdapUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_index "users", name: "index_users_on_ldap_name" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160531151718_add_display_name_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddDisplayNameToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :display_name, :string, default: nil 4 | add_index :users, :display_name, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160614114318_add_visibility_to_namespace.rb: -------------------------------------------------------------------------------- 1 | class AddVisibilityToNamespace < ActiveRecord::Migration[4.2] 2 | def up 3 | add_column :namespaces, :visibility, :integer 4 | end 5 | 6 | def down 7 | remove_column :namespaces, :visibility, :integer 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160614122012_remove_public_from_namespace.rb: -------------------------------------------------------------------------------- 1 | class RemovePublicFromNamespace < ActiveRecord::Migration[4.2] 2 | def up 3 | remove_column :namespaces, :public, :boolean 4 | end 5 | 6 | def down 7 | add_column :namespaces, :public, :boolean 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160825105515_add_username_to_tags.rb: -------------------------------------------------------------------------------- 1 | class AddUsernameToTags < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :tags, :username, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160927141850_create_registry_events.rb: -------------------------------------------------------------------------------- 1 | class CreateRegistryEvents < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :registry_events do |t| 4 | t.string :event_id, default: "" 5 | t.string :repository, default: "" 6 | t.string :tag, default: "" 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20170630194953_add_omniauth_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddOmniauthToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :provider, :string 4 | add_column :users, :uid, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20171011150408_remove_fulltext_index_repositories_on_name_index.rb: -------------------------------------------------------------------------------- 1 | class RemoveFulltextIndexRepositoriesOnNameIndex < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_index_if_exists :repositories, "fulltext_index_repositories_on_name" 4 | remove_index_if_exists :namespaces, "fulltext_index_namespaces_on_name" 5 | end 6 | 7 | def remove_index_if_exists(table, name) 8 | remove_index table, name: name if index_exists?(table, :name, name: name) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20171014192702_add_indexes_to_activities.rb: -------------------------------------------------------------------------------- 1 | class AddIndexesToActivities < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_index :activities, [:trackable_type] 4 | end 5 | 6 | def self.down 7 | remove_index :activities, [:trackable_type] 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20171031112721_remove_index_on_tag_name_repository_id.rb: -------------------------------------------------------------------------------- 1 | class RemoveIndexOnTagNameRepositoryId < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_index :tags, name: "index_tags_on_name_and_repository_id" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171130095821_add_scanned_and_vulns_to_tag.rb: -------------------------------------------------------------------------------- 1 | class AddScannedAndVulnsToTag < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :tags, :scanned, :integer, default: 0 4 | add_column :tags, :vulnerabilities, :text 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20171213093923_add_status_and_data_to_registry_event.rb: -------------------------------------------------------------------------------- 1 | class AddStatusAndDataToRegistryEvent < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :registry_events, :status, :integer, default: RegistryEvent.statuses[:done] 4 | add_column :registry_events, :data, :text 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20171213094038_remove_repository_and_tag_from_registry_event.rb: -------------------------------------------------------------------------------- 1 | class RemoveRepositoryAndTagFromRegistryEvent < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_column :registry_events, :repository, :string 4 | remove_column :registry_events, :tag, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20171220095321_drop_crono_jobs_table.rb: -------------------------------------------------------------------------------- 1 | class DropCronoJobsTable < ActiveRecord::Migration[4.2] 2 | def change 3 | drop_table :crono_jobs 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180109114124_add_name_to_webhooks.rb: -------------------------------------------------------------------------------- 1 | class AddNameToWebhooks < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :webhooks, :name, :string, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180207145522_change_vulnerabilities_to_medium_text.rb: -------------------------------------------------------------------------------- 1 | class ChangeVulnerabilitiesToMediumText < ActiveRecord::Migration[4.2] 2 | def change 3 | change_column :tags, :vulnerabilities, :text, limit: 16.megabytes - 1 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180409142550_create_vulnerabilities.rb: -------------------------------------------------------------------------------- 1 | class CreateVulnerabilities < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :vulnerabilities do |t| 4 | t.string :name, null: false 5 | t.string :scanner, null: false, default: "" 6 | 7 | t.string :severity, null: false, default: "" 8 | t.string :link, null: false, default: "" 9 | t.string :fixed_by, null: false, default: "" 10 | t.text :metadata 11 | t.timestamps null: false 12 | end 13 | 14 | add_index :vulnerabilities, :name, unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20180409143945_create_scan_results.rb: -------------------------------------------------------------------------------- 1 | class CreateScanResults < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :scan_results do |t| 4 | t.integer :tag_id 5 | t.integer :vulnerability_id 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20180411102022_remove_vulnerabilities_from_tag.rb: -------------------------------------------------------------------------------- 1 | class RemoveVulnerabilitiesFromTag < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_column :tags, :vulnerabilities, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180412140442_add_description_to_vulnerabilities.rb: -------------------------------------------------------------------------------- 1 | class AddDescriptionToVulnerabilities < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :vulnerabilities, :description, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180612145708_add_bot_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddBotToUser < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :bot, :bool, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20181213101302_add_index_scan_results_vulnerability_tag.rb: -------------------------------------------------------------------------------- 1 | class AddIndexScanResultsVulnerabilityTag < ActiveRecord::Migration[5.2] 2 | def change 3 | add_index(:scan_results, [:vulnerability_id, :tag_id]) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20181224123453_add_ldap_group_checked_to_teams.rb: -------------------------------------------------------------------------------- 1 | class AddLdapGroupCheckedToTeams < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :teams, :ldap_group_checked, :int, default: Team.ldap_statuses[:unchecked] 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190102143451_add_description_to_repositories.rb: -------------------------------------------------------------------------------- 1 | class AddDescriptionToRepositories < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :repositories, :description, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190103113934_add_checked_at_to_team.rb: -------------------------------------------------------------------------------- 1 | class AddCheckedAtToTeam < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :teams, :checked_at, :datetime, default: nil 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190103124548_add_ldap_group_checked_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddLdapGroupCheckedToUser < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :users, :ldap_group_checked, :integer, default: User.ldap_statuses[:unchecked] 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190109112643_add_size_to_tags.rb: -------------------------------------------------------------------------------- 1 | class AddSizeToTags < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :tags, :size, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190115133935_add_pulled_at_to_tags.rb: -------------------------------------------------------------------------------- 1 | class AddPulledAtToTags < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :tags, :pulled_at, :datetime, default: nil 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190314173309_change_tag_size.rb: -------------------------------------------------------------------------------- 1 | class ChangeTagSize < ActiveRecord::Migration[5.2] 2 | def change 3 | change_column :tags, :size, :bigint 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Adding the Portus user. 4 | 5 | if User.any? 6 | Rails.logger.fatal "The DB is not empty! Only seed for kick-starting your DB" 7 | exit(-1) 8 | end 9 | 10 | Rails.logger.info "Adding the \"portus\" user" 11 | User.create_portus_user! 12 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | This directory is no longer used for storing files regarding the documentation 4 | of Portus. Instead, you have two options: 5 | 6 | 1. The [wiki](https://github.com/SUSE/Portus/wiki): it contains all the 7 | documentation regarding the development of Portus. 8 | 2. Our [official site](http://port.us.org/): it contains all the user 9 | documentation, the list of features, etc. 10 | -------------------------------------------------------------------------------- /doc/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/doc/database.png -------------------------------------------------------------------------------- /examples/compose/.env: -------------------------------------------------------------------------------- 1 | MACHINE_FQDN=172.17.0.1 2 | 3 | SECRET_KEY_BASE=b494a25faa8d22e430e843e220e424e10ac84d2ce0e64231f5b636d21251eb6d267adb042ad5884cbff0f3891bcf911bdf8abb3ce719849ccda9a4889249e5c2 4 | PORTUS_PASSWORD=12341234 5 | DATABASE_PASSWORD=portus 6 | -------------------------------------------------------------------------------- /examples/compose/registry/config.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | storage: 3 | filesystem: 4 | rootdirectory: /var/lib/registry 5 | delete: 6 | enabled: true 7 | http: 8 | addr: 0.0.0.0:5000 9 | debug: 10 | addr: 0.0.0.0:5001 11 | -------------------------------------------------------------------------------- /examples/compose/registry/init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | cp /secrets/portus.crt /usr/local/share/ca-certificates 6 | update-ca-certificates 7 | registry serve /etc/docker/registry/config.yml 8 | -------------------------------------------------------------------------------- /examples/compose/secrets/.gitignore: -------------------------------------------------------------------------------- 1 | portus.* 2 | -------------------------------------------------------------------------------- /examples/development/compose/bootstrap-webpack: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Checking dependencies..." 4 | yarn check &> /dev/null 5 | if [ $? -ne 0 ]; then 6 | echo "Installing dependencies..." 7 | yarn install --no-emoji --no-progress 8 | fi 9 | 10 | yarn run webpack -------------------------------------------------------------------------------- /examples/development/compose/config.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | storage: 3 | filesystem: 4 | rootdirectory: /registry_data 5 | delete: 6 | enabled: true 7 | http: 8 | addr: 0.0.0.0:5000 9 | debug: 10 | addr: 0.0.0.0:5001 11 | auth: 12 | token: 13 | rootcertbundle: /etc/docker/registry/portus.crt 14 | notifications: 15 | endpoints: 16 | - name: portus 17 | url: http://portus:3000/v2/webhooks/events 18 | timeout: 500ms 19 | threshold: 5 20 | backoff: 1s 21 | -------------------------------------------------------------------------------- /examples/development/compose/init.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # First of all, wait until the database is up and running. This is useful in 5 | # containerized scenarios. 6 | # 7 | 8 | begin 9 | ::Portus::DB.wait_until(:ready) do |status| 10 | system("bundle exec rake db:setup") if status == :missing 11 | end 12 | rescue ::Portus::DB::TimeoutReachedError => e 13 | Rails.logger.error "Exception: #{e.message}" 14 | exit 1 15 | end 16 | 17 | # 18 | # The DB is up, now let's run puma 19 | # 20 | 21 | system("pumactl -F /srv/Portus/config/puma.rb start") 22 | -------------------------------------------------------------------------------- /examples/development/vagrant/conf/ca_bundle/README.md: -------------------------------------------------------------------------------- 1 | Password: `portus` 2 | -------------------------------------------------------------------------------- /examples/development/vagrant/conf/portus/httpd.conf.local: -------------------------------------------------------------------------------- 1 | LoadModule passenger_module /usr/lib64/ruby/gems/2.1.0/gems/passenger-5.0.7/buildout/apache2/mod_passenger.so 2 | 3 | PassengerRoot /usr/lib64/ruby/gems/2.1.0/gems/passenger-5.0.7 4 | PassengerDefaultRuby /usr/bin/ruby.ruby2.1 5 | 6 | -------------------------------------------------------------------------------- /examples/development/vagrant/conf/portus/portus.test.lan.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerName portus.test.lan 3 | # !!! Be sure to point DocumentRoot to 'public'! 4 | DocumentRoot /vagrant/public 5 | 6 | # This relaxes Apache security settings. 7 | AllowOverride all 8 | # MultiViews must be turned off. 9 | Options -MultiViews 10 | # Uncomment this if you're on Apache >= 2.4: 11 | Require all granted 12 | PassengerAppEnv development 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/development/vagrant/conf/registry-config.yml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | loglevel: debug 3 | storage: 4 | filesystem: 5 | rootdirectory: /var/lib/docker-registry 6 | delete: 7 | enabled: true 8 | http: 9 | addr: 0.0.0.0:80 10 | auth: 11 | token: 12 | realm: http://portus.test.lan/v2/token 13 | service: registry.test.lan 14 | issuer: portus.test.lan 15 | rootcertbundle: /etc/registry/portus.crt 16 | notifications: 17 | endpoints: 18 | - name: portus 19 | url: http://portus.test.lan/v2/webhooks/events 20 | timeout: 500ms 21 | threshold: 5 22 | backoff: 1s 23 | -------------------------------------------------------------------------------- /examples/development/vagrant/portus_required_packages.yml: -------------------------------------------------------------------------------- 1 | - ruby2.1-rubygem-rails-4_2 2 | - ruby2.1-rubygem-therubyracer 3 | - ruby2.1-rubygem-less-rails 4 | - ruby2.1-rubygem-slim 5 | - ruby2.1-rubygem-twitter-bootstrap-rails 6 | - ruby2.1-rubygem-jwt 7 | - ruby2.1-rubygem-devise 8 | - ruby2.1-rubygem-rake 9 | - ruby2.1-rubygem-json 10 | - ruby2.1-rubygem-pundit 11 | - ruby2.1-rubygem-sprockets-2_12 12 | - ruby2.1-rubygem-base32 13 | - ruby2.1-rubygem-jquery-turbolinks 14 | - ruby2.1-rubygem-gravatar_image_tag 15 | -------------------------------------------------------------------------------- /examples/development/vagrant/provision_client: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Install the registry 4 | zypper ar -f http://download.opensuse.org/repositories/Virtualization/openSUSE_13.2 Virtualization 5 | zypper --gpg-auto-import-keys ref 6 | 7 | zypper -n in docker 8 | 9 | # still required because we are not using our package 10 | cat < /usr/lib/systemd/system/docker.service 11 | [Unit] 12 | Description=Docker 13 | Requires=network.target 14 | After=multi-user.target 15 | 16 | [Service] 17 | Type=simple 18 | ExecStart=/usr/bin/docker -d --insecure-registry registry.test.lan 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | EOF 23 | 24 | usermod -aG docker vagrant 25 | 26 | systemctl enable docker 27 | systemctl start docker 28 | 29 | docker pull busybox 30 | -------------------------------------------------------------------------------- /examples/development/vagrant/provision_portus_by_rpms: -------------------------------------------------------------------------------- 1 | install missing packages 2 | 3 | bundle install --local --retry=3 --without test development --system 4 | 5 | zypper in rubygem-passenger-apache2 6 | 7 | mkdir /var/run/passenger 8 | chmow -R wwwrun /var/run/passenger 9 | sed -i 's/PassengerInstanceRegistryDir \/run\/passenger/PassengerInstanceRegistryDir \/var\/run\/passenger/' /etc/apache2/conf.d/mod_passenger.conf 10 | 11 | a2enmod passenger 12 | 13 | chown -R wwwrun /srv/www/portus/ 14 | -------------------------------------------------------------------------------- /examples/development/vagrant/provision_registry: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Install the registry 4 | zypper ar -f http://download.opensuse.org/repositories/Virtualization:/containers/openSUSE_13.2 Virtualization:containers 5 | zypper --gpg-auto-import-keys ref 6 | 7 | zypper -n in docker-distribution-registry 8 | 9 | systemctl enable registry 10 | systemctl start registry 11 | -------------------------------------------------------------------------------- /examples/kubernetes/README.md: -------------------------------------------------------------------------------- 1 | Under construction. See [PR #1311](https://github.com/SUSE/Portus/pull/1311). 2 | -------------------------------------------------------------------------------- /lib/api/helpers/comments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module API 4 | module Helpers 5 | # Helpers of teams 6 | module Comments 7 | # Return true if current_user has permission to destroy a comment 8 | # Returns false otherwise 9 | def can_destroy_comment?(comment, user) 10 | CommentPolicy.new(user, comment).destroy? 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/api/helpers/ordering.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module API 4 | module Helpers 5 | # Ordering implements a helper responsible for the resource entries ordering that 6 | # can be customized through request parameters like `sort_attr` and `sort_order`. 7 | module Ordering 8 | def order(relation) 9 | relation.order(sanitized_attr(relation) => sanitized_order) 10 | end 11 | 12 | private 13 | 14 | def sanitized_order 15 | params[:sort_order] == "asc" ? :asc : :desc 16 | end 17 | 18 | def sanitized_attr(relation) 19 | attribute = params[:sort_attr] 20 | relation.model.respond_to?(attribute) ? attribute : :id 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/api/helpers/repositories.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module API 4 | module Helpers 5 | # Helpers of repositories 6 | module Repositories 7 | def can_destroy_repository?(repository, user) 8 | RepositoryPolicy.new(user, repository).destroy? 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/api/helpers/webhooks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module API 4 | module Helpers 5 | # Helpers of webhooks 6 | module Webhooks 7 | # Return true if user has permission to update a webhook 8 | # Returns false otherwise 9 | def can_manage_webhook?(webhook, user) 10 | WebhookPolicy.new(user, webhook).update? 11 | end 12 | 13 | def can_destroy_webhook?(webhook, user) 14 | WebhookPolicy.new(user, webhook).create? 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/api/v1/ordering_params.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module API 4 | module V1 5 | # Concern for declaration of ordering parameters. 6 | # 7 | # @example 8 | # class ApiResource < Grape::API 9 | # include OrderingParams 10 | # 11 | # params do 12 | # use :ordering 13 | # end 14 | # end 15 | module OrderingParams 16 | extend ActiveSupport::Concern 17 | 18 | included do 19 | helpers do 20 | params :ordering do 21 | optional :sort_attr, type: String, default: :id, desc: "Current page number" 22 | optional :sort_order, type: String, default: :asc, desc: "Number of items per page" 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/portus/deprecation_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Portus 4 | # DeprecationError is the exception to be raised when certain functionality has been deprecated 5 | # in the latest version and we want to provide migration instructions 6 | class DeprecationError < StandardError 7 | def to_s 8 | "[DEPRECATED] " + super 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/portus/health_checks/db.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "portus/db" 4 | 5 | module Portus 6 | module HealthChecks 7 | # DB offers health check support for the database. 8 | class DB 9 | def self.name 10 | "database" 11 | end 12 | 13 | def self.ready 14 | case ::Portus::DB.ping 15 | when :ready 16 | ["database is up-to-date", true] 17 | when :empty 18 | ["database is initializing", false] 19 | when :missing 20 | ["database has not been created", false] 21 | when :down 22 | ["cannot connect to database", false] 23 | else 24 | ["unknown error", false] 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/portus/health_checks/registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Portus 4 | module HealthChecks 5 | # Registry offers health check support for the configured Docker 6 | # registry. Note that this will also fail if the registry is up and running 7 | # but there's something wrong with the configuration (e.g. SSL problem). 8 | class Registry 9 | def self.name 10 | "registry" 11 | end 12 | 13 | def self.ready 14 | return ["no registry configured", false] unless ::Registry.any? 15 | 16 | res = ::Registry.get.client.reachable? 17 | ["registry is#{res ? "" : " not"} reachable", res] 18 | rescue ::Portus::RequestError => e 19 | [e.to_s, false] 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/portus/migrate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Portus 4 | # The Portus::Migrate module implements some methods that are used to handle a 5 | # change from different versions of Portus where there is an old (and 6 | # deprecated) way of doing things and a preferred new one. 7 | module Migrate; end 8 | end 9 | -------------------------------------------------------------------------------- /lib/portus/request_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ::Portus 4 | # RequestError wraps any exception that might arise when performing an HTTP 5 | # request, and provides a common `msg` method. Moreover, all raised exceptions 6 | # will also be logged. 7 | class RequestError < StandardError 8 | # Given an inner exception and a message, it builds up a common error 9 | # message. 10 | def initialize(exception:, message:) 11 | @msg = "#{exception.class.name}: #{message}" 12 | Rails.logger.error @msg 13 | end 14 | 15 | def to_s 16 | @msg 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/portus/security_backends/fixtures/dummy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "CVE-2016-6304", 4 | "Link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-6304", 5 | "Severity": "High" 6 | }, 7 | { 8 | "Name": "CVE-2016-7052", 9 | "Link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-7052", 10 | "Severity": "Medium" 11 | }, 12 | { 13 | "Name": "CVE-2016-2178", 14 | "Link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-2178", 15 | "Severity": "Low" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /lib/portus/security_backends/fixtures/empty.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /lib/tasks/assets.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Tasks taken from Gitlab's yarn.rake and assets.rake files. 4 | 5 | namespace :portus do 6 | namespace :assets do 7 | desc "Compile all frontend assets" 8 | task compile: [ 9 | "assets:precompile", 10 | "webpack:compile" 11 | ] 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/tasks/brakeman.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if Rails.env.development? 4 | desc "Run Brakeman security checks" 5 | task :brakeman do 6 | require "brakeman" 7 | 8 | Brakeman.run( 9 | app_path: File.expand_path("../..", __dir__), 10 | print_report: true, 11 | exit_on_warn: true 12 | ) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/tasks/info.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :portus do 4 | desc "Get general info about the running instance" 5 | task info: :environment do 6 | # We cannot pass it with the dependency-syntax because we need to pass it an 7 | # argument. 8 | Rake::Task["cconfig:info"].reenable 9 | Rake::Task["cconfig:info"].invoke("portus") 10 | 11 | puts "\nPortus version: #{Version.value}" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/tasks/portus/api.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :portus do 4 | desc "Create the account used by Portus to talk with Registry's API" 5 | task create_api_account: :environment do 6 | User.create!( 7 | username: "portus", 8 | password: Rails.application.secrets.portus_password, 9 | email: "portus@portus.com", 10 | admin: true 11 | ) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/tasks/release/prepare.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../helpers" 4 | 5 | namespace :release do 6 | desc "Prepare new release" 7 | task :prepare, [:number] => :environment do |_, args| 8 | if args.to_hash.empty? 9 | puts "Usage: rake release:prepare[X.Y.Z]" 10 | exit(-1) 11 | end 12 | 13 | number = args[:number] 14 | ::Helpers.check_release_number(number) 15 | branch = ::Helpers.branch(number) 16 | 17 | puts "Things you have to do to prepare the release for #{number}" 18 | puts "1- Create new branch #{branch} if it does not exist" 19 | puts "2- Checkout #{branch}" # TODO, what happens if it already exists? 20 | puts "3- Review Gemfile.lock. Review the gem versions." 21 | puts "4- Test and small fixes" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /packaging/suse/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | Portus.spec 3 | 4 | -------------------------------------------------------------------------------- /packaging/suse/conf/registry.config.yml.in: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | loglevel: info 3 | storage: 4 | filesystem: 5 | rootdirectory: /var/lib/docker-registry 6 | delete: 7 | enabled: true 8 | http: 9 | addr: :5000 10 | tls: 11 | certificate: /etc/registry/ssl.crt/portus.crt 12 | key: /srv/Portus/config/server.key 13 | 14 | auth: 15 | token: 16 | realm: https://__HOSTNAME__/v2/token 17 | service: __HOSTNAME__:5000 18 | issuer: __HOSTNAME__ 19 | rootcertbundle: /etc/registry/ssl.crt/portus.crt 20 | notifications: 21 | endpoints: 22 | - name: portus 23 | url: https://__HOSTNAME__/v2/webhooks/events 24 | timeout: 500ms 25 | threshold: 5 26 | backoff: 1s 27 | 28 | -------------------------------------------------------------------------------- /packaging/suse/patches/README: -------------------------------------------------------------------------------- 1 | Add here patches to be included in the package in OBS. 2 | 3 | gem related patches (Gemfile|Gemfile.lock) need to end with `.gem.patch` 4 | app related patches need to end with `.rpm.patch` 5 | 6 | also the numbering prefix needs to be ascending 7 | -------------------------------------------------------------------------------- /packaging/suse/release/.gitignore: -------------------------------------------------------------------------------- 1 | project.xml 2 | -------------------------------------------------------------------------------- /packaging/suse/release/README: -------------------------------------------------------------------------------- 1 | release.sh automates the creation of a project in the open suse build service 2 | for a new release and it copies all the dependencies. 3 | 4 | To be more specific, it creates a subproject in Virtualization:containers:Portus 5 | and it copies all packages. Then, it will set the version in the spec file and 6 | get the tarball for that specific version (it assumes you have tagged git) 7 | 8 | 9 | -------------------------------------------------------------------------------- /packaging/suse/release/projectconfig.txt: -------------------------------------------------------------------------------- 1 | ## 2 | # Go 3 | 4 | Substitute: golang go 5 | Prefer: go 6 | Prefer: go-md2man 7 | 8 | ## 9 | # Ruby 10 | 11 | %define rb_default_ruby ruby26 12 | %define rb_default_ruby_suffix ruby2.6 13 | %define rb_default_ruby_abi ruby:2.6.0 14 | 15 | %define _with_ruby26 1 16 | Macros: 17 | %rb_default_ruby ruby26 18 | %rb_default_ruby_suffix ruby2.6 19 | %rb_default_ruby_abi ruby:2.6.0 20 | 21 | %_with_ruby26 1 22 | :Macros 23 | 24 | Prefer: rubygem(ruby:2.6.0:gem2rpm) 25 | Prefer: ruby2.6-rubygem-gem2rpm 26 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/android-chrome-144x144.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/android-chrome-36x36.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/android-chrome-48x48.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/android-chrome-72x72.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/android-chrome-96x96.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #da532c 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/favicon.ico -------------------------------------------------------------------------------- /public/favicon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/mstile-144x144.png -------------------------------------------------------------------------------- /public/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /public/favicon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/mstile-310x150.png -------------------------------------------------------------------------------- /public/favicon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/mstile-310x310.png -------------------------------------------------------------------------------- /public/favicon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/public/favicon/mstile-70x70.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | User-agent: * 5 | Disallow: / 6 | Allow: /explore 7 | -------------------------------------------------------------------------------- /spec/api/grape_api/v1/root_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | # TestAPI implements a MethodNotAllowed endpoint. 6 | class TestAPI < Grape::API 7 | version "v1", using: :path 8 | 9 | get "test" do 10 | raise Grape::Exceptions::MethodNotAllowed, "method not allowed" 11 | end 12 | end 13 | 14 | # Re-opening ::API::RootAPI to mount the TestAPI endpoints. 15 | class ::API::RootAPI 16 | mount ::TestAPI 17 | end 18 | 19 | describe API::RootAPI, type: :request do 20 | it "handles unknown routes" do 21 | get "/api/lala" 22 | expect(response).to have_http_status(:not_found) 23 | end 24 | 25 | it "rescues a method not allowed exception" do 26 | get "/api/v1/test" 27 | expect(response).to have_http_status(:method_not_allowed) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/api/v2/events_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe "/v2/token" do 6 | describe "create" do 7 | let(:data) { { "events" => [] } } 8 | 9 | it "handles the notification and accepts it" do 10 | expect(Portus::RegistryNotification).to receive(:process!) 11 | post v2_webhooks_events_url, params: data.to_json, headers: { format: :json } 12 | expect(response).to be_successful 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/factories/application_token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :application_token do 5 | sequence :application do |i| 6 | "application #{i}" 7 | end 8 | 9 | # the plain token is application 10 | token_salt { BCrypt::Engine.generate_salt } 11 | token_hash { BCrypt::Engine.hash_secret(application, token_salt) } 12 | 13 | association :user, factory: :user 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/factories/comments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: comments 6 | # 7 | # id :integer not null, primary key 8 | # body :text(65535) 9 | # repository_id :integer 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # user_id :integer 13 | # 14 | # Indexes 15 | # 16 | # index_comments_on_repository_id (repository_id) 17 | # index_comments_on_user_id (user_id) 18 | # 19 | 20 | FactoryBot.define do 21 | factory :comment do 22 | sequence :body do |b| 23 | "a short comment #{b}" 24 | end 25 | 26 | association :author, factory: :user 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/factories/scan_result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :scan_result do 5 | tag 6 | vulnerability 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/star.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :star do 5 | repository 6 | user 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :tag do 5 | sequence :name do |n| 6 | "tag#{n}" 7 | end 8 | repository 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/factories/teams_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :team do 5 | sequence(:name) { |n| "team_name#{n}" } 6 | owners { |t| [t.association(:user)] } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/users_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :user do 5 | sequence(:email) { |n| "test#{n}@localhost.test.lan" } 6 | password { "test-password" } 7 | sequence(:username) { |n| "username#{n}" } 8 | bot { false } 9 | 10 | factory :admin do 11 | admin { true } 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/factories/vulnerability.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :vulnerability do 5 | sequence(:name) { |n| "vulnerability#{n}" } 6 | severity { "High" } 7 | sequence(:link) { |n| "http://a.link#{n}" } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/webhook_headers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: webhook_headers 6 | # 7 | # id :integer not null, primary key 8 | # webhook_id :integer 9 | # name :string(255) 10 | # value :string(255) 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | # Indexes 15 | # 16 | # index_webhook_headers_on_webhook_id (webhook_id) 17 | # index_webhook_headers_on_webhook_id_and_name (webhook_id,name) UNIQUE 18 | # 19 | 20 | FactoryBot.define do 21 | factory :webhook_header do 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/helpers/search_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe SearchHelper, type: :helper do 6 | describe "build_search_category_url" do 7 | it "returns true if current user is an owner of the team" do 8 | expected = "#{search_index_path}?utf8=✓&search=&type=repositories" 9 | params = { utf8: "✓", search: "" } 10 | expect(helper.build_search_category_url(params, "repositories")).to eq expected 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/helpers/sessions_helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe SessionsHelper, type: :helper do 6 | describe "#social_login" do 7 | it "display social buttons" do 8 | APP_CONFIG["oauth"] = { "google_oauth2" => { "enabled" => true } } 9 | expect(helper.social_login).to match(/Social logins/) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/integration/health.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats -t 2 | 3 | load helpers 4 | 5 | function setup() { 6 | __setup minimal 7 | } 8 | 9 | @test "health runs just fine" { 10 | helper_runner curl.rb get /api/v1/health 11 | [[ "${lines[-1]}" =~ "database is up-to-date" ]] 12 | [[ "${lines[-1]}" =~ "clair is reachable" ]] 13 | [[ "${lines[-1]}" =~ "registry is reachable" ]] 14 | } 15 | 16 | @test "health reports an invalid registry" { 17 | # Modify the registry hostname to some unknown hostname. 18 | ruby_puts "Registry.get.update(hostname:\"wrong.whatever\")" 19 | 20 | helper_runner curl.rb get /api/v1/health 21 | [ $status -eq 1 ] 22 | [[ "${lines[-2]}" =~ "SocketError: connection refused" ]] 23 | } 24 | -------------------------------------------------------------------------------- /spec/integration/helpers/delete.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # It accepts exactly two arguments: the repository name (full name) and the tag 4 | # name. With these two things, it will simply issue a delete request to the 5 | # Registry. 6 | 7 | require "portus/registry_client" 8 | 9 | REPOSITORY = ARGV.first.dup 10 | TAG = ARGV.last.dup 11 | 12 | RegistryEvent.all.destroy_all 13 | 14 | client = Registry.get.client 15 | manifest = client.manifest(REPOSITORY, TAG) 16 | client.delete(REPOSITORY, manifest.digest, "manifests") 17 | -------------------------------------------------------------------------------- /spec/integration/helpers/eval.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Outputs the evaluated object from the first argument. 4 | 5 | # rubocop:disable Security/Eval 6 | puts eval(ARGV.first) 7 | # rubocop:enable Security/Eval 8 | -------------------------------------------------------------------------------- /spec/integration/ldap/health.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats -t 2 | 3 | load ../helpers 4 | 5 | function setup() { 6 | __setup ldap 7 | } 8 | 9 | @test "LDAP: health status is up" { 10 | helper_runner curl.rb get /api/v1/health 11 | [ $status -eq 0 ] 12 | [[ "${lines[-1]}" =~ "LDAP server is reachable" ]] 13 | } 14 | -------------------------------------------------------------------------------- /spec/integration/ldap/user.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats -t 2 | 3 | load ../helpers 4 | 5 | function setup() { 6 | __setup ldap 7 | } 8 | 9 | @test "LDAP: you can create a user that doesn't exist" { 10 | helper_runner curl.rb post /api/v1/users rllull user.username=mrodoreda,user.email=lala@example.org,user.password=12341234 11 | [ $status -eq 0 ] 12 | [[ "${lines[-1]}" =~ "\"username\":\"mrodoreda\"" ]] 13 | } 14 | 15 | @test "LDAP: you cannot create an existing user" { 16 | helper_runner curl.rb post /api/v1/users rllull user.username=jverdaguer,user.email=lala@example.org,user.password=12341234 17 | [ $status -eq 1 ] 18 | [[ "${lines[-2]}" =~ "Use another name to avoid name collision" ]] 19 | } 20 | -------------------------------------------------------------------------------- /spec/integration/login.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats -t 2 | 3 | load helpers 4 | 5 | function setup() { 6 | __setup minimal 7 | } 8 | 9 | @test "proper user can run docker login" { 10 | docker_run login -u admin -p 12341234 172.17.0.1:5000 11 | [ $status -eq 0 ] 12 | # The first line is a warning because we are passing the password directly 13 | # from the CLI. 14 | [[ "${lines[1]}" =~ "Login Succeeded" ]] 15 | } 16 | 17 | @test "unknown user cannot login" { 18 | docker_run login -u user -p 12341234 172.17.0.1:5000 19 | [ $status -eq 1 ] 20 | [[ "${lines[1]}" =~ "authentication required" ]] 21 | } 22 | -------------------------------------------------------------------------------- /spec/integration/profiles/full.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "shared" 4 | 5 | clean_db! 6 | create_registry! 7 | 8 | admin = User.create!( 9 | username: "admin", 10 | password: "12341234", 11 | email: "admin@example.local", 12 | admin: true 13 | ) 14 | 15 | contributor = User.create!( 16 | username: "user", 17 | password: "12341234", 18 | email: "user@example.local", 19 | admin: false 20 | ) 21 | 22 | viewer = User.create!( 23 | username: "viewer", 24 | password: "12341234", 25 | email: "viewer@example.local", 26 | admin: false 27 | ) 28 | 29 | t = Team.create!(name: "team", owners: [admin], contributors: [contributor], viewers: [viewer]) 30 | Namespace.create!(name: "namespace", team: t, registry: Registry.get) 31 | -------------------------------------------------------------------------------- /spec/integration/profiles/minimal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "shared" 4 | 5 | clean_db! 6 | create_registry! 7 | 8 | User.create!( 9 | username: "admin", 10 | password: "12341234", 11 | email: "admin@example.local", 12 | admin: true 13 | ) 14 | -------------------------------------------------------------------------------- /spec/javascripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true 7 | }, 8 | "rules": { 9 | "import/no-extraneous-dependencies": [ 10 | "error", 11 | { 12 | "devDependencies": true 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spec/javascripts/setup.js: -------------------------------------------------------------------------------- 1 | require('jsdom-global')(); 2 | 3 | const dayjs = require('dayjs'); 4 | const relativeTime = require('dayjs/plugin/relativeTime'); 5 | 6 | global.expect = require('expect'); 7 | 8 | dayjs.extend(relativeTime); 9 | -------------------------------------------------------------------------------- /spec/javascripts/shared/loading-icon.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils'; 2 | 3 | import LoadingIcon from '~/shared/components/loading-icon'; 4 | 5 | describe('loading-icon', () => { 6 | let wrapper; 7 | 8 | beforeEach(() => { 9 | wrapper = mount(LoadingIcon); 10 | }); 11 | 12 | it('shows loading icon with normal size (2x)', () => { 13 | expect(wrapper.find('.fa-spinner').classes()).toContain('fa-2x'); 14 | }); 15 | 16 | it('shows loading icon with custom size', () => { 17 | wrapper.setProps({ size: 3 }); 18 | expect(wrapper.find('.fa-spinner').classes()).toContain('fa-3x'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /spec/javascripts/utils/date.spec.js: -------------------------------------------------------------------------------- 1 | import DateUtil from '~/utils/date'; 2 | 3 | describe('DateUtil', () => { 4 | it('returns false if it\'s not a valid date string/object', () => { 5 | expect(DateUtil.isISO8601('asdasd')).toBe(false); 6 | expect(DateUtil.isISO8601(null)).toBe(false); 7 | expect(DateUtil.isISO8601('2018-222-222')).toBe(false); 8 | expect(DateUtil.isISO8601('')).toBe(false); 9 | expect(DateUtil.isISO8601('20180205T173027Z')).toBe(false); 10 | }); 11 | 12 | it('returns true if it\'s a valid date string/object', () => { 13 | expect(DateUtil.isISO8601('2018-07-20T18:14:43.000Z')).toBe(true); 14 | expect(DateUtil.isISO8601('2018-07-20T18:14:43Z')).toBe(true); 15 | expect(DateUtil.isISO8601('2018-02-05T17:14Z')).toBe(true); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /spec/javascripts/utils/range.spec.js: -------------------------------------------------------------------------------- 1 | import range from '~/utils/range'; 2 | 3 | describe('Range', () => { 4 | it('returns throws exception if start > end', () => { 5 | expect(() => { 6 | range(1, -2); 7 | }).toThrowError(); 8 | }); 9 | 10 | it('returns an array of integers', () => { 11 | expect(range(-1, 1)).toEqual([-1, 0, 1]); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /spec/lib/portus/deprecation_error_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe Portus::DeprecationError do 6 | subject(:error) { described_class.new(message) } 7 | 8 | let(:message) { "example" } 9 | 10 | context "#to_s" do 11 | subject { error.to_s } 12 | 13 | it "prefixes the message with [DEPRECATED]" do 14 | is_expected.to eq "[DEPRECATED] #{message}" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/lib/portus/migrate_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe Portus::Migrate do 6 | end 7 | -------------------------------------------------------------------------------- /spec/lib/portus/request_error_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe ::Portus::RequestError do 4 | it "sumarizes the given request error" do 5 | e = StandardError.new 6 | 7 | expect do 8 | raise ::Portus::RequestError.new(exception: e, message: "something") 9 | end.to raise_error(::Portus::RequestError, "StandardError: something") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/models/comment_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: comments 6 | # 7 | # id :integer not null, primary key 8 | # body :text(65535) 9 | # repository_id :integer 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # user_id :integer 13 | # 14 | # Indexes 15 | # 16 | # index_comments_on_repository_id (repository_id) 17 | # index_comments_on_user_id (user_id) 18 | # 19 | 20 | require "rails_helper" 21 | 22 | describe Comment do 23 | it { is_expected.to belong_to(:repository) } 24 | it { is_expected.to belong_to(:author) } 25 | 26 | it "has a valid factory" do 27 | expect { Factory.build(:comment).to be_valid } 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/policies/registry_policy_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe RegistryPolicy do 6 | subject { described_class } 7 | 8 | let(:user) { create(:user) } 9 | let(:admin) { create(:admin) } 10 | 11 | permissions :all? do 12 | it "allows access to admin users" do 13 | expect(subject).to permit(admin, nil) 14 | end 15 | 16 | it "does not allow access to regular users" do 17 | expect(subject).not_to permit(user, nil) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/requests/api/v2/ping_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe Api::V2::PingController do 6 | describe "#ping" do 7 | context "user authorized" do 8 | it "responds with 200" do 9 | sign_in(create(:user)) 10 | get v2_ping_url 11 | expect(response.status).to eq 200 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/requests/help_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe HelpController do 6 | describe "GET #index" do 7 | let!(:registry) { create(:registry, hostname: "registry.test.lan") } 8 | let!(:user) { create(:admin) } 9 | 10 | before do 11 | sign_in user 12 | end 13 | 14 | it "returns the page successfully" do 15 | get help_index_url 16 | expect(response.code.to_i).to eq 200 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/requests/search_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe SearchController do 6 | let(:registry) { create(:registry) } 7 | let(:user) { create(:user) } 8 | let(:team) { create(:team, owners: [user]) } 9 | 10 | before do 11 | sign_in user 12 | 13 | namespace = create(:namespace, team: team, registry: registry) 14 | @repository = create(:repository, namespace: namespace) 15 | end 16 | 17 | describe "GET #index" do 18 | it "returns http success" do 19 | get search_index_url(search: @repository.name) 20 | expect(response).to have_http_status(:success) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/containers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Module related to container's helpers stuff 4 | module Containers 5 | # Checks whether it's running inside of a Docker container or not 6 | def self.dockerized? 7 | @dockerized ||= File.read("/proc/1/cgroup").include?("docker") 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/devise.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Setup devise for tests. 4 | RSpec.configure do |config| 5 | config.include Devise::Test::ControllerHelpers, type: :controller 6 | config.include Devise::Test::ControllerHelpers, type: :helper 7 | config.include Devise::Test::ControllerHelpers, type: :view 8 | config.include Devise::Test::IntegrationHelpers, type: :request 9 | 10 | # Needed for methods such as `login_as`. 11 | config.include Warden::Test::Helpers 12 | config.before(:suite) { Warden.test_mode! } 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/models/registry_raw_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RegistryRawEvent 4 | attr_accessor :action, :target, :request, :actor 5 | 6 | def to_test_hash 7 | { 8 | "action" => action, 9 | "target" => target, 10 | "request" => request, 11 | "actor" => actor 12 | }.deep_dup 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/system/auth/logout_feature_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe "Logout feature", type: :system, js: true do 6 | let!(:registry) { create(:registry) } 7 | let!(:user) { create(:user) } 8 | 9 | before do 10 | login_as user 11 | visit authenticated_root_path 12 | end 13 | 14 | it "Redirects to login screen" do 15 | expect(page).to have_css("#logout") 16 | click_link("logout") 17 | expect(page).to have_content("Signed out successfully") 18 | end 19 | 20 | it "After login guest redirects to login page when he attempts to access dashboard again" do 21 | expect(page).to have_css("#logout") 22 | click_link("logout") 23 | 24 | visit authenticated_root_path 25 | expect(page).to have_content("Login") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/system/gravatar_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe "Gravatar support" do 6 | let!(:registry) { create(:registry) } 7 | let!(:user) { create(:user) } 8 | 9 | before do 10 | login user 11 | end 12 | 13 | it "If gravatar support is on, there should be an image" do 14 | APP_CONFIG["gravatar"] = { "enabled" => true } 15 | visit root_path 16 | expect(page).to have_selector(".user-header img") 17 | end 18 | 19 | it "If gravatar suppor is disabled, there should be an icon" do 20 | APP_CONFIG["gravatar"] = { "enabled" => false } 21 | visit root_path 22 | expect(page).to have_selector(".user-header .user-picture") 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/system/help_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe "Help page" do 6 | describe "API documentation support" do 7 | let!(:registry) { create(:registry) } 8 | let!(:user) { create(:admin) } 9 | 10 | before do 11 | login_as user, scope: :user 12 | end 13 | 14 | it "A user can go to the API documentation" do 15 | visit help_index_path 16 | click_link("API Documentation") 17 | 18 | expect(page).to have_content("Swagger") 19 | expect(page).to have_current_path("/documentation") 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/views/explore/index.html.slim_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe "explore/index", type: :view do 6 | it "renders the page successfully" do 7 | @repositories = [] 8 | render 9 | assert_select("#explore_search") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/views/help/index.html.slim_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe "help/index" do 6 | it "renders the page successfully" do 7 | render 8 | expect(assert_select("h5").text).to eq "Help" 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /vendor/assets/fonts/pacifico.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/vendor/assets/fonts/pacifico.woff2 -------------------------------------------------------------------------------- /vendor/assets/fonts/raleway-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/vendor/assets/fonts/raleway-bold.woff2 -------------------------------------------------------------------------------- /vendor/assets/fonts/raleway-heavy.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/vendor/assets/fonts/raleway-heavy.woff2 -------------------------------------------------------------------------------- /vendor/assets/fonts/raleway-light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/vendor/assets/fonts/raleway-light.woff2 -------------------------------------------------------------------------------- /vendor/assets/fonts/raleway.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUSE/Portus/2843437742515c1f0896fafaee166cde336f5b18/vendor/assets/fonts/raleway.woff2 -------------------------------------------------------------------------------- /vendor/assets/stylesheets/lifeitup/labels.scss: -------------------------------------------------------------------------------- 1 | .label { 2 | font-size: 85%; 3 | &.checkbox-label { 4 | display: inline-block; 5 | font-size: 95% 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/lifeitup/lifeitup.scss: -------------------------------------------------------------------------------- 1 | //Importing Lifeitup files https://github.com/cyntss/Lifeitup-panel 2 | //@import 'variables'; 3 | @import 'alerts'; 4 | @import 'buttons'; 5 | @import 'images'; 6 | @import 'labels'; 7 | @import 'layout'; 8 | @import 'links'; 9 | @import 'lists'; 10 | @import 'mixings'; 11 | @import 'panels'; 12 | @import 'table'; 13 | @import 'responsive_tools'; 14 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/lifeitup/links.scss: -------------------------------------------------------------------------------- 1 | a.add-more { 2 | background: $add-more-media-background; 3 | border: $add-more-media-border; 4 | border-radius: $border-radious; 5 | color: $add-more-media-color; 6 | display: inline-block; 7 | height: $max-image-size; 8 | margin: .5em .5em 0 0; 9 | padding: 3.5em 0 0 0; 10 | text-align: center; 11 | vertical-align: top; 12 | width: $max-image-size; 13 | } 14 | a.add-more:hover { 15 | background: $add-more-media-background-hover; 16 | border: $add-more-media-border-hover; 17 | color: $add-more-media-color-hover; 18 | } 19 | 20 | a[tabindex="0"] { 21 | cursor: pointer; 22 | } 23 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/lifeitup/mixings.scss: -------------------------------------------------------------------------------- 1 | .shadow-box { 2 | box-shadow: 0px 3px 0px $image-shadow; 3 | } 4 | .main-colours-mix { 5 | @include gradient-horizontal($first-colour, $second-colour, 0%, 100%); 6 | } 7 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/lifeitup/table.scss: -------------------------------------------------------------------------------- 1 | col { 2 | &.col-5 { 3 | width: 5%; 4 | } 5 | 6 | &.col-10 { 7 | width: 10%; 8 | } 9 | 10 | &.col-20 { 11 | width: 20%; 12 | } 13 | 14 | &.col-30 { 15 | width: 30%; 16 | } 17 | 18 | &.col-40 { 19 | width: 40%; 20 | } 21 | 22 | &.col-50 { 23 | width: 50%; 24 | } 25 | 26 | &.col-60 { 27 | width: 60%; 28 | } 29 | 30 | &.col-70 { 31 | width: 70%; 32 | } 33 | 34 | &.col-80 { 35 | width: 80%; 36 | } 37 | 38 | &.col-90 { 39 | width: 90%; 40 | } 41 | 42 | &.col-100 { 43 | width: 100%; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/pacifico.css.erb: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Pacifico'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Pacifico Regular'), local('Pacifico-Regular'), url('<%= asset_path("pacifico.woff2") %>') format('woff2'); 6 | } 7 | --------------------------------------------------------------------------------
8 | Nobody has left a comment yet 9 |